From c1e2e57dd99549bde421214c35ce01dd55105a38 Mon Sep 17 00:00:00 2001 From: jiaoqingbo <1178404354@qq.com> Date: Fri, 3 Feb 2023 05:41:22 +0000 Subject: [PATCH 001/760] [KYUUBI #4222] Use hiveTableCatalog to updateTableStats instead of sessionCatalog fix #4222 - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [x] Add screenshots for manual tests if appropriate - [ ] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4237 from jiaoqingbo/kyuubi4222. Closes #4237 7677d69f [jiaoqingbo] code review 538d436a [jiaoqingbo] [Kyuubi #4222] Use hiveTableCatalog to updateTableStats instead of sessionCatalog Authored-by: jiaoqingbo <1178404354@qq.com> Signed-off-by: Cheng Pan --- .../connector/hive/write/HiveBatchWrite.scala | 22 +++++++++++++++---- .../connector/hive/write/HiveWrite.scala | 2 ++ 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/extensions/spark/kyuubi-spark-connector-hive/src/main/scala/org/apache/kyuubi/spark/connector/hive/write/HiveBatchWrite.scala b/extensions/spark/kyuubi-spark-connector-hive/src/main/scala/org/apache/kyuubi/spark/connector/hive/write/HiveBatchWrite.scala index ba89ec715..c4e473ff2 100644 --- a/extensions/spark/kyuubi-spark-connector-hive/src/main/scala/org/apache/kyuubi/spark/connector/hive/write/HiveBatchWrite.scala +++ b/extensions/spark/kyuubi-spark-connector-hive/src/main/scala/org/apache/kyuubi/spark/connector/hive/write/HiveBatchWrite.scala @@ -33,11 +33,13 @@ import org.apache.spark.sql.execution.datasources.v2.FileBatchWrite import org.apache.spark.sql.hive.kyuubi.connector.HiveBridgeHelper.{hive, toSQLValue, HiveExternalCatalog} import org.apache.spark.sql.types.StringType -import org.apache.kyuubi.spark.connector.hive.KyuubiHiveConnectorException +import org.apache.kyuubi.spark.connector.hive.{HiveTableCatalog, KyuubiHiveConnectorException} import org.apache.kyuubi.spark.connector.hive.write.HiveWriteHelper.getPartitionSpec class HiveBatchWrite( + sparkSession: SparkSession, table: CatalogTable, + hiveTableCatalog: HiveTableCatalog, tmpLocation: Option[Path], partition: Map[String, Option[String]], partitionColumnNames: Seq[String], @@ -66,10 +68,22 @@ class HiveBatchWrite( deleteExternalTmpPath(hadoopConf) } - val sparkSession = SparkSession.active // un-cache this table. - CommandUtils.uncacheTableOrView(sparkSession, table.identifier.quotedString) - CommandUtils.updateTableStats(sparkSession, table) + hiveTableCatalog.catalog.invalidateCachedTable(table.identifier) + + val catalog = hiveTableCatalog.catalog + if (sparkSession.sessionState.conf.autoSizeUpdateEnabled) { + val newTable = catalog.getTableMetadata(table.identifier) + val newSize = CommandUtils.calculateTotalSize(sparkSession, newTable) + val newStats = CatalogStatistics(sizeInBytes = newSize) + catalog.alterTableStats(table.identifier, Some(newStats)) + } else if (table.stats.nonEmpty) { + catalog.alterTableStats(table.identifier, None) + } else { + // In other cases, we still need to invalidate the table relation cache. + catalog.refreshTable(table.identifier) + } + } override def abort(messages: Array[WriterCommitMessage]): Unit = { diff --git a/extensions/spark/kyuubi-spark-connector-hive/src/main/scala/org/apache/kyuubi/spark/connector/hive/write/HiveWrite.scala b/extensions/spark/kyuubi-spark-connector-hive/src/main/scala/org/apache/kyuubi/spark/connector/hive/write/HiveWrite.scala index 50ab2b5ed..486a7aa22 100644 --- a/extensions/spark/kyuubi-spark-connector-hive/src/main/scala/org/apache/kyuubi/spark/connector/hive/write/HiveWrite.scala +++ b/extensions/spark/kyuubi-spark-connector-hive/src/main/scala/org/apache/kyuubi/spark/connector/hive/write/HiveWrite.scala @@ -102,7 +102,9 @@ case class HiveWrite( committer.setupJob(job) new HiveBatchWrite( + sparkSession, table, + hiveTableCatalog, Some(tmpLocation), partition, partitionColumnNames, From eb1b11cd171fd4475cd2168e1d2d573232e16bd9 Mon Sep 17 00:00:00 2001 From: Cheng Pan Date: Fri, 3 Feb 2023 05:48:02 +0000 Subject: [PATCH 002/760] [KYUUBI #4152] Enhance LDAP authentication ### _Why are the changes needed?_ This PR proposes to enhance the LDAP support, which mainly referring the code introduced in HIVE-14713. Currently, Kyuubi has very limited LDAP support, and the implementation is from the early Hive codebase. Hive enhanced the LDAP support in later versions, considering the Hive ecosystem is quite mature, I think we'd better to porting this functionality and keep the same behavior w/ Hive first, and we can improve it if meet certain requirements/issues in the future. Basically, this PR introduces the following configurations ``` kyuubi.authentication.ldap.url (since 1.0.0) kyuubi.authentication.ldap.domain (since 1.0.0) kyuubi.authentication.ldap.guidKey (since 1.2.0) kyuubi.authentication.ldap.base.dn (since 1.0.0 deprecated) kyuubi.authentication.ldap.baseDN kyuubi.authentication.ldap.groupMembershipKey kyuubi.authentication.ldap.userMembershipKey kyuubi.authentication.ldap.groupClassKey kyuubi.authentication.ldap.groupDNPattern kyuubi.authentication.ldap.userDNPattern kyuubi.authentication.ldap.groupFilter kyuubi.authentication.ldap.userFilter kyuubi.authentication.ldap.customLDAPQuery kyuubi.authentication.ldap.binddn kyuubi.authentication.ldap.bindpw ``` ### _How was this patch tested?_ - [x] Add some test cases that check the changes thoroughly including negative and positive cases if possible This PR ports all LDAP-related UT&IT from Hive codebase - [ ] Add screenshots for manual tests if appropriate - [x] [Run test](https://kyuubi.apache.org/docs/latest/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4152 from pan3793/ldap. Closes #4152 d251c959 [Cheng Pan] nit 6d14f44b [Cheng Pan] nit 6b3d116c [Cheng Pan] nit ab47d822 [Cheng Pan] nit a56e8702 [Cheng Pan] nit 4624619a [Cheng Pan] nit b82c0c05 [Cheng Pan] LDAP test password uses alphanumeric 86a01cca [Cheng Pan] Enhance LDAP authentication Authored-by: Cheng Pan Signed-off-by: Cheng Pan --- .gitignore | 7 +- LICENSE-binary | 2 + dev/dependencyList | 2 + docs/deployment/settings.md | 38 +- kyuubi-common/pom.xml | 11 + .../scala/org/apache/kyuubi/Logging.scala | 12 + .../org/apache/kyuubi/config/KyuubiConf.scala | 108 +++- .../apache/kyuubi/service/ServiceUtils.scala | 24 + .../LdapAuthenticationProviderImpl.scala | 99 ++-- .../ldap/ChainFilterFactory.scala | 44 ++ .../ldap/CustomQueryFilterFactory.scala | 77 +++ .../authentication/ldap/DirSearch.scala | 73 +++ .../ldap/DirSearchFactory.scala | 39 ++ .../service/authentication/ldap/Filter.scala | 37 ++ .../authentication/ldap/FilterFactory.scala | 34 ++ .../ldap/GroupFilterFactory.scala | 108 ++++ .../authentication/ldap/LdapSearch.scala | 126 +++++ .../ldap/LdapSearchFactory.scala | 56 ++ .../authentication/ldap/LdapUtils.scala | 199 +++++++ .../service/authentication/ldap/Query.scala | 136 +++++ .../authentication/ldap/QueryFactory.scala | 145 ++++++ .../ldap/SearchResultHandler.scala | 148 ++++++ .../ldap/UserFilterFactory.scala | 46 ++ .../ldap/UserSearchFilterFactory.scala | 53 ++ .../test/resources/ldap/ad.example.com.ldif | 150 ++++++ .../src/test/resources/ldap/example.com.ldif | 113 ++++ .../test/resources/ldap/microsoft.schema.ldif | 62 +++ .../authentication/LdapAtnProviderSuite.scala | 493 ++++++++++++++++++ .../LdapAuthenticationProviderImplSuite.scala | 368 +++++++++++-- .../authentication/WithLdapServer.scala | 24 +- .../ldap/ChainFilterSuite.scala | 88 ++++ .../ldap/CustomQueryFilterSuite.scala | 69 +++ .../ldap/GroupFilterSuite.scala | 161 ++++++ .../ldap/LdapAuthenticationTestCase.scala | 117 +++++ .../authentication/ldap/LdapSearchSuite.scala | 298 +++++++++++ .../authentication/ldap/LdapTestUtils.scala | 116 +++++ .../authentication/ldap/LdapUtilsSuite.scala | 88 ++++ .../ldap/QueryFactorySuite.scala | 97 ++++ .../authentication/ldap/QuerySuite.scala | 54 ++ .../ldap/SearchResultHandlerSuite.scala | 203 ++++++++ .../authentication/ldap/UserFilterSuite.scala | 60 +++ .../ldap/UserSearchFilterSuite.scala | 79 +++ .../apache/kyuubi/RestClientTestHelper.scala | 2 +- ...biOperationKerberosAndPlainAuthSuite.scala | 5 +- ...nThriftHttpKerberosAndPlainAuthSuite.scala | 2 +- licenses-binary/LICENSE-antlr.txt | 8 + licenses-binary/LICENSE-antlr4.txt | 26 + pom.xml | 6 + 48 files changed, 4209 insertions(+), 104 deletions(-) create mode 100644 kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/ldap/ChainFilterFactory.scala create mode 100644 kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/ldap/CustomQueryFilterFactory.scala create mode 100644 kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/ldap/DirSearch.scala create mode 100644 kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/ldap/DirSearchFactory.scala create mode 100644 kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/ldap/Filter.scala create mode 100644 kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/ldap/FilterFactory.scala create mode 100644 kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/ldap/GroupFilterFactory.scala create mode 100644 kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/ldap/LdapSearch.scala create mode 100644 kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/ldap/LdapSearchFactory.scala create mode 100644 kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/ldap/LdapUtils.scala create mode 100644 kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/ldap/Query.scala create mode 100644 kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/ldap/QueryFactory.scala create mode 100644 kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/ldap/SearchResultHandler.scala create mode 100644 kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/ldap/UserFilterFactory.scala create mode 100644 kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/ldap/UserSearchFilterFactory.scala create mode 100644 kyuubi-common/src/test/resources/ldap/ad.example.com.ldif create mode 100644 kyuubi-common/src/test/resources/ldap/example.com.ldif create mode 100644 kyuubi-common/src/test/resources/ldap/microsoft.schema.ldif create mode 100644 kyuubi-common/src/test/scala/org/apache/kyuubi/service/authentication/LdapAtnProviderSuite.scala create mode 100644 kyuubi-common/src/test/scala/org/apache/kyuubi/service/authentication/ldap/ChainFilterSuite.scala create mode 100644 kyuubi-common/src/test/scala/org/apache/kyuubi/service/authentication/ldap/CustomQueryFilterSuite.scala create mode 100644 kyuubi-common/src/test/scala/org/apache/kyuubi/service/authentication/ldap/GroupFilterSuite.scala create mode 100644 kyuubi-common/src/test/scala/org/apache/kyuubi/service/authentication/ldap/LdapAuthenticationTestCase.scala create mode 100644 kyuubi-common/src/test/scala/org/apache/kyuubi/service/authentication/ldap/LdapSearchSuite.scala create mode 100644 kyuubi-common/src/test/scala/org/apache/kyuubi/service/authentication/ldap/LdapTestUtils.scala create mode 100644 kyuubi-common/src/test/scala/org/apache/kyuubi/service/authentication/ldap/LdapUtilsSuite.scala create mode 100644 kyuubi-common/src/test/scala/org/apache/kyuubi/service/authentication/ldap/QueryFactorySuite.scala create mode 100644 kyuubi-common/src/test/scala/org/apache/kyuubi/service/authentication/ldap/QuerySuite.scala create mode 100644 kyuubi-common/src/test/scala/org/apache/kyuubi/service/authentication/ldap/SearchResultHandlerSuite.scala create mode 100644 kyuubi-common/src/test/scala/org/apache/kyuubi/service/authentication/ldap/UserFilterSuite.scala create mode 100644 kyuubi-common/src/test/scala/org/apache/kyuubi/service/authentication/ldap/UserSearchFilterSuite.scala create mode 100644 licenses-binary/LICENSE-antlr.txt create mode 100644 licenses-binary/LICENSE-antlr4.txt diff --git a/.gitignore b/.gitignore index d2c1ba3b7..bbdd246c4 100644 --- a/.gitignore +++ b/.gitignore @@ -32,11 +32,7 @@ .ensime_lucene .generated-mima* .vscode/ -# The star is required for further !/.idea/ to work, see https://git-scm.com/docs/gitignore -/.idea/* -# Icon for JetBrains Toolbox -!/.idea/icon.png -!/.idea/vcs.xml +.idea/ .idea_modules/ .project .pydevproject @@ -59,7 +55,6 @@ hs_err_pid* spark-warehouse/ metastore_db derby.log -ldap **/dependency-reduced-pom.xml metrics/report.json metrics/.report.json.crc diff --git a/LICENSE-binary b/LICENSE-binary index 017dd53f8..92daf62ab 100644 --- a/LICENSE-binary +++ b/LICENSE-binary @@ -322,7 +322,9 @@ org.apache.zookeeper:zookeeper BSD ------------ +org.antlr:antlr-runtime org.antlr:antlr4-runtime +org.antlr:ST4 jline:jline com.thoughtworks.paranamer:paranamer dk.brics.automaton:automaton diff --git a/dev/dependencyList b/dev/dependencyList index 9b8064e42..10933b9fa 100644 --- a/dev/dependencyList +++ b/dev/dependencyList @@ -16,8 +16,10 @@ # HikariCP/4.0.3//HikariCP-4.0.3.jar +ST4/4.3.4//ST4-4.3.4.jar animal-sniffer-annotations/1.21//animal-sniffer-annotations-1.21.jar annotations/4.1.1.4//annotations-4.1.1.4.jar +antlr-runtime/3.5.3//antlr-runtime-3.5.3.jar antlr4-runtime/4.9.3//antlr4-runtime-4.9.3.jar aopalliance-repackaged/2.6.1//aopalliance-repackaged-2.6.1.jar automaton/1.11-8//automaton-1.11-8.jar diff --git a/docs/deployment/settings.md b/docs/deployment/settings.md index f8beaa83b..26147ff8a 100644 --- a/docs/deployment/settings.md +++ b/docs/deployment/settings.md @@ -132,20 +132,30 @@ You can configure the Kyuubi properties in `$KYUUBI_HOME/conf/kyuubi-defaults.co ### Authentication -| Key | Default | Meaning | Type | Since | -|-----------------------------------------|-------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------|-------| -| kyuubi.authentication | NONE | A comma-separated list of client authentication types.
  • NOSASL: raw transport.
  • NONE: no authentication check.
  • KERBEROS: Kerberos/GSSAPI authentication.
  • CUSTOM: User-defined authentication.
  • JDBC: JDBC query authentication.
  • LDAP: Lightweight Directory Access Protocol authentication.
The following tree describes the catalog of each option.
  • NOSASL
  • SASL
    • SASL/PLAIN
      • NONE
      • LDAP
      • JDBC
      • CUSTOM
    • SASL/GSSAPI
      • KERBEROS
Note that: for SASL authentication, KERBEROS and PLAIN auth types are supported at the same time, and only the first specified PLAIN auth type is valid. | seq | 1.0.0 | -| kyuubi.authentication.custom.class | <undefined> | User-defined authentication implementation of org.apache.kyuubi.service.authentication.PasswdAuthenticationProvider | string | 1.3.0 | -| kyuubi.authentication.jdbc.driver.class | <undefined> | Driver class name for JDBC Authentication Provider. | string | 1.6.0 | -| kyuubi.authentication.jdbc.password | <undefined> | Database password for JDBC Authentication Provider. | string | 1.6.0 | -| kyuubi.authentication.jdbc.query | <undefined> | Query SQL template with placeholders for JDBC Authentication Provider to execute. Authentication passes if the result set is not empty.The SQL statement must start with the `SELECT` clause. Available placeholders are `${user}` and `${password}`. | string | 1.6.0 | -| kyuubi.authentication.jdbc.url | <undefined> | JDBC URL for JDBC Authentication Provider. | string | 1.6.0 | -| kyuubi.authentication.jdbc.user | <undefined> | Database user for JDBC Authentication Provider. | string | 1.6.0 | -| kyuubi.authentication.ldap.base.dn | <undefined> | LDAP base DN. | string | 1.0.0 | -| kyuubi.authentication.ldap.domain | <undefined> | LDAP domain. | string | 1.0.0 | -| kyuubi.authentication.ldap.guidKey | uid | LDAP attribute name whose values are unique in this LDAP server.For example:uid or cn. | string | 1.2.0 | -| kyuubi.authentication.ldap.url | <undefined> | SPACE character separated LDAP connection URL(s). | string | 1.0.0 | -| kyuubi.authentication.sasl.qop | auth | Sasl QOP enable higher levels of protection for Kyuubi communication with clients.
  • auth - authentication only (default)
  • auth-int - authentication plus integrity protection
  • auth-conf - authentication plus integrity and confidentiality protection. This is applicable only if Kyuubi is configured to use Kerberos authentication.
| string | 1.0.0 | +| Key | Default | Meaning | Type | Since | +|-----------------------------------------------|-------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------|-------| +| kyuubi.authentication | NONE | A comma-separated list of client authentication types.
  • NOSASL: raw transport.
  • NONE: no authentication check.
  • KERBEROS: Kerberos/GSSAPI authentication.
  • CUSTOM: User-defined authentication.
  • JDBC: JDBC query authentication.
  • LDAP: Lightweight Directory Access Protocol authentication.
The following tree describes the catalog of each option.
  • NOSASL
  • SASL
    • SASL/PLAIN
      • NONE
      • LDAP
      • JDBC
      • CUSTOM
    • SASL/GSSAPI
      • KERBEROS
Note that: for SASL authentication, KERBEROS and PLAIN auth types are supported at the same time, and only the first specified PLAIN auth type is valid. | seq | 1.0.0 | +| kyuubi.authentication.custom.class | <undefined> | User-defined authentication implementation of org.apache.kyuubi.service.authentication.PasswdAuthenticationProvider | string | 1.3.0 | +| kyuubi.authentication.jdbc.driver.class | <undefined> | Driver class name for JDBC Authentication Provider. | string | 1.6.0 | +| kyuubi.authentication.jdbc.password | <undefined> | Database password for JDBC Authentication Provider. | string | 1.6.0 | +| kyuubi.authentication.jdbc.query | <undefined> | Query SQL template with placeholders for JDBC Authentication Provider to execute. Authentication passes if the result set is not empty.The SQL statement must start with the `SELECT` clause. Available placeholders are `${user}` and `${password}`. | string | 1.6.0 | +| kyuubi.authentication.jdbc.url | <undefined> | JDBC URL for JDBC Authentication Provider. | string | 1.6.0 | +| kyuubi.authentication.jdbc.user | <undefined> | Database user for JDBC Authentication Provider. | string | 1.6.0 | +| kyuubi.authentication.ldap.baseDN | <undefined> | LDAP base DN. | string | 1.7.0 | +| kyuubi.authentication.ldap.binddn | <undefined> | The user with which to bind to the LDAP server, and search for the full domain name of the user being authenticated. This should be the full domain name of the user, and should have search access across all users in the LDAP tree. If not specified, then the user being authenticated will be used as the bind user. For example: CN=bindUser,CN=Users,DC=subdomain,DC=domain,DC=com | string | 1.7.0 | +| kyuubi.authentication.ldap.bindpw | <undefined> | The password for the bind user, to be used to search for the full name of the user being authenticated. If the username is specified, this parameter must also be specified. | string | 1.7.0 | +| kyuubi.authentication.ldap.customLDAPQuery | <undefined> | A full LDAP query that LDAP Atn provider uses to execute against LDAP Server. If this query returns a null resultset, the LDAP Provider fails the Authentication request, succeeds if the user is part of the resultset.For example: `(&(objectClass=group)(objectClass=top)(instanceType=4)(cn=Domain*))`, `(&(objectClass=person)(|(sAMAccountName=admin)(|(memberOf=CN=Domain Admins,CN=Users,DC=domain,DC=com)(memberOf=CN=Administrators,CN=Builtin,DC=domain,DC=com))))` | string | 1.7.0 | +| kyuubi.authentication.ldap.domain | <undefined> | LDAP domain. | string | 1.0.0 | +| kyuubi.authentication.ldap.groupClassKey | groupOfNames | LDAP attribute name on the group entry that is to be used in LDAP group searches. For example: group, groupOfNames or groupOfUniqueNames. | string | 1.7.0 | +| kyuubi.authentication.ldap.groupDNPattern | <undefined> | COLON-separated list of patterns to use to find DNs for group entities in this directory. Use %s where the actual group name is to be substituted for. For example: CN=%s,CN=Groups,DC=subdomain,DC=domain,DC=com. | string | 1.7.0 | +| kyuubi.authentication.ldap.groupFilter || COMMA-separated list of LDAP Group names (short name not full DNs). For example: HiveAdmins,HadoopAdmins,Administrators | seq | 1.7.0 | +| kyuubi.authentication.ldap.groupMembershipKey | member | LDAP attribute name on the group object that contains the list of distinguished names for the user, group, and contact objects that are members of the group. For example: member, uniqueMember or memberUid | string | 1.7.0 | +| kyuubi.authentication.ldap.guidKey | uid | LDAP attribute name whose values are unique in this LDAP server. For example: uid or CN. | string | 1.2.0 | +| kyuubi.authentication.ldap.url | <undefined> | SPACE character separated LDAP connection URL(s). | string | 1.0.0 | +| kyuubi.authentication.ldap.userDNPattern | <undefined> | COLON-separated list of patterns to use to find DNs for users in this directory. Use %s where the actual group name is to be substituted for. For example: CN=%s,CN=Users,DC=subdomain,DC=domain,DC=com. | string | 1.7.0 | +| kyuubi.authentication.ldap.userFilter || COMMA-separated list of LDAP usernames (just short names, not full DNs). For example: hiveuser,impalauser,hiveadmin,hadoopadmin | seq | 1.7.0 | +| kyuubi.authentication.ldap.userMembershipKey | <undefined> | LDAP attribute name on the user object that contains groups of which the user is a direct member, except for the primary group, which is represented by the primaryGroupId. For example: memberOf | string | 1.7.0 | +| kyuubi.authentication.sasl.qop | auth | Sasl QOP enable higher levels of protection for Kyuubi communication with clients.
  • auth - authentication only (default)
  • auth-int - authentication plus integrity protection
  • auth-conf - authentication plus integrity and confidentiality protection. This is applicable only if Kyuubi is configured to use Kerberos authentication.
| string | 1.0.0 | ### Backend diff --git a/kyuubi-common/pom.xml b/kyuubi-common/pom.xml index 26cdc271d..61ae4c0e7 100644 --- a/kyuubi-common/pom.xml +++ b/kyuubi-common/pom.xml @@ -88,6 +88,11 @@ runtime + + org.antlr + ST4 + + org.apache.commons commons-lang3 @@ -141,6 +146,12 @@ test + + org.scalatestplus + mockito-4-6_${scala.binary.version} + test + + com.google.guava failureaccess diff --git a/kyuubi-common/src/main/scala/org/apache/kyuubi/Logging.scala b/kyuubi-common/src/main/scala/org/apache/kyuubi/Logging.scala index 4944b9fcc..d70df0952 100644 --- a/kyuubi-common/src/main/scala/org/apache/kyuubi/Logging.scala +++ b/kyuubi-common/src/main/scala/org/apache/kyuubi/Logging.scala @@ -54,12 +54,24 @@ trait Logging { } } + def debug(message: => Any, t: Throwable): Unit = { + if (logger.isDebugEnabled) { + logger.debug(message.toString, t) + } + } + def info(message: => Any): Unit = { if (logger.isInfoEnabled) { logger.info(message.toString) } } + def info(message: => Any, t: Throwable): Unit = { + if (logger.isInfoEnabled) { + logger.info(message.toString, t) + } + } + def warn(message: => Any): Unit = { if (logger.isWarnEnabled) { logger.warn(message.toString) diff --git a/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala b/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala index f9d9bcda6..e4c48218a 100644 --- a/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala +++ b/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala @@ -783,10 +783,11 @@ object KyuubiConf { .stringConf .createOptional - val AUTHENTICATION_LDAP_BASEDN: OptionalConfigEntry[String] = - buildConf("kyuubi.authentication.ldap.base.dn") + val AUTHENTICATION_LDAP_BASE_DN: OptionalConfigEntry[String] = + buildConf("kyuubi.authentication.ldap.baseDN") + .withAlternative("kyuubi.authentication.ldap.base.dn") .doc("LDAP base DN.") - .version("1.0.0") + .version("1.7.0") .stringConf .createOptional @@ -797,14 +798,109 @@ object KyuubiConf { .stringConf .createOptional - val AUTHENTICATION_LDAP_GUIDKEY: ConfigEntry[String] = + val AUTHENTICATION_LDAP_GROUP_DN_PATTERN: OptionalConfigEntry[String] = + buildConf("kyuubi.authentication.ldap.groupDNPattern") + .doc("COLON-separated list of patterns to use to find DNs for group entities in " + + "this directory. Use %s where the actual group name is to be substituted for. " + + "For example: CN=%s,CN=Groups,DC=subdomain,DC=domain,DC=com.") + .version("1.7.0") + .stringConf + .createOptional + + val AUTHENTICATION_LDAP_USER_DN_PATTERN: OptionalConfigEntry[String] = + buildConf("kyuubi.authentication.ldap.userDNPattern") + .doc("COLON-separated list of patterns to use to find DNs for users in this directory. " + + "Use %s where the actual group name is to be substituted for. " + + "For example: CN=%s,CN=Users,DC=subdomain,DC=domain,DC=com.") + .version("1.7.0") + .stringConf + .createOptional + + val AUTHENTICATION_LDAP_GROUP_FILTER: ConfigEntry[Seq[String]] = + buildConf("kyuubi.authentication.ldap.groupFilter") + .doc("COMMA-separated list of LDAP Group names (short name not full DNs). " + + "For example: HiveAdmins,HadoopAdmins,Administrators") + .version("1.7.0") + .stringConf + .toSequence() + .createWithDefault(Nil) + + val AUTHENTICATION_LDAP_USER_FILTER: ConfigEntry[Seq[String]] = + buildConf("kyuubi.authentication.ldap.userFilter") + .doc("COMMA-separated list of LDAP usernames (just short names, not full DNs). " + + "For example: hiveuser,impalauser,hiveadmin,hadoopadmin") + .version("1.7.0") + .stringConf + .toSequence() + .createWithDefault(Nil) + + val AUTHENTICATION_LDAP_GUID_KEY: ConfigEntry[String] = buildConf("kyuubi.authentication.ldap.guidKey") - .doc("LDAP attribute name whose values are unique in this LDAP server." + - "For example:uid or cn.") + .doc("LDAP attribute name whose values are unique in this LDAP server. " + + "For example: uid or CN.") .version("1.2.0") .stringConf .createWithDefault("uid") + val AUTHENTICATION_LDAP_GROUP_MEMBERSHIP_KEY: ConfigEntry[String] = + buildConf("kyuubi.authentication.ldap.groupMembershipKey") + .doc("LDAP attribute name on the group object that contains the list of distinguished " + + "names for the user, group, and contact objects that are members of the group. " + + "For example: member, uniqueMember or memberUid") + .version("1.7.0") + .stringConf + .createWithDefault("member") + + val AUTHENTICATION_LDAP_USER_MEMBERSHIP_KEY: OptionalConfigEntry[String] = + buildConf("kyuubi.authentication.ldap.userMembershipKey") + .doc("LDAP attribute name on the user object that contains groups of which the user is " + + "a direct member, except for the primary group, which is represented by the " + + "primaryGroupId. For example: memberOf") + .version("1.7.0") + .stringConf + .createOptional + + val AUTHENTICATION_LDAP_GROUP_CLASS_KEY: ConfigEntry[String] = + buildConf("kyuubi.authentication.ldap.groupClassKey") + .doc("LDAP attribute name on the group entry that is to be used in LDAP group searches. " + + "For example: group, groupOfNames or groupOfUniqueNames.") + .version("1.7.0") + .stringConf + .createWithDefault("groupOfNames") + + val AUTHENTICATION_LDAP_CUSTOM_LDAP_QUERY: OptionalConfigEntry[String] = + buildConf("kyuubi.authentication.ldap.customLDAPQuery") + .doc("A full LDAP query that LDAP Atn provider uses to execute against LDAP Server. " + + "If this query returns a null resultset, the LDAP Provider fails the Authentication " + + "request, succeeds if the user is part of the resultset." + + "For example: `(&(objectClass=group)(objectClass=top)(instanceType=4)(cn=Domain*))`, " + + "`(&(objectClass=person)(|(sAMAccountName=admin)" + + "(|(memberOf=CN=Domain Admins,CN=Users,DC=domain,DC=com)" + + "(memberOf=CN=Administrators,CN=Builtin,DC=domain,DC=com))))`") + .version("1.7.0") + .stringConf + .createOptional + + val AUTHENTICATION_LDAP_BIND_USER: OptionalConfigEntry[String] = + buildConf("kyuubi.authentication.ldap.binddn") + .doc("The user with which to bind to the LDAP server, and search for the full domain name " + + "of the user being authenticated. This should be the full domain name of the user, and " + + "should have search access across all users in the LDAP tree. If not specified, then " + + "the user being authenticated will be used as the bind user. " + + "For example: CN=bindUser,CN=Users,DC=subdomain,DC=domain,DC=com") + .version("1.7.0") + .stringConf + .createOptional + + val AUTHENTICATION_LDAP_BIND_PASSWORD: OptionalConfigEntry[String] = + buildConf("kyuubi.authentication.ldap.bindpw") + .doc("The password for the bind user, to be used to search for the full name of the " + + "user being authenticated. If the username is specified, this parameter must also be " + + "specified.") + .version("1.7.0") + .stringConf + .createOptional + val AUTHENTICATION_JDBC_DRIVER: OptionalConfigEntry[String] = buildConf("kyuubi.authentication.jdbc.driver.class") .doc("Driver class name for JDBC Authentication Provider.") diff --git a/kyuubi-common/src/main/scala/org/apache/kyuubi/service/ServiceUtils.scala b/kyuubi-common/src/main/scala/org/apache/kyuubi/service/ServiceUtils.scala index d481aea77..955144af8 100644 --- a/kyuubi-common/src/main/scala/org/apache/kyuubi/service/ServiceUtils.scala +++ b/kyuubi-common/src/main/scala/org/apache/kyuubi/service/ServiceUtils.scala @@ -17,6 +17,10 @@ package org.apache.kyuubi.service +import java.io.{Closeable, IOException} + +import org.slf4j.Logger + object ServiceUtils { /** @@ -49,4 +53,24 @@ object ServiceUtils { userName.substring(0, indexOfDomainMatch) } } + + /** + * Close the Closeable objects and ignore any [[IOException]] or + * null pointers. Must only be used for cleanup in exception handlers. + * + * @param log the log to record problems to at debug level. Can be null. + * @param closeables the objects to close + */ + def cleanup(log: Logger, closeables: Closeable*): Unit = { + closeables.filter(_ != null).foreach { c => + try { + c.close() + } catch { + case e: IOException => + if (log != null && log.isDebugEnabled) { + log.debug(s"Exception in closing $c", e) + } + } + } + } } diff --git a/kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/LdapAuthenticationProviderImpl.scala b/kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/LdapAuthenticationProviderImpl.scala index b5e08def5..06d08f3e4 100644 --- a/kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/LdapAuthenticationProviderImpl.scala +++ b/kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/LdapAuthenticationProviderImpl.scala @@ -17,17 +17,25 @@ package org.apache.kyuubi.service.authentication -import javax.naming.{Context, NamingException} -import javax.naming.directory.InitialDirContext +import javax.naming.NamingException import javax.security.sasl.AuthenticationException import org.apache.commons.lang3.StringUtils +import org.apache.kyuubi.Logging import org.apache.kyuubi.config.KyuubiConf -import org.apache.kyuubi.config.KyuubiConf._ import org.apache.kyuubi.service.ServiceUtils +import org.apache.kyuubi.service.authentication.LdapAuthenticationProviderImpl.FILTER_FACTORIES +import org.apache.kyuubi.service.authentication.ldap._ -class LdapAuthenticationProviderImpl(conf: KyuubiConf) extends PasswdAuthenticationProvider { +class LdapAuthenticationProviderImpl( + conf: KyuubiConf, + searchFactory: DirSearchFactory = new LdapSearchFactory) + extends PasswdAuthenticationProvider with Logging { + + private val filterOpt: Option[Filter] = FILTER_FACTORIES + .map { f => f.getInstance(conf) } + .collectFirst { case Some(f: Filter) => f } /** * The authenticate method is called by the Kyuubi Server authentication layer @@ -41,47 +49,72 @@ class LdapAuthenticationProviderImpl(conf: KyuubiConf) extends PasswdAuthenticat * @throws AuthenticationException When a user is found to be invalid by the implementation */ override def authenticate(user: String, password: String): Unit = { + + val (usedBind, bindUser, bindPassword) = ( + conf.get(KyuubiConf.AUTHENTICATION_LDAP_BIND_USER), + conf.get(KyuubiConf.AUTHENTICATION_LDAP_BIND_PASSWORD)) match { + case (Some(_bindUser), Some(_bindPw)) => (true, _bindUser, _bindPw) + case _ => + // If no bind user or bind password was specified, + // we assume the user we are authenticating has the ability to search + // the LDAP tree, so we use it as the "binding" account. + // This is the way it worked before bind users were allowed in the LDAP authenticator, + // so we keep existing systems working. + (false, user, password) + } + + var search: DirSearch = null + try { + search = createDirSearch(bindUser, bindPassword) + applyFilter(search, user) + if (usedBind) { + // If we used the bind user, then we need to authenticate again, + // this time using the full user name we got during the bind process. + createDirSearch(search.findUserDn(user), password) + } + } catch { + case e: NamingException => + throw new AuthenticationException( + s"Unable to find the user in the LDAP tree. ${e.getMessage}") + } finally { + ServiceUtils.cleanup(logger, search) + } + } + + @throws[AuthenticationException] + private def createDirSearch(user: String, password: String): DirSearch = { if (StringUtils.isBlank(user)) { throw new AuthenticationException(s"Error validating LDAP user, user is null" + s" or contains blank space") } - if (StringUtils.isBlank(password)) { + if (StringUtils.isBlank(password) || password.getBytes()(0) == 0) { throw new AuthenticationException(s"Error validating LDAP user, password is null" + s" or contains blank space") } - val env = new java.util.Hashtable[String, Any]() - env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory") - env.put(Context.SECURITY_AUTHENTICATION, "simple") - - conf.get(AUTHENTICATION_LDAP_URL).foreach(env.put(Context.PROVIDER_URL, _)) - - val domain = conf.get(AUTHENTICATION_LDAP_DOMAIN) - val u = - if (!hasDomain(user) && domain.nonEmpty) { - user + "@" + domain.get - } else { - user + val principals = LdapUtils.createCandidatePrincipals(conf, user) + val iterator = principals.iterator + while (iterator.hasNext) { + val principal = iterator.next + try { + return searchFactory.getInstance(conf, principal, password) + } catch { + case ex: AuthenticationException => if (iterator.isEmpty) throw ex } - - val guidKey = conf.get(AUTHENTICATION_LDAP_GUIDKEY) - val bindDn = conf.get(AUTHENTICATION_LDAP_BASEDN) match { - case Some(dn) => guidKey + "=" + u + "," + dn - case _ => u } + throw new AuthenticationException(s"No candidate principals for $user was found.") + } - env.put(Context.SECURITY_PRINCIPAL, bindDn) - env.put(Context.SECURITY_CREDENTIALS, password) - - try { - val ctx = new InitialDirContext(env) - ctx.close() - } catch { - case e: NamingException => - throw new AuthenticationException(s"Error validating LDAP user: $bindDn", e) - } + @throws[AuthenticationException] + private def applyFilter(client: DirSearch, user: String): Unit = filterOpt.foreach { filter => + val username = if (LdapUtils.hasDomain(user)) LdapUtils.extractUserName(user) else user + filter.apply(client, username) } +} - private def hasDomain(userName: String): Boolean = ServiceUtils.indexOfDomainMatch(userName) > 0 +object LdapAuthenticationProviderImpl { + val FILTER_FACTORIES: Array[FilterFactory] = Array[FilterFactory]( + CustomQueryFilterFactory, + new ChainFilterFactory(UserSearchFilterFactory, UserFilterFactory, GroupFilterFactory)) } diff --git a/kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/ldap/ChainFilterFactory.scala b/kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/ldap/ChainFilterFactory.scala new file mode 100644 index 000000000..a5badb15d --- /dev/null +++ b/kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/ldap/ChainFilterFactory.scala @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.kyuubi.service.authentication.ldap + +import javax.security.sasl.AuthenticationException + +import org.apache.kyuubi.config.KyuubiConf + +/** + * A factory that produces a [[Filter]] that is implemented as a chain of other filters. + * The chain of filters are created as a result of [[ChainFilterFactory#getInstance]] method call. + * The resulting object filters out all users that don't pass all chained filters. + * The filters will be applied in the order they are mentioned in the factory constructor. + */ + +class ChainFilterFactory(chainedFactories: FilterFactory*) extends FilterFactory { + override def getInstance(conf: KyuubiConf): Option[Filter] = { + val maybeFilters = chainedFactories.map(_.getInstance(conf)) + val filters = maybeFilters.flatten + if (filters.isEmpty) None else Some(new ChainFilter(filters)) + } +} + +class ChainFilter(chainedFilters: Seq[Filter]) extends Filter { + @throws[AuthenticationException] + override def apply(client: DirSearch, user: String): Unit = { + chainedFilters.foreach(_.apply(client, user)) + } +} diff --git a/kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/ldap/CustomQueryFilterFactory.scala b/kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/ldap/CustomQueryFilterFactory.scala new file mode 100644 index 000000000..d10e6523b --- /dev/null +++ b/kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/ldap/CustomQueryFilterFactory.scala @@ -0,0 +1,77 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.kyuubi.service.authentication.ldap + +import javax.naming.NamingException +import javax.security.sasl.AuthenticationException + +import org.apache.kyuubi.Logging +import org.apache.kyuubi.config.KyuubiConf + +/** + * A factory for a [[Filter]] based on a custom query. + *
+ * The produced filter object filters out all users that are not found in the search result + * of the query provided in Kyuubi configuration. + * + * @see [[KyuubiConf.AUTHENTICATION_LDAP_CUSTOM_LDAP_QUERY]] + */ +object CustomQueryFilterFactory extends FilterFactory { + override def getInstance(conf: KyuubiConf): Option[Filter] = + conf.get(KyuubiConf.AUTHENTICATION_LDAP_CUSTOM_LDAP_QUERY) + .map { customQuery => new CustomQueryFilter(customQuery) } +} +class CustomQueryFilter(query: String) extends Filter with Logging { + @throws[AuthenticationException] + override def apply(client: DirSearch, user: String): Unit = { + var resultList: Array[String] = null + try { + resultList = client.executeCustomQuery(query) + } catch { + case e: NamingException => + throw new AuthenticationException(s"LDAP Authentication failed for $user", e) + } + if (resultList != null) { + resultList.foreach { matchedDn => + val shortUserName = LdapUtils.getShortName(matchedDn) + info(s"") + if (shortUserName.equalsIgnoreCase(user) || matchedDn.equalsIgnoreCase(user)) { + info("Authentication succeeded based on result set from LDAP query") + return + } + } + // try a generic user search + if (query.contains("%s")) { + val userSearchQuery = query.replace("%s", user) + info("Trying with generic user search in ldap:" + userSearchQuery) + try resultList = client.executeCustomQuery(userSearchQuery) + catch { + case e: NamingException => + throw new AuthenticationException("LDAP Authentication failed for user", e) + } + if (resultList != null && resultList.length == 1) { + info("Authentication succeeded based on result from custom user search query") + return + } + } + } + info("Authentication failed based on result set from custom LDAP query") + throw new AuthenticationException( + "Authentication failed: LDAP query from property returned no data") + } +} diff --git a/kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/ldap/DirSearch.scala b/kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/ldap/DirSearch.scala new file mode 100644 index 000000000..c1c4d5060 --- /dev/null +++ b/kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/ldap/DirSearch.scala @@ -0,0 +1,73 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.kyuubi.service.authentication.ldap + +import java.io.Closeable +import javax.naming.NamingException + +/** + * The object used for executing queries on the Directory Service. + */ +trait DirSearch extends Closeable { + + /** + * Finds user's distinguished name. + * + * @param user username + * @return DN for the specified username + */ + @throws[NamingException] + def findUserDn(user: String): String + + /** + * Finds group's distinguished name. + * + * @param group group name or unique identifier + * @return DN for the specified group name + */ + @throws[NamingException] + def findGroupDn(group: String): String + + /** + * Verifies that specified user is a member of specified group. + * + * @param user user id or distinguished name + * @param groupDn group's DN + * @return true if the user is a member of the group, false - otherwise. + */ + @throws[NamingException] + def isUserMemberOfGroup(user: String, groupDn: String): Boolean + + /** + * Finds groups that contain the specified user. + * + * @param userDn user's distinguished name + * @return list of groups + */ + @throws[NamingException] + def findGroupsForUser(userDn: String): Array[String] + + /** + * Executes an arbitrary query. + * + * @param query any query + * @return list of names in the namespace + */ + @throws[NamingException] + def executeCustomQuery(query: String): Array[String] +} diff --git a/kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/ldap/DirSearchFactory.scala b/kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/ldap/DirSearchFactory.scala new file mode 100644 index 000000000..2046632d8 --- /dev/null +++ b/kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/ldap/DirSearchFactory.scala @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.kyuubi.service.authentication.ldap + +import javax.security.sasl.AuthenticationException + +import org.apache.kyuubi.config.KyuubiConf + +/** + * A factory for [[DirSearch]]. + */ +trait DirSearchFactory { + + /** + * Returns an instance of [[DirSearch]]. + * + * @param conf Kyuubi configuration + * @param user username + * @param password user password + * @return instance of [[DirSearch]] + */ + @throws[AuthenticationException] + def getInstance(conf: KyuubiConf, user: String, password: String): DirSearch +} diff --git a/kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/ldap/Filter.scala b/kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/ldap/Filter.scala new file mode 100644 index 000000000..e57eddb0d --- /dev/null +++ b/kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/ldap/Filter.scala @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.kyuubi.service.authentication.ldap + +import javax.security.sasl.AuthenticationException + +/** + * The object that filters LDAP users. + *
+ * The assumption is that this user was already authenticated by a previous bind operation. + */ +trait Filter { + + /** + * Applies this filter to the authenticated user. + * + * @param client LDAP client that will be used for execution of LDAP queries. + * @param user username + */ + @throws[AuthenticationException] + def apply(client: DirSearch, user: String): Unit +} diff --git a/kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/ldap/FilterFactory.scala b/kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/ldap/FilterFactory.scala new file mode 100644 index 000000000..d85104684 --- /dev/null +++ b/kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/ldap/FilterFactory.scala @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.kyuubi.service.authentication.ldap + +import org.apache.kyuubi.config.KyuubiConf + +/** + * Factory for the filter. + */ +trait FilterFactory { + + /** + * Returns an instance of the corresponding filter. + * + * @param conf Kyuubi configurations used to configure the filter. + * @return Some(filter) or None if this filter doesn't support provided set of properties + */ + def getInstance(conf: KyuubiConf): Option[Filter] +} diff --git a/kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/ldap/GroupFilterFactory.scala b/kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/ldap/GroupFilterFactory.scala new file mode 100644 index 000000000..fd1c907ec --- /dev/null +++ b/kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/ldap/GroupFilterFactory.scala @@ -0,0 +1,108 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.kyuubi.service.authentication.ldap + +import javax.naming.NamingException +import javax.security.sasl.AuthenticationException + +import scala.collection.mutable.ArrayBuffer + +import org.apache.kyuubi.Logging +import org.apache.kyuubi.config.KyuubiConf + +object GroupFilterFactory extends FilterFactory { + override def getInstance(conf: KyuubiConf): Option[Filter] = { + val groupFilter = conf.get(KyuubiConf.AUTHENTICATION_LDAP_GROUP_FILTER) + if (groupFilter.isEmpty) { + None + } else if (conf.get(KyuubiConf.AUTHENTICATION_LDAP_USER_MEMBERSHIP_KEY).isDefined) { + Some(new UserMembershipKeyFilter(groupFilter)) + } else { + Some(new GroupMembershipKeyFilter(groupFilter)) + } + } +} + +class GroupMembershipKeyFilter(groupFilter: Seq[String]) extends Filter with Logging { + + @throws[AuthenticationException] + override def apply(ldap: DirSearch, user: String): Unit = { + info(s"Authenticating user '$user' using ${classOf[GroupMembershipKeyFilter].getSimpleName})") + + var memberOf: Array[String] = null + try { + val userDn = ldap.findUserDn(user) + // Workaround for magic things on Mockito: + // unmatched invocation returns an empty list if the method return type is JList, + // but null if the method return type is Array + memberOf = Option(ldap.findGroupsForUser(userDn)).getOrElse(Array.empty) + debug(s"User $userDn member of: ${memberOf.mkString(",")}") + } catch { + case e: NamingException => + throw new AuthenticationException("LDAP Authentication failed for user", e) + } + memberOf.foreach { groupDn => + val shortName = LdapUtils.getShortName(groupDn) + if (groupFilter.exists(shortName.equalsIgnoreCase)) { + debug(s"GroupMembershipKeyFilter passes: user '$user' is a member of '$groupDn' group") + info("Authentication succeeded based on group membership") + return + } + } + info("Authentication failed based on user membership") + throw new AuthenticationException( + "Authentication failed: User not a member of specified list") + } +} + +class UserMembershipKeyFilter(groupFilter: Seq[String]) extends Filter with Logging { + @throws[AuthenticationException] + override def apply(ldap: DirSearch, user: String): Unit = { + info(s"Authenticating user '$user' using $classOf[UserMembershipKeyFilter].getSimpleName") + val groupDns = new ArrayBuffer[String] + groupFilter.foreach { groupId => + try { + val groupDn = ldap.findGroupDn(groupId) + groupDns += groupDn + } catch { + case e: NamingException => + warn("Cannot find DN for group", e) + debug(s"Cannot find DN for group $groupId", e) + } + } + if (groupDns.isEmpty) { + debug(s"No DN(s) has been found for any of group(s): ${groupFilter.mkString(",")}") + throw new AuthenticationException("No DN(s) has been found for any of specified group(s)") + } + groupDns.foreach { groupDn => + try { + if (ldap.isUserMemberOfGroup(user, groupDn)) { + debug(s"UserMembershipKeyFilter passes: user '$user' is a member of '$groupDn' group") + info("Authentication succeeded based on user membership") + return + } + } catch { + case e: NamingException => + warn("Cannot match user and group", e) + debug(s"Cannot match user '$user' and group '$groupDn'", e) + } + } + throw new AuthenticationException( + s"Authentication failed: User '$user' is not a member of listed groups") + } +} diff --git a/kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/ldap/LdapSearch.scala b/kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/ldap/LdapSearch.scala new file mode 100644 index 000000000..09dca1d5c --- /dev/null +++ b/kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/ldap/LdapSearch.scala @@ -0,0 +1,126 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.kyuubi.service.authentication.ldap + +import javax.naming.{NamingEnumeration, NamingException} +import javax.naming.directory.{DirContext, SearchResult} + +import scala.collection.mutable.ArrayBuffer + +import org.apache.kyuubi.Logging +import org.apache.kyuubi.config.KyuubiConf + +/** + * Implements search for LDAP. + * @param conf Kyuubi configuration + * @param ctx Directory service that will be used for the queries. + */ +class LdapSearch(conf: KyuubiConf, ctx: DirContext) extends DirSearch with Logging { + + final private val baseDn = conf.get(KyuubiConf.AUTHENTICATION_LDAP_BASE_DN).orNull + final private val groupBases: Array[String] = + LdapUtils.patternsToBaseDns( + LdapUtils.parseDnPatterns(conf, KyuubiConf.AUTHENTICATION_LDAP_GROUP_DN_PATTERN)) + final private val userPatterns: Array[String] = + LdapUtils.parseDnPatterns(conf, KyuubiConf.AUTHENTICATION_LDAP_USER_DN_PATTERN) + final private val userBases: Array[String] = LdapUtils.patternsToBaseDns(userPatterns) + final private val queries: QueryFactory = new QueryFactory(conf) + + /** + * Closes this search object and releases any system resources associated + * with it. If the search object is already closed then invoking this + * method has no effect. + */ + override def close(): Unit = { + try ctx.close() + catch { + case e: NamingException => + warn("Exception when closing LDAP context:", e) + } + } + + @throws[NamingException] + override def findUserDn(user: String): String = { + var allLdapNames: Array[String] = null + if (LdapUtils.isDn(user)) { + val userBaseDn: String = LdapUtils.extractBaseDn(user) + val userRdn: String = LdapUtils.extractFirstRdn(user) + allLdapNames = execute(Array(userBaseDn), queries.findUserDnByRdn(userRdn)).getAllLdapNames + } else { + allLdapNames = findDnByPattern(userPatterns, user) + if (allLdapNames.isEmpty) { + allLdapNames = execute(userBases, queries.findUserDnByName(user)).getAllLdapNames + } + } + if (allLdapNames.length == 1) allLdapNames.head + else { + info(s"Expected exactly one user result for the user: $user, " + + s"but got ${allLdapNames.length}. Returning null") + debug("Matched users: $allLdapNames") + null + } + } + + @throws[NamingException] + private def findDnByPattern(patterns: Seq[String], name: String): Array[String] = { + for (pattern <- patterns) { + val baseDnFromPattern: String = LdapUtils.extractBaseDn(pattern) + val rdn = LdapUtils.extractFirstRdn(pattern).replaceAll("%s", name) + val names = execute(Array(baseDnFromPattern), queries.findDnByPattern(rdn)).getAllLdapNames + if (!names.isEmpty) return names + } + Array.empty + } + + @throws[NamingException] + override def findGroupDn(group: String): String = + execute(groupBases, queries.findGroupDnById(group)).getSingleLdapName + + @throws[NamingException] + override def isUserMemberOfGroup(user: String, groupDn: String): Boolean = { + val userId = LdapUtils.extractUserName(user) + execute(userBases, queries.isUserMemberOfGroup(userId, groupDn)).hasSingleResult + } + + @throws[NamingException] + override def findGroupsForUser(userDn: String): Array[String] = { + val userName = LdapUtils.extractUserName(userDn) + execute(groupBases, queries.findGroupsForUser(userName, userDn)).getAllLdapNames + } + + @throws[NamingException] + override def executeCustomQuery(query: String): Array[String] = + execute(Array(baseDn), queries.customQuery(query)).getAllLdapNamesAndAttributes + + private def execute(baseDns: Array[String], query: Query): SearchResultHandler = { + val searchResults = new ArrayBuffer[NamingEnumeration[SearchResult]] + debug(s"Executing a query: '${query.filter}' with base DNs ${baseDns.mkString(",")}") + baseDns.foreach { baseDn => + try { + val searchResult = ctx.search(baseDn, query.filter, query.controls) + if (searchResult != null) searchResults += searchResult + } catch { + case ex: NamingException => + debug( + s"Exception happened for query '${query.filter}' with base DN '$baseDn'", + ex) + } + } + new SearchResultHandler(searchResults.toArray) + } +} diff --git a/kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/ldap/LdapSearchFactory.scala b/kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/ldap/LdapSearchFactory.scala new file mode 100644 index 000000000..e3649d359 --- /dev/null +++ b/kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/ldap/LdapSearchFactory.scala @@ -0,0 +1,56 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.kyuubi.service.authentication.ldap + +import java.util +import javax.naming.{Context, NamingException} +import javax.naming.directory.{DirContext, InitialDirContext} +import javax.security.sasl.AuthenticationException + +import org.apache.kyuubi.Logging +import org.apache.kyuubi.config.KyuubiConf + +class LdapSearchFactory extends DirSearchFactory with Logging { + @throws[AuthenticationException] + override def getInstance(conf: KyuubiConf, principal: String, password: String): DirSearch = { + try { + val ctx = createDirContext(conf, principal, password) + new LdapSearch(conf, ctx) + } catch { + case e: NamingException => + debug(s"Could not connect to the LDAP Server: Authentication failed for $principal") + throw new AuthenticationException(s"Error validating LDAP user: $principal", e) + } + } + + @throws[NamingException] + private def createDirContext( + conf: KyuubiConf, + principal: String, + password: String): DirContext = { + val ldapUrl = conf.get(KyuubiConf.AUTHENTICATION_LDAP_URL) + val env = new util.Hashtable[String, AnyRef] + ldapUrl.foreach(env.put(Context.PROVIDER_URL, _)) + env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory") + env.put(Context.SECURITY_AUTHENTICATION, "simple") + env.put(Context.SECURITY_PRINCIPAL, principal) + env.put(Context.SECURITY_CREDENTIALS, password) + debug(s"Connecting using principal $principal to ldap server: ${ldapUrl.orNull}") + new InitialDirContext(env) + } +} diff --git a/kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/ldap/LdapUtils.scala b/kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/ldap/LdapUtils.scala new file mode 100644 index 000000000..a48f9f48f --- /dev/null +++ b/kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/ldap/LdapUtils.scala @@ -0,0 +1,199 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.kyuubi.service.authentication.ldap + +import scala.collection.mutable.ArrayBuffer + +import org.apache.kyuubi.Logging +import org.apache.kyuubi.config.{KyuubiConf, OptionalConfigEntry} +import org.apache.kyuubi.service.ServiceUtils + +/** + * Static utility methods related to LDAP authentication module. + */ +object LdapUtils extends Logging { + + /** + * Extracts a base DN from the provided distinguished name. + *
+ * Example: + *
+ * "ou=CORP,dc=mycompany,dc=com" is the base DN for "cn=user1,ou=CORP,dc=mycompany,dc=com" + * + * @param dn distinguished name + * @return base DN + */ + def extractBaseDn(dn: String): String = { + val indexOfFirstDelimiter = dn.indexOf(",") + if (indexOfFirstDelimiter > -1) { + return dn.substring(indexOfFirstDelimiter + 1) + } + null + } + + /** + * Extracts the first Relative Distinguished Name (RDN). + *
+ * Example: + *
+ * For DN "cn=user1,ou=CORP,dc=mycompany,dc=com" this method will return "cn=user1" + * + * @param dn distinguished name + * @return first RDN + */ + def extractFirstRdn(dn: String): String = dn.substring(0, dn.indexOf(",")) + + /** + * Extracts username from user DN. + *
+ * Examples: + *
+   * LdapUtils.extractUserName("UserName")                        = "UserName"
+   * LdapUtils.extractUserName("UserName@mycorp.com")             = "UserName"
+   * LdapUtils.extractUserName("cn=UserName,dc=mycompany,dc=com") = "UserName"
+   * 
+ */ + def extractUserName(userDn: String): String = { + if (!isDn(userDn) && !hasDomain(userDn)) { + return userDn + } + val domainIdx: Int = ServiceUtils.indexOfDomainMatch(userDn) + if (domainIdx > 0) { + return userDn.substring(0, domainIdx) + } + if (userDn.contains("=")) { + return userDn.substring(userDn.indexOf("=") + 1, userDn.indexOf(",")) + } + userDn + } + + /** + * Gets value part of the first attribute in the provided RDN. + *
+ * Example: + *
+ * For RDN "cn=user1,ou=CORP" this method will return "user1" + * + * @param rdn Relative Distinguished Name + * @return value part of the first attribute + */ + def getShortName(rdn: String): String = rdn.split(",")(0).split("=")(1) + + /** + * Check for a domain part in the provided username. + *
+ * Example: + *
+ *
+   * LdapUtils.hasDomain("user1@mycorp.com") = true
+   * LdapUtils.hasDomain("user1")            = false
+   * 
+ * + * @param userName username + * @return true if `userName`` contains `@` part + */ + def hasDomain(userName: String): Boolean = { + ServiceUtils.indexOfDomainMatch(userName) > 0 + } + + /** + * Detects DN names. + *
+ * Example: + *
+ *
+   * LdapUtils.isDn("cn=UserName,dc=mycompany,dc=com") = true
+   * LdapUtils.isDn("user1")                           = false
+   * 
+ * + * @param name name to be checked + * @return true if the provided name is a distinguished name + */ + def isDn(name: String): Boolean = { + name.contains("=") + } + + /** + * Reads and parses DN patterns from Kyuubi configuration. + *
+ * If no patterns are provided in the configuration, then the base DN will be used. + * + * @param conf Kyuubi configuration + * @param confKey configuration key to be read + * @return a list of DN patterns + * @see [[KyuubiConf.AUTHENTICATION_LDAP_BASE_DN]] + * @see [[KyuubiConf.AUTHENTICATION_LDAP_GUID_KEY]] + * @see [[KyuubiConf.AUTHENTICATION_LDAP_GROUP_DN_PATTERN]] + * @see [[KyuubiConf.AUTHENTICATION_LDAP_USER_DN_PATTERN]] + */ + def parseDnPatterns(conf: KyuubiConf, confKey: OptionalConfigEntry[String]): Array[String] = { + val result = new ArrayBuffer[String] + conf.get(confKey).map { patternsString => + patternsString.split(":").foreach { pattern => + if (pattern.contains(",") && pattern.contains("=")) { + result += pattern + } else { + warn(s"Unexpected format for $confKey, ignoring $pattern") + } + } + }.getOrElse { + val guidAttr = conf.get(KyuubiConf.AUTHENTICATION_LDAP_GUID_KEY) + conf.get(KyuubiConf.AUTHENTICATION_LDAP_BASE_DN).foreach { defaultBaseDn => + result += s"$guidAttr=%s,$defaultBaseDn" + } + } + result.toArray + } + + private def patternToBaseDn(pattern: String): String = + if (pattern.contains("=%s")) pattern.split(",", 2)(1) else pattern + + /** + * Converts a collection of Distinguished Name patterns to a collection of base DNs. + * + * @param patterns Distinguished Name patterns + * @return a list of base DNs + * @see [[KyuubiConf.AUTHENTICATION_LDAP_GROUP_DN_PATTERN]] + * @see [[KyuubiConf.AUTHENTICATION_LDAP_USER_DN_PATTERN]] + */ + def patternsToBaseDns(patterns: Array[String]): Array[String] = { + patterns.map(patternToBaseDn) + } + + /** + * Creates a list of principals to be used for user authentication. + * + * @param conf Kyuubi configuration + * @param user username + * @return a list of user's principals + */ + def createCandidatePrincipals(conf: KyuubiConf, user: String): Array[String] = { + if (hasDomain(user) || isDn(user)) { + return Array(user) + } + conf.get(KyuubiConf.AUTHENTICATION_LDAP_DOMAIN).map { ldapDomain => + Array(user + "@" + ldapDomain) + }.getOrElse { + val userPatterns = parseDnPatterns(conf, KyuubiConf.AUTHENTICATION_LDAP_USER_DN_PATTERN) + if (userPatterns.isEmpty) { + return Array(user) + } + userPatterns.map(_.replaceAll("%s", user)) + } + } +} diff --git a/kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/ldap/Query.scala b/kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/ldap/Query.scala new file mode 100644 index 000000000..ce9a7d472 --- /dev/null +++ b/kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/ldap/Query.scala @@ -0,0 +1,136 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.kyuubi.service.authentication.ldap + +import java.util +import javax.naming.directory.SearchControls + +import org.stringtemplate.v4.ST + +/** + * The object that encompasses all components of a Directory Service search query. + * + * @see [[LdapSearch]] + */ +object Query { + + /** + * Creates Query Builder. + * + * @return query builder. + */ + def builder: Query.QueryBuilder = new Query.QueryBuilder + + /** + * A builder of the [[Query]]. + */ + final class QueryBuilder { + private var filterTemplate: ST = _ + private val controls: SearchControls = { + val _controls = new SearchControls + _controls.setSearchScope(SearchControls.SUBTREE_SCOPE) + _controls.setReturningAttributes(new Array[String](0)) + _controls + } + private val returningAttributes: util.List[String] = new util.ArrayList[String] + + /** + * Sets search filter template. + * + * @param filterTemplate search filter template + * @return the current instance of the builder + */ + def filter(filterTemplate: String): Query.QueryBuilder = { + this.filterTemplate = new ST(filterTemplate) + this + } + + /** + * Sets mapping between names in the search filter template and actual values. + * + * @param key marker in the search filter template. + * @param value actual value + * @return the current instance of the builder + */ + def map(key: String, value: String): Query.QueryBuilder = { + filterTemplate.add(key, value) + this + } + + /** + * Sets mapping between names in the search filter template and actual values. + * + * @param key marker in the search filter template. + * @param values array of values + * @return the current instance of the builder + */ + def map(key: String, values: Array[String]): Query.QueryBuilder = { + filterTemplate.add(key, values) + this + } + + /** + * Sets attribute that should be returned in results for the query. + * + * @param attributeName attribute name + * @return the current instance of the builder + */ + def returnAttribute(attributeName: String): Query.QueryBuilder = { + returningAttributes.add(attributeName) + this + } + + /** + * Sets the maximum number of entries to be returned as a result of the search. + *
+ * 0 indicates no limit: all entries will be returned. + * + * @param limit The maximum number of entries that will be returned. + * @return the current instance of the builder + */ + def limit(limit: Int): Query.QueryBuilder = { + controls.setCountLimit(limit) + this + } + + private def validate(): Unit = { + require(filterTemplate != null, "filter is required for LDAP search query") + } + + private def createFilter: String = filterTemplate.render + + private def updateControls(): Unit = { + if (!returningAttributes.isEmpty) controls.setReturningAttributes( + returningAttributes.toArray(new Array[String](returningAttributes.size))) + } + + /** + * Builds an instance of [[Query]]. + * + * @return configured directory service query + */ + def build: Query = { + validate() + val filter: String = createFilter + updateControls() + new Query(filter, controls) + } + } +} + +case class Query(filter: String, controls: SearchControls) diff --git a/kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/ldap/QueryFactory.scala b/kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/ldap/QueryFactory.scala new file mode 100644 index 000000000..849006e38 --- /dev/null +++ b/kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/ldap/QueryFactory.scala @@ -0,0 +1,145 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.kyuubi.service.authentication.ldap + +import org.apache.kyuubi.config.KyuubiConf + +/** + * A factory for common types of directory service search queries. + */ +final class QueryFactory(conf: KyuubiConf) { + private val USER_OBJECT_CLASSES = Array("person", "user", "inetOrgPerson") + + private val guidAttr = conf.get(KyuubiConf.AUTHENTICATION_LDAP_GUID_KEY) + private val groupClassAttr = conf.get(KyuubiConf.AUTHENTICATION_LDAP_GROUP_CLASS_KEY) + private val groupMembershipAttr = conf.get(KyuubiConf.AUTHENTICATION_LDAP_GROUP_MEMBERSHIP_KEY) + private val userMembershipAttrOpt = conf.get(KyuubiConf.AUTHENTICATION_LDAP_USER_MEMBERSHIP_KEY) + + /** + * Returns a query for finding Group DN based on group unique ID. + * + * @param groupId group unique identifier + * @return an instance of [[Query]] + */ + def findGroupDnById(groupId: String): Query = Query.builder + .filter("(&(objectClass=)(=))") + .map("guidAttr", guidAttr) + .map("groupClassAttr", groupClassAttr) + .map("groupID", groupId).limit(2) + .build + + /** + * Returns a query for finding user DN based on user RDN. + * + * @param userRdn user RDN + * @return an instance of [[Query]] + */ + def findUserDnByRdn(userRdn: String): Query = Query.builder + .filter("(&(|)}>)())") + .limit(2) + .map("classes", USER_OBJECT_CLASSES) + .map("userRdn", userRdn).build + + /** + * Returns a query for finding user DN based on DN pattern. + *
+ * Name of this method was derived from the original implementation of LDAP authentication. + * This method should be replaced by [[QueryFactory.findUserDnByRdn]]. + * + * @param rdn user RDN + * @return an instance of [[Query]] + */ + def findDnByPattern(rdn: String): Query = Query.builder + .filter("()") + .map("rdn", rdn) + .limit(2) + .build + + /** + * Returns a query for finding user DN based on user unique name. + * + * @param userName user unique name (uid or sAMAccountName) + * @return an instance of [[Query]] + */ + def findUserDnByName(userName: String): Query = Query.builder + .filter("(&(|)}>)" + + "(|(uid=)(sAMAccountName=)))") + .map("classes", USER_OBJECT_CLASSES) + .map("userName", userName) + .limit(2) + .build + + /** + * Returns a query for finding groups to which the user belongs. + * + * @param userName username + * @param userDn user DN + * @return an instance of [[Query]] + */ + def findGroupsForUser(userName: String, userDn: String): Query = Query.builder + .filter("(&(objectClass=)" + + "(|(=)(=)))") + .map("groupClassAttr", groupClassAttr) + .map("groupMembershipAttr", groupMembershipAttr) + .map("userName", userName) + .map("userDn", userDn) + .build + + /** + * Returns a query for checking whether specified user is a member of specified group. + * + * The query requires [[KyuubiConf.AUTHENTICATION_LDAP_USER_MEMBERSHIP_KEY]] + * configuration property to be set. + * + * @param userId user unique identifier + * @param groupDn group DN + * @return an instance of [[Query]] + * @see [[KyuubiConf.AUTHENTICATION_LDAP_USER_MEMBERSHIP_KEY]] + */ + def isUserMemberOfGroup(userId: String, groupDn: String): Query = { + require( + userMembershipAttrOpt.isDefined, + s"${KyuubiConf.AUTHENTICATION_LDAP_USER_MEMBERSHIP_KEY.key} is not configured.") + + Query.builder + .filter("(&(|)}>)" + + "(=)(=))") + .map("classes", USER_OBJECT_CLASSES) + .map("guidAttr", guidAttr) + .map("userMembershipAttr", userMembershipAttrOpt.get) + .map("userId", userId) + .map("groupDn", groupDn) + .limit(2) + .build + } + + /** + * Returns a query object created for the custom filter. + *
+ * This query is configured to return a group membership attribute as part of the search result. + * + * @param searchFilter custom search filter + * @return an instance of [[Query]] + */ + def customQuery(searchFilter: String): Query = { + val builder = Query.builder + builder.filter(searchFilter) + builder.returnAttribute(groupMembershipAttr) + builder.build + } +} diff --git a/kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/ldap/SearchResultHandler.scala b/kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/ldap/SearchResultHandler.scala new file mode 100644 index 000000000..52d5b6a90 --- /dev/null +++ b/kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/ldap/SearchResultHandler.scala @@ -0,0 +1,148 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.kyuubi.service.authentication.ldap + +import javax.naming.{NamingEnumeration, NamingException} +import javax.naming.directory.SearchResult + +import scala.collection.mutable.ArrayBuffer + +import org.apache.kyuubi.Logging + +/** + * The object that handles Directory Service search results. + * In most cases it converts search results into a list of names in the namespace. + */ +object SearchResultHandler { + + /** + * An interface used by [[SearchResultHandler]] for processing records of + * a [[SearchResult]] on a per-record basis. + *
+ * Implementations of this interface perform the actual work of processing each record, + * but don't need to worry about exception handling, closing underlying data structures, + * and combining results from several search requests. + * + * @see SearchResultHandler + */ + trait RecordProcessor extends (SearchResult => Boolean) { + + /** + * Implementations must implement this method to process each record in [[SearchResult]]. + * + * @param record the [[SearchResult]] to precess + * @return true to continue processing, false to stop iterating + * over search results + */ + @throws[NamingException] + override def apply(record: SearchResult): Boolean + } +} + +/** + * Constructs a search result handler object for the provided search results. + * + * @param searchResults directory service search results + */ +class SearchResultHandler(val searchResults: Array[NamingEnumeration[SearchResult]]) + extends Logging { + + /** + * Returns all entries from the search result. + * + * @return a list of names in the namespace + */ + @throws[NamingException] + def getAllLdapNames: Array[String] = { + val result = new ArrayBuffer[String] + handle { record => result += record.getNameInNamespace; true } + result.toArray + } + + /** + * Checks whether search result contains exactly one entry. + * + * @return true if the search result contains a single entry. + */ + @throws[NamingException] + def hasSingleResult: Boolean = { + val allResults = getAllLdapNames + allResults != null && allResults.length == 1 + } + + /** + * Returns a single entry from the search result. + * Throws [[NamingException]] if the search result doesn't contain exactly one entry. + * + * @return name in the namespace + */ + @throws[NamingException] + def getSingleLdapName: String = { + val allLdapNames = getAllLdapNames + if (allLdapNames.length == 1) return allLdapNames.head + throw new NamingException("Single result was expected") + } + + /** + * Returns all entries and all attributes for these entries. + * + * @return a list that includes all entries and all attributes from these entries. + */ + @throws[NamingException] + def getAllLdapNamesAndAttributes: Array[String] = { + val result = new ArrayBuffer[String] + + @throws[NamingException] + def addAllAttributeValuesToResult(values: NamingEnumeration[_]): Unit = { + while (values.hasMore) result += String.valueOf(values.next) + } + handle { record => + result += record.getNameInNamespace + val allAttributes = record.getAttributes.getAll + while (allAttributes.hasMore) { + val attribute = allAttributes.next + addAllAttributeValuesToResult(attribute.getAll) + } + true + } + result.toArray + } + + /** + * Allows for custom processing of the search results. + * + * @param processor [[SearchResultHandler.RecordProcessor]] implementation + */ + @throws[NamingException] + def handle(processor: SearchResultHandler.RecordProcessor): Unit = { + try { + searchResults.foreach { searchResult => + while (searchResult.hasMore) if (!processor.apply(searchResult.next)) return + } + } finally { + searchResults.foreach { searchResult => + try { + searchResult.close() + } catch { + case ex: NamingException => + warn("Failed to close LDAP search result", ex) + } + } + } + } +} diff --git a/kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/ldap/UserFilterFactory.scala b/kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/ldap/UserFilterFactory.scala new file mode 100644 index 000000000..7c2f22ed8 --- /dev/null +++ b/kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/ldap/UserFilterFactory.scala @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.kyuubi.service.authentication.ldap + +import javax.security.sasl.AuthenticationException + +import org.apache.kyuubi.Logging +import org.apache.kyuubi.config.KyuubiConf + +object UserFilterFactory extends FilterFactory with Logging { + override def getInstance(conf: KyuubiConf): Option[Filter] = { + val userFilter = conf.get(KyuubiConf.AUTHENTICATION_LDAP_USER_FILTER) + if (userFilter.isEmpty) None else Some(new UserFilter(userFilter)) + } +} + +class UserFilter(_userFilter: Seq[String]) extends Filter with Logging { + + lazy val userFilter: Seq[String] = _userFilter.map(_.toLowerCase) + + @throws[AuthenticationException] + override def apply(ldap: DirSearch, user: String): Unit = { + info("Authenticating user '$user' using user filter") + val userName = LdapUtils.extractUserName(user).toLowerCase + if (!userFilter.contains(userName)) { + info("Authentication failed based on user membership") + throw new AuthenticationException( + "Authentication failed: User not a member of specified list") + } + } +} diff --git a/kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/ldap/UserSearchFilterFactory.scala b/kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/ldap/UserSearchFilterFactory.scala new file mode 100644 index 000000000..9e8bdf364 --- /dev/null +++ b/kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/ldap/UserSearchFilterFactory.scala @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.kyuubi.service.authentication.ldap + +import javax.naming.NamingException +import javax.security.sasl.AuthenticationException + +import org.apache.kyuubi.config.KyuubiConf + +/** + * A factory for a [[Filter]] that check whether provided user could be found in the directory. + *
+ * The produced filter object filters out all users that are not found in the directory. + */ +object UserSearchFilterFactory extends FilterFactory { + override def getInstance(conf: KyuubiConf): Option[Filter] = { + val groupFilter = conf.get(KyuubiConf.AUTHENTICATION_LDAP_GROUP_FILTER) + val userFilter = conf.get(KyuubiConf.AUTHENTICATION_LDAP_USER_FILTER) + if (groupFilter.isEmpty && userFilter.isEmpty) None else Some(UserSearchFilter) + } +} + +object UserSearchFilter extends Filter { + @throws[AuthenticationException] + override def apply(client: DirSearch, user: String): Unit = { + try { + val userDn = client.findUserDn(user) + // This should not be null because we were allowed to bind with this username + // safe check in case we were able to bind anonymously. + if (userDn == null) { + throw new AuthenticationException("Authentication failed: User search failed") + } + } catch { + case e: NamingException => + throw new AuthenticationException("LDAP Authentication failed for user", e) + } + } +} diff --git a/kyuubi-common/src/test/resources/ldap/ad.example.com.ldif b/kyuubi-common/src/test/resources/ldap/ad.example.com.ldif new file mode 100644 index 000000000..68cd01d0f --- /dev/null +++ b/kyuubi-common/src/test/resources/ldap/ad.example.com.ldif @@ -0,0 +1,150 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +dn: dc=ad,dc=example,dc=com +dc: ad +objectClass: top +objectClass: domain + +dn: ou=Engineering,dc=ad,dc=example,dc=com +objectClass: top +objectClass: organizationalUnit +ou: Engineering + +dn: ou=Management,dc=ad,dc=example,dc=com +objectClass: top +objectClass: organizationalUnit +ou: Management + +dn: ou=Administration,dc=ad,dc=example,dc=com +objectClass: top +objectClass: organizationalUnit +ou: Administration + +dn: ou=Teams,dc=ad,dc=example,dc=com +objectClass: top +objectClass: organizationalUnit +ou: Teams + +dn: ou=Resources,dc=ad,dc=example,dc=com +objectClass: top +objectClass: organizationalUnit +ou: Resources + +dn: cn=Team 1,ou=Teams,dc=ad,dc=example,dc=com +objectClass: top +objectClass: groupOfNames +objectClass: microsoftSecurityPrincipal +sAMAccountName: team1 +cn: Team 1 +member: sAMAccountName=engineer1,ou=Engineering,dc=ad,dc=example,dc=com +member: sAMAccountName=manager1,ou=Management,dc=ad,dc=example,dc=com + +dn: cn=Team 2,ou=Teams,dc=ad,dc=example,dc=com +objectClass: top +objectClass: groupOfNames +objectClass: microsoftSecurityPrincipal +sAMAccountName: team2 +cn: Team 2 +member: sAMAccountName=engineer2,ou=Engineering,dc=ad,dc=example,dc=com +member: sAMAccountName=manager2,ou=Management,dc=ad,dc=example,dc=com + +dn: cn=Resource 1,ou=Resources,dc=ad,dc=example,dc=com +objectClass: top +objectClass: groupOfNames +objectClass: microsoftSecurityPrincipal +sAMAccountName: resource1 +cn: Resource 1 +member: sAMAccountName=engineer1,ou=Engineering,dc=ad,dc=example,dc=com + +dn: cn=Resource 2,ou=Resources,dc=ad,dc=example,dc=com +objectClass: top +objectClass: groupOfNames +objectClass: microsoftSecurityPrincipal +sAMAccountName: resource2 +cn: Resource 2 +member: sAMAccountName=engineer2,ou=Engineering,dc=ad,dc=example,dc=com + +dn: cn=Admins,ou=Administration,dc=ad,dc=example,dc=com +objectClass: top +objectClass: groupOfUniqueNames +objectClass: microsoftSecurityPrincipal +sAMAccountName: admins +cn: Admins +uniqueMember: sAMAccountName=admin1,ou=Administration,dc=ad,dc=example,dc=com + +dn: sAMAccountName=engineer1,ou=Engineering,dc=ad,dc=example,dc=com +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: inetOrgPerson +objectClass: microsoftSecurityPrincipal +sAMAccountName: engineer1 +cn: Engineer 1 +sn: Surname 1 +userPassword: engineer1-password +memberOf: cn=Team 1,ou=Teams,dc=ad,dc=example,dc=com +memberOf: cn=Resource 1,ou=Resources,dc=ad,dc=example,dc=com + +dn: sAMAccountName=engineer2,ou=Engineering,dc=ad,dc=example,dc=com +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: inetOrgPerson +objectClass: microsoftSecurityPrincipal +sAMAccountName: engineer2 +cn: Engineer 2 +sn: Surname 2 +userPassword: engineer2-password +memberOf: cn=Team 2,ou=Teams,dc=ad,dc=example,dc=com +memberOf: cn=Resource 2,ou=Resources,dc=ad,dc=example,dc=com + +dn: sAMAccountName=manager1,ou=Management,dc=ad,dc=example,dc=com +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: inetOrgPerson +objectClass: microsoftSecurityPrincipal +sAMAccountName: manager1 +cn: Manager 1 +sn: Surname 1 +userPassword: manager1-password +memberOf: cn=Team 1,ou=Teams,dc=ad,dc=example,dc=com + +dn: sAMAccountName=manager2,ou=Management,dc=ad,dc=example,dc=com +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: inetOrgPerson +objectClass: microsoftSecurityPrincipal +sAMAccountName: manager2 +cn: Manager 2 +sn: Surname 2 +userPassword: manager2-password +memberOf: cn=Team 2,ou=Teams,dc=ad,dc=example,dc=com + +dn: sAMAccountName=admin1,ou=Administration,dc=ad,dc=example,dc=com +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: inetOrgPerson +objectClass: microsoftSecurityPrincipal +sAMAccountName: admin1 +cn: Admin 1 +sn: Surname 1 +userPassword: admin1-password +memberOf: cn=Admins,ou=Administration,dc=ad,dc=example,dc=com diff --git a/kyuubi-common/src/test/resources/ldap/example.com.ldif b/kyuubi-common/src/test/resources/ldap/example.com.ldif new file mode 100644 index 000000000..f19eb2f93 --- /dev/null +++ b/kyuubi-common/src/test/resources/ldap/example.com.ldif @@ -0,0 +1,113 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +dn: ou=People,dc=example,dc=com +objectClass: top +objectClass: organizationalUnit +ou: People +description: Contains entries which describe persons (seamen) + +dn: ou=Groups,dc=example,dc=com +objectClass: top +objectClass: organizationalUnit +ou: Groups +description: Contains entries which describe groups (crews, for instance) + +dn: uid=group1,ou=Groups,dc=example,dc=com +objectClass: top +objectClass: groupOfNames +objectClass: uidObject +uid: group1 +cn: group1 +ou: Groups +member: uid=user1,ou=People,dc=example,dc=com + +dn: uid=group2,ou=Groups,dc=example,dc=com +objectClass: top +objectClass: groupOfNames +objectClass: uidObject +uid: group2 +cn: group2 +ou: Groups +member: uid=user2,ou=People,dc=example,dc=com + +dn: cn=group3,ou=Groups,dc=example,dc=com +objectClass: top +objectClass: groupOfNames +objectClass: uidObject +uid: group3 +cn: group3 +ou: Groups +member: cn=user3,ou=People,dc=example,dc=com + +dn: cn=group4,ou=Groups,dc=example,dc=com +objectClass: top +objectClass: groupOfUniqueNames +objectClass: uidObject +uid: group4 +ou: Groups +cn: group4 +uniqueMember: cn=user4,ou=People,dc=example,dc=com + +dn: uid=user1,ou=People,dc=example,dc=com +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: inetOrgPerson +objectClass: uidObject +givenName: Test1 +cn: Test User1 +sn: user1 +uid: user1 +userPassword: user1 + +dn: uid=user2,ou=People,dc=example,dc=com +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: inetOrgPerson +objectClass: uidObject +givenName: Test2 +cn: Test User2 +sn: user2 +uid: user2 +userPassword: user2 + +dn: cn=user3,ou=People,dc=example,dc=com +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: inetOrgPerson +objectClass: uidObject +givenName: Test3 +cn: Test User3 +sn: user3 +uid: user3 +userPassword: user3 + +dn: cn=user4,ou=People,dc=example,dc=com +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: inetOrgPerson +objectClass: uidObject +givenName: Test4 +cn: Test User4 +sn: user4 +uid: user4 +userPassword: user4 + diff --git a/kyuubi-common/src/test/resources/ldap/microsoft.schema.ldif b/kyuubi-common/src/test/resources/ldap/microsoft.schema.ldif new file mode 100644 index 000000000..3e3a9a5c1 --- /dev/null +++ b/kyuubi-common/src/test/resources/ldap/microsoft.schema.ldif @@ -0,0 +1,62 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +dn: cn=microsoft, ou=schema +objectclass: metaSchema +objectclass: top +cn: microsoft + +dn: ou=attributetypes, cn=microsoft, ou=schema +objectclass: organizationalUnit +objectclass: top +ou: attributetypes + +dn: m-oid=1.2.840.113556.1.4.221, ou=attributetypes, cn=microsoft, ou=schema +objectclass: metaAttributeType +objectclass: metaTop +objectclass: top +m-oid: 1.2.840.113556.1.4.221 +m-name: sAMAccountName +m-equality: caseIgnoreMatch +m-syntax: 1.3.6.1.4.1.1466.115.121.1.15 +m-singleValue: TRUE + +dn: m-oid=1.2.840.113556.1.4.222, ou=attributetypes, cn=microsoft, ou=schema +objectclass: metaAttributeType +objectclass: metaTop +objectclass: top +m-oid: 1.2.840.113556.1.4.222 +m-name: memberOf +m-equality: caseIgnoreMatch +m-syntax: 1.3.6.1.4.1.1466.115.121.1.15 +m-singleValue: FALSE + +dn: ou=objectClasses, cn=microsoft, ou=schema +objectclass: organizationalUnit +objectclass: top +ou: objectClasses + +dn: m-oid=1.2.840.113556.1.5.6, ou=objectClasses, cn=microsoft, ou=schema +objectclass: metaObjectClass +objectclass: metaTop +objectclass: top +m-oid: 1.2.840.113556.1.5.6 +m-name: microsoftSecurityPrincipal +m-supObjectClass: top +m-typeObjectClass: AUXILIARY +m-must: sAMAccountName +m-may: memberOf diff --git a/kyuubi-common/src/test/scala/org/apache/kyuubi/service/authentication/LdapAtnProviderSuite.scala b/kyuubi-common/src/test/scala/org/apache/kyuubi/service/authentication/LdapAtnProviderSuite.scala new file mode 100644 index 000000000..c3c67e421 --- /dev/null +++ b/kyuubi-common/src/test/scala/org/apache/kyuubi/service/authentication/LdapAtnProviderSuite.scala @@ -0,0 +1,493 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.kyuubi.service.authentication + +import com.unboundid.ldap.sdk.Entry + +import org.apache.kyuubi.service.authentication.ldap.{LdapAuthenticationTestCase, User} + +class LdapAtnProviderSuite extends WithLdapServer { + + override protected val ldapBaseDn: Array[String] = Array( + "dc=example,dc=com", + "cn=microsoft,ou=schema") + + private val GROUP1_NAME = "group1" + private val GROUP2_NAME = "group2" + private val GROUP3_NAME = "group3" + private val GROUP4_NAME = "group4" + + private val GROUP_ADMINS_NAME = "admins" + private val GROUP_TEAM1_NAME = "team1" + private val GROUP_TEAM2_NAME = "team2" + private val GROUP_RESOURCE1_NAME = "resource1" + private val GROUP_RESOURCE2_NAME = "resource2" + + private val USER1 = + User.useIdForPassword(id = "user1", dn = "uid=user1,ou=People,dc=example,dc=com") + + private val USER2 = + User.useIdForPassword(id = "user2", dn = "uid=user2,ou=People,dc=example,dc=com") + + private val USER3 = + User.useIdForPassword(id = "user3", dn = "cn=user3,ou=People,dc=example,dc=com") + + private val USER4 = + User.useIdForPassword(id = "user4", dn = "cn=user4,ou=People,dc=example,dc=com") + + private val ENGINEER_1 = User( + id = "engineer1", + dn = "sAMAccountName=engineer1,ou=Engineering,dc=ad,dc=example,dc=com", + password = "engineer1-password") + + private val ENGINEER_2 = User( + id = "engineer2", + dn = "sAMAccountName=engineer2,ou=Engineering,dc=ad,dc=example,dc=com", + password = "engineer2-password") + + private val MANAGER_1 = User( + id = "manager1", + dn = "sAMAccountName=manager1,ou=Management,dc=ad,dc=example,dc=com", + password = "manager1-password") + + private val MANAGER_2 = User( + id = "manager2", + dn = "sAMAccountName=manager2,ou=Management,dc=ad,dc=example,dc=com", + password = "manager2-password") + + private val ADMIN_1 = User( + id = "admin1", + dn = "sAMAccountName=admin1,ou=Administration,dc=ad,dc=example,dc=com", + password = "admin1-password") + + private var testCase: LdapAuthenticationTestCase = _ + + private def defaultBuilder = LdapAuthenticationTestCase.builder.ldapUrl(ldapUrl) + + override def beforeAll(): Unit = { + super.beforeAll() + ldapServer.add(new Entry( + "dn: dc=example,dc=com", + "dc: example", + "objectClass: top", + "objectClass: domain")) + + applyLDIF("ldap/example.com.ldif") + applyLDIF("ldap/microsoft.schema.ldif") + applyLDIF("ldap/ad.example.com.ldif") + } + + test("In-Memory LDAP server is started") { + assert(ldapServer.getListenPort > 0) + } + + test("UserBindPositiveWithShortname") { + testCase = defaultBuilder + .userDNPatterns("uid=%s,ou=People,dc=example,dc=com") + .groupDNPatterns("uid=%s,ou=Groups,dc=example,dc=com") + .build + testCase.assertAuthenticatePasses(USER1.credentialsWithId) + testCase.assertAuthenticatePasses(USER2.credentialsWithId) + } + + test("UserBindPositiveWithShortnameOldConfig") { + testCase = defaultBuilder + .baseDN("ou=People,dc=example,dc=com") + .build + testCase.assertAuthenticatePasses(USER1.credentialsWithId) + testCase.assertAuthenticatePasses(USER2.credentialsWithId) + } + + test("UserBindNegativeWithShortname") { + testCase = defaultBuilder + .userDNPatterns("uid=%s,ou=People,dc=example,dc=com") + .groupDNPatterns("uid=%s,ou=Groups,dc=example,dc=com") + .build + testCase.assertAuthenticateFailsUsingWrongPassword(USER1.credentialsWithId) + testCase.assertAuthenticateFailsUsingWrongPassword(USER2.credentialsWithId) + } + + test("UserBindNegativeWithShortnameOldConfig") { + testCase = defaultBuilder + .baseDN("ou=People,dc=example,dc=com") + .build + testCase.assertAuthenticateFailsUsingWrongPassword(USER1.credentialsWithId) + testCase.assertAuthenticateFails(USER1.dn, USER2.password) + testCase.assertAuthenticateFailsUsingWrongPassword(USER2.credentialsWithId) + } + + test("UserBindPositiveWithDN") { + testCase = defaultBuilder + .userDNPatterns("uid=%s,ou=People,dc=example,dc=com") + .groupDNPatterns("uid=%s,ou=Groups,dc=example,dc=com") + .build + testCase.assertAuthenticatePasses(USER1.credentialsWithDn) + testCase.assertAuthenticatePasses(USER2.credentialsWithDn) + } + + test("UserBindPositiveWithDNOldConfig") { + testCase = defaultBuilder + .baseDN("ou=People,dc=example,dc=com") + .build + testCase.assertAuthenticatePasses(USER1.credentialsWithDn) + testCase.assertAuthenticatePasses(USER2.credentialsWithDn) + } + + test("UserBindPositiveWithDNWrongOldConfig") { + testCase = defaultBuilder + .baseDN("ou=DummyPeople,dc=example,dc=com") + .build + testCase.assertAuthenticatePasses(USER1.credentialsWithDn) + testCase.assertAuthenticatePasses(USER2.credentialsWithDn) + } + + test("UserBindPositiveWithDNWrongConfig") { + testCase = defaultBuilder + .userDNPatterns("uid=%s,ou=DummyPeople,dc=example,dc=com") + .groupDNPatterns("uid=%s,ou=DummyGroups,dc=example,dc=com") + .build + testCase.assertAuthenticatePasses(USER1.credentialsWithDn) + testCase.assertAuthenticatePasses(USER2.credentialsWithDn) + } + + test("UserBindPositiveWithDNBlankConfig") { + testCase = defaultBuilder + .userDNPatterns(" ") + .groupDNPatterns(" ") + .build + testCase.assertAuthenticatePasses(USER1.credentialsWithDn) + testCase.assertAuthenticatePasses(USER2.credentialsWithDn) + } + + test("UserBindPositiveWithDNBlankOldConfig") { + testCase = defaultBuilder.baseDN("").build + testCase.assertAuthenticatePasses(USER1.credentialsWithDn) + testCase.assertAuthenticatePasses(USER2.credentialsWithDn) + } + + test("UserBindNegativeWithDN") { + testCase = defaultBuilder + .userDNPatterns("uid=%s,ou=People,dc=example,dc=com") + .groupDNPatterns("uid=%s,ou=Groups,dc=example,dc=com") + .build + testCase.assertAuthenticateFailsUsingWrongPassword(USER1.credentialsWithDn) + testCase.assertAuthenticateFails(USER1.dn, USER2.password) + testCase.assertAuthenticateFailsUsingWrongPassword(USER2.credentialsWithDn) + } + + test("UserBindNegativeWithDNOldConfig") { + testCase = defaultBuilder + .baseDN("ou=People,dc=example,dc=com") + .build + testCase.assertAuthenticateFailsUsingWrongPassword(USER1.credentialsWithDn) + testCase.assertAuthenticateFails(USER1.dn, USER2.password) + testCase.assertAuthenticateFailsUsingWrongPassword(USER2.credentialsWithDn) + } + + test("UserFilterPositive") { + testCase = defaultBuilder + .userDNPatterns("uid=%s,ou=People,dc=example,dc=com") + .userFilters(USER1.id) + .build + testCase.assertAuthenticatePasses(USER1.credentialsWithId) + testCase.assertAuthenticatePasses(USER1.credentialsWithDn) + testCase = defaultBuilder + .userDNPatterns("uid=%s,ou=People,dc=example,dc=com") + .userFilters(USER2.id) + .build + testCase.assertAuthenticatePasses(USER2.credentialsWithId) + testCase.assertAuthenticatePasses(USER2.credentialsWithDn) + testCase = defaultBuilder + .userDNPatterns("uid=%s,ou=People,dc=example,dc=com") + .userFilters(USER1.id, USER2.id) + .build + testCase.assertAuthenticatePasses(USER1.credentialsWithId) + testCase.assertAuthenticatePasses(USER1.credentialsWithDn) + testCase.assertAuthenticatePasses(USER2.credentialsWithId) + testCase.assertAuthenticatePasses(USER2.credentialsWithDn) + } + + test("UserFilterNegative") { + testCase = defaultBuilder + .userDNPatterns("uid=%s,ou=People,dc=example,dc=com") + .userFilters(USER2.id) + .build + testCase.assertAuthenticateFails(USER1.credentialsWithId) + testCase.assertAuthenticateFails(USER1.credentialsWithDn) + testCase = defaultBuilder + .userDNPatterns("uid=%s,ou=People,dc=example,dc=com") + .userFilters(USER1.id) + .build + testCase.assertAuthenticateFails(USER2.credentialsWithId) + testCase.assertAuthenticateFails(USER2.credentialsWithDn) + testCase = defaultBuilder + .userDNPatterns("uid=%s,ou=People,dc=example,dc=com") + .userFilters(USER3.id) + .build + testCase.assertAuthenticateFails(USER1.credentialsWithId) + testCase.assertAuthenticateFails(USER2.credentialsWithId) + } + + test("GroupFilterPositive") { + testCase = defaultBuilder + .userDNPatterns("uid=%s,ou=People,dc=example,dc=com") + .groupDNPatterns("uid=%s,ou=Groups,dc=example,dc=com") + .groupFilters(GROUP1_NAME, GROUP2_NAME) + .build + testCase.assertAuthenticatePasses(USER1.credentialsWithId) + testCase.assertAuthenticatePasses(USER1.credentialsWithDn) + testCase.assertAuthenticatePasses(USER2.credentialsWithId) + testCase.assertAuthenticatePasses(USER2.credentialsWithDn) + testCase = defaultBuilder + .userDNPatterns("uid=%s,ou=People,dc=example,dc=com") + .groupDNPatterns("uid=%s,ou=Groups,dc=example,dc=com") + .groupFilters(GROUP2_NAME) + .build + testCase.assertAuthenticatePasses(USER2.credentialsWithId) + testCase.assertAuthenticatePasses(USER2.credentialsWithDn) + } + + test("GroupFilterNegative") { + testCase = defaultBuilder + .userDNPatterns("uid=%s,ou=People,dc=example,dc=com") + .groupDNPatterns("uid=%s,ou=Groups,dc=example,dc=com") + .groupFilters(GROUP2_NAME) + .build + testCase.assertAuthenticateFails(USER1.credentialsWithId) + testCase.assertAuthenticateFails(USER1.credentialsWithDn) + testCase = defaultBuilder + .userDNPatterns("uid=%s,ou=People,dc=example,dc=com") + .groupDNPatterns("uid=%s,ou=Groups,dc=example,dc=com") + .groupFilters(GROUP1_NAME) + .build + testCase.assertAuthenticateFails(USER2.credentialsWithId) + testCase.assertAuthenticateFails(USER2.credentialsWithDn) + } + + test("UserAndGroupFilterPositive") { + testCase = defaultBuilder + .userDNPatterns("uid=%s,ou=People,dc=example,dc=com") + .groupDNPatterns("uid=%s,ou=Groups,dc=example,dc=com") + .userFilters(USER1.id, USER2.id) + .groupFilters(GROUP1_NAME, GROUP2_NAME) + .build + testCase.assertAuthenticatePasses(USER1.credentialsWithId) + testCase.assertAuthenticatePasses(USER1.credentialsWithDn) + testCase.assertAuthenticatePasses(USER2.credentialsWithId) + testCase.assertAuthenticatePasses(USER2.credentialsWithDn) + } + + test("UserAndGroupFilterNegative") { + testCase = defaultBuilder + .userDNPatterns("uid=%s,ou=People,dc=example,dc=com") + .groupDNPatterns("uid=%s,ou=Groups,dc=example,dc=com") + .userFilters(USER1.id, USER2.id) + .groupFilters(GROUP3_NAME, GROUP3_NAME) + .build + testCase.assertAuthenticateFails(USER2.credentialsWithDn) + testCase.assertAuthenticateFails(USER2.credentialsWithId) + testCase.assertAuthenticateFails(USER3.credentialsWithDn) + testCase.assertAuthenticateFails(USER3.credentialsWithId) + } + + test("CustomQueryPositive") { + testCase = defaultBuilder + .baseDN("ou=People,dc=example,dc=com") + .userDNPatterns("cn=%s,ou=People,dc=example,dc=com", "uid=%s,ou=People,dc=example,dc=com") + .groupDNPatterns("cn=%s,ou=People,dc=example,dc=com") + .customQuery(String.format("(&(objectClass=person)(|(uid=%s)(uid=%s)))", USER1.id, USER4.id)) + .build + testCase.assertAuthenticatePasses(USER1.credentialsWithId) + testCase.assertAuthenticatePasses(USER1.credentialsWithDn) + testCase.assertAuthenticatePasses(USER4.credentialsWithId) + testCase.assertAuthenticatePasses(USER4.credentialsWithDn) + testCase = defaultBuilder + .baseDN("ou=People,dc=example,dc=com") + .customQuery("(&(objectClass=person)(uid=%s))") + .build + testCase.assertAuthenticatePasses(USER1.credentialsWithId) + testCase.assertAuthenticatePasses(USER2.credentialsWithId) + } + + test("CustomQueryNegative") { + testCase = defaultBuilder + .baseDN("ou=People,dc=example,dc=com") + .customQuery("(&(objectClass=person)(cn=%s))") + .build + testCase.assertAuthenticateFails(USER2.credentialsWithDn) + testCase.assertAuthenticateFails(USER2.credentialsWithId) + } + + /** + * Test to test the LDAP Atn to use a custom LDAP query that returns + * a) A set of group DNs + * b) A combination of group(s) DN and user DN + * LDAP atn is expected to extract the members of the group using the attribute value for + * `kyuubi.authentication.ldap.userMembershipKey` + */ + test("CustomQueryWithGroupsPositive") { + testCase = defaultBuilder + .baseDN("dc=example,dc=com") + .userDNPatterns("cn=%s,ou=People,dc=example,dc=com", "uid=%s,ou=People,dc=example,dc=com") + .customQuery(s"(&(objectClass=groupOfNames)(|(cn=$GROUP1_NAME)(cn=$GROUP2_NAME)))") + .build + testCase.assertAuthenticatePasses(USER1.credentialsWithId) + testCase.assertAuthenticatePasses(USER1.credentialsWithDn) + testCase.assertAuthenticatePasses(USER2.credentialsWithId) + testCase.assertAuthenticatePasses(USER2.credentialsWithDn) + // the following test uses a query that returns a group and a user entry. + // the ldap atn should use the groupMembershipKey to identify the users for the returned group + // and the authentication should succeed for the users of that group as well as the lone user4 + // in this case + testCase = defaultBuilder + .baseDN("dc=example,dc=com") + .userDNPatterns("cn=%s,ou=People,dc=example,dc=com", "uid=%s,ou=People,dc=example,dc=com") + .customQuery( + s"(|(&(objectClass=groupOfNames)(cn=$GROUP1_NAME))(&(objectClass=person)(sn=${USER4.id})))") + .build + testCase.assertAuthenticatePasses(USER1.credentialsWithId) + testCase.assertAuthenticatePasses(USER1.credentialsWithDn) + testCase.assertAuthenticatePasses(USER4.credentialsWithId) + testCase.assertAuthenticatePasses(USER4.credentialsWithDn) + testCase = defaultBuilder + .baseDN("dc=example,dc=com") + .userDNPatterns("cn=%s,ou=People,dc=example,dc=com", "uid=%s,ou=People,dc=example,dc=com") + .groupMembershipKey("uniqueMember") + .customQuery(s"(&(objectClass=groupOfUniqueNames)(cn=$GROUP4_NAME))") + .build + testCase.assertAuthenticatePasses(USER4.credentialsWithId) + testCase.assertAuthenticatePasses(USER4.credentialsWithDn) + } + + test("CustomQueryWithGroupsNegative") { + testCase = defaultBuilder + .baseDN("dc=example,dc=com") + .userDNPatterns("cn=%s,ou=People,dc=example,dc=com", "uid=%s,ou=People,dc=example,dc=com") + .customQuery(s"(&(objectClass=groupOfNames)(|(cn=$GROUP1_NAME)(cn=$GROUP2_NAME)))") + .build + testCase.assertAuthenticateFails(USER3.credentialsWithDn) + testCase.assertAuthenticateFails(USER3.credentialsWithId) + } + + test("GroupFilterPositiveWithCustomGUID") { + testCase = defaultBuilder + .userDNPatterns("cn=%s,ou=People,dc=example,dc=com") + .groupDNPatterns("cn=%s,ou=Groups,dc=example,dc=com") + .groupFilters(GROUP3_NAME) + .guidKey("cn") + .build + testCase.assertAuthenticatePasses(USER3.credentialsWithId) + testCase.assertAuthenticatePasses(USER3.credentialsWithDn) + } + + test("GroupFilterPositiveWithCustomAttributes") { + testCase = defaultBuilder + .userDNPatterns("cn=%s,ou=People,dc=example,dc=com") + .groupDNPatterns("cn=%s,ou=Groups,dc=example,dc=com") + .groupFilters(GROUP4_NAME) + .guidKey("cn") + .groupMembershipKey("uniqueMember") + .groupClassKey("groupOfUniqueNames") + .build + testCase.assertAuthenticatePasses(USER4.credentialsWithId) + testCase.assertAuthenticatePasses(USER4.credentialsWithDn) + } + + test("DirectUserMembershipGroupFilterPositive") { + testCase = defaultBuilder + .userDNPatterns( + "sAMAccountName=%s,ou=Engineering,dc=ad,dc=example,dc=com", + "sAMAccountName=%s,ou=Management,dc=ad,dc=example,dc=com") + .groupDNPatterns( + "sAMAccountName=%s,ou=Teams,dc=ad,dc=example,dc=com", + "sAMAccountName=%s,ou=Resources,dc=ad,dc=example,dc=com") + .groupFilters(GROUP_TEAM1_NAME, GROUP_TEAM2_NAME, GROUP_RESOURCE1_NAME, GROUP_RESOURCE2_NAME) + .guidKey("sAMAccountName") + .userMembershipKey("memberOf") + .build + testCase.assertAuthenticatePasses(ENGINEER_1.credentialsWithId) + testCase.assertAuthenticatePasses(ENGINEER_2.credentialsWithId) + testCase.assertAuthenticatePasses(MANAGER_1.credentialsWithId) + testCase.assertAuthenticatePasses(MANAGER_2.credentialsWithId) + } + + test("DirectUserMembershipGroupFilterNegative") { + testCase = defaultBuilder + .userDNPatterns( + "sAMAccountName=%s,ou=Engineering,dc=ad,dc=example,dc=com", + "sAMAccountName=%s,ou=Management,dc=ad,dc=example,dc=com") + .groupDNPatterns("cn=%s,ou=Teams,dc=ad,dc=example,dc=com") + .groupFilters(GROUP_TEAM1_NAME) + .guidKey("sAMAccountName") + .userMembershipKey("memberOf") + .build + testCase.assertAuthenticateFails(ENGINEER_2.credentialsWithId) + testCase.assertAuthenticateFails(MANAGER_2.credentialsWithId) + } + + test("DirectUserMembershipGroupFilterNegativeWithoutUserBases") { + testCase = defaultBuilder + .groupDNPatterns("cn=%s,ou=Teams,dc=ad,dc=example,dc=com") + .groupFilters(GROUP_TEAM1_NAME) + .guidKey("sAMAccountName") + .userMembershipKey("memberOf") + .build + testCase.assertAuthenticateFails(ENGINEER_1.credentialsWithId) + testCase.assertAuthenticateFails(ENGINEER_2.credentialsWithId) + testCase.assertAuthenticateFails(MANAGER_1.credentialsWithId) + testCase.assertAuthenticateFails(MANAGER_2.credentialsWithId) + } + + test("DirectUserMembershipGroupFilterWithDNCredentials") { + testCase = defaultBuilder + .userDNPatterns("sAMAccountName=%s,ou=Engineering,dc=ad,dc=example,dc=com") + .groupDNPatterns("cn=%s,ou=Teams,dc=ad,dc=example,dc=com") + .groupFilters(GROUP_TEAM1_NAME) + .guidKey("sAMAccountName") + .userMembershipKey("memberOf") + .build + testCase.assertAuthenticatePasses(ENGINEER_1.credentialsWithDn) + testCase.assertAuthenticateFails(MANAGER_1.credentialsWithDn) + } + + test("DirectUserMembershipGroupFilterWithDifferentGroupClassKey") { + testCase = defaultBuilder + .userDNPatterns("sAMAccountName=%s,ou=Administration,dc=ad,dc=example,dc=com") + .groupDNPatterns("cn=%s,ou=Administration,dc=ad,dc=example,dc=com") + .groupFilters(GROUP_ADMINS_NAME).guidKey("sAMAccountName") + .userMembershipKey("memberOf") + .groupClassKey("groupOfUniqueNames") + .build + testCase.assertAuthenticatePasses(ADMIN_1.credentialsWithId) + testCase.assertAuthenticateFails(ENGINEER_1.credentialsWithId) + testCase.assertAuthenticateFails(MANAGER_1.credentialsWithDn) + } + + test("DirectUserMembershipGroupFilterNegativeWithWrongGroupClassKey") { + testCase = defaultBuilder + .userDNPatterns("sAMAccountName=%s,ou=Administration,dc=ad,dc=example,dc=com") + .groupDNPatterns("cn=%s,ou=Administration,dc=ad,dc=example,dc=com") + .groupFilters(GROUP_ADMINS_NAME).guidKey("sAMAccountName") + .userMembershipKey("memberOf") + .groupClassKey("wrongClass") + .build + testCase.assertAuthenticateFails(ADMIN_1.credentialsWithId) + testCase.assertAuthenticateFails(ENGINEER_1.credentialsWithId) + testCase.assertAuthenticateFails(MANAGER_1.credentialsWithDn) + } +} diff --git a/kyuubi-common/src/test/scala/org/apache/kyuubi/service/authentication/LdapAuthenticationProviderImplSuite.scala b/kyuubi-common/src/test/scala/org/apache/kyuubi/service/authentication/LdapAuthenticationProviderImplSuite.scala index 639411628..718fc6f6e 100644 --- a/kyuubi-common/src/test/scala/org/apache/kyuubi/service/authentication/LdapAuthenticationProviderImplSuite.scala +++ b/kyuubi-common/src/test/scala/org/apache/kyuubi/service/authentication/LdapAuthenticationProviderImplSuite.scala @@ -17,63 +17,357 @@ package org.apache.kyuubi.service.authentication -import javax.naming.CommunicationException +import javax.naming.NamingException import javax.security.sasl.AuthenticationException +import org.mockito.ArgumentMatchers.{any, anyString, eq => mockEq, isA} +import org.mockito.Mockito._ +import org.scalatestplus.mockito.MockitoSugar.mock + import org.apache.kyuubi.config.KyuubiConf import org.apache.kyuubi.config.KyuubiConf._ +import org.apache.kyuubi.service.authentication.ldap.{DirSearch, DirSearchFactory, LdapSearchFactory} class LdapAuthenticationProviderImplSuite extends WithLdapServer { - override protected val ldapUser: String = "kentyao" - override protected val ldapUserPasswd: String = "kentyao" - private val conf = new KyuubiConf() + private var conf: KyuubiConf = _ + private var factory: DirSearchFactory = _ + private var search: DirSearch = _ + private var auth: LdapAuthenticationProviderImpl = _ - override def beforeAll(): Unit = { - super.beforeAll() + override protected def beforeEach(): Unit = { + super.beforeEach() + conf = new KyuubiConf() conf.set(AUTHENTICATION_LDAP_URL, ldapUrl) + factory = mock[DirSearchFactory] + search = mock[DirSearch] + when(factory.getInstance(any(classOf[KyuubiConf]), anyString, anyString)) + .thenReturn(search) + } + + test("authenticateGivenBlankOrNullPassword") { + Seq("", "\0", null).foreach { pwd => + auth = new LdapAuthenticationProviderImpl(conf, new LdapSearchFactory) + val thrown = intercept[AuthenticationException] { + auth.authenticate("user", pwd) + } + assert(thrown.getMessage.contains("is null or contains blank space")) + } + } + + test("AuthenticateNoUserOrGroupFilter") { + conf.set( + AUTHENTICATION_LDAP_USER_DN_PATTERN, + "cn=%s,ou=Users,dc=mycorp,dc=com:cn=%s,ou=PowerUsers,dc=mycorp,dc=com") + val factory = mock[DirSearchFactory] + lenient + .when(search.findUserDn("user1")) + .thenReturn("cn=user1,ou=PowerUsers,dc=mycorp,dc=com") + when(factory.getInstance(conf, "cn=user1,ou=PowerUsers,dc=mycorp,dc=com", "Blah")) + .thenReturn(search) + when(factory.getInstance(conf, "cn=user1,ou=Users,dc=mycorp,dc=com", "Blah")) + .thenThrow(classOf[AuthenticationException]) + + auth = new LdapAuthenticationProviderImpl(conf, factory) + auth.authenticate("user1", "Blah") + + verify(factory, times(2)).getInstance(isA(classOf[KyuubiConf]), anyString, mockEq("Blah")) + verify(search, atLeastOnce).close() + } + + test("AuthenticateWhenUserFilterPasses") { + conf.set(AUTHENTICATION_LDAP_USER_FILTER.key, "user1,user2") + + when(search.findUserDn("user1")).thenReturn("cn=user1,ou=PowerUsers,dc=mycorp,dc=com") + when(search.findUserDn("user2")).thenReturn("cn=user2,ou=PowerUsers,dc=mycorp,dc=com") + + authenticateUserAndCheckSearchIsClosed("user1") + authenticateUserAndCheckSearchIsClosed("user2") + } + + test("AuthenticateWhenLoginWithDomainAndUserFilterPasses") { + conf.set(AUTHENTICATION_LDAP_USER_FILTER.key, "user1") + + when(search.findUserDn("user1")).thenReturn("cn=user1,ou=PowerUsers,dc=mycorp,dc=com") + + authenticateUserAndCheckSearchIsClosed("user1@mydomain.com") + } + + test("AuthenticateWhenLoginWithDnAndUserFilterPasses") { + conf.set(AUTHENTICATION_LDAP_USER_FILTER.key, "user1") + + when(search.findUserDn("cn=user1,ou=PowerUsers,dc=mycorp,dc=com")) + .thenReturn("cn=user1,ou=PowerUsers,dc=mycorp,dc=com") + + authenticateUserAndCheckSearchIsClosed("cn=user1,ou=PowerUsers,dc=mycorp,dc=com") } - override def afterAll(): Unit = { - super.afterAll() + test("AuthenticateWhenUserSearchFails") { + conf.set(AUTHENTICATION_LDAP_USER_FILTER.key, "user1,user2") + intercept[AuthenticationException] { + when(search.findUserDn("user1")).thenReturn(null) + authenticateUserAndCheckSearchIsClosed("user1") + } } - test("ldap server is started") { - assert(ldapServer.getListenPort > 0) + test("AuthenticateWhenUserFilterFails") { + conf.set(AUTHENTICATION_LDAP_USER_FILTER.key, "user1,user2") + intercept[AuthenticationException] { + when(search.findUserDn("user3")).thenReturn("cn=user3,ou=PowerUsers,dc=mycorp,dc=com") + authenticateUserAndCheckSearchIsClosed("user3") + } } - test("authenticate tests") { - val providerImpl = new LdapAuthenticationProviderImpl(conf) - val e1 = intercept[AuthenticationException](providerImpl.authenticate("", "")) - assert(e1.getMessage.contains("user is null")) - val e2 = intercept[AuthenticationException](providerImpl.authenticate("kyuubi", "")) - assert(e2.getMessage.contains("password is null")) + test("AuthenticateWhenGroupMembershipKeyFilterPasses") { + conf.set(AUTHENTICATION_LDAP_GROUP_FILTER.key, "group1,group2") + + when(search.findUserDn("user1")).thenReturn("cn=user1,ou=PowerUsers,dc=mycorp,dc=com") + when(search.findUserDn("user2")).thenReturn("cn=user2,ou=PowerUsers,dc=mycorp,dc=com") - val user = "uid=kentyao,ou=users" - providerImpl.authenticate(user, "kentyao") - val e3 = intercept[AuthenticationException]( - providerImpl.authenticate(user, "kent")) - assert(e3.getMessage.contains(user)) - assert(e3.getCause.isInstanceOf[javax.naming.AuthenticationException]) + when(search.findGroupsForUser("cn=user1,ou=PowerUsers,dc=mycorp,dc=com")) + .thenReturn(Array( + "cn=testGroup,ou=Groups,dc=mycorp,dc=com", + "cn=group1,ou=Groups,dc=mycorp,dc=com")) + when(search.findGroupsForUser("cn=user2,ou=PowerUsers,dc=mycorp,dc=com")) + .thenReturn(Array( + "cn=testGroup,ou=Groups,dc=mycorp,dc=com", + "cn=group2,ou=Groups,dc=mycorp,dc=com")) + + authenticateUserAndCheckSearchIsClosed("user1") + authenticateUserAndCheckSearchIsClosed("user2") + } - conf.set(AUTHENTICATION_LDAP_BASEDN, ldapBaseDn) - val providerImpl2 = new LdapAuthenticationProviderImpl(conf) - providerImpl2.authenticate("kentyao", "kentyao") + test("AuthenticateWhenUserAndGroupMembershipKeyFiltersPass") { + conf.set(AUTHENTICATION_LDAP_GROUP_FILTER.key, "group1,group2") + conf.set(AUTHENTICATION_LDAP_USER_FILTER.key, "user1,user2") - val e4 = intercept[AuthenticationException]( - providerImpl.authenticate("kentyao", "kent")) - assert(e4.getMessage.contains(user)) + when(search.findUserDn("user1")).thenReturn("cn=user1,ou=PowerUsers,dc=mycorp,dc=com") + when(search.findUserDn("user2")).thenReturn("cn=user2,ou=PowerUsers,dc=mycorp,dc=com") - conf.unset(AUTHENTICATION_LDAP_URL) - val providerImpl3 = new LdapAuthenticationProviderImpl(conf) - val e5 = intercept[AuthenticationException]( - providerImpl3.authenticate("kentyao", "kentyao")) + when(search.findGroupsForUser("cn=user1,ou=PowerUsers,dc=mycorp,dc=com")) + .thenReturn(Array( + "cn=testGroup,ou=Groups,dc=mycorp,dc=com", + "cn=group1,ou=Groups,dc=mycorp,dc=com")) + when(search.findGroupsForUser("cn=user2,ou=PowerUsers,dc=mycorp,dc=com")) + .thenReturn(Array( + "cn=testGroup,ou=Groups,dc=mycorp,dc=com", + "cn=group2,ou=Groups,dc=mycorp,dc=com")) + + authenticateUserAndCheckSearchIsClosed("user1") + authenticateUserAndCheckSearchIsClosed("user2") + } + + test("AuthenticateWhenUserFilterPassesAndGroupMembershipKeyFilterFails") { + conf.set(AUTHENTICATION_LDAP_GROUP_FILTER.key, "group1,group2") + conf.set(AUTHENTICATION_LDAP_USER_FILTER.key, "user1,user2") + intercept[AuthenticationException] { + when(search.findUserDn("user1")).thenReturn("cn=user1,ou=PowerUsers,dc=mycorp,dc=com") + when(search.findGroupsForUser("cn=user1,ou=PowerUsers,dc=mycorp,dc=com")) + .thenReturn(Array( + "cn=testGroup,ou=Groups,dc=mycorp,dc=com", + "cn=OtherGroup,ou=Groups,dc=mycorp,dc=com")) + authenticateUserAndCheckSearchIsClosed("user1") + } + } + + test("AuthenticateWhenUserFilterFailsAndGroupMembershipKeyFilterPasses") { + conf.set(AUTHENTICATION_LDAP_GROUP_FILTER.key, "group3") + conf.set(AUTHENTICATION_LDAP_USER_FILTER.key, "user1,user2") + intercept[AuthenticationException] { + when(search.findUserDn("user3")).thenReturn("cn=user3,ou=PowerUsers,dc=mycorp,dc=com") + lenient.when(search.findGroupsForUser("cn=user3,ou=PowerUsers,dc=mycorp,dc=com")) + .thenReturn(Array( + "cn=testGroup,ou=Groups,dc=mycorp,dc=com", + "cn=group3,ou=Groups,dc=mycorp,dc=com")) + authenticateUserAndCheckSearchIsClosed("user3") + } + } + + test("AuthenticateWhenCustomQueryFilterPasses") { + conf.set(AUTHENTICATION_LDAP_BASE_DN, "dc=mycorp,dc=com") + conf.set( + AUTHENTICATION_LDAP_CUSTOM_LDAP_QUERY, + "(&(objectClass=person)(|(memberOf=CN=Domain Admins,CN=Users,DC=apache,DC=org)" + + "(memberOf=CN=Administrators,CN=Builtin,DC=apache,DC=org)))") + + when(search.executeCustomQuery(anyString)) + .thenReturn(Array( + "cn=user1,ou=PowerUsers,dc=mycorp,dc=com", + "cn=user2,ou=PowerUsers,dc=mycorp,dc=com")) + + authenticateUserAndCheckSearchIsClosed("user1") + } + + test("AuthenticateWhenCustomQueryFilterFailsAndUserFilterPasses") { + conf.set(AUTHENTICATION_LDAP_BASE_DN, "dc=mycorp,dc=com") + conf.set( + AUTHENTICATION_LDAP_CUSTOM_LDAP_QUERY, + "(&(objectClass=person)(|(memberOf=CN=Domain Admins,CN=Users,DC=apache,DC=org)" + + "(memberOf=CN=Administrators,CN=Builtin,DC=apache,DC=org)))") + conf.set(AUTHENTICATION_LDAP_USER_FILTER.key, "user3") + intercept[AuthenticationException] { + lenient.when(search.findUserDn("user3")).thenReturn("cn=user3,ou=PowerUsers,dc=mycorp,dc=com") + when(search.executeCustomQuery(anyString)) + .thenReturn(Array( + "cn=user1,ou=PowerUsers,dc=mycorp,dc=com", + "cn=user2,ou=PowerUsers,dc=mycorp,dc=com")) + authenticateUserAndCheckSearchIsClosed("user3") + } + } - assert(e5.getMessage.contains(user)) - assert(e5.getCause.isInstanceOf[CommunicationException]) + test("AuthenticateWhenUserMembershipKeyFilterPasses") { + conf.set(AUTHENTICATION_LDAP_BASE_DN, "dc=mycorp,dc=com") + conf.set(AUTHENTICATION_LDAP_GROUP_FILTER.key, "HIVE-USERS") + conf.set(AUTHENTICATION_LDAP_USER_MEMBERSHIP_KEY, "memberOf") + + when(search.findUserDn("user1")).thenReturn("cn=user1,ou=PowerUsers,dc=mycorp,dc=com") + + val groupDn = "cn=HIVE-USERS,ou=Groups,dc=mycorp,dc=com" + when(search.findGroupDn("HIVE-USERS")).thenReturn(groupDn) + when(search.isUserMemberOfGroup("user1", groupDn)).thenReturn(true) + + auth = new LdapAuthenticationProviderImpl(conf, factory) + auth.authenticate("user1", "Blah") + + verify(factory, times(1)).getInstance(isA(classOf[KyuubiConf]), anyString, mockEq("Blah")) + verify(search, times(1)).findGroupDn(anyString) + verify(search, times(1)).isUserMemberOfGroup(anyString, anyString) + verify(search, atLeastOnce).close() + } + + test("AuthenticateWhenUserMembershipKeyFilterFails") { + conf.set(AUTHENTICATION_LDAP_BASE_DN, "dc=mycorp,dc=com") + conf.set(AUTHENTICATION_LDAP_GROUP_FILTER.key, "HIVE-USERS") + conf.set(AUTHENTICATION_LDAP_USER_MEMBERSHIP_KEY, "memberOf") + intercept[AuthenticationException] { + when(search.findUserDn("user1")).thenReturn("cn=user1,ou=PowerUsers,dc=mycorp,dc=com") + + val groupDn = "cn=HIVE-USERS,ou=Groups,dc=mycorp,dc=com" + when(search.findGroupDn("HIVE-USERS")).thenReturn(groupDn) + when(search.isUserMemberOfGroup("user1", groupDn)).thenReturn(false) + + auth = new LdapAuthenticationProviderImpl(conf, factory) + auth.authenticate("user1", "Blah") + } + } + + test("AuthenticateWhenUserMembershipKeyFilter2x2PatternsPasses") { + conf.set(AUTHENTICATION_LDAP_GROUP_FILTER.key, "HIVE-USERS1,HIVE-USERS2") + conf.set(AUTHENTICATION_LDAP_GROUP_DN_PATTERN, "cn=%s,ou=Groups,ou=branch1,dc=mycorp,dc=com") + conf.set(AUTHENTICATION_LDAP_USER_DN_PATTERN, "cn=%s,ou=Userss,ou=branch1,dc=mycorp,dc=com") + conf.set(AUTHENTICATION_LDAP_USER_MEMBERSHIP_KEY, "memberOf") + + when(search.findUserDn("user1")).thenReturn("cn=user1,ou=PowerUsers,dc=mycorp,dc=com") + + when(search.findGroupDn("HIVE-USERS1")) + .thenReturn("cn=HIVE-USERS1,ou=Groups,ou=branch1,dc=mycorp,dc=com") + when(search.findGroupDn("HIVE-USERS2")) + .thenReturn("cn=HIVE-USERS2,ou=Groups,ou=branch1,dc=mycorp,dc=com") + + when(search.isUserMemberOfGroup( + "user1", + "cn=HIVE-USERS1,ou=Groups,ou=branch1,dc=mycorp,dc=com")) + .thenThrow(classOf[NamingException]) + when(search.isUserMemberOfGroup( + "user1", + "cn=HIVE-USERS2,ou=Groups,ou=branch1,dc=mycorp,dc=com")) + .thenReturn(true) + + auth = new LdapAuthenticationProviderImpl(conf, factory) + auth.authenticate("user1", "Blah") + + verify(factory, times(1)).getInstance(isA(classOf[KyuubiConf]), anyString, mockEq("Blah")) + verify(search, times(2)).findGroupDn(anyString) + verify(search, times(2)).isUserMemberOfGroup(anyString, anyString) + verify(search, atLeastOnce).close() + } + + // Kyuubi does not implement it + // test("AuthenticateWithBindInCredentialFilePasses") + // test("testAuthenticateWithBindInMissingCredentialFilePasses") + + test("AuthenticateWithBindUserPasses") { + val bindUser = "cn=BindUser,ou=Users,ou=branch1,dc=mycorp,dc=com" + val bindPass = "Blah" + val authFullUser = "cn=user1,ou=Users,ou=branch1,dc=mycorp,dc=com" + val authUser = "user1" + val authPass = "Blah2" + conf.set(AUTHENTICATION_LDAP_BIND_USER, bindUser) + conf.set(AUTHENTICATION_LDAP_BIND_PASSWORD, bindPass) + + when(search.findUserDn(mockEq(authUser))).thenReturn(authFullUser) + + auth = new LdapAuthenticationProviderImpl(conf, factory) + auth.authenticate(authUser, authPass) + + verify(factory, times(1)).getInstance( + isA(classOf[KyuubiConf]), + mockEq(bindUser), + mockEq(bindPass)) + verify(factory, times(1)).getInstance( + isA(classOf[KyuubiConf]), + mockEq(authFullUser), + mockEq(authPass)) + verify(search, times(1)).findUserDn(mockEq(authUser)) + } + + test("AuthenticateWithBindUserFailsOnAuthentication") { + val bindUser = "cn=BindUser,ou=Users,ou=branch1,dc=mycorp,dc=com" + val bindPass = "Blah" + val authFullUser = "cn=user1,ou=Users,ou=branch1,dc=mycorp,dc=com" + val authUser = "user1" + val authPass = "Blah2" + conf.set(AUTHENTICATION_LDAP_BIND_USER, bindUser) + conf.set(AUTHENTICATION_LDAP_BIND_PASSWORD, bindPass) + + intercept[AuthenticationException] { + when( + factory.getInstance( + any(classOf[KyuubiConf]), + mockEq(authFullUser), + mockEq(authPass))).thenThrow(classOf[AuthenticationException]) + when(search.findUserDn(mockEq(authUser))).thenReturn(authFullUser) + + auth = new LdapAuthenticationProviderImpl(conf, factory) + auth.authenticate(authUser, authPass) + } + } + + test("AuthenticateWithBindUserFailsOnGettingDn") { + val bindUser = "cn=BindUser,ou=Users,ou=branch1,dc=mycorp,dc=com" + val bindPass = "Blah" + val authUser = "user1" + val authPass = "Blah2" + conf.set(AUTHENTICATION_LDAP_BIND_USER, bindUser) + conf.set(AUTHENTICATION_LDAP_BIND_PASSWORD, bindPass) + + intercept[AuthenticationException] { + when(search.findUserDn(mockEq(authUser))).thenThrow(classOf[NamingException]) + auth = new LdapAuthenticationProviderImpl(conf, factory) + auth.authenticate(authUser, authPass) + } + } + + test("AuthenticateWithBindUserFailsOnBinding") { + val bindUser = "cn=BindUser,ou=Users,ou=branch1,dc=mycorp,dc=com" + val bindPass = "Blah" + val authUser = "user1" + val authPass = "Blah2" + conf.set(AUTHENTICATION_LDAP_BIND_USER, bindUser) + conf.set(AUTHENTICATION_LDAP_BIND_PASSWORD, bindPass) + + intercept[AuthenticationException] { + when(factory.getInstance(any(classOf[KyuubiConf]), mockEq(bindUser), mockEq(bindPass))) + .thenThrow(classOf[AuthenticationException]) + + auth = new LdapAuthenticationProviderImpl(conf, factory) + auth.authenticate(authUser, authPass) + } + } - conf.set(AUTHENTICATION_LDAP_DOMAIN, "kyuubi.com") - val providerImpl4 = new LdapAuthenticationProviderImpl(conf) - intercept[AuthenticationException](providerImpl4.authenticate("kentyao", "kentyao")) + private def authenticateUserAndCheckSearchIsClosed(user: String): Unit = { + auth = new LdapAuthenticationProviderImpl(conf, factory) + try auth.authenticate(user, "password doesn't matter") + finally verify(search, atLeastOnce).close() } } diff --git a/kyuubi-common/src/test/scala/org/apache/kyuubi/service/authentication/WithLdapServer.scala b/kyuubi-common/src/test/scala/org/apache/kyuubi/service/authentication/WithLdapServer.scala index 0bb38684e..b31a06f20 100644 --- a/kyuubi-common/src/test/scala/org/apache/kyuubi/service/authentication/WithLdapServer.scala +++ b/kyuubi-common/src/test/scala/org/apache/kyuubi/service/authentication/WithLdapServer.scala @@ -17,20 +17,36 @@ package org.apache.kyuubi.service.authentication +import scala.util.Random + import com.unboundid.ldap.listener.{InMemoryDirectoryServer, InMemoryDirectoryServerConfig} +import com.unboundid.ldif.LDIFReader import org.apache.kyuubi.{KyuubiFunSuite, Utils} trait WithLdapServer extends KyuubiFunSuite { protected var ldapServer: InMemoryDirectoryServer = _ - protected val ldapBaseDn = "ou=users" - protected val ldapUser = Utils.currentUser - protected val ldapUserPasswd = "ldapPassword" + protected val ldapBaseDn: Array[String] = Array("ou=users") + protected val ldapUser: String = Utils.currentUser + protected val ldapUserPasswd: String = Random.alphanumeric.take(16).mkString protected def ldapUrl = s"ldap://localhost:${ldapServer.getListenPort}" + /** + * Apply LDIF files + * @param resource the LDIF file under classpath + */ + def applyLDIF(resource: String): Unit = { + ldapServer.applyChangesFromLDIF( + new LDIFReader(Utils.getContextOrKyuubiClassLoader.getResource(resource).openStream())) + } + override def beforeAll(): Unit = { - val config = new InMemoryDirectoryServerConfig(ldapBaseDn) + val config = new InMemoryDirectoryServerConfig(ldapBaseDn: _*) + // disable the schema so that we can apply LDIF which contains Microsoft's Active Directory + // specific definitions. + // https://myshittycode.com/2017/03/28/ + config.setSchema(null) config.addAdditionalBindCredentials(s"uid=$ldapUser,ou=users", ldapUserPasswd) ldapServer = new InMemoryDirectoryServer(config) ldapServer.startListening() diff --git a/kyuubi-common/src/test/scala/org/apache/kyuubi/service/authentication/ldap/ChainFilterSuite.scala b/kyuubi-common/src/test/scala/org/apache/kyuubi/service/authentication/ldap/ChainFilterSuite.scala new file mode 100644 index 000000000..d76611b6e --- /dev/null +++ b/kyuubi-common/src/test/scala/org/apache/kyuubi/service/authentication/ldap/ChainFilterSuite.scala @@ -0,0 +1,88 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.kyuubi.service.authentication.ldap + +import javax.security.sasl.AuthenticationException + +import org.mockito.ArgumentMatchers.{any, anyString} +import org.mockito.Mockito.{doThrow, times, verify, when} +import org.scalatestplus.mockito.MockitoSugar.mock + +import org.apache.kyuubi.KyuubiFunSuite +import org.apache.kyuubi.config.KyuubiConf + +class ChainFilterSuite extends KyuubiFunSuite { + private var conf: KyuubiConf = _ + private var filter1: Filter = _ + private var filter2: Filter = _ + private var filter3: Filter = _ + private var factory1: FilterFactory = _ + private var factory2: FilterFactory = _ + private var factory3: FilterFactory = _ + private var factory: FilterFactory = _ + private var search: DirSearch = _ + + override def beforeEach(): Unit = { + conf = new KyuubiConf() + filter1 = mock[Filter] + filter2 = mock[Filter] + filter3 = mock[Filter] + factory1 = mock[FilterFactory] + factory2 = mock[FilterFactory] + factory3 = mock[FilterFactory] + factory = new ChainFilterFactory(factory1, factory2, factory3) + search = mock[DirSearch] + super.beforeEach() + } + + test("FactoryAllNull") { + when(factory1.getInstance(any(classOf[KyuubiConf]))).thenReturn(None) + when(factory2.getInstance(any(classOf[KyuubiConf]))).thenReturn(None) + when(factory3.getInstance(any(classOf[KyuubiConf]))).thenReturn(None) + assert(factory.getInstance(conf).isEmpty) + } + + test("FactoryAllEmpty") { + val emptyFactory = new ChainFilterFactory() + assert(emptyFactory.getInstance(conf).isEmpty) + } + + test("Factory") { + when(factory1.getInstance(any(classOf[KyuubiConf]))).thenReturn(Some(filter1)) + when(factory2.getInstance(any(classOf[KyuubiConf]))).thenReturn(Some(filter2)) + when(factory3.getInstance(any(classOf[KyuubiConf]))).thenReturn(Some(filter3)) + val filter = factory.getInstance(conf).get + filter.apply(search, "User") + verify(filter1, times(1)).apply(search, "User") + verify(filter2, times(1)).apply(search, "User") + verify(filter3, times(1)).apply(search, "User") + } + + test("ApplyNegative") { + intercept[AuthenticationException] { + doThrow(classOf[AuthenticationException]) + .when(filter3) + .apply(any().asInstanceOf[DirSearch], anyString) + when(factory1.getInstance(any(classOf[KyuubiConf]))).thenReturn(Some(filter1)) + when(factory2.getInstance(any(classOf[KyuubiConf]))).thenReturn(None) + when(factory3.getInstance(any(classOf[KyuubiConf]))).thenReturn(Some(filter3)) + val filter = factory.getInstance(conf).get + filter.apply(search, "User") + } + } +} diff --git a/kyuubi-common/src/test/scala/org/apache/kyuubi/service/authentication/ldap/CustomQueryFilterSuite.scala b/kyuubi-common/src/test/scala/org/apache/kyuubi/service/authentication/ldap/CustomQueryFilterSuite.scala new file mode 100644 index 000000000..5ece4c88c --- /dev/null +++ b/kyuubi-common/src/test/scala/org/apache/kyuubi/service/authentication/ldap/CustomQueryFilterSuite.scala @@ -0,0 +1,69 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.kyuubi.service.authentication.ldap + +import javax.security.sasl.AuthenticationException + +import org.mockito.ArgumentMatchers.{eq => mockEq} +import org.mockito.Mockito.when +import org.scalatestplus.mockito.MockitoSugar.mock + +import org.apache.kyuubi.KyuubiFunSuite +import org.apache.kyuubi.config.KyuubiConf + +class CustomQueryFilterSuite extends KyuubiFunSuite { + private val USER2_DN: String = "uid=user2,ou=People,dc=example,dc=com" + private val USER1_DN: String = "uid=user1,ou=People,dc=example,dc=com" + private val CUSTOM_QUERY: String = "(&(objectClass=person)(|(uid=user1)(uid=user2)))" + + private val factory: FilterFactory = CustomQueryFilterFactory + private var conf: KyuubiConf = _ + private var search: DirSearch = _ + + override def beforeEach(): Unit = { + conf = new KyuubiConf() + search = mock[DirSearch] + super.beforeEach() + } + + test("Factory") { + conf.unset(KyuubiConf.AUTHENTICATION_LDAP_CUSTOM_LDAP_QUERY) + assert(factory.getInstance(conf).isEmpty) + conf.set(KyuubiConf.AUTHENTICATION_LDAP_CUSTOM_LDAP_QUERY, CUSTOM_QUERY) + assert(factory.getInstance(conf).isDefined) + } + + test("ApplyPositive") { + conf.set(KyuubiConf.AUTHENTICATION_LDAP_CUSTOM_LDAP_QUERY, CUSTOM_QUERY) + when(search.executeCustomQuery(mockEq(CUSTOM_QUERY))) + .thenReturn(Array(USER1_DN, USER2_DN)) + val filter: Filter = factory.getInstance(conf).get + filter.apply(search, "user1") + filter.apply(search, "user2") + } + + test("ApplyNegative") { + intercept[AuthenticationException] { + conf.set(KyuubiConf.AUTHENTICATION_LDAP_CUSTOM_LDAP_QUERY, CUSTOM_QUERY) + when(search.executeCustomQuery(mockEq(CUSTOM_QUERY))) + .thenReturn(Array(USER1_DN, USER2_DN)) + val filter: Filter = factory.getInstance(conf).get + filter.apply(search, "user3") + } + } +} diff --git a/kyuubi-common/src/test/scala/org/apache/kyuubi/service/authentication/ldap/GroupFilterSuite.scala b/kyuubi-common/src/test/scala/org/apache/kyuubi/service/authentication/ldap/GroupFilterSuite.scala new file mode 100644 index 000000000..f1e3c3581 --- /dev/null +++ b/kyuubi-common/src/test/scala/org/apache/kyuubi/service/authentication/ldap/GroupFilterSuite.scala @@ -0,0 +1,161 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.kyuubi.service.authentication.ldap + +import javax.security.sasl.AuthenticationException + +import org.mockito.ArgumentMatchers.{eq => mockEq} +import org.mockito.Mockito.{lenient, when} +import org.scalatestplus.mockito.MockitoSugar.mock + +import org.apache.kyuubi.KyuubiFunSuite +import org.apache.kyuubi.config.KyuubiConf + +class GroupFilterSuite extends KyuubiFunSuite { + private val factory: FilterFactory = GroupFilterFactory + private var conf: KyuubiConf = _ + private var search: DirSearch = _ + + override def beforeEach(): Unit = { + conf = new KyuubiConf + search = mock[DirSearch] + super.beforeEach() + } + + test("GetInstanceWhenGroupFilterIsEmpty") { + conf.unset(KyuubiConf.AUTHENTICATION_LDAP_GROUP_FILTER) + assert(factory.getInstance(conf).isEmpty) + } + + test("GetInstanceOfGroupMembershipKeyFilter") { + conf.set(KyuubiConf.AUTHENTICATION_LDAP_GROUP_FILTER.key, "G1") + val instance: Filter = factory.getInstance(conf).get + assert(instance.isInstanceOf[GroupMembershipKeyFilter]) + } + + test("GetInstanceOfUserMembershipKeyFilter") { + conf.set(KyuubiConf.AUTHENTICATION_LDAP_GROUP_FILTER.key, "G1") + conf.set(KyuubiConf.AUTHENTICATION_LDAP_USER_MEMBERSHIP_KEY, "memberof") + val instance: Filter = factory.getInstance(conf).get + assert(instance.isInstanceOf[UserMembershipKeyFilter]) + } + + test("GroupMembershipKeyFilterApplyPositive") { + conf.set(KyuubiConf.AUTHENTICATION_LDAP_GROUP_FILTER.key, "HiveUsers") + when(search.findUserDn(mockEq("user1"))) + .thenReturn("cn=user1,ou=People,dc=example,dc=com") + when(search.findUserDn(mockEq("cn=user2,dc=example,dc=com"))) + .thenReturn("cn=user2,ou=People,dc=example,dc=com") + when(search.findUserDn(mockEq("user3@mydomain.com"))) + .thenReturn("cn=user3,ou=People,dc=example,dc=com") + when(search.findGroupsForUser(mockEq("cn=user1,ou=People,dc=example,dc=com"))) + .thenReturn(Array( + "cn=SuperUsers,ou=Groups,dc=example,dc=com", + "cn=Office1,ou=Groups,dc=example,dc=com", + "cn=HiveUsers,ou=Groups,dc=example,dc=com", + "cn=G1,ou=Groups,dc=example,dc=com")) + when(search.findGroupsForUser(mockEq("cn=user2,ou=People,dc=example,dc=com"))) + .thenReturn(Array("cn=HiveUsers,ou=Groups,dc=example,dc=com")) + when(search.findGroupsForUser(mockEq("cn=user3,ou=People,dc=example,dc=com"))) + .thenReturn(Array( + "cn=HiveUsers,ou=Groups,dc=example,dc=com", + "cn=G1,ou=Groups,dc=example,dc=com", + "cn=G2,ou=Groups,dc=example,dc=com")) + val filter: Filter = factory.getInstance(conf).get + filter.apply(search, "user1") + filter.apply(search, "cn=user2,dc=example,dc=com") + filter.apply(search, "user3@mydomain.com") + } + + test("GroupMembershipKeyCaseInsensitiveFilterApplyPositive") { + conf.set(KyuubiConf.AUTHENTICATION_LDAP_GROUP_FILTER.key, "hiveusers,g1") + when(search.findUserDn(mockEq("user1"))) + .thenReturn("cn=user1,ou=People,dc=example,dc=com") + when(search.findUserDn(mockEq("cn=user2,dc=example,dc=com"))) + .thenReturn("cn=user2,ou=People,dc=example,dc=com") + when(search.findUserDn(mockEq("user3@mydomain.com"))) + .thenReturn("cn=user3,ou=People,dc=example,dc=com") + when(search.findGroupsForUser(mockEq("cn=user1,ou=People,dc=example,dc=com"))) + .thenReturn(Array( + "cn=SuperUsers,ou=Groups,dc=example,dc=com", + "cn=Office1,ou=Groups,dc=example,dc=com", + "cn=HiveUsers,ou=Groups,dc=example,dc=com", + "cn=G1,ou=Groups,dc=example,dc=com")) + when(search.findGroupsForUser(mockEq("cn=user2,ou=People,dc=example,dc=com"))) + .thenReturn(Array("cn=HiveUsers,ou=Groups,dc=example,dc=com")) + when(search.findGroupsForUser(mockEq("cn=user3,ou=People,dc=example,dc=com"))) + .thenReturn(Array( + "cn=G1,ou=Groups,dc=example,dc=com", + "cn=G2,ou=Groups,dc=example,dc=com")) + val filter: Filter = factory.getInstance(conf).get + filter.apply(search, "user1") + filter.apply(search, "cn=user2,dc=example,dc=com") + filter.apply(search, "user3@mydomain.com") + } + + test("GroupMembershipKeyCaseInsensitiveFilterApplyNegative") { + intercept[AuthenticationException] { + conf.set(KyuubiConf.AUTHENTICATION_LDAP_GROUP_FILTER.key, "hiveusers,containsg1") + lenient.when(search.findGroupsForUser(mockEq("user1"))) + .thenReturn(Array("SuperUsers", "Office1", "G1", "G2")) + val filter: Filter = factory.getInstance(conf).get + filter.apply(search, "user1") + } + } + + test("GroupMembershipKeyFilterApplyNegative") { + intercept[AuthenticationException] { + conf.set(KyuubiConf.AUTHENTICATION_LDAP_GROUP_FILTER.key, "HiveUsers") + lenient.when(search.findGroupsForUser(mockEq("user1"))) + .thenReturn(Array("SuperUsers", "Office1", "G1", "G2")) + val filter: Filter = factory.getInstance(conf).get + filter.apply(search, "user1") + } + } + + test("UserMembershipKeyFilterApplyPositiveWithUserId") { + conf.set(KyuubiConf.AUTHENTICATION_LDAP_USER_MEMBERSHIP_KEY.key, "memberOf") + conf.set(KyuubiConf.AUTHENTICATION_LDAP_GROUP_FILTER.key, "Group1,Group2") + when(search.findGroupDn("Group1")).thenReturn("cn=Group1,dc=a,dc=b") + when(search.findGroupDn("Group2")).thenReturn("cn=Group2,dc=a,dc=b") + when(search.isUserMemberOfGroup("User1", "cn=Group2,dc=a,dc=b")).thenReturn(true) + val filter: Filter = factory.getInstance(conf).get + filter.apply(search, "User1") + } + + test("UserMembershipKeyFilterApplyPositiveWithUserDn") { + conf.set(KyuubiConf.AUTHENTICATION_LDAP_USER_MEMBERSHIP_KEY.key, "memberOf") + conf.set(KyuubiConf.AUTHENTICATION_LDAP_GROUP_FILTER.key, "Group1,Group2") + when(search.findGroupDn("Group1")).thenReturn("cn=Group1,dc=a,dc=b") + when(search.findGroupDn("Group2")).thenReturn("cn=Group2,dc=a,dc=b") + when(search.isUserMemberOfGroup("cn=User1,dc=a,dc=b", "cn=Group2,dc=a,dc=b")).thenReturn(true) + val filter: Filter = factory.getInstance(conf).get + filter.apply(search, "cn=User1,dc=a,dc=b") + } + + test("UserMembershipKeyFilterApplyNegative") { + intercept[AuthenticationException] { + conf.set(KyuubiConf.AUTHENTICATION_LDAP_USER_MEMBERSHIP_KEY.key, "memberOf") + conf.set(KyuubiConf.AUTHENTICATION_LDAP_GROUP_FILTER.key, "Group1,Group2") + when(search.findGroupDn("Group1")).thenReturn("cn=Group1,dc=a,dc=b") + when(search.findGroupDn("Group2")).thenReturn("cn=Group2,dc=a,dc=b") + val filter: Filter = factory.getInstance(conf).get + filter.apply(search, "User1") + } + } +} diff --git a/kyuubi-common/src/test/scala/org/apache/kyuubi/service/authentication/ldap/LdapAuthenticationTestCase.scala b/kyuubi-common/src/test/scala/org/apache/kyuubi/service/authentication/ldap/LdapAuthenticationTestCase.scala new file mode 100644 index 000000000..e8b92ebc0 --- /dev/null +++ b/kyuubi-common/src/test/scala/org/apache/kyuubi/service/authentication/ldap/LdapAuthenticationTestCase.scala @@ -0,0 +1,117 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.kyuubi.service.authentication.ldap + +import javax.security.sasl.AuthenticationException + +import scala.collection.mutable + +import org.scalatest.Assertions.{fail, intercept} + +import org.apache.kyuubi.config.{ConfigEntry, KyuubiConf} +import org.apache.kyuubi.service.authentication.LdapAuthenticationProviderImpl + +object LdapAuthenticationTestCase { + def builder: LdapAuthenticationTestCase.Builder = new LdapAuthenticationTestCase.Builder + + class Builder { + private val overrides: mutable.Map[ConfigEntry[_], String] = new mutable.HashMap + + var conf: KyuubiConf = _ + + def baseDN(baseDN: String): LdapAuthenticationTestCase.Builder = + setVarOnce(KyuubiConf.AUTHENTICATION_LDAP_BASE_DN, baseDN) + + def guidKey(guidKey: String): LdapAuthenticationTestCase.Builder = + setVarOnce(KyuubiConf.AUTHENTICATION_LDAP_GUID_KEY, guidKey) + + def userDNPatterns(userDNPatterns: String*): LdapAuthenticationTestCase.Builder = + setVarOnce(KyuubiConf.AUTHENTICATION_LDAP_USER_DN_PATTERN, userDNPatterns.mkString(":")) + + def userFilters(userFilters: String*): LdapAuthenticationTestCase.Builder = + setVarOnce(KyuubiConf.AUTHENTICATION_LDAP_USER_FILTER, userFilters.mkString(",")) + + def groupDNPatterns(groupDNPatterns: String*): LdapAuthenticationTestCase.Builder = + setVarOnce(KyuubiConf.AUTHENTICATION_LDAP_GROUP_DN_PATTERN, groupDNPatterns.mkString(":")) + + def groupFilters(groupFilters: String*): LdapAuthenticationTestCase.Builder = + setVarOnce(KyuubiConf.AUTHENTICATION_LDAP_GROUP_FILTER, groupFilters.mkString(",")) + + def groupClassKey(groupClassKey: String): LdapAuthenticationTestCase.Builder = + setVarOnce(KyuubiConf.AUTHENTICATION_LDAP_GROUP_CLASS_KEY, groupClassKey) + + def ldapUrl(ldapUrl: String): LdapAuthenticationTestCase.Builder = + setVarOnce(KyuubiConf.AUTHENTICATION_LDAP_URL, ldapUrl) + + def customQuery(customQuery: String): LdapAuthenticationTestCase.Builder = + setVarOnce(KyuubiConf.AUTHENTICATION_LDAP_CUSTOM_LDAP_QUERY, customQuery) + + def groupMembershipKey(groupMembershipKey: String): LdapAuthenticationTestCase.Builder = + setVarOnce(KyuubiConf.AUTHENTICATION_LDAP_GROUP_MEMBERSHIP_KEY, groupMembershipKey) + + def userMembershipKey(userMembershipKey: String): LdapAuthenticationTestCase.Builder = + setVarOnce(KyuubiConf.AUTHENTICATION_LDAP_USER_MEMBERSHIP_KEY, userMembershipKey) + + private def setVarOnce( + confVar: ConfigEntry[_], + value: String): LdapAuthenticationTestCase.Builder = { + require(!overrides.contains(confVar), s"Property $confVar has been set already") + overrides.put(confVar, value) + this + } + + def build: LdapAuthenticationTestCase = { + require(conf == null, "Test Case Builder should not be reused. Please create a new instance.") + conf = new KyuubiConf() + overrides.foreach { case (k, v) => conf.set(k.key, v) } + new LdapAuthenticationTestCase(this) + } + } +} + +final class LdapAuthenticationTestCase(builder: LdapAuthenticationTestCase.Builder) { + + private val ldapProvider = new LdapAuthenticationProviderImpl(builder.conf) + + def assertAuthenticatePasses(credentials: Credentials): Unit = + try { + ldapProvider.authenticate(credentials.user, credentials.password) + } catch { + case e: AuthenticationException => + throw new AssertionError( + s"Authentication failed for user '${credentials.user}' " + + s"with password '${credentials.password}'", + e) + } + + def assertAuthenticateFails(credentials: Credentials): Unit = { + assertAuthenticateFails(credentials.user, credentials.password) + } + + def assertAuthenticateFailsUsingWrongPassword(credentials: Credentials): Unit = { + assertAuthenticateFails(credentials.user, "not" + credentials.password) + } + + def assertAuthenticateFails(user: String, password: String): Unit = { + val e = intercept[AuthenticationException] { + ldapProvider.authenticate(user, password) + fail(s"Expected authentication to fail for $user") + } + assert(e != null) + } +} diff --git a/kyuubi-common/src/test/scala/org/apache/kyuubi/service/authentication/ldap/LdapSearchSuite.scala b/kyuubi-common/src/test/scala/org/apache/kyuubi/service/authentication/ldap/LdapSearchSuite.scala new file mode 100644 index 000000000..3bf27127b --- /dev/null +++ b/kyuubi-common/src/test/scala/org/apache/kyuubi/service/authentication/ldap/LdapSearchSuite.scala @@ -0,0 +1,298 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.kyuubi.service.authentication.ldap + +import javax.naming.{NamingEnumeration, NamingException} +import javax.naming.directory.{DirContext, SearchControls, SearchResult} + +import org.mockito.ArgumentMatchers.{any, anyString, contains, eq => mockEq} +import org.mockito.Mockito.{atLeastOnce, verify, when} +import org.scalatestplus.mockito.MockitoSugar.mock + +import org.apache.kyuubi.KyuubiFunSuite +import org.apache.kyuubi.config.KyuubiConf +import org.apache.kyuubi.service.authentication.ldap.LdapTestUtils._ + +class LdapSearchSuite extends KyuubiFunSuite { + private var conf: KyuubiConf = _ + private var ctx: DirContext = _ + private var search: LdapSearch = _ + + override protected def beforeEach(): Unit = { + conf = new KyuubiConf() + conf.set(KyuubiConf.AUTHENTICATION_LDAP_USER_MEMBERSHIP_KEY, "memberOf") + ctx = mock[DirContext] + super.beforeEach() + } + + test("close") { + search = new LdapSearch(conf, ctx) + search.close() + verify(ctx, atLeastOnce).close() + } + + test("FindUserDnWhenUserDnPositive") { + val searchResult: NamingEnumeration[SearchResult] = + mockNamingEnumeration("CN=User1,OU=org1,DC=foo,DC=bar") + when(ctx.search(anyString, anyString, any(classOf[SearchControls]))) + .thenReturn(searchResult) + .thenThrow(classOf[NamingException]) + search = new LdapSearch(conf, ctx) + val expected: String = "CN=User1,OU=org1,DC=foo,DC=bar" + val actual: String = search.findUserDn("CN=User1,OU=org1") + assert(expected === actual) + } + + test("FindUserDnWhenUserDnNegativeDuplicates") { + val searchResult: NamingEnumeration[SearchResult] = + mockNamingEnumeration("CN=User1,OU=org1,DC=foo,DC=bar", "CN=User1,OU=org2,DC=foo,DC=bar") + when(ctx.search(anyString, anyString, any(classOf[SearchControls]))) + .thenReturn(searchResult) + search = new LdapSearch(conf, ctx) + assert(search.findUserDn("CN=User1,DC=foo,DC=bar") === null) + } + + test("FindUserDnWhenUserDnNegativeNone") { + val searchResult: NamingEnumeration[SearchResult] = mockEmptyNamingEnumeration + when(ctx.search(anyString, anyString, any(classOf[SearchControls]))) + .thenReturn(searchResult) + search = new LdapSearch(conf, ctx) + assert(search.findUserDn("CN=User1,DC=foo,DC=bar") === null) + } + + test("FindUserDnWhenUserPatternFoundBySecondPattern") { + conf.set( + KyuubiConf.AUTHENTICATION_LDAP_USER_DN_PATTERN, + "CN=%s,OU=org1,DC=foo,DC=bar:CN=%s,OU=org2,DC=foo,DC=bar") + val emptyResult: NamingEnumeration[SearchResult] = mockEmptyNamingEnumeration + val validResult: NamingEnumeration[SearchResult] = + mockNamingEnumeration("CN=User1,OU=org2,DC=foo,DC=bar") + when(ctx.search(anyString, anyString, any(classOf[SearchControls]))) + .thenReturn(emptyResult) + .thenReturn(validResult) + search = new LdapSearch(conf, ctx) + val expected: String = "CN=User1,OU=org2,DC=foo,DC=bar" + val actual: String = search.findUserDn("User1") + assert(expected === actual) + verify(ctx).search( + mockEq("OU=org1,DC=foo,DC=bar"), + contains("CN=User1"), + any(classOf[SearchControls])) + verify(ctx).search( + mockEq("OU=org2,DC=foo,DC=bar"), + contains("CN=User1"), + any(classOf[SearchControls])) + } + + test("FindUserDnWhenUserPatternFoundByFirstPattern") { + conf.set( + KyuubiConf.AUTHENTICATION_LDAP_USER_DN_PATTERN, + "CN=%s,OU=org1,DC=foo,DC=bar:CN=%s,OU=org2,DC=foo,DC=bar") + val emptyResult: NamingEnumeration[SearchResult] = mockEmptyNamingEnumeration + val validResult: NamingEnumeration[SearchResult] = + mockNamingEnumeration("CN=User1,OU=org2,DC=foo,DC=bar") + when(ctx.search(anyString, anyString, any(classOf[SearchControls]))) + .thenReturn(validResult) + .thenReturn(emptyResult) + search = new LdapSearch(conf, ctx) + val expected: String = "CN=User1,OU=org2,DC=foo,DC=bar" + val actual: String = search.findUserDn("User1") + assert(expected === actual) + verify(ctx).search( + mockEq("OU=org1,DC=foo,DC=bar"), + contains("CN=User1"), + any(classOf[SearchControls])) + } + + test("FindUserDnWhenUserPatternFoundByUniqueIdentifier") { + conf.set( + KyuubiConf.AUTHENTICATION_LDAP_USER_DN_PATTERN, + "CN=%s,OU=org1,DC=foo,DC=bar") + val validResult: NamingEnumeration[SearchResult] = + mockNamingEnumeration("CN=User1,OU=org1,DC=foo,DC=bar") + when(ctx.search(anyString, anyString, any(classOf[SearchControls]))) + .thenReturn(null) + .thenReturn(validResult) + search = new LdapSearch(conf, ctx) + val expected: String = "CN=User1,OU=org1,DC=foo,DC=bar" + val actual: String = search.findUserDn("User1") + assert(expected === actual) + verify(ctx).search( + mockEq("OU=org1,DC=foo,DC=bar"), + contains("CN=User1"), + any(classOf[SearchControls])) + verify(ctx).search( + mockEq("OU=org1,DC=foo,DC=bar"), + contains("uid=User1"), + any(classOf[SearchControls])) + } + + test("FindUserDnWhenUserPatternFoundByUniqueIdentifierNegativeNone") { + conf.set( + KyuubiConf.AUTHENTICATION_LDAP_USER_DN_PATTERN, + "CN=%s,OU=org1,DC=foo,DC=bar") + when(ctx.search(anyString, anyString, any(classOf[SearchControls]))) + .thenReturn(null) + .thenReturn(null) + search = new LdapSearch(conf, ctx) + assert(search.findUserDn("User1") === null) + } + + test("FindUserDnWhenUserPatternFoundByUniqueIdentifierNegativeMany") { + conf.set( + KyuubiConf.AUTHENTICATION_LDAP_USER_DN_PATTERN, + "CN=%s,OU=org1,DC=foo,DC=bar") + val manyResult: NamingEnumeration[SearchResult] = + mockNamingEnumeration("CN=User1,OU=org1,DC=foo,DC=bar", "CN=User12,OU=org1,DC=foo,DC=bar") + when(ctx.search(anyString, anyString, any(classOf[SearchControls]))) + .thenReturn(null) + .thenReturn(manyResult) + search = new LdapSearch(conf, ctx) + assert(search.findUserDn("User1") === null) + } + + test("FindGroupsForUser") { + conf.set( + KyuubiConf.AUTHENTICATION_LDAP_GROUP_DN_PATTERN, + "CN=%s,OU=org1,DC=foo,DC=bar") + val groupsResult: NamingEnumeration[SearchResult] = + mockNamingEnumeration("CN=Group1,OU=org1,DC=foo,DC=bar") + when( + ctx.search( + mockEq("OU=org1,DC=foo,DC=bar"), + contains("User1"), + any(classOf[SearchControls]))).thenReturn(groupsResult) + search = new LdapSearch(conf, ctx) + val expected = Array("CN=Group1,OU=org1,DC=foo,DC=bar") + val actual = search.findGroupsForUser("CN=User1,OU=org1,DC=foo,DC=bar") + assert(expected === actual) + } + + test("ExecuteCustomQuery") { + conf.set(KyuubiConf.AUTHENTICATION_LDAP_BASE_DN, "dc=example,dc=com") + val customQueryResult: NamingEnumeration[SearchResult] = mockNamingEnumeration(Array( + mockSearchResult( + "uid=group1,ou=Groups,dc=example,dc=com", + mockAttributes("member", "uid=user1,ou=People,dc=example,dc=com")), + mockSearchResult( + "uid=group2,ou=Groups,dc=example,dc=com", + mockAttributes("member", "uid=user2,ou=People,dc=example,dc=com")))) + when( + ctx.search( + mockEq("dc=example,dc=com"), + anyString, + any(classOf[SearchControls]))) + .thenReturn(customQueryResult) + search = new LdapSearch(conf, ctx) + val expected = Array( + "uid=group1,ou=Groups,dc=example,dc=com", + "uid=user1,ou=People,dc=example,dc=com", + "uid=group2,ou=Groups,dc=example,dc=com", + "uid=user2,ou=People,dc=example,dc=com") + val actual = search.executeCustomQuery("(&(objectClass=groupOfNames)(|(cn=group1)(cn=group2)))") + assert(expected.sorted === actual.sorted) + } + + test("FindGroupDnPositive") { + conf.set( + KyuubiConf.AUTHENTICATION_LDAP_GROUP_DN_PATTERN, + "CN=%s,OU=org1,DC=foo,DC=bar") + val groupDn: String = "CN=Group1" + val result: NamingEnumeration[SearchResult] = mockNamingEnumeration(groupDn) + when(ctx.search(anyString, anyString, any(classOf[SearchControls]))) + .thenReturn(result) + search = new LdapSearch(conf, ctx) + val expected: String = groupDn + val actual: String = search.findGroupDn("grp1") + assert(expected === actual) + } + + test("FindGroupDNNoResults") { + intercept[NamingException] { + conf.set( + KyuubiConf.AUTHENTICATION_LDAP_GROUP_DN_PATTERN, + "CN=%s,OU=org1,DC=foo,DC=bar") + val result: NamingEnumeration[SearchResult] = mockEmptyNamingEnumeration + when(ctx.search(anyString, anyString, any(classOf[SearchControls]))) + .thenReturn(result) + search = new LdapSearch(conf, ctx) + search.findGroupDn("anyGroup") + } + } + + test("FindGroupDNTooManyResults") { + intercept[NamingException] { + conf.set( + KyuubiConf.AUTHENTICATION_LDAP_GROUP_DN_PATTERN, + "CN=%s,OU=org1,DC=foo,DC=bar") + val result: NamingEnumeration[SearchResult] = + LdapTestUtils.mockNamingEnumeration("Result1", "Result2", "Result3") + when(ctx.search(anyString, anyString, any(classOf[SearchControls]))) + .thenReturn(result) + search = new LdapSearch(conf, ctx) + search.findGroupDn("anyGroup") + } + + } + + test("FindGroupDNWhenExceptionInSearch") { + conf.set( + KyuubiConf.AUTHENTICATION_LDAP_GROUP_DN_PATTERN, + Array("CN=%s,OU=org1,DC=foo,DC=bar", "CN=%s,OU=org2,DC=foo,DC=bar").mkString(":")) + val result: NamingEnumeration[SearchResult] = LdapTestUtils.mockNamingEnumeration("CN=Group1") + when(ctx.search(anyString, anyString, any(classOf[SearchControls]))) + .thenReturn(result) + .thenThrow(classOf[NamingException]) + search = new LdapSearch(conf, ctx) + val expected: String = "CN=Group1" + val actual: String = search.findGroupDn("grp1") + assert(expected === actual) + } + + test("IsUserMemberOfGroupWhenUserId") { + conf.set( + KyuubiConf.AUTHENTICATION_LDAP_USER_DN_PATTERN, + "CN=%s,OU=org1,DC=foo,DC=bar") + val validResult: NamingEnumeration[SearchResult] = + LdapTestUtils.mockNamingEnumeration("CN=User1") + val emptyResult: NamingEnumeration[SearchResult] = LdapTestUtils.mockEmptyNamingEnumeration + when(ctx.search(anyString, contains("(uid=usr1)"), any(classOf[SearchControls]))) + .thenReturn(validResult) + when(ctx.search(anyString, contains("(uid=usr2)"), any(classOf[SearchControls]))) + .thenReturn(emptyResult) + search = new LdapSearch(conf, ctx) + assert(search.isUserMemberOfGroup("usr1", "grp1")) + assert(!search.isUserMemberOfGroup("usr2", "grp2")) + } + + test("IsUserMemberOfGroupWhenUserDn") { + conf.set( + KyuubiConf.AUTHENTICATION_LDAP_USER_DN_PATTERN, + "CN=%s,OU=org1,DC=foo,DC=bar") + val validResult: NamingEnumeration[SearchResult] = + LdapTestUtils.mockNamingEnumeration("CN=User1") + val emptyResult: NamingEnumeration[SearchResult] = LdapTestUtils.mockEmptyNamingEnumeration + when(ctx.search(anyString, contains("(uid=User1)"), any(classOf[SearchControls]))) + .thenReturn(validResult) + when(ctx.search(anyString, contains("(uid=User2)"), any(classOf[SearchControls]))) + .thenReturn(emptyResult) + search = new LdapSearch(conf, ctx) + assert(search.isUserMemberOfGroup("CN=User1,OU=org1,DC=foo,DC=bar", "grp1")) + assert(!search.isUserMemberOfGroup("CN=User2,OU=org1,DC=foo,DC=bar", "grp2")) + } +} diff --git a/kyuubi-common/src/test/scala/org/apache/kyuubi/service/authentication/ldap/LdapTestUtils.scala b/kyuubi-common/src/test/scala/org/apache/kyuubi/service/authentication/ldap/LdapTestUtils.scala new file mode 100644 index 000000000..49340f2c4 --- /dev/null +++ b/kyuubi-common/src/test/scala/org/apache/kyuubi/service/authentication/ldap/LdapTestUtils.scala @@ -0,0 +1,116 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.kyuubi.service.authentication.ldap + +import javax.naming.{NamingEnumeration, NamingException} +import javax.naming.directory._ + +import org.mockito.Mockito.when +import org.mockito.stubbing.OngoingStubbing +import org.scalatestplus.mockito.MockitoSugar + +case class NameValues(name: String, values: String*) +case class Credentials(user: String, password: String) + +case class User(dn: String, id: String, password: String) { + + def credentialsWithDn: Credentials = Credentials(dn, password) + + def credentialsWithId: Credentials = Credentials(id, password) +} + +object User { + def useIdForPassword(dn: String, id: String): User = User(dn, id, id) +} + +object LdapTestUtils extends MockitoSugar { + @throws[NamingException] + def mockEmptyNamingEnumeration: NamingEnumeration[SearchResult] = + mockNamingEnumeration(new Array[SearchResult](0)) + + @throws[NamingException] + def mockNamingEnumeration(dns: String*): NamingEnumeration[SearchResult] = + mockNamingEnumeration(mockSearchResults(dns.toArray)) + + @throws[NamingException] + def mockNamingEnumeration(searchResults: Array[SearchResult]): NamingEnumeration[SearchResult] = { + val ne = mock[NamingEnumeration[SearchResult]] + mockHasMoreMethod(ne, searchResults.length) + if (searchResults.nonEmpty) { + val mockedResults = Array(searchResults: _*) + mockNextMethod(ne, mockedResults) + } + ne + } + + @throws[NamingException] + def mockHasMoreMethod(ne: NamingEnumeration[SearchResult], length: Int): Unit = { + var hasMoreStub: OngoingStubbing[Boolean] = when(ne.hasMore) + (0 until length).foreach(_ => hasMoreStub = hasMoreStub.thenReturn(true)) + hasMoreStub.thenReturn(false) + } + + @throws[NamingException] + def mockNextMethod( + ne: NamingEnumeration[SearchResult], + searchResults: Array[SearchResult]): Unit = { + var nextStub: OngoingStubbing[SearchResult] = when(ne.next) + searchResults.foreach { searchResult => + nextStub = nextStub.thenReturn(searchResult) + } + } + + def mockSearchResults(dns: Array[String]): Array[SearchResult] = { + dns.map(mockSearchResult(_, null)) + } + + def mockSearchResult(dn: String, attributes: Attributes): SearchResult = { + val searchResult = mock[SearchResult] + when(searchResult.getNameInNamespace).thenReturn(dn) + when(searchResult.getAttributes).thenReturn(attributes) + searchResult + } + + @throws[NamingException] + def mockEmptyAttributes(): Attributes = mockAttributes() + + @throws[NamingException] + def mockAttributes(name: String, value: String): Attributes = + mockAttributes(NameValues(name, value)) + + @throws[NamingException] + def mockAttributes(name1: String, value1: String, name2: String, value2: String): Attributes = + if (name1 == name2) { + mockAttributes(NameValues(name1, value1, value2)) + } else { + mockAttributes( + NameValues(name1, value1), + NameValues(name2, value2)) + } + + @throws[NamingException] + private def mockAttributes(namedValues: NameValues*): Attributes = { + val attributes = new BasicAttributes + namedValues.foreach { namedValue => + val attr = new BasicAttribute(namedValue.name) + namedValue.values.foreach(attr.add) + attributes.put(attr) + } + attributes + } +} diff --git a/kyuubi-common/src/test/scala/org/apache/kyuubi/service/authentication/ldap/LdapUtilsSuite.scala b/kyuubi-common/src/test/scala/org/apache/kyuubi/service/authentication/ldap/LdapUtilsSuite.scala new file mode 100644 index 000000000..1ef371051 --- /dev/null +++ b/kyuubi-common/src/test/scala/org/apache/kyuubi/service/authentication/ldap/LdapUtilsSuite.scala @@ -0,0 +1,88 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.kyuubi.service.authentication.ldap + +import org.apache.kyuubi.KyuubiFunSuite +import org.apache.kyuubi.config.KyuubiConf + +class LdapUtilsSuite extends KyuubiFunSuite { + test("CreateCandidatePrincipalsForUserDn") { + val conf = new KyuubiConf() + val userDn = "cn=user1,ou=CORP,dc=mycompany,dc=com" + val expected = Array(userDn) + val actual = LdapUtils.createCandidatePrincipals(conf, userDn) + assert(actual === expected) + } + + test("CreateCandidatePrincipalsForUserWithDomain") { + val conf = new KyuubiConf() + val userWithDomain: String = "user1@mycompany.com" + val expected = Array(userWithDomain) + val actual = LdapUtils.createCandidatePrincipals(conf, userWithDomain) + assert(actual === expected) + } + + test("CreateCandidatePrincipalsLdapDomain") { + val conf = new KyuubiConf() + .set(KyuubiConf.AUTHENTICATION_LDAP_DOMAIN, "mycompany.com") + val expected = Array("user1@mycompany.com") + val actual = LdapUtils.createCandidatePrincipals(conf, "user1") + assert(actual === expected) + } + + test("CreateCandidatePrincipalsUserPatternsDefaultBaseDn") { + val conf = new KyuubiConf() + .set(KyuubiConf.AUTHENTICATION_LDAP_GUID_KEY, "sAMAccountName") + .set(KyuubiConf.AUTHENTICATION_LDAP_BASE_DN, "dc=mycompany,dc=com") + val expected = Array("sAMAccountName=user1,dc=mycompany,dc=com") + val actual = LdapUtils.createCandidatePrincipals(conf, "user1") + assert(actual === expected) + } + + test("CreateCandidatePrincipals") { + val conf = new KyuubiConf() + .set(KyuubiConf.AUTHENTICATION_LDAP_BASE_DN, "dc=mycompany,dc=com") + .set( + KyuubiConf.AUTHENTICATION_LDAP_USER_DN_PATTERN, + "cn=%s,ou=CORP1,dc=mycompany,dc=com:cn=%s,ou=CORP2,dc=mycompany,dc=com") + val expected = Array( + "cn=user1,ou=CORP1,dc=mycompany,dc=com", + "cn=user1,ou=CORP2,dc=mycompany,dc=com") + val actual = LdapUtils.createCandidatePrincipals(conf, "user1") + assert(actual.sorted === expected.sorted) + } + + test("ExtractFirstRdn") { + val dn = "cn=user1,ou=CORP1,dc=mycompany,dc=com" + val expected = "cn=user1" + val actual = LdapUtils.extractFirstRdn(dn) + assert(actual === expected) + } + + test("ExtractBaseDn") { + val dn: String = "cn=user1,ou=CORP1,dc=mycompany,dc=com" + val expected = "ou=CORP1,dc=mycompany,dc=com" + val actual = LdapUtils.extractBaseDn(dn) + assert(actual === expected) + } + + test("ExtractBaseDnNegative") { + val dn: String = "cn=user1" + assert(LdapUtils.extractBaseDn(dn) === null) + } +} diff --git a/kyuubi-common/src/test/scala/org/apache/kyuubi/service/authentication/ldap/QueryFactorySuite.scala b/kyuubi-common/src/test/scala/org/apache/kyuubi/service/authentication/ldap/QueryFactorySuite.scala new file mode 100644 index 000000000..568009680 --- /dev/null +++ b/kyuubi-common/src/test/scala/org/apache/kyuubi/service/authentication/ldap/QueryFactorySuite.scala @@ -0,0 +1,97 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.kyuubi.service.authentication.ldap + +import org.apache.kyuubi.KyuubiFunSuite +import org.apache.kyuubi.config.KyuubiConf + +class QueryFactorySuite extends KyuubiFunSuite { + private var conf: KyuubiConf = _ + private var queries: QueryFactory = _ + + override def beforeEach(): Unit = { + conf = new KyuubiConf() + conf.set(KyuubiConf.AUTHENTICATION_LDAP_GUID_KEY, "guid") + conf.set(KyuubiConf.AUTHENTICATION_LDAP_GROUP_CLASS_KEY, "superGroups") + conf.set(KyuubiConf.AUTHENTICATION_LDAP_GROUP_MEMBERSHIP_KEY, "member") + conf.set(KyuubiConf.AUTHENTICATION_LDAP_USER_MEMBERSHIP_KEY, "partOf") + queries = new QueryFactory(conf) + super.beforeEach() + } + + test("FindGroupDnById") { + val q = queries.findGroupDnById("unique_group_id") + val expected = "(&(objectClass=superGroups)(guid=unique_group_id))" + val actual = q.filter + assert(expected === actual) + } + + test("FindUserDnByRdn") { + val q = queries.findUserDnByRdn("cn=User1") + val expected = + "(&(|(objectClass=person)(objectClass=user)(objectClass=inetOrgPerson))(cn=User1))" + val actual = q.filter + assert(expected === actual) + } + + test("FindDnByPattern") { + val q = queries.findDnByPattern("cn=User1") + val expected = "(cn=User1)" + val actual = q.filter + assert(expected === actual) + } + + test("FindUserDnByName") { + val q = queries.findUserDnByName("unique_user_id") + val expected = + "(&(|(objectClass=person)(objectClass=user)(objectClass=inetOrgPerson))" + + "(|(uid=unique_user_id)(sAMAccountName=unique_user_id)))" + val actual = q.filter + assert(expected === actual) + } + + test("FindGroupsForUser") { + val q = queries.findGroupsForUser("user_name", "user_Dn") + val expected = "(&(objectClass=superGroups)(|(member=user_Dn)(member=user_name)))" + val actual = q.filter + assert(expected === actual) + } + + test("IsUserMemberOfGroup") { + val q = queries.isUserMemberOfGroup("unique_user", "cn=MyGroup,ou=Groups,dc=mycompany,dc=com") + val expected = + "(&(|(objectClass=person)(objectClass=user)(objectClass=inetOrgPerson))" + + "(partOf=cn=MyGroup,ou=Groups,dc=mycompany,dc=com)(guid=unique_user))" + val actual = q.filter + assert(expected === actual) + } + + test("IsUserMemberOfGroupWhenMisconfigured") { + intercept[IllegalArgumentException] { + val misconfiguredQueryFactory = new QueryFactory(new KyuubiConf()) + misconfiguredQueryFactory.isUserMemberOfGroup("user", "cn=MyGroup") + } + } + + test("FindGroupDNByID") { + val q = queries.findGroupDnById("unique_group_id") + val expected = "(&(objectClass=superGroups)(guid=unique_group_id))" + val actual = q.filter + assert(expected === actual) + } +} diff --git a/kyuubi-common/src/test/scala/org/apache/kyuubi/service/authentication/ldap/QuerySuite.scala b/kyuubi-common/src/test/scala/org/apache/kyuubi/service/authentication/ldap/QuerySuite.scala new file mode 100644 index 000000000..ffe330cce --- /dev/null +++ b/kyuubi-common/src/test/scala/org/apache/kyuubi/service/authentication/ldap/QuerySuite.scala @@ -0,0 +1,54 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.kyuubi.service.authentication.ldap + +import org.apache.kyuubi.KyuubiFunSuite + +class QuerySuite extends KyuubiFunSuite { + + test("QueryBuilderFilter") { + val q = Query.builder + .filter("test = query") + .map("uid_attr", "uid") + .map("value", "Hello!") + .build + assert("test uid=Hello! query" === q.filter) + assert(0 === q.controls.getCountLimit) + } + + test("QueryBuilderLimit") { + val q = Query.builder + .filter(",") + .map("key1", "value1") + .map("key2", "value2") + .limit(8) + .build + assert("value1,value2" === q.filter) + assert(8 === q.controls.getCountLimit) + } + + test("QueryBuilderReturningAttributes") { + val q = Query.builder + .filter("(query)") + .returnAttribute("attr1") + .returnAttribute("attr2") + .build + assert("(query)" === q.filter) + assert(Array("attr1", "attr2") === q.controls.getReturningAttributes) + } +} diff --git a/kyuubi-common/src/test/scala/org/apache/kyuubi/service/authentication/ldap/SearchResultHandlerSuite.scala b/kyuubi-common/src/test/scala/org/apache/kyuubi/service/authentication/ldap/SearchResultHandlerSuite.scala new file mode 100644 index 000000000..4e92f7f5f --- /dev/null +++ b/kyuubi-common/src/test/scala/org/apache/kyuubi/service/authentication/ldap/SearchResultHandlerSuite.scala @@ -0,0 +1,203 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.kyuubi.service.authentication.ldap + +import java.util +import javax.naming.{NamingEnumeration, NamingException} +import javax.naming.directory.SearchResult + +import scala.collection.mutable.ArrayBuffer + +import org.mockito.Mockito.{atLeastOnce, doThrow, verify} + +import org.apache.kyuubi.KyuubiFunSuite +import org.apache.kyuubi.service.authentication.ldap.LdapTestUtils._ + +class SearchResultHandlerSuite extends KyuubiFunSuite { + private var handler: SearchResultHandler = _ + + test("handle") { + val resultCollection = new MockResultCollectionBuilder() + .addSearchResultWithDns("1") + .addSearchResultWithDns("2", "3") + .build + handler = new SearchResultHandler(resultCollection) + val expected: util.List[String] = util.Arrays.asList("1", "2") + val actual: util.List[String] = new util.ArrayList[String] + handler.handle { record => + actual.add(record.getNameInNamespace) + actual.size < 2 + } + assert(expected === actual) + assertAllNamingEnumerationsClosed(resultCollection) + } + + test("GetAllLdapNamesNoRecords") { + val resultCollection = new MockResultCollectionBuilder() + .addEmptySearchResult() + .build + handler = new SearchResultHandler(resultCollection) + val actual = handler.getAllLdapNames + assert(actual.isEmpty, "ResultSet size") + assertAllNamingEnumerationsClosed(resultCollection) + } + + test("GetAllLdapNamesWithExceptionInNamingEnumerationClose") { + val resultCollection = new MockResultCollectionBuilder() + .addSearchResultWithDns("1") + .addSearchResultWithDns("2") + .build + doThrow(classOf[NamingException]).when(resultCollection.iterator.next).close() + handler = new SearchResultHandler(resultCollection) + val actual = handler.getAllLdapNames + assert(actual.length === 2, "ResultSet size") + assertAllNamingEnumerationsClosed(resultCollection) + } + + test("GetAllLdapNames") { + val objectDn1: String = "cn=a1,dc=b,dc=c" + val objectDn2: String = "cn=a2,dc=b,dc=c" + val objectDn3: String = "cn=a3,dc=b,dc=c" + val resultCollection = new MockResultCollectionBuilder() + .addSearchResultWithDns(objectDn1) + .addSearchResultWithDns(objectDn2, objectDn3) + .build + handler = new SearchResultHandler(resultCollection) + val expected = Array(objectDn1, objectDn2, objectDn3) + val actual = handler.getAllLdapNames + assert(expected.sorted === actual.sorted) + assertAllNamingEnumerationsClosed(resultCollection) + } + + test("GetAllLdapNamesAndAttributes") { + val searchResult1 = mockSearchResult( + "cn=a1,dc=b,dc=c", + mockAttributes("attr1", "attr1value1")) + val searchResult2 = mockSearchResult( + "cn=a2,dc=b,dc=c", + mockAttributes("attr1", "attr1value2", "attr2", "attr2value1")) + val searchResult3 = mockSearchResult( + "cn=a3,dc=b,dc=c", + mockAttributes("attr1", "attr1value3", "attr1", "attr1value4")) + val searchResult4 = mockSearchResult( + "cn=a4,dc=b,dc=c", + mockEmptyAttributes()) + val resultCollection = new MockResultCollectionBuilder() + .addSearchResults(searchResult1) + .addSearchResults(searchResult2, searchResult3) + .addSearchResults(searchResult4) + .build + handler = new SearchResultHandler(resultCollection) + val expected = Array( + "cn=a1,dc=b,dc=c", + "attr1value1", + "cn=a2,dc=b,dc=c", + "attr1value2", + "attr2value1", + "cn=a3,dc=b,dc=c", + "attr1value3", + "attr1value4", + "cn=a4,dc=b,dc=c") + val actual = handler.getAllLdapNamesAndAttributes + assert(expected.sorted === actual.sorted) + assertAllNamingEnumerationsClosed(resultCollection) + } + + test("HasSingleResultNoRecords") { + val resultCollection = new MockResultCollectionBuilder() + .addEmptySearchResult() + .build + handler = new SearchResultHandler(resultCollection) + assert(!handler.hasSingleResult) + assertAllNamingEnumerationsClosed(resultCollection) + } + + test("HasSingleResult") { + val resultCollection = new MockResultCollectionBuilder() + .addSearchResultWithDns("1") + .build + handler = new SearchResultHandler(resultCollection) + assert(handler.hasSingleResult) + assertAllNamingEnumerationsClosed(resultCollection) + } + + test("HasSingleResultManyRecords") { + val resultCollection = new MockResultCollectionBuilder() + .addSearchResultWithDns("1") + .addSearchResultWithDns("2") + .build + handler = new SearchResultHandler(resultCollection) + assert(!handler.hasSingleResult) + assertAllNamingEnumerationsClosed(resultCollection) + } + + test("GetSingleLdapNameNoRecords") { + intercept[NamingException] { + val resultCollection = new MockResultCollectionBuilder() + .addEmptySearchResult() + .build + handler = new SearchResultHandler(resultCollection) + try handler.getSingleLdapName + finally { + assertAllNamingEnumerationsClosed(resultCollection) + } + } + } + + test("GetSingleLdapName") { + val objectDn: String = "cn=a,dc=b,dc=c" + val resultCollection = new MockResultCollectionBuilder() + .addEmptySearchResult() + .addSearchResultWithDns(objectDn) + .build + handler = new SearchResultHandler(resultCollection) + val expected: String = objectDn + val actual: String = handler.getSingleLdapName + assert(expected === actual) + assertAllNamingEnumerationsClosed(resultCollection) + } + + private def assertAllNamingEnumerationsClosed( + resultCollection: Array[NamingEnumeration[SearchResult]]): Unit = { + for (namingEnumeration <- resultCollection) { + verify(namingEnumeration, atLeastOnce).close() + } + } +} + +class MockResultCollectionBuilder { + + val results = new ArrayBuffer[NamingEnumeration[SearchResult]] + + def addSearchResultWithDns(dns: String*): MockResultCollectionBuilder = { + results += mockNamingEnumeration(dns: _*) + this + } + + def addSearchResults(dns: SearchResult*): MockResultCollectionBuilder = { + results += mockNamingEnumeration(dns.toArray) + this + } + + def addEmptySearchResult(): MockResultCollectionBuilder = { + addSearchResults() + this + } + + def build: Array[NamingEnumeration[SearchResult]] = results.toArray +} diff --git a/kyuubi-common/src/test/scala/org/apache/kyuubi/service/authentication/ldap/UserFilterSuite.scala b/kyuubi-common/src/test/scala/org/apache/kyuubi/service/authentication/ldap/UserFilterSuite.scala new file mode 100644 index 000000000..4fc6cba49 --- /dev/null +++ b/kyuubi-common/src/test/scala/org/apache/kyuubi/service/authentication/ldap/UserFilterSuite.scala @@ -0,0 +1,60 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.kyuubi.service.authentication.ldap + +import javax.security.sasl.AuthenticationException + +import org.scalatestplus.mockito.MockitoSugar.mock + +import org.apache.kyuubi.KyuubiFunSuite +import org.apache.kyuubi.config.KyuubiConf + +class UserFilterSuite extends KyuubiFunSuite { + private val factory: FilterFactory = UserFilterFactory + private var conf: KyuubiConf = _ + private var search: DirSearch = _ + + override def beforeEach(): Unit = { + conf = new KyuubiConf() + search = mock[DirSearch] + super.beforeEach() + } + + test("Factory") { + conf.unset(KyuubiConf.AUTHENTICATION_LDAP_USER_FILTER) + assert(factory.getInstance(conf).isEmpty) + conf.set(KyuubiConf.AUTHENTICATION_LDAP_USER_FILTER.key, "User1") + assert(factory.getInstance(conf).isDefined) + } + + test("ApplyPositive") { + conf.set(KyuubiConf.AUTHENTICATION_LDAP_USER_FILTER.key, "User1,User2,uSeR3") + val filter = factory.getInstance(conf).get + filter.apply(search, "User1") + filter.apply(search, "uid=user2,ou=People,dc=example,dc=com") + filter.apply(search, "User3@mydomain.com") + } + + test("ApplyNegative") { + intercept[AuthenticationException] { + conf.set(KyuubiConf.AUTHENTICATION_LDAP_USER_FILTER.key, "User1,User2") + val filter = factory.getInstance(conf).get + filter.apply(search, "User3") + } + } +} diff --git a/kyuubi-common/src/test/scala/org/apache/kyuubi/service/authentication/ldap/UserSearchFilterSuite.scala b/kyuubi-common/src/test/scala/org/apache/kyuubi/service/authentication/ldap/UserSearchFilterSuite.scala new file mode 100644 index 000000000..1a711a6d9 --- /dev/null +++ b/kyuubi-common/src/test/scala/org/apache/kyuubi/service/authentication/ldap/UserSearchFilterSuite.scala @@ -0,0 +1,79 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.kyuubi.service.authentication.ldap + +import javax.naming.NamingException +import javax.security.sasl.AuthenticationException + +import org.mockito.ArgumentMatchers.anyString +import org.mockito.Mockito.when +import org.scalatestplus.mockito.MockitoSugar.mock + +import org.apache.kyuubi.KyuubiFunSuite +import org.apache.kyuubi.config.KyuubiConf + +class UserSearchFilterSuite extends KyuubiFunSuite { + private val factory: FilterFactory = UserSearchFilterFactory + private var conf: KyuubiConf = _ + private var search: DirSearch = _ + + override def beforeEach(): Unit = { + conf = new KyuubiConf() + search = mock[DirSearch] + super.beforeEach() + } + + test("FactoryWhenNoGroupOrUserFilters") { + assert(factory.getInstance(conf).isEmpty) + } + + test("FactoryWhenGroupFilter") { + conf.set(KyuubiConf.AUTHENTICATION_LDAP_GROUP_FILTER.key, "Grp1,Grp2") + assert(factory.getInstance(conf).isDefined) + } + + test("FactoryWhenUserFilter") { + conf.set(KyuubiConf.AUTHENTICATION_LDAP_USER_FILTER.key, "User1,User2") + assert(factory.getInstance(conf).isDefined) + } + + test("ApplyPositive") { + conf.set(KyuubiConf.AUTHENTICATION_LDAP_USER_FILTER.key, "User1") + val filter = factory.getInstance(conf).get + when(search.findUserDn(anyString)).thenReturn("cn=User1,ou=People,dc=example,dc=com") + filter.apply(search, "User1") + } + + test("ApplyWhenNamingException") { + intercept[AuthenticationException] { + conf.set(KyuubiConf.AUTHENTICATION_LDAP_USER_FILTER.key, "User1") + val filter = factory.getInstance(conf).get + when(search.findUserDn(anyString)).thenThrow(classOf[NamingException]) + filter.apply(search, "User3") + } + } + + test("ApplyWhenNotFound") { + intercept[AuthenticationException] { + conf.set(KyuubiConf.AUTHENTICATION_LDAP_USER_FILTER.key, "User1") + val filter = factory.getInstance(conf).get + when(search.findUserDn(anyString)).thenReturn(null) + filter.apply(search, "User3") + } + } +} diff --git a/kyuubi-server/src/test/scala/org/apache/kyuubi/RestClientTestHelper.scala b/kyuubi-server/src/test/scala/org/apache/kyuubi/RestClientTestHelper.scala index 5b3627381..8344cdef0 100644 --- a/kyuubi-server/src/test/scala/org/apache/kyuubi/RestClientTestHelper.scala +++ b/kyuubi-server/src/test/scala/org/apache/kyuubi/RestClientTestHelper.scala @@ -54,7 +54,7 @@ trait RestClientTestHelper extends RestFrontendTestHelper with KerberizedTestHel .set(KyuubiConf.SERVER_SPNEGO_KEYTAB, testKeytab) .set(KyuubiConf.SERVER_SPNEGO_PRINCIPAL, testSpnegoPrincipal) .set(KyuubiConf.AUTHENTICATION_LDAP_URL, ldapUrl) - .set(KyuubiConf.AUTHENTICATION_LDAP_BASEDN, ldapBaseDn) + .set(KyuubiConf.AUTHENTICATION_LDAP_BASE_DN, ldapBaseDn.head) .set( KyuubiConf.AUTHENTICATION_CUSTOM_CLASS, classOf[UserDefineAuthenticationProviderImpl].getCanonicalName) diff --git a/kyuubi-server/src/test/scala/org/apache/kyuubi/operation/KyuubiOperationKerberosAndPlainAuthSuite.scala b/kyuubi-server/src/test/scala/org/apache/kyuubi/operation/KyuubiOperationKerberosAndPlainAuthSuite.scala index fc8e1ec70..31cde6397 100644 --- a/kyuubi-server/src/test/scala/org/apache/kyuubi/operation/KyuubiOperationKerberosAndPlainAuthSuite.scala +++ b/kyuubi-server/src/test/scala/org/apache/kyuubi/operation/KyuubiOperationKerberosAndPlainAuthSuite.scala @@ -63,11 +63,12 @@ class KyuubiOperationKerberosAndPlainAuthSuite extends WithKyuubiServer with Ker UserGroupInformation.setConfiguration(config) assert(UserGroupInformation.isSecurityEnabled) - KyuubiConf().set(KyuubiConf.AUTHENTICATION_METHOD, Seq("KERBEROS", "LDAP", "CUSTOM")) + KyuubiConf() + .set(KyuubiConf.AUTHENTICATION_METHOD, Seq("KERBEROS", "LDAP", "CUSTOM")) .set(KyuubiConf.SERVER_KEYTAB, testKeytab) .set(KyuubiConf.SERVER_PRINCIPAL, testPrincipal) .set(KyuubiConf.AUTHENTICATION_LDAP_URL, ldapUrl) - .set(KyuubiConf.AUTHENTICATION_LDAP_BASEDN, ldapBaseDn) + .set(KyuubiConf.AUTHENTICATION_LDAP_BASE_DN, ldapBaseDn.head) .set( KyuubiConf.AUTHENTICATION_CUSTOM_CLASS, classOf[UserDefineAuthenticationProviderImpl].getCanonicalName) diff --git a/kyuubi-server/src/test/scala/org/apache/kyuubi/operation/thrift/http/KyuubiOperationThriftHttpKerberosAndPlainAuthSuite.scala b/kyuubi-server/src/test/scala/org/apache/kyuubi/operation/thrift/http/KyuubiOperationThriftHttpKerberosAndPlainAuthSuite.scala index 4f6ae92f1..941e121a6 100644 --- a/kyuubi-server/src/test/scala/org/apache/kyuubi/operation/thrift/http/KyuubiOperationThriftHttpKerberosAndPlainAuthSuite.scala +++ b/kyuubi-server/src/test/scala/org/apache/kyuubi/operation/thrift/http/KyuubiOperationThriftHttpKerberosAndPlainAuthSuite.scala @@ -53,7 +53,7 @@ class KyuubiOperationThriftHttpKerberosAndPlainAuthSuite .set(KyuubiConf.SERVER_KEYTAB, testKeytab) .set(KyuubiConf.SERVER_PRINCIPAL, testPrincipal) .set(KyuubiConf.AUTHENTICATION_LDAP_URL, ldapUrl) - .set(KyuubiConf.AUTHENTICATION_LDAP_BASEDN, ldapBaseDn) + .set(KyuubiConf.AUTHENTICATION_LDAP_BASE_DN, ldapBaseDn.head) .set( KyuubiConf.AUTHENTICATION_CUSTOM_CLASS, classOf[UserDefineAuthenticationProviderImpl].getCanonicalName) diff --git a/licenses-binary/LICENSE-antlr.txt b/licenses-binary/LICENSE-antlr.txt new file mode 100644 index 000000000..3021ea043 --- /dev/null +++ b/licenses-binary/LICENSE-antlr.txt @@ -0,0 +1,8 @@ +[The BSD License] +Copyright (c) 2012 Terence Parr and Sam Harwell +All rights reserved. +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: +Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +Neither the name of the author nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/licenses-binary/LICENSE-antlr4.txt b/licenses-binary/LICENSE-antlr4.txt new file mode 100644 index 000000000..21c16a132 --- /dev/null +++ b/licenses-binary/LICENSE-antlr4.txt @@ -0,0 +1,26 @@ +[The "BSD 3-clause license"] +Copyright (c) 2012-2017 The ANTLR Project. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/pom.xml b/pom.xml index e4c6671c9..723123475 100644 --- a/pom.xml +++ b/pom.xml @@ -118,6 +118,7 @@ 10.0.1 4.9.3 + 4.3.4 https://archive.apache.org/dist 1.67 4.2.8 @@ -252,6 +253,11 @@ antlr4-runtime ${antlr4.version}
+ + org.antlr + ST4 + ${antlr.st4.version} + org.apache.arrow arrow-vector From 62eefdb57e792489362a500d4589ee934454f755 Mon Sep 17 00:00:00 2001 From: liangbowen Date: Fri, 3 Feb 2023 14:01:11 +0800 Subject: [PATCH 003/760] [KYUUBI #4235] [DOCS] Prefer `https://` URLs in docs ### _Why are the changes needed?_ - Prefer `https://` URLs in docs, and all changed URLs are validated. ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [x] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4235 from bowenliang123/https-link. Closes #4235 f114dde2 [liangbowen] update AllKyuubiConfiguration ad8aaedf [liangbowen] style e973be5a [liangbowen] update 2370f4bf [liangbowen] prefer https URLs in docs Authored-by: liangbowen Signed-off-by: liangbowen --- docs/appendix/terminology.md | 2 +- docs/client/cli/hive_beeline.rst | 2 +- docs/deployment/engine_on_kubernetes.md | 8 ++++---- docs/deployment/engine_on_yarn.md | 10 +++++----- docs/deployment/hive_metastore.md | 8 ++++---- docs/deployment/settings.md | 10 +++++----- docs/develop_tools/building.md | 2 +- docs/develop_tools/testing.md | 4 ++-- docs/extensions/engines/spark/lineage.md | 2 +- docs/make.bat | 2 +- docs/monitor/logging.md | 2 +- docs/overview/architecture.md | 4 ++-- docs/overview/kyuubi_vs_hive.md | 16 ++++++++-------- docs/overview/kyuubi_vs_thriftserver.md | 2 +- docs/security/authorization/spark/build.md | 2 +- docs/security/kinit.md | 2 +- .../kyuubi/config/AllKyuubiConfiguration.scala | 12 ++++++------ 17 files changed, 45 insertions(+), 45 deletions(-) diff --git a/docs/appendix/terminology.md b/docs/appendix/terminology.md index 21b8cb1b6..b81fa25fe 100644 --- a/docs/appendix/terminology.md +++ b/docs/appendix/terminology.md @@ -139,7 +139,7 @@ Kyuubi unifies DataLake & LakeHouse access in the simplest pure SQL way, meanwhi

-http://iceberg.apache.org/ +https://iceberg.apache.org/

diff --git a/docs/client/cli/hive_beeline.rst b/docs/client/cli/hive_beeline.rst index fda925aa1..f75e00819 100644 --- a/docs/client/cli/hive_beeline.rst +++ b/docs/client/cli/hive_beeline.rst @@ -17,7 +17,7 @@ Hive Beeline ============ Kyuubi supports Apache Hive beeline that works with Kyuubi server. -Hive beeline is a `SQLLine CLI `_ based on the `Hive JDBC Driver <../jdbc/hive_jdbc.html>`_. +Hive beeline is a `SQLLine CLI `_ based on the `Hive JDBC Driver <../jdbc/hive_jdbc.html>`_. Prerequisites ------------- diff --git a/docs/deployment/engine_on_kubernetes.md b/docs/deployment/engine_on_kubernetes.md index ae8edcb75..44fca1602 100644 --- a/docs/deployment/engine_on_kubernetes.md +++ b/docs/deployment/engine_on_kubernetes.md @@ -21,7 +21,7 @@ When you want to run Kyuubi's Spark SQL engines on Kubernetes, you'd better have cognition upon the following things. -* Read about [Running Spark On Kubernetes](http://spark.apache.org/docs/latest/running-on-kubernetes.html) +* Read about [Running Spark On Kubernetes](https://spark.apache.org/docs/latest/running-on-kubernetes.html) * An active Kubernetes cluster * [Kubectl](https://kubernetes.io/docs/reference/kubectl/overview/) * KubeConfig of the target cluster @@ -97,7 +97,7 @@ As it known to us all, Kubernetes can use configurations to mount volumes into d * persistentVolumeClaim: mounts a PersistentVolume into a pod. Note: Please -see [the Security section of this document](http://spark.apache.org/docs/latest/running-on-kubernetes.html#security) for security issues related to volume mounts. +see [the Security section of this document](https://spark.apache.org/docs/latest/running-on-kubernetes.html#security) for security issues related to volume mounts. ``` spark.kubernetes.driver.volumes...options.path= @@ -107,7 +107,7 @@ spark.kubernetes.executor.volumes...options.path= spark.kubernetes.executor.volumes...mount.path= ``` -Read [Using Kubernetes Volumes](http://spark.apache.org/docs/latest/running-on-kubernetes.html#using-kubernetes-volumes) for more about volumes. +Read [Using Kubernetes Volumes](https://spark.apache.org/docs/latest/running-on-kubernetes.html#using-kubernetes-volumes) for more about volumes. ### PodTemplateFile @@ -117,4 +117,4 @@ To do so, specify the spark properties `spark.kubernetes.driver.podTemplateFile` ### Other -You can read Spark's official documentation for [Running on Kubernetes](http://spark.apache.org/docs/latest/running-on-kubernetes.html) for more information. +You can read Spark's official documentation for [Running on Kubernetes](https://spark.apache.org/docs/latest/running-on-kubernetes.html) for more information. diff --git a/docs/deployment/engine_on_yarn.md b/docs/deployment/engine_on_yarn.md index cb5bdd9e0..6812afa46 100644 --- a/docs/deployment/engine_on_yarn.md +++ b/docs/deployment/engine_on_yarn.md @@ -23,11 +23,11 @@ When you want to deploy Kyuubi's Spark SQL engines on YARN, you'd better have cognition upon the following things. -- Knowing the basics about [Running Spark on YARN](http://spark.apache.org/docs/latest/running-on-yarn.html) +- Knowing the basics about [Running Spark on YARN](https://spark.apache.org/docs/latest/running-on-yarn.html) - A binary distribution of Spark which is built with YARN support - You can use the built-in Spark distribution - You can get it from [Spark official website](https://spark.apache.org/downloads.html) directly - - You can [Build Spark](http://spark.apache.org/docs/latest/building-spark.html#specifying-the-hadoop-version-and-enabling-yarn) with `-Pyarn` maven option + - You can [Build Spark](https://spark.apache.org/docs/latest/building-spark.html#specifying-the-hadoop-version-and-enabling-yarn) with `-Pyarn` maven option - An active [Apache Hadoop YARN](https://hadoop.apache.org/docs/current/hadoop-yarn/hadoop-yarn-site/YARN.html) cluster - An active Apache Hadoop HDFS cluster - Setup Hadoop client configurations at the machine the Kyuubi server locates @@ -92,7 +92,7 @@ and how many cpus and memory will Spark driver, ApplicationMaster and each execu | spark.executor.memory | 1g | Amount of memory to use for the executor process | | spark.executor.memoryOverhead | executorMemory * 0.10, with minimum of 384 | Amount of additional memory to be allocated per executor process. This is memory that accounts for things like VM overheads, interned strings other native overheads, etc | -It is recommended to use [Dynamic Allocation](http://spark.apache.org/docs/3.0.1/configuration.html#dynamic-allocation) with Kyuubi, +It is recommended to use [Dynamic Allocation](https://spark.apache.org/docs/3.0.1/configuration.html#dynamic-allocation) with Kyuubi, since the SQL engine will be long-running for a period, execute user's queries from clients periodically, and the demand for computing resources is not the same for those queries. It is better for Spark to release some executors when either the query is lightweight, or the SQL engine is being idled. @@ -104,11 +104,11 @@ which allows YARN to cache it on nodes so that it doesn't need to be distributed ##### Others -Please refer to [Spark properties](http://spark.apache.org/docs/latest/running-on-yarn.html#spark-properties) to check other acceptable configs. +Please refer to [Spark properties](https://spark.apache.org/docs/latest/running-on-yarn.html#spark-properties) to check other acceptable configs. ### Kerberos -Kyuubi currently does not support Spark's [YARN-specific Kerberos Configuration](http://spark.apache.org/docs/3.0.1/running-on-yarn.html#kerberos), +Kyuubi currently does not support Spark's [YARN-specific Kerberos Configuration](https://spark.apache.org/docs/3.0.1/running-on-yarn.html#kerberos), so `spark.kerberos.keytab` and `spark.kerberos.principal` should not use now. Instead, you can schedule a periodically `kinit` process via `crontab` task on the local machine that hosts Kyuubi server or simply use [Kyuubi Kinit](settings.html#kinit). diff --git a/docs/deployment/hive_metastore.md b/docs/deployment/hive_metastore.md index f3a24d897..f60465a1a 100644 --- a/docs/deployment/hive_metastore.md +++ b/docs/deployment/hive_metastore.md @@ -30,7 +30,7 @@ In this section, you will learn how to configure Kyuubi to interact with Hive Me - A Spark binary distribution built with `-Phive` support - Use the built-in one in the Kyuubi distribution - Download from [Spark official website](https://spark.apache.org/downloads.html) - - Build from Spark source, [Building With Hive and JDBC Support](http://spark.apache.org/docs/latest/building-spark.html#building-with-hive-and-jdbc-support) + - Build from Spark source, [Building With Hive and JDBC Support](https://spark.apache.org/docs/latest/building-spark.html#building-with-hive-and-jdbc-support) - A copy of Hive client configuration So the whole thing here is to let Spark applications use this copy of Hive configuration to start a Hive metastore client for their own to talk to the Hive metastore server. @@ -199,13 +199,13 @@ Caused by: org.apache.thrift.TApplicationException: Invalid method name: 'get_ta ... 93 more ``` -To prevent this problem, we can use Spark's [Interacting with Different Versions of Hive Metastore](http://spark.apache.org/docs/latest/sql-data-sources-hive-tables.html#interacting-with-different-versions-of-hive-metastore). +To prevent this problem, we can use Spark's [Interacting with Different Versions of Hive Metastore](https://spark.apache.org/docs/latest/sql-data-sources-hive-tables.html#interacting-with-different-versions-of-hive-metastore). ## Further Readings - Hive Wiki - [Hive Metastore Administration](https://cwiki.apache.org/confluence/display/Hive/AdminManual+Metastore+Administration) - Spark Online Documentation - - [Custom Hadoop/Hive Configuration](http://spark.apache.org/docs/latest/configuration.html#custom-hadoophive-configuration) - - [Hive Tables](http://spark.apache.org/docs/latest/sql-data-sources-hive-tables.html) + - [Custom Hadoop/Hive Configuration](https://spark.apache.org/docs/latest/configuration.html#custom-hadoophive-configuration) + - [Hive Tables](https://spark.apache.org/docs/latest/sql-data-sources-hive-tables.html) diff --git a/docs/deployment/settings.md b/docs/deployment/settings.md index 26147ff8a..9cd8a681d 100644 --- a/docs/deployment/settings.md +++ b/docs/deployment/settings.md @@ -532,7 +532,7 @@ You can configure the Kyuubi properties in `$KYUUBI_HOME/conf/kyuubi-defaults.co ### Via spark-defaults.conf -Setting them in `$SPARK_HOME/conf/spark-defaults.conf` supplies with default values for SQL engine application. Available properties can be found at Spark official online documentation for [Spark Configurations](http://spark.apache.org/docs/latest/configuration.html) +Setting them in `$SPARK_HOME/conf/spark-defaults.conf` supplies with default values for SQL engine application. Available properties can be found at Spark official online documentation for [Spark Configurations](https://spark.apache.org/docs/latest/configuration.html) ### Via kyuubi-defaults.conf @@ -543,13 +543,13 @@ Setting them in `$KYUUBI_HOME/conf/kyuubi-defaults.conf` supplies with default v Setting them in the JDBC Connection URL supplies session-specific for each SQL engine. For example: ```jdbc:hive2://localhost:10009/default;#spark.sql.shuffle.partitions=2;spark.executor.memory=5g``` - **Runtime SQL Configuration** - - For [Runtime SQL Configurations](http://spark.apache.org/docs/latest/configuration.html#runtime-sql-configuration), they will take affect every time + - For [Runtime SQL Configurations](https://spark.apache.org/docs/latest/configuration.html#runtime-sql-configuration), they will take affect every time - **Static SQL and Spark Core Configuration** - - For [Static SQL Configurations](http://spark.apache.org/docs/latest/configuration.html#static-sql-configuration) and other spark core configs, e.g. `spark.executor.memory`, they will take effect if there is no existing SQL engine application. Otherwise, they will just be ignored + - For [Static SQL Configurations](https://spark.apache.org/docs/latest/configuration.html#static-sql-configuration) and other spark core configs, e.g. `spark.executor.memory`, they will take effect if there is no existing SQL engine application. Otherwise, they will just be ignored ### Via SET Syntax -Please refer to the Spark official online documentation for [SET Command](http://spark.apache.org/docs/latest/sql-ref-syntax-aux-conf-mgmt-set.html) +Please refer to the Spark official online documentation for [SET Command](https://spark.apache.org/docs/latest/sql-ref-syntax-aux-conf-mgmt-set.html) ## Flink Configurations @@ -651,7 +651,7 @@ Kyuubi uses [log4j](https://logging.apache.org/log4j/2.x/) for logging. You can ### Hadoop Configurations -Specifying `HADOOP_CONF_DIR` to the directory containing Hadoop configuration files or treating them as Spark properties with a `spark.hadoop.` prefix. Please refer to the Spark official online documentation for [Inheriting Hadoop Cluster Configuration](http://spark.apache.org/docs/latest/configuration.html#inheriting-hadoop-cluster-configuration). Also, please refer to the [Apache Hadoop](http://hadoop.apache.org)'s online documentation for an overview on how to configure Hadoop. +Specifying `HADOOP_CONF_DIR` to the directory containing Hadoop configuration files or treating them as Spark properties with a `spark.hadoop.` prefix. Please refer to the Spark official online documentation for [Inheriting Hadoop Cluster Configuration](https://spark.apache.org/docs/latest/configuration.html#inheriting-hadoop-cluster-configuration). Also, please refer to the [Apache Hadoop](https://hadoop.apache.org)'s online documentation for an overview on how to configure Hadoop. ### Hive Configurations diff --git a/docs/develop_tools/building.md b/docs/develop_tools/building.md index 9dfc01f42..d4582dc8d 100644 --- a/docs/develop_tools/building.md +++ b/docs/develop_tools/building.md @@ -19,7 +19,7 @@ ## Building Kyuubi with Apache Maven -**Kyuubi** is built based on [Apache Maven](http://maven.apache.org), +**Kyuubi** is built based on [Apache Maven](https://maven.apache.org), ```bash ./build/mvn clean package -DskipTests diff --git a/docs/develop_tools/testing.md b/docs/develop_tools/testing.md index 48a2e9787..3e63aa1a2 100644 --- a/docs/develop_tools/testing.md +++ b/docs/develop_tools/testing.md @@ -17,8 +17,8 @@ # Running Tests -**Kyuubi** can be tested based on [Apache Maven](http://maven.apache.org) and the ScalaTest Maven Plugin, -please refer to the [ScalaTest documentation](http://www.scalatest.org/user_guide/using_the_scalatest_maven_plugin), +**Kyuubi** can be tested based on [Apache Maven](https://maven.apache.org) and the ScalaTest Maven Plugin, +please refer to the [ScalaTest documentation](https://www.scalatest.org/user_guide/using_the_scalatest_maven_plugin), ## Running Tests Fully diff --git a/docs/extensions/engines/spark/lineage.md b/docs/extensions/engines/spark/lineage.md index 1ef28c173..8f2f76c9f 100644 --- a/docs/extensions/engines/spark/lineage.md +++ b/docs/extensions/engines/spark/lineage.md @@ -97,7 +97,7 @@ Currently supported column lineage for spark's `Command` and `Query` type: ### Build with Apache Maven -Kyuubi Spark Lineage Listener Extension is built using [Apache Maven](http://maven.apache.org). +Kyuubi Spark Lineage Listener Extension is built using [Apache Maven](https://maven.apache.org). To build it, `cd` to the root direct of kyuubi project and run: ```shell diff --git a/docs/make.bat b/docs/make.bat index 1f441aefc..b8c48a2db 100644 --- a/docs/make.bat +++ b/docs/make.bat @@ -38,7 +38,7 @@ if errorlevel 9009 ( echo.may add the Sphinx directory to PATH. echo. echo.If you don't have Sphinx installed, grab it from - echo.http://sphinx-doc.org/ + echo.https://www.sphinx-doc.org/ exit /b 1 ) diff --git a/docs/monitor/logging.md b/docs/monitor/logging.md index 8d373f5a9..24a5a88d6 100644 --- a/docs/monitor/logging.md +++ b/docs/monitor/logging.md @@ -265,5 +265,5 @@ You will both get the final results and the corresponding operation logs telling - [Monitoring Kyuubi - Server Metrics](metrics.md) - [Trouble Shooting](trouble_shooting.md) - Spark Online Documentation - - [Monitoring and Instrumentation](http://spark.apache.org/docs/latest/monitoring.html) + - [Monitoring and Instrumentation](https://spark.apache.org/docs/latest/monitoring.html) diff --git a/docs/overview/architecture.md b/docs/overview/architecture.md index ec4dc0d8d..4df5e24a4 100644 --- a/docs/overview/architecture.md +++ b/docs/overview/architecture.md @@ -107,7 +107,7 @@ and these applications can be placed in different shared domains for other conne Kyuubi does not occupy any resources from the Cluster Manager(e.g. Yarn) during startup and will give all resources back if there is not any active session interacting with a `SparkContext`. -Spark also provides [Dynamic Resource Allocation](http://spark.apache.org/docs/latest/job-scheduling.html#dynamic-resource-allocation) to dynamically adjust the resources your application occupies based on the workload. It means +Spark also provides [Dynamic Resource Allocation](https://spark.apache.org/docs/latest/job-scheduling.html#dynamic-resource-allocation) to dynamically adjust the resources your application occupies based on the workload. It means that your application may give resources back to the cluster if they are no longer used and request them again later when there is demand. This feature is handy if multiple applications share resources in your Spark cluster. @@ -172,5 +172,5 @@ We also create a [Submarine: Spark Security](https://mvnrepository.com/artifact/ ## Conclusions -Kyuubi is a unified multi-tenant JDBC interface for large-scale data processing and analytics, built on top of [Apache Spark™](http://spark.apache.org/). +Kyuubi is a unified multi-tenant JDBC interface for large-scale data processing and analytics, built on top of [Apache Spark™](https://spark.apache.org/). It extends the Spark Thrift Server's scenarios in enterprise applications, the most important of which is multi-tenancy support. diff --git a/docs/overview/kyuubi_vs_hive.md b/docs/overview/kyuubi_vs_hive.md index f69215240..43ffac146 100644 --- a/docs/overview/kyuubi_vs_hive.md +++ b/docs/overview/kyuubi_vs_hive.md @@ -34,14 +34,14 @@ have multiple reducer stages. - -| Kyuubi | HiveServer2 | -|--------------------------------|--------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| ** Language ** | Spark SQL | Hive QL | -| ** Optimizer ** | Spark SQL Catalyst | Hive Optimizer | -| ** Engine ** | up to Spark 3.x | MapReduce/[up to Spark 2.3](https://cwiki.apache.org/confluence/display/Hive/Hive+on+Spark%3A+Getting+Started#HiveonSpark:GettingStarted-VersionCompatibility)/Tez | -| ** Performance ** | High | Low | -| ** Compatibility with Spark ** | Good | Bad(need to rebuild on a specific version) | -| ** Data Types ** | [Spark Data Types](http://spark.apache.org/docs/latest/sql-ref-datatypes.html) | [Hive Data Types](https://cwiki.apache.org/confluence/display/Hive/LanguageManual+Types) | +| Kyuubi | HiveServer2 | +|--------------------------------|---------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| ** Language ** | Spark SQL | Hive QL | +| ** Optimizer ** | Spark SQL Catalyst | Hive Optimizer | +| ** Engine ** | up to Spark 3.x | MapReduce/[up to Spark 2.3](https://cwiki.apache.org/confluence/display/Hive/Hive+on+Spark%3A+Getting+Started#HiveonSpark:GettingStarted-VersionCompatibility)/Tez | +| ** Performance ** | High | Low | +| ** Compatibility with Spark ** | Good | Bad(need to rebuild on a specific version) | +| ** Data Types ** | [Spark Data Types](https://spark.apache.org/docs/latest/sql-ref-datatypes.html) | [Hive Data Types](https://cwiki.apache.org/confluence/display/Hive/LanguageManual+Types) | ## Performance diff --git a/docs/overview/kyuubi_vs_thriftserver.md b/docs/overview/kyuubi_vs_thriftserver.md index 00a03c3b2..66f900c74 100644 --- a/docs/overview/kyuubi_vs_thriftserver.md +++ b/docs/overview/kyuubi_vs_thriftserver.md @@ -19,7 +19,7 @@ ## Introductions -The Apache Spark [Thrift JDBC/ODBC Server](http://spark.apache.org/docs/latest/sql-distributed-sql-engine.html) is a Thrift service implemented by the Apache Spark community based on HiveServer2. +The Apache Spark [Thrift JDBC/ODBC Server](https://spark.apache.org/docs/latest/sql-distributed-sql-engine.html) is a Thrift service implemented by the Apache Spark community based on HiveServer2. Designed to be seamlessly compatible with HiveServer2, it provides Spark SQL capabilities to end-users in a pure SQL way through a JDBC interface. This "out-of-the-box" model minimizes the barriers and costs for users to use Spark. diff --git a/docs/security/authorization/spark/build.md b/docs/security/authorization/spark/build.md index 2756cc356..3886f08df 100644 --- a/docs/security/authorization/spark/build.md +++ b/docs/security/authorization/spark/build.md @@ -19,7 +19,7 @@ ## Build with Apache Maven -Kyuubi Spark AuthZ Plugin is built using [Apache Maven](http://maven.apache.org). +Kyuubi Spark AuthZ Plugin is built using [Apache Maven](https://maven.apache.org). To build it, `cd` to the root direct of kyuubi project and run: ```shell diff --git a/docs/security/kinit.md b/docs/security/kinit.md index e9dfbc491..0d613e000 100644 --- a/docs/security/kinit.md +++ b/docs/security/kinit.md @@ -104,5 +104,5 @@ hadoop.proxyuser..hosts * ## Further Readings - [Hadoop in Secure Mode](https://hadoop.apache.org/docs/current/hadoop-project-dist/hadoop-common/SecureMode.html) -- [Use Kerberos for authentication in Spark](http://spark.apache.org/docs/latest/security.html#kerberos) +- [Use Kerberos for authentication in Spark](https://spark.apache.org/docs/latest/security.html#kerberos) diff --git a/kyuubi-server/src/test/scala/org/apache/kyuubi/config/AllKyuubiConfiguration.scala b/kyuubi-server/src/test/scala/org/apache/kyuubi/config/AllKyuubiConfiguration.scala index 28a752368..7ea4d49b2 100644 --- a/kyuubi-server/src/test/scala/org/apache/kyuubi/config/AllKyuubiConfiguration.scala +++ b/kyuubi-server/src/test/scala/org/apache/kyuubi/config/AllKyuubiConfiguration.scala @@ -165,7 +165,7 @@ class AllKyuubiConfiguration extends KyuubiFunSuite { newOutput += ("Setting them in `$SPARK_HOME/conf/spark-defaults.conf`" + " supplies with default values for SQL engine application. Available properties can be" + " found at Spark official online documentation for" + - " [Spark Configurations](http://spark.apache.org/docs/latest/configuration.html)") + " [Spark Configurations](https://spark.apache.org/docs/latest/configuration.html)") newOutput += "" newOutput += ("### Via kyuubi-defaults.conf") @@ -187,19 +187,19 @@ class AllKyuubiConfiguration extends KyuubiFunSuite { newOutput += ("- **Runtime SQL Configuration**") newOutput += "" newOutput += (" - For [Runtime SQL Configurations](" + - "http://spark.apache.org/docs/latest/configuration.html#runtime-sql-configuration), they" + + "https://spark.apache.org/docs/latest/configuration.html#runtime-sql-configuration), they" + " will take affect every time") newOutput += ("- **Static SQL and Spark Core Configuration**") newOutput += "" newOutput += (" - For [Static SQL Configurations](" + - "http://spark.apache.org/docs/latest/configuration.html#static-sql-configuration) and" + + "https://spark.apache.org/docs/latest/configuration.html#static-sql-configuration) and" + " other spark core configs, e.g. `spark.executor.memory`, they will take effect if there" + " is no existing SQL engine application. Otherwise, they will just be ignored") newOutput += "" newOutput += ("### Via SET Syntax") newOutput += "" newOutput += ("Please refer to the Spark official online documentation for" + - " [SET Command](http://spark.apache.org/docs/latest/sql-ref-syntax-aux-conf-mgmt-set.html)") + " [SET Command](https://spark.apache.org/docs/latest/sql-ref-syntax-aux-conf-mgmt-set.html)") newOutput += "" newOutput += ("## Flink Configurations") @@ -259,9 +259,9 @@ class AllKyuubiConfiguration extends KyuubiFunSuite { newOutput += ("Specifying `HADOOP_CONF_DIR` to the directory containing Hadoop configuration" + " files or treating them as Spark properties with a `spark.hadoop.` prefix." + " Please refer to the Spark official online documentation for" + - " [Inheriting Hadoop Cluster Configuration](http://spark.apache.org/docs/latest/" + + " [Inheriting Hadoop Cluster Configuration](https://spark.apache.org/docs/latest/" + "configuration.html#inheriting-hadoop-cluster-configuration)." + - " Also, please refer to the [Apache Hadoop](http://hadoop.apache.org)'s" + + " Also, please refer to the [Apache Hadoop](https://hadoop.apache.org)'s" + " online documentation for an overview on how to configure Hadoop.") newOutput += "" newOutput += ("### Hive Configurations") From eef6947ccab41570676c7fe0a3d8cd71ac8816fb Mon Sep 17 00:00:00 2001 From: fwang12 Date: Fri, 3 Feb 2023 16:17:11 +0800 Subject: [PATCH 004/760] [KYUUBI #4144][FOLLOWUP] Do not cleanup upload dir because of batch recovery and fix temp file leak ### _Why are the changes needed?_ Address comments: https://github.com/apache/kyuubi/pull/4144#issuecomment-1412078077 ### _How was this patch tested?_ - [x] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [x] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4231 from turboFei/uploading_resource_followup. Closes #4144 93ccd9534 [fwang12] comments 81a224dce [fwang12] ut 6b226969a [fwang12] prevent temp file leak 17a4e394c [fwang12] Do not remove upload dir Authored-by: fwang12 Signed-off-by: fwang12 --- .../main/scala/org/apache/kyuubi/Utils.scala | 14 -------------- .../engine/KyuubiApplicationManager.scala | 14 +------------- .../kyuubi/operation/BatchJobSubmission.scala | 19 +++++++++++++++---- .../server/api/v1/BatchesResource.scala | 2 +- .../server/api/v1/BatchesResourceSuite.scala | 8 +++++++- 5 files changed, 24 insertions(+), 33 deletions(-) diff --git a/kyuubi-common/src/main/scala/org/apache/kyuubi/Utils.scala b/kyuubi-common/src/main/scala/org/apache/kyuubi/Utils.scala index 7283ea040..7ab312fa1 100644 --- a/kyuubi-common/src/main/scala/org/apache/kyuubi/Utils.scala +++ b/kyuubi-common/src/main/scala/org/apache/kyuubi/Utils.scala @@ -143,20 +143,6 @@ object Utils extends Logging { f.delete() } - /** - * delete file in path with logging - * @param filePath path to file for deletion - * @param errorMessage message as prefix logging with error exception - */ - def deleteFile(filePath: String, errorMessage: String): Unit = { - try { - Files.delete(Paths.get(filePath)) - } catch { - case e: Exception => - error(s"$errorMessage: $filePath ", e) - } - } - /** * Create a temporary directory inside the given parent directory. The directory will be * automatically deleted when the VM shuts down. diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/engine/KyuubiApplicationManager.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/engine/KyuubiApplicationManager.scala index b76f08833..70c130012 100644 --- a/kyuubi-server/src/main/scala/org/apache/kyuubi/engine/KyuubiApplicationManager.scala +++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/engine/KyuubiApplicationManager.scala @@ -59,7 +59,6 @@ class KyuubiApplicationManager extends AbstractService("KyuubiApplicationManager case NonFatal(e) => warn(s"Error stopping ${op.getClass.getSimpleName}: ${e.getMessage}") } } - deleteTempDirForUpload() super.stop() } @@ -92,17 +91,6 @@ class KyuubiApplicationManager extends AbstractService("KyuubiApplicationManager case None => None } } - - private def deleteTempDirForUpload(): Unit = { - try { - Utils.deleteDirectoryRecursively(KyuubiApplicationManager.tempDirForUpload.toFile) - } catch { - case e: Exception => error( - "Failed to delete temporary folder for uploading " + - s"${KyuubiApplicationManager.tempDirForUpload}", - e) - } - } } object KyuubiApplicationManager { @@ -122,7 +110,7 @@ object KyuubiApplicationManager { conf.set(FlinkProcessBuilder.TAG_KEY, newTag) } - lazy val tempDirForUpload: Path = { + val uploadWorkDir: Path = { val path = Utils.getAbsolutePathFromWork("upload") val pathFile = path.toFile if (!pathFile.exists()) { diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/operation/BatchJobSubmission.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/operation/BatchJobSubmission.scala index 4436926db..f061d977d 100644 --- a/kyuubi-server/src/main/scala/org/apache/kyuubi/operation/BatchJobSubmission.scala +++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/operation/BatchJobSubmission.scala @@ -18,6 +18,7 @@ package org.apache.kyuubi.operation import java.io.IOException +import java.nio.file.{Files, Paths} import java.util.Locale import java.util.concurrent.TimeUnit @@ -25,7 +26,7 @@ import com.codahale.metrics.MetricRegistry import com.google.common.annotations.VisibleForTesting import org.apache.hive.service.rpc.thrift._ -import org.apache.kyuubi.{KyuubiException, KyuubiSQLException, Utils} +import org.apache.kyuubi.{KyuubiException, KyuubiSQLException} import org.apache.kyuubi.config.KyuubiConf import org.apache.kyuubi.engine.{ApplicationInfo, ApplicationState, KillResponse, ProcBuilder} import org.apache.kyuubi.engine.spark.SparkBatchProcessBuilder @@ -267,9 +268,7 @@ class BatchJobSubmission( } } finally { builder.close() - if (session.isResourceUploaded) { - Utils.deleteFile(resource, "Failed to delete temporary uploaded resource file") - } + cleanupUploadedResourceIfNeeded() } } @@ -327,12 +326,14 @@ class BatchJobSubmission( if (isTerminalState(state)) { killMessage = (false, s"batch $batchId is already terminal so can not kill it.") builder.close() + cleanupUploadedResourceIfNeeded() return } try { killMessage = killBatchApplication() builder.close() + cleanupUploadedResourceIfNeeded() } finally { if (state == OperationState.INITIALIZED) { // if state is INITIALIZED, it means that the batch submission has not started to run, set @@ -363,6 +364,16 @@ class BatchJobSubmission( override def isTimedOut: Boolean = false override protected def eventEnabled: Boolean = true + + private def cleanupUploadedResourceIfNeeded(): Unit = { + if (session.isResourceUploaded) { + try { + Files.deleteIfExists(Paths.get(resource)) + } catch { + case e: Throwable => error(s"Error deleting the uploaded resource: $resource", e) + } + } + } } object BatchJobSubmission { diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/BatchesResource.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/BatchesResource.scala index 24f99a24c..969362f7d 100644 --- a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/BatchesResource.scala +++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/BatchesResource.scala @@ -184,7 +184,7 @@ private[v1] class BatchesResource extends ApiRequestContext with Logging { " of batchRequest is application/json") val tempFile = Utils.writeToTempFile( resourceFileInputStream, - KyuubiApplicationManager.tempDirForUpload, + KyuubiApplicationManager.uploadWorkDir, resourceFileMetadata.getFileName) batchRequest.setResource(tempFile.getPath) openBatchSessionInternal(batchRequest, isResourceFromUpload = true) diff --git a/kyuubi-server/src/test/scala/org/apache/kyuubi/server/api/v1/BatchesResourceSuite.scala b/kyuubi-server/src/test/scala/org/apache/kyuubi/server/api/v1/BatchesResourceSuite.scala index 7c06063a6..c77d364f3 100644 --- a/kyuubi-server/src/test/scala/org/apache/kyuubi/server/api/v1/BatchesResourceSuite.scala +++ b/kyuubi-server/src/test/scala/org/apache/kyuubi/server/api/v1/BatchesResourceSuite.scala @@ -35,7 +35,7 @@ import org.apache.kyuubi.{BatchTestHelper, KyuubiFunSuite, RestFrontendTestHelpe import org.apache.kyuubi.client.api.v1.dto._ import org.apache.kyuubi.config.KyuubiConf import org.apache.kyuubi.config.KyuubiConf._ -import org.apache.kyuubi.engine.ApplicationInfo +import org.apache.kyuubi.engine.{ApplicationInfo, KyuubiApplicationManager} import org.apache.kyuubi.engine.spark.SparkBatchProcessBuilder import org.apache.kyuubi.metrics.{MetricsConstants, MetricsSystem} import org.apache.kyuubi.operation.{BatchJobSubmission, OperationState} @@ -219,6 +219,12 @@ class BatchesResourceSuite extends KyuubiFunSuite with RestFrontendTestHelper wi assert(batch.getName === sparkBatchTestAppName) assert(batch.getCreateTime > 0) assert(batch.getEndTime === 0) + + webTarget.path(s"api/v1/batches/${batch.getId()}").request( + MediaType.APPLICATION_JSON_TYPE).delete() + eventually(timeout(3.seconds)) { + assert(KyuubiApplicationManager.uploadWorkDir.toFile.listFiles().isEmpty) + } } test("get batch session list") { From 265aaf28543a659e6590bc1ecc86c7f2f53fba50 Mon Sep 17 00:00:00 2001 From: fwang12 Date: Fri, 3 Feb 2023 16:17:39 +0800 Subject: [PATCH 005/760] [KYUUBI #3658][FOLLOWUP] Show ssl enabled protocols ### _Why are the changes needed?_ Before: ``` 2023-02-02 22:21:35.507 INFO org.apache.kyuubi.server.KyuubiTBinaryFrontendService: SSL Server Socket enabled protocols: [Ljava.lang.String;2fca3eb5 ``` Seems `enabledProtocols` is a java array, the output is not friendly. ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [x] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4238 from turboFei/ssl_followup. Closes #3658 999ef90e6 [fwang12] Show enabled ssl protocols Authored-by: fwang12 Signed-off-by: fwang12 --- .../org/apache/kyuubi/service/TBinaryFrontendService.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kyuubi-common/src/main/scala/org/apache/kyuubi/service/TBinaryFrontendService.scala b/kyuubi-common/src/main/scala/org/apache/kyuubi/service/TBinaryFrontendService.scala index 74cf4e2e6..2e8a8b765 100644 --- a/kyuubi-common/src/main/scala/org/apache/kyuubi/service/TBinaryFrontendService.scala +++ b/kyuubi-common/src/main/scala/org/apache/kyuubi/service/TBinaryFrontendService.scala @@ -163,7 +163,7 @@ abstract class TBinaryFrontendService(name: String) } } sslServerSocket.setEnabledProtocols(enabledProtocols) - info(s"SSL Server Socket enabled protocols: $enabledProtocols") + info(s"SSL Server Socket enabled protocols: ${enabledProtocols.mkString(",")}") case _ => } From f6778487ea8071443fe185114dd74f7d6326f1ad Mon Sep 17 00:00:00 2001 From: liangbowen Date: Fri, 3 Feb 2023 16:57:12 +0800 Subject: [PATCH 006/760] [KYUUBI #4239] Remove duplicate buildConf methods and use `KyuubiConf.buildConf` directly ### _Why are the changes needed?_ - Remove duplicate buildConf methods in a series configs , change to import and use `KyuubiConf.buildConf` directly ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [x] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4239 from bowenliang123/duplicate-buildconf. Closes #4239 04495e69 [liangbowen] remove duplciate buildConf methods Authored-by: liangbowen Signed-off-by: liangbowen --- .../src/main/scala/org/apache/kyuubi/ctl/CtlConf.scala | 5 ++--- .../scala/org/apache/kyuubi/ha/HighAvailabilityConf.scala | 5 ++--- .../main/scala/org/apache/kyuubi/metrics/MetricsConf.scala | 5 ++--- .../kyuubi/server/metadata/jdbc/JDBCMetadataStoreConf.scala | 5 ++--- .../scala/org/apache/kyuubi/zookeeper/ZookeeperConf.scala | 5 ++--- 5 files changed, 10 insertions(+), 15 deletions(-) diff --git a/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/CtlConf.scala b/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/CtlConf.scala index f299a5a88..58b65582a 100644 --- a/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/CtlConf.scala +++ b/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/CtlConf.scala @@ -19,12 +19,11 @@ package org.apache.kyuubi.ctl import java.time.Duration -import org.apache.kyuubi.config.{ConfigBuilder, ConfigEntry, KyuubiConf, OptionalConfigEntry} +import org.apache.kyuubi.config.{ConfigEntry, OptionalConfigEntry} +import org.apache.kyuubi.config.KyuubiConf.buildConf object CtlConf { - private def buildConf(key: String): ConfigBuilder = KyuubiConf.buildConf(key) - val CTL_REST_CLIENT_BASE_URL: OptionalConfigEntry[String] = buildConf("kyuubi.ctl.rest.base.url") .doc("The REST API base URL, " + diff --git a/kyuubi-ha/src/main/scala/org/apache/kyuubi/ha/HighAvailabilityConf.scala b/kyuubi-ha/src/main/scala/org/apache/kyuubi/ha/HighAvailabilityConf.scala index 6052e31f5..148a21e4d 100644 --- a/kyuubi-ha/src/main/scala/org/apache/kyuubi/ha/HighAvailabilityConf.scala +++ b/kyuubi-ha/src/main/scala/org/apache/kyuubi/ha/HighAvailabilityConf.scala @@ -21,14 +21,13 @@ import java.time.Duration import org.apache.hadoop.security.UserGroupInformation -import org.apache.kyuubi.config.{ConfigBuilder, ConfigEntry, KyuubiConf, OptionalConfigEntry} +import org.apache.kyuubi.config.{ConfigEntry, KyuubiConf, OptionalConfigEntry} +import org.apache.kyuubi.config.KyuubiConf.buildConf import org.apache.kyuubi.ha.client.AuthTypes import org.apache.kyuubi.ha.client.RetryPolicies object HighAvailabilityConf { - private def buildConf(key: String): ConfigBuilder = KyuubiConf.buildConf(key) - @deprecated("using kyuubi.ha.addresses instead", "1.6.0") val HA_ZK_QUORUM: ConfigEntry[String] = buildConf("kyuubi.ha.zookeeper.quorum") .doc("(deprecated) The connection string for the ZooKeeper ensemble") diff --git a/kyuubi-metrics/src/main/scala/org/apache/kyuubi/metrics/MetricsConf.scala b/kyuubi-metrics/src/main/scala/org/apache/kyuubi/metrics/MetricsConf.scala index daa221b78..ad734ced5 100644 --- a/kyuubi-metrics/src/main/scala/org/apache/kyuubi/metrics/MetricsConf.scala +++ b/kyuubi-metrics/src/main/scala/org/apache/kyuubi/metrics/MetricsConf.scala @@ -19,13 +19,12 @@ package org.apache.kyuubi.metrics import java.time.Duration -import org.apache.kyuubi.config.{ConfigBuilder, ConfigEntry, KyuubiConf} +import org.apache.kyuubi.config.ConfigEntry +import org.apache.kyuubi.config.KyuubiConf.buildConf import org.apache.kyuubi.metrics.ReporterType._ object MetricsConf { - private def buildConf(key: String): ConfigBuilder = KyuubiConf.buildConf(key) - val METRICS_ENABLED: ConfigEntry[Boolean] = buildConf("kyuubi.metrics.enabled") .doc("Set to true to enable kyuubi metrics system") diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/metadata/jdbc/JDBCMetadataStoreConf.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/metadata/jdbc/JDBCMetadataStoreConf.scala index 84067b8d0..de30b6e66 100644 --- a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/metadata/jdbc/JDBCMetadataStoreConf.scala +++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/metadata/jdbc/JDBCMetadataStoreConf.scala @@ -19,13 +19,12 @@ package org.apache.kyuubi.server.metadata.jdbc import java.util.{Locale, Properties} -import org.apache.kyuubi.config.{ConfigBuilder, ConfigEntry, KyuubiConf, OptionalConfigEntry} +import org.apache.kyuubi.config.{ConfigEntry, KyuubiConf, OptionalConfigEntry} +import org.apache.kyuubi.config.KyuubiConf.buildConf object JDBCMetadataStoreConf { final val METADATA_STORE_JDBC_DATASOURCE_PREFIX = "kyuubi.metadata.store.jdbc.datasource" - private def buildConf(key: String): ConfigBuilder = KyuubiConf.buildConf(key) - /** Get metadata store jdbc datasource properties. */ def getMetadataStoreJDBCDataSourceProperties(conf: KyuubiConf): Properties = { val datasourceProperties = new Properties() diff --git a/kyuubi-zookeeper/src/main/scala/org/apache/kyuubi/zookeeper/ZookeeperConf.scala b/kyuubi-zookeeper/src/main/scala/org/apache/kyuubi/zookeeper/ZookeeperConf.scala index ee1fe00dc..c6256a5e3 100644 --- a/kyuubi-zookeeper/src/main/scala/org/apache/kyuubi/zookeeper/ZookeeperConf.scala +++ b/kyuubi-zookeeper/src/main/scala/org/apache/kyuubi/zookeeper/ZookeeperConf.scala @@ -17,12 +17,11 @@ package org.apache.kyuubi.zookeeper -import org.apache.kyuubi.config.{ConfigBuilder, ConfigEntry, KyuubiConf, OptionalConfigEntry} +import org.apache.kyuubi.config.{ConfigEntry, OptionalConfigEntry} +import org.apache.kyuubi.config.KyuubiConf.buildConf object ZookeeperConf { - private def buildConf(key: String): ConfigBuilder = KyuubiConf.buildConf(key) - @deprecated("using kyuubi.zookeeper.embedded.client.port instead", since = "1.2.0") val EMBEDDED_ZK_PORT: ConfigEntry[Int] = buildConf("kyuubi.zookeeper.embedded.port") .doc("The port of the embedded ZooKeeper server") From 4dd00c14995a2814971a416902eb6451cef96c35 Mon Sep 17 00:00:00 2001 From: zwangsheng <2213335496@qq.com> Date: Mon, 6 Feb 2023 09:50:23 +0800 Subject: [PATCH 007/760] [KYUUBI #4247] Bump maven from 3.8.6 to 3.6.7 ### _Why are the changes needed?_ For `maven version` in CI is 3.8.7(get info by adding `/usr/bin/mvn --version` in `.github/workflows/dep.yaml`), ![popo_2023-02-06 09-34-07](https://user-images.githubusercontent.com/52876270/216862168-beda949f-a77b-44db-ad11-b959e27627ef.jpg) We should bump maven version property in parent pom.yaml 3.8.6 => 3.8.7. ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [ ] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4247 from zwangsheng/bump/build_mvn_version. Closes #4247 431c248e8 [zwangsheng] [Bump] Maven Version for build/mvn Authored-by: zwangsheng <2213335496@qq.com> Signed-off-by: Cheng Pan --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 723123475..8a86d8501 100644 --- a/pom.xml +++ b/pom.xml @@ -108,7 +108,7 @@ 1.8 - 3.8.6 + 3.8.7 ${java.version} ${java.version} 2.12.17 From 8a99750a20b593f97ecf8a5d57efde5aedfa91a0 Mon Sep 17 00:00:00 2001 From: dnskr Date: Mon, 6 Feb 2023 10:11:00 +0800 Subject: [PATCH 008/760] [KYUUBI #4147] [K8S][HELM] Add configuration support for multiple frontends to helm chart ### _Why are the changes needed?_ The changes allow to configure multiple frontends in the helm chart. Notes: - Removed unused code - `server.confDir` renamed to `kyuubiConfDir` - `server.conf` renamed to `kyuubiConf` - `livenessProbe` and `readinessProbe` changed to execute `bin/kyuubi status` which actually prints `Kyuubi is not running`. The issue needs to be reviewed. Also `livenessProbe` and `readinessProbe` should be revisited in the next PR. ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [ ] [Run test](https://kyuubi.apache.org/docs/latest/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4147 from dnskr/helm_frontend_configs. Closes #4147 ab10dd15b [dnskr] [K8S][HELM] Address PR comments 65e1f593e [dnskr] [K8S][HELM] Address some PR comments 36b5fc64f [dnskr] [K8S][HELM] Add configuration support for multiple frontends to helm chart Authored-by: dnskr Signed-off-by: Cheng Pan --- charts/kyuubi/templates/NOTES.txt | 30 +++++++- charts/kyuubi/templates/_helpers.tpl | 37 +++------ charts/kyuubi/templates/kyuubi-configmap.yaml | 14 ++-- .../kyuubi/templates/kyuubi-deployment.yaml | 19 +++-- charts/kyuubi/templates/kyuubi-service.yaml | 35 +++++---- charts/kyuubi/values.yaml | 76 +++++++++++++------ 6 files changed, 130 insertions(+), 81 deletions(-) diff --git a/charts/kyuubi/templates/NOTES.txt b/charts/kyuubi/templates/NOTES.txt index 44a35b6b7..be29b8048 100644 --- a/charts/kyuubi/templates/NOTES.txt +++ b/charts/kyuubi/templates/NOTES.txt @@ -15,7 +15,29 @@ # limitations under the License. # -Get kyuubi expose URL by running these commands: - export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "kyuubi.fullname" . }}-nodeport) - export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") - echo $NODE_IP:$NODE_PORT \ No newline at end of file +The chart has been installed! + +In order to check the release status, use: + helm status {{ .Release.Name }} -n {{ .Release.Namespace }} + or for more detailed info + helm get all {{ .Release.Name }} -n {{ .Release.Namespace }} + +************************ +******* Services ******* +************************ +{{- range $name, $frontend := .Values.server }} +{{- if $frontend.enabled }} +{{ $name | snakecase | upper }}: +- To access {{ $.Release.Name }}-{{ $name | kebabcase }} service within the cluster, use the following URL: + {{ $.Release.Name }}-{{ $name | kebabcase }}.{{ $.Release.Namespace }}.svc.cluster.local +- To access {{ $.Release.Name }}-{{ $name | kebabcase }} service from outside the cluster for debugging, run the following command: + kubectl port-forward svc/{{ $.Release.Name }}-{{ $name | kebabcase }} {{ tpl $frontend.service.port $ }}:{{ tpl $frontend.service.port $ }} -n {{ $.Release.Namespace }} + and use 127.0.0.1:{{ tpl $frontend.service.port $ }} +{{- if eq $frontend.service.type "NodePort" }} +- To access {{ $.Release.Name }}-{{ $name | kebabcase }} service from outside the cluster through configured NodePort, run the following commands: + export NODE_PORT=$(kubectl get service {{ $.Release.Name }}-{{ $name | kebabcase }} -n {{ $.Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}") + export NODE_IP=$(kubectl get nodes -n {{ $.Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") + echo http://$NODE_IP:$NODE_PORT +{{- end }} +{{- end }} +{{- end }} diff --git a/charts/kyuubi/templates/_helpers.tpl b/charts/kyuubi/templates/_helpers.tpl index 684c1f354..cd4865a12 100644 --- a/charts/kyuubi/templates/_helpers.tpl +++ b/charts/kyuubi/templates/_helpers.tpl @@ -16,33 +16,18 @@ */}} {{/* -Expand the name of the chart. +A comma separated string of enabled frontend protocols, e.g. "REST,THRIFT_BINARY". +For details, see 'kyuubi.frontend.protocols': https://kyuubi.readthedocs.io/en/master/deployment/settings.html#frontend */}} -{{- define "kyuubi.name" -}} -{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} -{{- end }} - -{{/* -Create a default fully qualified app name. -We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). -If release name contains chart name it will be used as a full name. -*/}} -{{- define "kyuubi.fullname" -}} -{{- if .Values.fullnameOverride }} -{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} -{{- else }} -{{- $name := default .Chart.Name .Values.nameOverride }} -{{- if contains $name .Release.Name }} -{{- .Release.Name | trunc 63 | trimSuffix "-" }} -{{- else }} -{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- define "kyuubi.frontend.protocols" -}} +{{- $protocols := list }} +{{- range $name, $frontend := .Values.server }} + {{- if $frontend.enabled }} + {{- $protocols = $name | snakecase | upper | append $protocols }} + {{- end }} {{- end }} +{{- if not $protocols }} + {{ fail "At least one frontend protocol must be enabled!" }} {{- end }} +{{- $protocols | join "," }} {{- end }} - -{{/* -Create chart name and version as used by the chart label. -*/}} -{{- define "kyuubi.chart" -}} -{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} -{{- end }} \ No newline at end of file diff --git a/charts/kyuubi/templates/kyuubi-configmap.yaml b/charts/kyuubi/templates/kyuubi-configmap.yaml index ada9e3dc8..7a96daaf7 100644 --- a/charts/kyuubi/templates/kyuubi-configmap.yaml +++ b/charts/kyuubi/templates/kyuubi-configmap.yaml @@ -26,22 +26,26 @@ metadata: app.kubernetes.io/version: {{ .Values.image.tag | default .Chart.AppVersion | quote }} app.kubernetes.io/managed-by: {{ .Release.Service }} data: - {{- with .Values.server.conf.kyuubiEnv }} + {{- with .Values.kyuubiConf.kyuubiEnv }} kyuubi-env.sh: | #!/usr/bin/env bash {{- tpl . $ | nindent 4 }} {{- end }} kyuubi-defaults.conf: | ## Helm chart provided Kyuubi configurations - kyuubi.frontend.bind.host={{ .Values.server.bind.host }} - kyuubi.frontend.bind.port={{ .Values.server.bind.port }} kyuubi.kubernetes.namespace={{ .Release.Namespace }} + kyuubi.frontend.bind.host=localhost + kyuubi.frontend.thrift.binary.bind.port={{ .Values.server.thriftBinary.port }} + kyuubi.frontend.thrift.http.bind.port={{ .Values.server.thriftHttp.port }} + kyuubi.frontend.rest.bind.port={{ .Values.server.rest.port }} + kyuubi.frontend.mysql.bind.port={{ .Values.server.mysql.port }} + kyuubi.frontend.protocols={{ include "kyuubi.frontend.protocols" . }} ## User provided Kyuubi configurations - {{- with .Values.server.conf.kyuubiDefaults }} + {{- with .Values.kyuubiConf.kyuubiDefaults }} {{- tpl . $ | nindent 4 }} {{- end }} - {{- with .Values.server.conf.log4j2 }} + {{- with .Values.kyuubiConf.log4j2 }} log4j2.xml: | {{- tpl . $ | nindent 4 }} {{- end }} diff --git a/charts/kyuubi/templates/kyuubi-deployment.yaml b/charts/kyuubi/templates/kyuubi-deployment.yaml index 941fdf164..998a87776 100644 --- a/charts/kyuubi/templates/kyuubi-deployment.yaml +++ b/charts/kyuubi/templates/kyuubi-deployment.yaml @@ -57,13 +57,16 @@ spec: envFrom: {{- tpl (toYaml .) $ | nindent 12 }} {{- end }} ports: - - name: frontend-port - containerPort: {{ .Values.server.bind.port }} - protocol: TCP + {{- range $name, $frontend := .Values.server }} + {{- if $frontend.enabled }} + - name: {{ $name | kebabcase }} + containerPort: {{ $frontend.port }} + {{- end }} + {{- end }} {{- if .Values.probe.liveness.enabled }} livenessProbe: - tcpSocket: - port: {{ .Values.server.bind.port }} + exec: + command: ["/bin/bash", "-c", "bin/kyuubi status"] initialDelaySeconds: {{ .Values.probe.liveness.initialDelaySeconds }} periodSeconds: {{ .Values.probe.liveness.periodSeconds }} timeoutSeconds: {{ .Values.probe.liveness.timeoutSeconds }} @@ -72,8 +75,8 @@ spec: {{- end }} {{- if .Values.probe.readiness.enabled }} readinessProbe: - tcpSocket: - port: {{ .Values.server.bind.port }} + exec: + command: ["/bin/bash", "-c", "$KYUUBI_HOME/bin/kyuubi status"] initialDelaySeconds: {{ .Values.probe.readiness.initialDelaySeconds }} periodSeconds: {{ .Values.probe.readiness.periodSeconds }} timeoutSeconds: {{ .Values.probe.readiness.timeoutSeconds }} @@ -85,7 +88,7 @@ spec: {{- end }} volumeMounts: - name: conf - mountPath: {{ .Values.server.confDir }} + mountPath: {{ .Values.kyuubiConfDir }} {{- with .Values.volumeMounts }} {{- tpl (toYaml .) $ | nindent 12 }} {{- end }} diff --git a/charts/kyuubi/templates/kyuubi-service.yaml b/charts/kyuubi/templates/kyuubi-service.yaml index 0152bd23d..963f1fcc7 100644 --- a/charts/kyuubi/templates/kyuubi-service.yaml +++ b/charts/kyuubi/templates/kyuubi-service.yaml @@ -15,27 +15,34 @@ # limitations under the License. # +{{- range $name, $frontend := .Values.server }} +{{- if $frontend.enabled }} apiVersion: v1 kind: Service metadata: - name: {{ .Release.Name }} + name: {{ $.Release.Name }}-{{ $name | kebabcase }} labels: - helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version }} - app.kubernetes.io/name: {{ .Chart.Name }} - app.kubernetes.io/instance: {{ .Release.Name }} - app.kubernetes.io/version: {{ .Values.image.tag | default .Chart.AppVersion | quote }} - app.kubernetes.io/managed-by: {{ .Release.Service }} - {{- with .Values.service.annotations }} + helm.sh/chart: {{ $.Chart.Name }}-{{ $.Chart.Version }} + app.kubernetes.io/name: {{ $.Chart.Name }} + app.kubernetes.io/instance: {{ $.Release.Name }} + app.kubernetes.io/version: {{ $.Values.image.tag | default $.Chart.AppVersion | quote }} + app.kubernetes.io/managed-by: {{ $.Release.Service }} + {{- with $frontend.service.annotations }} annotations: {{- toYaml . | nindent 4 }} {{- end }} spec: + type: {{ $frontend.service.type }} ports: - - name: http - nodePort: {{ .Values.service.port }} - port: {{ .Values.server.bind.port }} - protocol: TCP - type: {{ .Values.service.type }} + - name: {{ $name | kebabcase }} + port: {{ tpl $frontend.service.port $ }} + targetPort: {{ $frontend.port }} + {{- if and (eq $frontend.service.type "NodePort") ($frontend.service.nodePort) }} + nodePort: {{ $frontend.service.nodePort }} + {{- end }} selector: - app.kubernetes.io/name: {{ .Chart.Name }} - app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/name: {{ $.Chart.Name }} + app.kubernetes.io/instance: {{ $.Release.Name }} +--- +{{- end }} +{{- end }} diff --git a/charts/kyuubi/values.yaml b/charts/kyuubi/values.yaml index 22ae9d5a9..ddd16a9b7 100644 --- a/charts/kyuubi/values.yaml +++ b/charts/kyuubi/values.yaml @@ -58,22 +58,59 @@ probe: successThreshold: 1 server: - bind: - host: 0.0.0.0 + # Thrift Binary protocol (HiveServer2 compatible) + thriftBinary: + enabled: true port: 10009 - confDir: /opt/kyuubi/conf - conf: - # The value (templated string) is used for kyuubi-env.sh file - # See https://kyuubi.apache.org/docs/latest/deployment/settings.html#environments for more details - kyuubiEnv: ~ - - # The value (templated string) is used for kyuubi-defaults.conf file - # See https://kyuubi.apache.org/docs/latest/deployment/settings.html#kyuubi-configurations for more details - kyuubiDefaults: ~ - - # The value (templated string) is used for log4j2.xml file - # See https://kyuubi.apache.org/docs/latest/deployment/settings.html#logging for more details - log4j2: ~ + service: + type: ClusterIP + port: "{{ .Values.server.thriftBinary.port }}" + nodePort: ~ + annotations: {} + + # Thrift HTTP protocol (HiveServer2 compatible) + thriftHttp: + enabled: false + port: 10010 + service: + type: ClusterIP + port: "{{ .Values.server.thriftHttp.port }}" + nodePort: ~ + annotations: {} + + # REST API protocol (experimental) + rest: + enabled: false + port: 10099 + service: + type: ClusterIP + port: "{{ .Values.server.rest.port }}" + nodePort: ~ + annotations: {} + + # MySQL compatible text protocol (experimental) + mysql: + enabled: false + port: 3309 + service: + type: ClusterIP + port: "{{ .Values.server.mysql.port }}" + nodePort: ~ + annotations: {} + +kyuubiConfDir: /opt/kyuubi/conf +kyuubiConf: + # The value (templated string) is used for kyuubi-env.sh file + # See https://kyuubi.apache.org/docs/latest/deployment/settings.html#environments for more details + kyuubiEnv: ~ + + # The value (templated string) is used for kyuubi-defaults.conf file + # See https://kyuubi.apache.org/docs/latest/deployment/settings.html#kyuubi-configurations for more details + kyuubiDefaults: ~ + + # The value (templated string) is used for log4j2.xml file + # See https://kyuubi.apache.org/docs/latest/deployment/settings.html#logging for more details + log4j2: ~ # Environment variables (templated) env: [] @@ -89,15 +126,6 @@ initContainers: [] # Additional containers for Kyuubi pod (templated) containers: [] -service: - type: NodePort - # The default port limit of kubernetes is 30000-32767 - # to change: - # vim kube-apiserver.yaml (usually under path: /etc/kubernetes/manifests/) - # add or change line 'service-node-port-range=1-32767' under kube-apiserver - port: 30009 - annotations: {} - resources: {} # Used to specify resource, default unlimited. # If you do want to specify resources: From 2b958c69e1c768f8982e8f9a59fe84bb116cf0b0 Mon Sep 17 00:00:00 2001 From: Alex Wiss-Wolferding Date: Mon, 6 Feb 2023 10:20:23 +0800 Subject: [PATCH 009/760] [KYUUBI #4218] Using DB and table name when checking Delta table schema. ### _Why are the changes needed?_ To close #4218 . This change ensures BI tools can list columns on Delta Lake tables in all schemas. image image ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [x] Add screenshots for manual tests if appropriate - [ ] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4219 from nousot-cloud-guy/feature/delta-db-schema. Closes #4218 569843213 [Alex Wiss-Wolferding] Reversing match order in getColumnsByCatalog. a6d973a3e [Alex Wiss-Wolferding] Revert "[KYUUBI #1458] Delta lake table columns won't show up in DBeaver." 20337dc96 [Alex Wiss-Wolferding] Revert "Using DB and table name when checking Delta table schema." f7e4675a7 [Alex Wiss-Wolferding] Using DB and table name when checking Delta table schema. Authored-by: Alex Wiss-Wolferding Signed-off-by: Cheng Pan --- .../engine/spark/shim/CatalogShim_v2_4.scala | 8 +------- .../engine/spark/shim/CatalogShim_v3_0.scala | 16 ++++++++-------- 2 files changed, 9 insertions(+), 15 deletions(-) diff --git a/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/shim/CatalogShim_v2_4.scala b/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/shim/CatalogShim_v2_4.scala index 5977cd415..3478abc66 100644 --- a/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/shim/CatalogShim_v2_4.scala +++ b/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/shim/CatalogShim_v2_4.scala @@ -139,13 +139,7 @@ class CatalogShim_v2_4 extends SparkCatalogShim { databases.flatMap { db => val identifiers = catalog.listTables(db, tablePattern, includeLocalTempViews = true) catalog.getTablesByName(identifiers).flatMap { t => - val tableSchema = - if (t.provider.getOrElse("").equalsIgnoreCase("delta")) { - spark.table(t.identifier.table).schema - } else { - t.schema - } - tableSchema.zipWithIndex.filter(f => columnPattern.matcher(f._1.name).matches()) + t.schema.zipWithIndex.filter(f => columnPattern.matcher(f._1.name).matches()) .map { case (f, i) => toColumnResult(catalogName, t.database, t.identifier.table, f, i) } } } diff --git a/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/shim/CatalogShim_v3_0.scala b/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/shim/CatalogShim_v3_0.scala index d60f94ac7..50e641b59 100644 --- a/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/shim/CatalogShim_v3_0.scala +++ b/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/shim/CatalogShim_v3_0.scala @@ -188,14 +188,6 @@ class CatalogShim_v3_0 extends CatalogShim_v2_4 { val catalog = getCatalog(spark, catalogName) catalog match { - case builtin if builtin.name() == SESSION_CATALOG => - super.getColumnsByCatalog( - spark, - SESSION_CATALOG, - schemaPattern, - tablePattern, - columnPattern) - case tc: TableCatalog => val namespaces = listNamespacesWithPattern(catalog, schemaPattern) val tp = tablePattern.r.pattern @@ -210,6 +202,14 @@ class CatalogShim_v3_0 extends CatalogShim_v2_4 { table.schema.zipWithIndex.filter(f => columnPattern.matcher(f._1.name).matches()) .map { case (f, i) => toColumnResult(tc.name(), namespace, tableName, f, i) } } + + case builtin if builtin.name() == SESSION_CATALOG => + super.getColumnsByCatalog( + spark, + SESSION_CATALOG, + schemaPattern, + tablePattern, + columnPattern) } } } From b84cb2f2d7a00c3f3a8f08dbab95f553b547fe3b Mon Sep 17 00:00:00 2001 From: runzhliu Date: Mon, 6 Feb 2023 10:27:23 +0800 Subject: [PATCH 010/760] [KYUUBI #4234] Keep the same version for Integration Test on Kubernetes ### _Why are the changes needed?_ Keep the same version for Integration Tests on Kubernetes and Minikube. ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [ ] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4234 from runzhliu/master. Closes #4234 94345659d [runzhliu] Keep the same version for Integration test on Kubernetes Authored-by: runzhliu Signed-off-by: Cheng Pan --- .github/workflows/master.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index 6bb2658ef..59bcdbd58 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -398,8 +398,9 @@ jobs: - name: Setup Minikube uses: manusa/actions-setup-minikube@v2.7.2 with: - minikube version: 'v1.25.2' - kubernetes version: 'v1.23.3' + minikube version: 'v1.28.0' + kubernetes version: 'v1.25.4' + github token: ${{ secrets.GITHUB_TOKEN }} driver: docker start args: '--extra-config=kubeadm.ignore-preflight-errors=NumCPU --force --cpus 2 --memory 4096' # in case: https://spark.apache.org/docs/latest/running-on-kubernetes.html#rbac From 8760eeef0eb1fba98db1b892510d89285b4f1727 Mon Sep 17 00:00:00 2001 From: ulysses-you Date: Mon, 6 Feb 2023 11:12:00 +0800 Subject: [PATCH 011/760] [KYUUBI #4241] Only force close engine ref when open session failed ### _Why are the changes needed?_ We do not need force close engine builder and application if open session succeded. The engine itself will shutdown in connection level. This pr tries to fix regression of https://github.com/apache/kyuubi/pull/2482 ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [ ] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4241 from ulysses-you/skip-close. Closes #4241 0b280411 [ulysses-you] style 16369123 [ulysses-you] Only force close engine ref when open session failed Authored-by: ulysses-you Signed-off-by: ulyssesyou --- .../scala/org/apache/kyuubi/session/KyuubiSessionImpl.scala | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/session/KyuubiSessionImpl.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/session/KyuubiSessionImpl.scala index 536034b9c..ce94b0275 100644 --- a/kyuubi-server/src/main/scala/org/apache/kyuubi/session/KyuubiSessionImpl.scala +++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/session/KyuubiSessionImpl.scala @@ -105,6 +105,8 @@ class KyuubiSessionImpl( private var _engineSessionHandle: SessionHandle = _ + private var openSessionError: Option[Throwable] = None + override def open(): Unit = handleSessionException { traceMetricsOnOpen() @@ -170,6 +172,7 @@ class KyuubiSessionImpl( s"Opening engine [${engine.defaultEngineName} $host:$port]" + s" for $user session failed", e) + openSessionError = Some(e) throw e } finally { attempt += 1 @@ -247,7 +250,7 @@ class KyuubiSessionImpl( try { if (_client != null) _client.closeSession() } finally { - if (engine != null) engine.close() + openSessionError.foreach { _ => if (engine != null) engine.close() } sessionEvent.endTime = System.currentTimeMillis() EventBus.post(sessionEvent) traceMetricsOnClose() From 5b70b41a010bf8c90037d35a6f354be56f3f95d7 Mon Sep 17 00:00:00 2001 From: liangbowen Date: Mon, 6 Feb 2023 14:51:17 +0800 Subject: [PATCH 012/760] [KYUUBI #4244] Improvement in auto-generated Markdown docs with MarkdownBuilder ### _Why are the changes needed?_ Note: - No output changes to existed generated docs as in `settings.md` and `functions.md`. Improvement: - readability in Scala code for doc auto-generation, and easy to maintain docs in group of sections - reline on markdown linting from `flexmark`, and reducing un-meaningful blank lines or alignment - declaration over operation by replacing repeated usages of `newOutput` itself by elegantly wrapped `.line`,`.lines`and etc. - less fragile and more handy for changing in a long single line, as now using auto margin stripping in `.line()` method - reusable extracted licence and auto-generation hints - more elegant and safer way to read and appending file content - possible less memory footprint by apply operators to Stream instead of to ArrayBuffer ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [x] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4244 from bowenliang123/config-regen. Closes #4244 3d90ad304 [liangbowen] make buffer private in MarkdownBuilder 8250a55f3 [liangbowen] remove licence.md a4c7baf78 [liangbowen] add MarkdownBuilder.apply 248a046a4 [liangbowen] Improvement in auto-generated Markdown docs with MarkdownBuilder Authored-by: liangbowen Signed-off-by: Cheng Pan --- .../udf/KyuubiDefinedFunctionSuite.scala | 55 +-- .../org/apache/kyuubi/MarkdownUtils.scala | 213 +++++++++ .../scala/org/apache/kyuubi/TestUtils.scala | 84 ---- .../config/AllKyuubiConfiguration.scala | 413 ++++++++---------- 4 files changed, 406 insertions(+), 359 deletions(-) create mode 100644 kyuubi-common/src/test/scala/org/apache/kyuubi/MarkdownUtils.scala diff --git a/externals/kyuubi-spark-sql-engine/src/test/scala/org/apache/kyuubi/engine/spark/udf/KyuubiDefinedFunctionSuite.scala b/externals/kyuubi-spark-sql-engine/src/test/scala/org/apache/kyuubi/engine/spark/udf/KyuubiDefinedFunctionSuite.scala index dc0513ed3..f355e1e6b 100644 --- a/externals/kyuubi-spark-sql-engine/src/test/scala/org/apache/kyuubi/engine/spark/udf/KyuubiDefinedFunctionSuite.scala +++ b/externals/kyuubi-spark-sql-engine/src/test/scala/org/apache/kyuubi/engine/spark/udf/KyuubiDefinedFunctionSuite.scala @@ -19,9 +19,7 @@ package org.apache.kyuubi.engine.spark.udf import java.nio.file.Paths -import scala.collection.mutable.ArrayBuffer - -import org.apache.kyuubi.{KyuubiFunSuite, TestUtils, Utils} +import org.apache.kyuubi.{KyuubiFunSuite, MarkdownBuilder, MarkdownUtils, Utils} // scalastyle:off line.size.limit /** @@ -48,44 +46,25 @@ class KyuubiDefinedFunctionSuite extends KyuubiFunSuite { .toAbsolutePath test("verify or update kyuubi spark sql functions") { - val newOutput = new ArrayBuffer[String]() - newOutput += "" - newOutput += "" - newOutput += "" - newOutput += "" - newOutput += "" - newOutput += "# Auxiliary SQL Functions" - newOutput += "" - newOutput += "Kyuubi provides several auxiliary SQL functions as supplement to Spark's " + - "[Built-in Functions](https://spark.apache.org/docs/latest/api/sql/index.html#" + - "built-in-functions)" - newOutput += "" - newOutput += "Name | Description | Return Type | Since" - newOutput += "--- | --- | --- | ---" - KDFRegistry + val builder = MarkdownBuilder(licenced = true, getClass.getName) + + builder + .line("# Auxiliary SQL Functions") + .line("""Kyuubi provides several auxiliary SQL functions as supplement to Spark's + | [Built-in Functions](https://spark.apache.org/docs/latest/api/sql/index.html# + |built-in-functions)""") + .lines(""" + | Name | Description | Return Type | Since + | --- | --- | --- | --- + | + |""") KDFRegistry.registeredFunctions.foreach { func => - newOutput += s"${func.name} | ${func.description} | ${func.returnType} | ${func.since}" + builder.line(s"${func.name} | ${func.description} | ${func.returnType} | ${func.since}") } - newOutput += "" - TestUtils.verifyOutput( + + MarkdownUtils.verifyOutput( markdown, - newOutput, + builder, getClass.getCanonicalName, "externals/kyuubi-spark-sql-engine") } diff --git a/kyuubi-common/src/test/scala/org/apache/kyuubi/MarkdownUtils.scala b/kyuubi-common/src/test/scala/org/apache/kyuubi/MarkdownUtils.scala new file mode 100644 index 000000000..45568df25 --- /dev/null +++ b/kyuubi-common/src/test/scala/org/apache/kyuubi/MarkdownUtils.scala @@ -0,0 +1,213 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.kyuubi + +import java.nio.charset.StandardCharsets +import java.nio.file.{Files, Path, StandardOpenOption} + +import scala.collection.JavaConverters._ +import scala.collection.mutable.ArrayBuffer +import scala.compat.Platform.EOL + +import com.vladsch.flexmark.formatter.Formatter +import com.vladsch.flexmark.parser.{Parser, ParserEmulationProfile, PegdownExtensions} +import com.vladsch.flexmark.profile.pegdown.PegdownOptionsAdapter +import com.vladsch.flexmark.util.data.{MutableDataHolder, MutableDataSet} +import org.scalatest.Assertions.{assertResult, withClue} + +object MarkdownUtils { + + def verifyOutput( + markdown: Path, + newOutput: MarkdownBuilder, + agent: String, + module: String): Unit = { + val formatted = newOutput.formatMarkdown() + if (System.getenv("KYUUBI_UPDATE") == "1") { + Files.write( + markdown, + formatted.asJava, + StandardOpenOption.CREATE, + StandardOpenOption.TRUNCATE_EXISTING) + } else { + val linesInFile = Files.readAllLines(markdown, StandardCharsets.UTF_8) + linesInFile.asScala.zipWithIndex.zip(formatted).foreach { case ((str1, index), str2) => + withClue(s"$markdown out of date, as line ${index + 1} is not expected." + + " Please update doc with KYUUBI_UPDATE=1 build/mvn clean test" + + s" -pl $module -am -Pflink-provided,spark-provided,hive-provided" + + s" -DwildcardSuites=$agent") { + assertResult(str2)(str1) + } + } + } + } + + def line(str: String): String = { + str.stripMargin.replaceAll(EOL, "") + } + + def appendBlankLine(buffer: ArrayBuffer[String]): Unit = buffer += "" + + def appendFileContent(buffer: ArrayBuffer[String], path: Path): Unit = { + buffer += "```bash" + buffer ++= Files.readAllLines(path).asScala + buffer += "```" + } +} + +class MarkdownBuilder { + private val buffer = new ArrayBuffer[String]() + + /** + * append a single line + * with replacing EOL to empty string + * @param str single line + * @return + */ + def line(str: String = ""): MarkdownBuilder = { + buffer += str.stripMargin.replaceAll(EOL, "") + this + } + + /** + * append the multiline + * with splitting EOL into single lines + * @param multiline multiline with default line margin "|" + * @return + */ + def lines(multiline: String): MarkdownBuilder = { + buffer ++= multiline.stripMargin.split(EOL) + this + } + + /** + * append the licence + * @return + */ + def licence(): MarkdownBuilder = { + lines(""" + | + |""") + } + + /** + * append the auto-generation hint + * @param className the full class name of agent suite + * @return + */ + def generationHint(className: String): MarkdownBuilder = { + lines(s""" + | + | + |""") + } + + /** + * append file content + * @param path file path + * @return + */ + def file(path: Path): MarkdownBuilder = { + buffer ++= Files.readAllLines(path).asScala + this + } + + /** + * append file content with code block quote + * @param path path to file + * @param language language of codeblock + * @return + */ + def fileWithBlock(path: Path, language: String = "bash"): MarkdownBuilder = { + buffer += s"```$language" + file(path) + buffer += "```" + this + } + + def formatMarkdown(): Stream[String] = { + def createParserOptions(emulationProfile: ParserEmulationProfile): MutableDataHolder = { + PegdownOptionsAdapter.flexmarkOptions(PegdownExtensions.ALL).toMutable + .set(Parser.PARSER_EMULATION_PROFILE, emulationProfile) + } + + def createFormatterOptions( + parserOptions: MutableDataHolder, + emulationProfile: ParserEmulationProfile): MutableDataSet = { + new MutableDataSet() + .set(Parser.EXTENSIONS, Parser.EXTENSIONS.get(parserOptions)) + .set(Formatter.FORMATTER_EMULATION_PROFILE, emulationProfile) + } + + val emulationProfile = ParserEmulationProfile.COMMONMARK + val parserOptions = createParserOptions(emulationProfile) + val formatterOptions = createFormatterOptions(parserOptions, emulationProfile) + val parser = Parser.builder(parserOptions).build + val renderer = Formatter.builder(formatterOptions).build + val document = parser.parse(buffer.mkString(EOL)) + val formattedLines = new ArrayBuffer[String](buffer.length) + val formattedLinesAppendable = new Appendable { + override def append(csq: CharSequence): Appendable = { + if (csq.length() > 0) { + formattedLines.append(csq.toString) + } + this + } + + override def append(csq: CharSequence, start: Int, end: Int): Appendable = { + append(csq.toString.substring(start, end)) + } + + override def append(c: Char): Appendable = { + append(c.toString) + } + } + renderer.render(document, formattedLinesAppendable) + // trim the ending EOL appended by renderer for each line + formattedLines.toStream.map(str => + if (str.endsWith(EOL)) { + str.substring(0, str.length - 1) + } else { + str + }) + } +} + +object MarkdownBuilder { + def apply(licenced: Boolean = true, className: String = null): MarkdownBuilder = { + val builder = new MarkdownBuilder + if (licenced) { builder.licence() } + if (className != null) { builder.generationHint(className) } + builder + } +} diff --git a/kyuubi-common/src/test/scala/org/apache/kyuubi/TestUtils.scala b/kyuubi-common/src/test/scala/org/apache/kyuubi/TestUtils.scala index 16a49388f..97675768a 100644 --- a/kyuubi-common/src/test/scala/org/apache/kyuubi/TestUtils.scala +++ b/kyuubi-common/src/test/scala/org/apache/kyuubi/TestUtils.scala @@ -14,99 +14,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.apache.kyuubi -import java.nio.charset.StandardCharsets -import java.nio.file.{Files, Path, StandardOpenOption} import java.sql.ResultSet -import scala.collection.JavaConverters._ import scala.collection.mutable.ArrayBuffer import com.jakewharton.fliptables.FlipTable -import com.vladsch.flexmark.formatter.Formatter -import com.vladsch.flexmark.parser.{Parser, ParserEmulationProfile, PegdownExtensions} -import com.vladsch.flexmark.profile.pegdown.PegdownOptionsAdapter -import com.vladsch.flexmark.util.data.{MutableDataHolder, MutableDataSet} -import com.vladsch.flexmark.util.sequence.SequenceUtils -import org.scalatest.Assertions.{assertResult, withClue} object TestUtils { - - private def formatMarkdown(lines: ArrayBuffer[String]): ArrayBuffer[String] = { - def createParserOptions(emulationProfile: ParserEmulationProfile): MutableDataHolder = { - PegdownOptionsAdapter.flexmarkOptions(PegdownExtensions.ALL).toMutable - .set(Parser.PARSER_EMULATION_PROFILE, emulationProfile) - } - - def createFormatterOptions( - parserOptions: MutableDataHolder, - emulationProfile: ParserEmulationProfile): MutableDataSet = { - new MutableDataSet() - .set(Parser.EXTENSIONS, Parser.EXTENSIONS.get(parserOptions)) - .set(Formatter.FORMATTER_EMULATION_PROFILE, emulationProfile) - } - - val emulationProfile = ParserEmulationProfile.valueOf("COMMONMARK") - val parserOptions = createParserOptions(emulationProfile) - val formatterOptions = createFormatterOptions(parserOptions, emulationProfile) - val parser = Parser.builder(parserOptions).build - val renderer = Formatter.builder(formatterOptions).build - val document = parser.parse(lines.mkString(SequenceUtils.EOL)) - val formattedLines = new ArrayBuffer[String] - val formattedLinesAppendable = new Appendable { - override def append(csq: CharSequence): Appendable = { - if (csq.length() > 0) { - formattedLines.append(csq.toString) - } - this - } - - override def append(csq: CharSequence, start: Int, end: Int): Appendable = { - append(csq.toString.substring(start, end)) - } - - override def append(c: Char): Appendable = { - append(c.toString) - } - } - renderer.render(document, formattedLinesAppendable) - // trim the ending EOL appended by renderer for each line - formattedLines.map(str => - if (str.nonEmpty && str.endsWith(SequenceUtils.EOL)) { - str.substring(0, str.length - 1) - } else { - str - }) - } - - def verifyOutput( - markdown: Path, - newOutput: ArrayBuffer[String], - agent: String, - module: String): Unit = { - if (System.getenv("KYUUBI_UPDATE") == "1") { - val formatted = formatMarkdown(newOutput) - Files.write( - markdown, - formatted.asJava, - StandardOpenOption.CREATE, - StandardOpenOption.TRUNCATE_EXISTING) - } else { - val linesInFile = Files.readAllLines(markdown, StandardCharsets.UTF_8) - val formatted = formatMarkdown(newOutput) - linesInFile.asScala.zipWithIndex.zip(formatted).foreach { case ((str1, index), str2) => - withClue(s"$markdown out of date, as line ${index + 1} is not expected." + - " Please update doc with KYUUBI_UPDATE=1 build/mvn clean test" + - s" -pl $module -am -Pflink-provided,spark-provided,hive-provided" + - s" -DwildcardSuites=$agent") { - assertResult(str2)(str1) - } - } - } - } - def displayResultSet(resultSet: ResultSet): Unit = { if (resultSet == null) throw new NullPointerException("resultSet == null") val resultSetMetaData = resultSet.getMetaData diff --git a/kyuubi-server/src/test/scala/org/apache/kyuubi/config/AllKyuubiConfiguration.scala b/kyuubi-server/src/test/scala/org/apache/kyuubi/config/AllKyuubiConfiguration.scala index 7ea4d49b2..1d0e09544 100644 --- a/kyuubi-server/src/test/scala/org/apache/kyuubi/config/AllKyuubiConfiguration.scala +++ b/kyuubi-server/src/test/scala/org/apache/kyuubi/config/AllKyuubiConfiguration.scala @@ -17,13 +17,11 @@ package org.apache.kyuubi.config -import java.nio.charset.StandardCharsets -import java.nio.file.{Files, Path, Paths} +import java.nio.file.Paths import scala.collection.JavaConverters._ -import scala.collection.mutable.ArrayBuffer -import org.apache.kyuubi.{KyuubiFunSuite, TestUtils, Utils} +import org.apache.kyuubi.{KyuubiFunSuite, MarkdownBuilder, MarkdownUtils, Utils} import org.apache.kyuubi.ctl.CtlConf import org.apache.kyuubi.ha.HighAvailabilityConf import org.apache.kyuubi.metrics.MetricsConf @@ -51,255 +49,196 @@ class AllKyuubiConfiguration extends KyuubiFunSuite { private val markdown = Paths.get(kyuubiHome, "docs", "deployment", "settings.md") .toAbsolutePath - def rewriteToConf(path: Path, buffer: ArrayBuffer[String]): Unit = { - val env = - Files.newBufferedReader(path, StandardCharsets.UTF_8) - - try { - buffer += "```bash" - var line = env.readLine() - while (line != null) { - buffer += line - line = env.readLine() - } - buffer += "```" - } finally { - env.close() - } - } + private def loadConfigs = Array( + KyuubiConf, + CtlConf, + HighAvailabilityConf, + JDBCMetadataStoreConf, + MetricsConf, + ZookeeperConf) test("Check all kyuubi configs") { - KyuubiConf - CtlConf - HighAvailabilityConf - JDBCMetadataStoreConf - MetricsConf - ZookeeperConf - - val newOutput = new ArrayBuffer[String]() - newOutput += "" - newOutput += "" - newOutput += "" - newOutput += "" - newOutput += "" - newOutput += "# Introduction to the Kyuubi Configurations System" - newOutput += "" - newOutput += "Kyuubi provides several ways to configure the system and corresponding engines." - newOutput += "" - newOutput += "" - newOutput += "## Environments" - newOutput += "" - newOutput += "" - newOutput += "You can configure the environment variables in" + - " `$KYUUBI_HOME/conf/kyuubi-env.sh`, e.g, `JAVA_HOME`, then this java runtime will be used" + - " both for Kyuubi server instance and the applications it launches. You can also change" + - " the variable in the subprocess's env configuration file, e.g." + - "`$SPARK_HOME/conf/spark-env.sh` to use more specific ENV for SQL engine applications." - - rewriteToConf(Paths.get(kyuubiHome, "conf", "kyuubi-env.sh.template"), newOutput) - - newOutput += "" - newOutput += "For the environment variables that only needed to be transferred into engine" + - " side, you can set it with a Kyuubi configuration item formatted" + - " `kyuubi.engineEnv.VAR_NAME`. For example, with `kyuubi.engineEnv.SPARK_DRIVER_MEMORY=4g`," + - " the environment variable `SPARK_DRIVER_MEMORY` with value `4g` would be transferred into" + - " engine side. With `kyuubi.engineEnv.SPARK_CONF_DIR=/apache/confs/spark/conf`, the" + - " value of `SPARK_CONF_DIR` on the engine side is set to `/apache/confs/spark/conf`." - - newOutput += "" - newOutput += "## Kyuubi Configurations" - newOutput += "" - - newOutput += "You can configure the Kyuubi properties in" + - " `$KYUUBI_HOME/conf/kyuubi-defaults.conf`. For example:" - - rewriteToConf(Paths.get(kyuubiHome, "conf", "kyuubi-defaults.conf.template"), newOutput) + loadConfigs + + val builder = MarkdownBuilder(licenced = true, getClass.getName) + + builder + .lines(s""" + |# Introduction to the Kyuubi Configurations System + | + |Kyuubi provides several ways to configure the system and corresponding engines. + | + |## Environments + | + |""") + .line("""You can configure the environment variables in `$KYUUBI_HOME/conf/kyuubi-env.sh`, + | e.g, `JAVA_HOME`, then this java runtime will be used both for Kyuubi server instance and + | the applications it launches. You can also change the variable in the subprocess's env + | configuration file, e.g.`$SPARK_HOME/conf/spark-env.sh` to use more specific ENV for + | SQL engine applications. + | """) + .fileWithBlock(Paths.get(kyuubiHome, "conf", "kyuubi-env.sh.template")) + .line( + """ + | For the environment variables that only needed to be transferred into engine + | side, you can set it with a Kyuubi configuration item formatted + | `kyuubi.engineEnv.VAR_NAME`. For example, with `kyuubi.engineEnv.SPARK_DRIVER_MEMORY=4g`, + | the environment variable `SPARK_DRIVER_MEMORY` with value `4g` would be transferred into + | engine side. With `kyuubi.engineEnv.SPARK_CONF_DIR=/apache/confs/spark/conf`, the + | value of `SPARK_CONF_DIR` on the engine side is set to `/apache/confs/spark/conf`. + | """) + .line("## Kyuubi Configurations") + .line(""" You can configure the Kyuubi properties in + | `$KYUUBI_HOME/conf/kyuubi-defaults.conf`. For example: """) + .fileWithBlock(Paths.get(kyuubiHome, "conf", "kyuubi-defaults.conf.template")) KyuubiConf.getConfigEntries().asScala - .toSeq + .toStream .filterNot(_.internal) .groupBy(_.key.split("\\.")(1)) .toSeq.sortBy(_._1).foreach { case (category, entries) => - newOutput += "" - newOutput += s"### ${category.capitalize}" - newOutput += "" - - newOutput += "Key | Default | Meaning | Type | Since" - newOutput += "--- | --- | --- | --- | ---" + builder.lines( + s"""### ${category.capitalize} + | Key | Default | Meaning | Type | Since + | --- | --- | --- | --- | --- + |""") entries.sortBy(_.key).foreach { c => val dft = c.defaultValStr.replace("<", "<").replace(">", ">") - val seq = Seq( + builder.line(Seq( s"${c.key}", s"$dft", s"${c.doc}", s"${c.typ}", - s"${c.version}") - newOutput += seq.mkString("|") + s"${c.version}").mkString("|")) } - newOutput += "" } - newOutput += ("## Spark Configurations") - newOutput += "" - - newOutput += ("### Via spark-defaults.conf") - newOutput += "" - - newOutput += ("Setting them in `$SPARK_HOME/conf/spark-defaults.conf`" + - " supplies with default values for SQL engine application. Available properties can be" + - " found at Spark official online documentation for" + - " [Spark Configurations](https://spark.apache.org/docs/latest/configuration.html)") - - newOutput += "" - newOutput += ("### Via kyuubi-defaults.conf") - newOutput += "" - newOutput += ("Setting them in `$KYUUBI_HOME/conf/kyuubi-defaults.conf`" + - " supplies with default values for SQL engine application too. These properties will" + - " override all settings in `$SPARK_HOME/conf/spark-defaults.conf`") - - newOutput += "" - newOutput += ("### Via JDBC Connection URL") - newOutput += "" - newOutput += ("Setting them in the JDBC Connection URL" + - " supplies session-specific for each SQL engine. For example: " + - "```" + - "jdbc:hive2://localhost:10009/default;#" + - "spark.sql.shuffle.partitions=2;spark.executor.memory=5g" + - "```") - newOutput += "" - newOutput += ("- **Runtime SQL Configuration**") - newOutput += "" - newOutput += (" - For [Runtime SQL Configurations](" + - "https://spark.apache.org/docs/latest/configuration.html#runtime-sql-configuration), they" + - " will take affect every time") - newOutput += ("- **Static SQL and Spark Core Configuration**") - newOutput += "" - newOutput += (" - For [Static SQL Configurations](" + - "https://spark.apache.org/docs/latest/configuration.html#static-sql-configuration) and" + - " other spark core configs, e.g. `spark.executor.memory`, they will take effect if there" + - " is no existing SQL engine application. Otherwise, they will just be ignored") - newOutput += "" - newOutput += ("### Via SET Syntax") - newOutput += "" - newOutput += ("Please refer to the Spark official online documentation for" + - " [SET Command](https://spark.apache.org/docs/latest/sql-ref-syntax-aux-conf-mgmt-set.html)") - newOutput += "" - - newOutput += ("## Flink Configurations") - newOutput += "" - - newOutput += ("### Via flink-conf.yaml") - newOutput += "" - newOutput += ("Setting them in `$FLINK_HOME/conf/flink-conf.yaml`" + - " supplies with default values for SQL engine application." + - " Available properties can be found at Flink official online documentation for" + - " [Flink Configurations]" + - "(https://nightlies.apache.org/flink/flink-docs-stable/docs/deployment/config/)") - newOutput += "" - - newOutput += ("### Via kyuubi-defaults.conf") - newOutput += "" - newOutput += ("Setting them in `$KYUUBI_HOME/conf/kyuubi-defaults.conf`" + - " supplies with default values for SQL engine application too." + - " You can use properties with the additional prefix `flink.` to override settings in" + - " `$FLINK_HOME/conf/flink-conf.yaml`.") - newOutput += "" - newOutput += ("For example:") - newOutput += ("```") - newOutput += ("flink.parallelism.default 2") - newOutput += ("flink.taskmanager.memory.process.size 5g") - newOutput += ("```") - newOutput += "" - newOutput += ("The below options in `kyuubi-defaults.conf` will set `parallelism.default: 2`" + - " and `taskmanager.memory.process.size: 5g` into flink configurations.") - newOutput += "" - - newOutput += ("### Via JDBC Connection URL") - newOutput += "" - newOutput += "Setting them in the JDBC Connection URL supplies session-specific" + - " for each SQL engine. For example: ```jdbc:hive2://localhost:10009/default;" + - "#parallelism.default=2;taskmanager.memory.process.size=5g```" - newOutput += "" - - newOutput += ("### Via SET Statements") - newOutput += "" - newOutput += ("Please refer to the Flink official online documentation for [SET Statements]" + - "(https://nightlies.apache.org/flink/flink-docs-stable/docs/dev/table/sql/set/)") - newOutput += "" - - newOutput += ("## Logging") - newOutput += "" - newOutput += ("Kyuubi uses [log4j](https://logging.apache.org/log4j/2.x/) for logging." + - " You can configure it using `$KYUUBI_HOME/conf/log4j2.xml`.") - - rewriteToConf(Paths.get(kyuubiHome, "conf", "log4j2.xml.template"), newOutput) - - newOutput += "" - newOutput += ("## Other Configurations") - newOutput += "" - newOutput += ("### Hadoop Configurations") - newOutput += "" - newOutput += ("Specifying `HADOOP_CONF_DIR` to the directory containing Hadoop configuration" + - " files or treating them as Spark properties with a `spark.hadoop.` prefix." + - " Please refer to the Spark official online documentation for" + - " [Inheriting Hadoop Cluster Configuration](https://spark.apache.org/docs/latest/" + - "configuration.html#inheriting-hadoop-cluster-configuration)." + - " Also, please refer to the [Apache Hadoop](https://hadoop.apache.org)'s" + - " online documentation for an overview on how to configure Hadoop.") - newOutput += "" - newOutput += ("### Hive Configurations") - newOutput += "" - newOutput += ("These configurations are used for SQL engine application to talk to" + - " Hive MetaStore and could be configured in a `hive-site.xml`." + - " Placed it in `$SPARK_HOME/conf` directory, or treat them as Spark properties with" + - " a `spark.hadoop.` prefix.") - - newOutput += "" - newOutput += ("## User Defaults") - newOutput += "" - newOutput += ("In Kyuubi, we can configure user default settings to meet separate needs." + - " These user defaults override system defaults, but will be overridden by those from" + - " [JDBC Connection URL](#via-jdbc-connection-url) or [Set Command](#via-set-syntax)" + - " if could be. They will take effect when creating the SQL engine application ONLY.") - newOutput += ("User default settings are in the form of `___{username}___.{config key}`." + - " There are three continuous underscores(`_`) at both sides of the `username` and" + - " a dot(`.`) that separates the config key and the prefix. For example:") - newOutput += ("```bash") - newOutput += ("# For system defaults") - newOutput += ("spark.master=local") - newOutput += ("spark.sql.adaptive.enabled=true") - newOutput += ("# For a user named kent") - newOutput += ("___kent___.spark.master=yarn") - newOutput += ("___kent___.spark.sql.adaptive.enabled=false") - newOutput += ("# For a user named bob") - newOutput += ("___bob___.spark.master=spark://master:7077") - newOutput += ("___bob___.spark.executor.memory=8g") - newOutput += ("```") - newOutput += "" - newOutput += "In the above case, if there are related configurations from" + - " [JDBC Connection URL](#via-jdbc-connection-url), `kent` will run his SQL engine" + - " application on YARN and prefer the Spark AQE to be off, while `bob` will activate" + - " his SQL engine application on a Spark standalone cluster with 8g heap memory for each" + - " executor and obey the Spark AQE behavior of Kyuubi system default. On the other hand," + - " for those users who do not have custom configurations will use system defaults." - - TestUtils.verifyOutput(markdown, newOutput, getClass.getCanonicalName, "kyuubi-server") + builder + .lines(""" + |## Spark Configurations + |### Via spark-defaults.conf + |""") + .line(""" + | Setting them in `$SPARK_HOME/conf/spark-defaults.conf` + | supplies with default values for SQL engine application. Available properties can be + | found at Spark official online documentation for + | [Spark Configurations](https://spark.apache.org/docs/latest/configuration.html) + | """) + .line("### Via kyuubi-defaults.conf") + .line(""" + | Setting them in `$KYUUBI_HOME/conf/kyuubi-defaults.conf` + | supplies with default values for SQL engine application too. These properties will + | override all settings in `$SPARK_HOME/conf/spark-defaults.conf`""") + .line("### Via JDBC Connection URL") + .line(""" + | Setting them in the JDBC Connection URL + | supplies session-specific for each SQL engine. For example: + | ``` + |jdbc:hive2://localhost:10009/default;# + |spark.sql.shuffle.partitions=2;spark.executor.memory=5g + |```""") + .line() + .line("- **Runtime SQL Configuration**") + .line(""" - For [Runtime SQL Configurations]( + |https://spark.apache.org/docs/latest/configuration.html#runtime-sql-configuration), they + | will take affect every time""") + .line("- **Static SQL and Spark Core Configuration**") + .line(""" - For [Static SQL Configurations]( + |https://spark.apache.org/docs/latest/configuration.html#static-sql-configuration) and + | other spark core configs, e.g. `spark.executor.memory`, they will take effect if there + | is no existing SQL engine application. Otherwise, they will just be ignored""") + .line("### Via SET Syntax") + .line("""Please refer to the Spark official online documentation for + | [SET Command](https://spark.apache.org/docs/latest/sql-ref-syntax-aux-conf-mgmt-set.html) + |""") + + builder + .lines(""" + |## Flink Configurations + |### Via flink-conf.yaml""") + .line("""Setting them in `$FLINK_HOME/conf/flink-conf.yaml` + | supplies with default values for SQL engine application. + | Available properties can be found at Flink official online documentation for + | [Flink Configurations] + |(https://nightlies.apache.org/flink/flink-docs-stable/docs/deployment/config/)""") + .line("### Via kyuubi-defaults.conf") + .line("""Setting them in `$KYUUBI_HOME/conf/kyuubi-defaults.conf` + | supplies with default values for SQL engine application too. + | You can use properties with the additional prefix `flink.` to override settings in + | `$FLINK_HOME/conf/flink-conf.yaml`.""") + .lines(""" + | + |For example: + |``` + |flink.parallelism.default 2 + |flink.taskmanager.memory.process.size 5g + |```""") + .line("""The below options in `kyuubi-defaults.conf` will set `parallelism.default: 2` + | and `taskmanager.memory.process.size: 5g` into flink configurations.""") + .line("### Via JDBC Connection URL") + .line("""Setting them in the JDBC Connection URL supplies session-specific + | for each SQL engine. For example: ```jdbc:hive2://localhost:10009/default; + |#parallelism.default=2;taskmanager.memory.process.size=5g``` + |""") + .line("### Via SET Statements") + .line("""Please refer to the Flink official online documentation for [SET Statements] + |(https://nightlies.apache.org/flink/flink-docs-stable/docs/dev/table/sql/set/)""") + + builder + .line("## Logging") + .line("""Kyuubi uses [log4j](https://logging.apache.org/log4j/2.x/) for logging. + | You can configure it using `$KYUUBI_HOME/conf/log4j2.xml`.""") + .fileWithBlock(Paths.get(kyuubiHome, "conf", "log4j2.xml.template")) + + builder + .lines(""" + |## Other Configurations + |### Hadoop Configurations + |""") + .line("""Specifying `HADOOP_CONF_DIR` to the directory containing Hadoop configuration + | files or treating them as Spark properties with a `spark.hadoop.` prefix. + | Please refer to the Spark official online documentation for + | [Inheriting Hadoop Cluster Configuration](https://spark.apache.org/docs/latest/ + |configuration.html#inheriting-hadoop-cluster-configuration). + | Also, please refer to the [Apache Hadoop](https://hadoop.apache.org)'s + | online documentation for an overview on how to configure Hadoop.""") + .line("### Hive Configurations") + .line("""These configurations are used for SQL engine application to talk to + | Hive MetaStore and could be configured in a `hive-site.xml`. + | Placed it in `$SPARK_HOME/conf` directory, or treat them as Spark properties with + | a `spark.hadoop.` prefix.""") + + builder + .line("## User Defaults") + .line("""In Kyuubi, we can configure user default settings to meet separate needs. + | These user defaults override system defaults, but will be overridden by those from + | [JDBC Connection URL](#via-jdbc-connection-url) or [Set Command](#via-set-syntax) + | if could be. They will take effect when creating the SQL engine application ONLY.""") + .line("""User default settings are in the form of `___{username}___.{config key}`. + | There are three continuous underscores(`_`) at both sides of the `username` and + | a dot(`.`) that separates the config key and the prefix. For example:""") + .lines(""" + |```bash + |# For system defaults + |spark.master=local + |spark.sql.adaptive.enabled=true + |# For a user named kent + |___kent___.spark.master=yarn + |___kent___.spark.sql.adaptive.enabled=false + |# For a user named bob + |___bob___.spark.master=spark://master:7077 + |___bob___.spark.executor.memory=8g + |``` + | + |""") + .line("""In the above case, if there are related configurations from + | [JDBC Connection URL](#via-jdbc-connection-url), `kent` will run his SQL engine + | application on YARN and prefer the Spark AQE to be off, while `bob` will activate + | his SQL engine application on a Spark standalone cluster with 8g heap memory for each + | executor and obey the Spark AQE behavior of Kyuubi system default. On the other hand, + | for those users who do not have custom configurations will use system defaults.""") + + MarkdownUtils.verifyOutput(markdown, builder, getClass.getCanonicalName, "kyuubi-server") } } From 534fc9f20f864069b76f40c3e5b0e183bee8dd62 Mon Sep 17 00:00:00 2001 From: liangbowen Date: Mon, 6 Feb 2023 15:49:56 +0800 Subject: [PATCH 013/760] [KYUUBI #4209] [Authz] Set logging level to DEBUG in PrivilegesBuilder for multiple specs ### _Why are the changes needed?_ - change logging level from `warn` to `debug` in Authz - to eliminate duplicated unuseful messages (as screenshot below) with multiple specs in extracting resources with lower Spark versions ![WechatIMG7517](https://user-images.githubusercontent.com/1935105/215329357-ad242cf4-9c98-44b0-978a-f37304a85ef0.png) ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [ ] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4209 from bowenliang123/authz-debug. Closes #4209 f3b949bc [liangbowen] change logging level from `warn` to `debug` in multi extractor rule in Authz serde Authored-by: liangbowen Signed-off-by: liangbowen --- .../kyuubi/plugin/spark/authz/PrivilegesBuilder.scala | 6 +++--- .../kyuubi/plugin/spark/authz/serde/CommandSpec.scala | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/extensions/spark/kyuubi-spark-authz/src/main/scala/org/apache/kyuubi/plugin/spark/authz/PrivilegesBuilder.scala b/extensions/spark/kyuubi-spark-authz/src/main/scala/org/apache/kyuubi/plugin/spark/authz/PrivilegesBuilder.scala index cee79b87d..51f5694e1 100644 --- a/extensions/spark/kyuubi-spark-authz/src/main/scala/org/apache/kyuubi/plugin/spark/authz/PrivilegesBuilder.scala +++ b/extensions/spark/kyuubi-spark-authz/src/main/scala/org/apache/kyuubi/plugin/spark/authz/PrivilegesBuilder.scala @@ -144,7 +144,7 @@ object PrivilegesBuilder { } } catch { case e: Exception => - LOG.warn(tableDesc.error(plan, e)) + LOG.debug(tableDesc.error(plan, e)) Nil } } @@ -162,7 +162,7 @@ object PrivilegesBuilder { } } catch { case e: Exception => - LOG.warn(databaseDesc.error(plan, e)) + LOG.debug(databaseDesc.error(plan, e)) } } desc.operationType @@ -193,7 +193,7 @@ object PrivilegesBuilder { } } catch { case e: Exception => - LOG.warn(fd.error(plan, e)) + LOG.debug(fd.error(plan, e)) } } spec.operationType diff --git a/extensions/spark/kyuubi-spark-authz/src/main/scala/org/apache/kyuubi/plugin/spark/authz/serde/CommandSpec.scala b/extensions/spark/kyuubi-spark-authz/src/main/scala/org/apache/kyuubi/plugin/spark/authz/serde/CommandSpec.scala index d72d78932..e96ef8cbf 100644 --- a/extensions/spark/kyuubi-spark-authz/src/main/scala/org/apache/kyuubi/plugin/spark/authz/serde/CommandSpec.scala +++ b/extensions/spark/kyuubi-spark-authz/src/main/scala/org/apache/kyuubi/plugin/spark/authz/serde/CommandSpec.scala @@ -85,7 +85,7 @@ case class TableCommandSpec( qd.extract(plan) } catch { case e: Exception => - LOG.warn(qd.error(plan, e)) + LOG.debug(qd.error(plan, e)) None } } @@ -102,7 +102,7 @@ case class ScanSpec( td.extract(plan, spark) } catch { case e: Exception => - LOG.warn(td.error(plan, e)) + LOG.debug(td.error(plan, e)) None } } From 82a1883561b95f4d4edd65480c6d43e986148ef5 Mon Sep 17 00:00:00 2001 From: Cheng Pan Date: Mon, 6 Feb 2023 23:11:15 +0800 Subject: [PATCH 014/760] [KYUUBI #4258] Bump testcontainers-scala 0.40.12 ### _Why are the changes needed?_ Keep deps update-to-date https://mvnrepository.com/artifact/com.dimafeng/testcontainers-scala ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [x] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4258 from pan3793/testcontainers-scala. Closes #4258 1352f013 [Cheng Pan] Bump testcontainers-scala 0.40.12 Authored-by: Cheng Pan Signed-off-by: liangbowen --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 8a86d8501..80d210c0c 100644 --- a/pom.xml +++ b/pom.xml @@ -194,7 +194,7 @@ false 2.2.1 4.9.1 - 0.40.7 + 0.40.12 0.9.3 363 1.4 From f62a7ac5870de622c3b7c41713527a6dd9375e2d Mon Sep 17 00:00:00 2001 From: fwang12 Date: Tue, 7 Feb 2023 10:51:51 +0800 Subject: [PATCH 015/760] [KYUUBI #3968][FOLLOWUP] Get schema url with correct version ordering ### _Why are the changes needed?_ Get schema url with correct version ordering. ### _How was this patch tested?_ - [x] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [x] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4254 from turboFei/version_order. Closes #3968 57474e65c [fwang12] refactor 561a1cde9 [fwang12] refactor 0a3275aa7 [fwang12] save 64236740d [fwang12] save a723a4023 [fwang12] save c31564865 [fwang12] save 13b3d29e0 [fwang12] save 347081d77 [fwang12] save Authored-by: fwang12 Signed-off-by: fwang12 --- .../metadata/jdbc/JDBCMetadataStore.scala | 43 ++++++++++++------- .../jdbc/JDBCMetadataStoreSuite.scala | 16 +++++++ 2 files changed, 44 insertions(+), 15 deletions(-) diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/metadata/jdbc/JDBCMetadataStore.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/metadata/jdbc/JDBCMetadataStore.scala index 151d846d8..f6caa9c1a 100644 --- a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/metadata/jdbc/JDBCMetadataStore.scala +++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/metadata/jdbc/JDBCMetadataStore.scala @@ -96,37 +96,49 @@ class JDBCMetadataStore(conf: KyuubiConf) extends MetadataStore with Logging { private[jdbc] def getInitSchema(dbType: DatabaseType): Option[String] = { val classLoader = Utils.getContextOrKyuubiClassLoader val schemaPackage = s"sql/${dbType.toString.toLowerCase}" - val schemaUrlPattern = """^metadata-store-schema-(\d+)\.(\d+)\.(\d+)\.(.*)\.sql$""".r - val schemaUrls = ListBuffer[String]() - Option(classLoader.getResource(schemaPackage)).map(_.toURI).foreach { uri => + Option(classLoader.getResource(schemaPackage)).map(_.toURI).flatMap { uri => val pathNames = if (uri.getScheme == "jar") { val fs = FileSystems.newFileSystem(uri, Map.empty[String, AnyRef].asJava) try { Files.walk(fs.getPath(schemaPackage), 1).iterator().asScala.map( _.getFileName.toString).filter { name => - schemaUrlPattern.findFirstMatchIn(name).isDefined + SCHEMA_URL_PATTERN.findFirstMatchIn(name).isDefined }.toArray } finally { fs.close() } } else { Paths.get(uri).toFile.listFiles((_, name) => { - schemaUrlPattern.findFirstMatchIn(name).isDefined + SCHEMA_URL_PATTERN.findFirstMatchIn(name).isDefined }).map(_.getName) } - pathNames.foreach(name => schemaUrls += s"$schemaPackage/$name") - } - - schemaUrls.sorted.lastOption.map { schemaUrl => - val inputStream = classLoader.getResourceAsStream(schemaUrl) - try { - new BufferedReader(new InputStreamReader(inputStream)).lines() - .collect(Collectors.joining("\n")) - } finally { - inputStream.close() + getLatestSchemaUrl(pathNames).map(name => s"$schemaPackage/$name").map { schemaUrl => + val inputStream = classLoader.getResourceAsStream(schemaUrl) + try { + new BufferedReader(new InputStreamReader(inputStream)).lines() + .collect(Collectors.joining("\n")) + } finally { + inputStream.close() + } } + }.headOption + } + + def getSchemaVersion(schemaUrl: String): (Int, Int, Int) = + SCHEMA_URL_PATTERN.findFirstMatchIn(schemaUrl) match { + case Some(m) => (m.group(1).toInt, m.group(2).toInt, m.group(3).toInt) + case _ => throw new KyuubiException(s"Invalid schema url: $schemaUrl") } + + def getLatestSchemaUrl(schemaUrls: Seq[String]): Option[String] = { + schemaUrls.sortWith { (u1, u2) => + val v1 = getSchemaVersion(u1) + val v2 = getSchemaVersion(u2) + v1._1 > v2._1 || + (v1._1 == v2._1 && v1._2 > v2._2) || + (v1._1 == v2._1 && v1._2 == v2._2 && v1._3 > v2._3) + }.headOption } override def close(): Unit = { @@ -505,6 +517,7 @@ class JDBCMetadataStore(conf: KyuubiConf) extends MetadataStore with Logging { } object JDBCMetadataStore { + private val SCHEMA_URL_PATTERN = """^metadata-store-schema-(\d+)\.(\d+)\.(\d+)\.(.*)\.sql$""".r private val METADATA_TABLE = "metadata" private val METADATA_STATE_ONLY_COLUMNS = Seq( "identifier", diff --git a/kyuubi-server/src/test/scala/org/apache/kyuubi/server/metadata/jdbc/JDBCMetadataStoreSuite.scala b/kyuubi-server/src/test/scala/org/apache/kyuubi/server/metadata/jdbc/JDBCMetadataStoreSuite.scala index 73dc105c3..aa53af3a9 100644 --- a/kyuubi-server/src/test/scala/org/apache/kyuubi/server/metadata/jdbc/JDBCMetadataStoreSuite.scala +++ b/kyuubi-server/src/test/scala/org/apache/kyuubi/server/metadata/jdbc/JDBCMetadataStoreSuite.scala @@ -279,4 +279,20 @@ class JDBCMetadataStoreSuite extends KyuubiFunSuite { jdbcMetadataStore.updateMetadata(metadata) } } + + test("get schema urls with correct version ordering") { + val url1 = "metadata-store-schema-1.7.0.mysql.sql" + val url2 = "metadata-store-schema-1.7.1.mysql.sql" + val url3 = "metadata-store-schema-1.8.0.mysql.sql" + val url4 = "metadata-store-schema-1.10.0.mysql.sql" + val url5 = "metadata-store-schema-2.1.0.mysql.sql" + assert(jdbcMetadataStore.getSchemaVersion(url1) === ((1, 7, 0))) + assert(jdbcMetadataStore.getSchemaVersion(url2) === ((1, 7, 1))) + assert(jdbcMetadataStore.getSchemaVersion(url3) === ((1, 8, 0))) + assert(jdbcMetadataStore.getSchemaVersion(url4) === ((1, 10, 0))) + assert(jdbcMetadataStore.getSchemaVersion(url5) === ((2, 1, 0))) + assert(jdbcMetadataStore.getLatestSchemaUrl(Seq(url1, url2, url3, url4)).get === url4) + assert(jdbcMetadataStore.getLatestSchemaUrl(Seq(url1, url3, url4, url2)).get === url4) + assert(jdbcMetadataStore.getLatestSchemaUrl(Seq(url1, url2, url3, url4, url5)).get === url5) + } } From 1b92d80678d6a05ca8a4e9f3ddded6deaf0b3d9e Mon Sep 17 00:00:00 2001 From: yehere <867171931@qq.com> Date: Tue, 7 Feb 2023 11:08:48 +0800 Subject: [PATCH 016/760] [KYUUBI #3934] Compatiable with Trino rest dto ### _Why are the changes needed?_ close #3934 ### _How was this patch tested?_ - [x] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [x] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4182 from yehere/kyuubi-3934. Closes #3934 ced64e2c [yehere] [KYUUBI #3934] Add more result types support 3ef85230 [yehere] [KYUUBI #3934] Optimization for code review 69e1f442 [yehere] [KYUUBI #3934] Merge the test class to TrinoContextSuite 4f0a0152 [yehere] [KYUUBI #3934] Merge the class to TrinoContext 7c9473f6 [yehere] [KYUUBI #3934] Format style, with Copyright Profiles 2023f3ce [yehere] [KYUUBI #3934] Format and add test case a2243b46 [yehere] [KYUUBI #3934] Compatiable with Trino rest dto Authored-by: yehere <867171931@qq.com> Signed-off-by: ulyssesyou --- .../server/trino/api/TrinoContext.scala | 235 ++++++++++++++++-- .../server/trino/api/TrinoContextSuite.scala | 94 ++++++- 2 files changed, 310 insertions(+), 19 deletions(-) diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/trino/api/TrinoContext.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/trino/api/TrinoContext.scala index 8f3131f61..4a0736ddb 100644 --- a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/trino/api/TrinoContext.scala +++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/trino/api/TrinoContext.scala @@ -18,34 +18,38 @@ package org.apache.kyuubi.server.trino.api import java.io.UnsupportedEncodingException -import java.net.{URLDecoder, URLEncoder} +import java.net.{URI, URLDecoder, URLEncoder} +import java.util import javax.ws.rs.core.{HttpHeaders, Response} import scala.collection.JavaConverters._ +import io.trino.client.{ClientStandardTypes, ClientTypeSignature, Column, QueryError, QueryResults, StatementStats, Warning} import io.trino.client.ProtocolHeaders.TRINO_HEADERS -import io.trino.client.QueryResults +import org.apache.hive.service.rpc.thrift.{TGetResultSetMetadataResp, TRowSet, TTypeId} + +import org.apache.kyuubi.operation.OperationStatus /** * The description and functionality of trino request * and response's context * - * @param user Specifies the session user, must be supplied with every query - * @param timeZone The timezone for query processing + * @param user Specifies the session user, must be supplied with every query + * @param timeZone The timezone for query processing * @param clientCapabilities Exclusive for trino server - * @param source This supplies the name of the software that submitted the query, - * e.g. `trino-jdbc` or `trino-cli` by default - * @param catalog The catalog context for query processing, will be set response - * @param schema The schema context for query processing - * @param language The language to use when processing the query and formatting results, - * formatted as a Java Locale string, e.g., en-US for US English - * @param traceToken Trace token for correlating requests across systems - * @param clientInfo Extra information about the client - * @param clientTags Client tags for selecting resource groups. Example: abc,xyz - * @param preparedStatement `preparedStatement` are kv pairs, where the names - * are names of previously prepared SQL statements, - * and the values are keys that identify the - * executable form of the named prepared statements + * @param source This supplies the name of the software that submitted the query, + * e.g. `trino-jdbc` or `trino-cli` by default + * @param catalog The catalog context for query processing, will be set response + * @param schema The schema context for query processing + * @param language The language to use when processing the query and formatting results, + * formatted as a Java Locale string, e.g., en-US for US English + * @param traceToken Trace token for correlating requests across systems + * @param clientInfo Extra information about the client + * @param clientTags Client tags for selecting resource groups. Example: abc,xyz + * @param preparedStatement `preparedStatement` are kv pairs, where the names + * are names of previously prepared SQL statements, + * and the values are keys that identify the + * executable form of the named prepared statements */ case class TrinoContext( user: String, @@ -63,6 +67,11 @@ case class TrinoContext( object TrinoContext { + private val defaultWarning: util.List[Warning] = new util.ArrayList[Warning]() + private val GENERIC_INTERNAL_ERROR_CODE = 65536 + private val GENERIC_INTERNAL_ERROR_NAME = "GENERIC_INTERNAL_ERROR_NAME" + private val GENERIC_INTERNAL_ERROR_TYPE = "INTERNAL_ERROR" + def apply(headers: HttpHeaders): TrinoContext = { apply(headers.getRequestHeaders.asScala.toMap.map { case (k, v) => (k, v.asScala.toList) @@ -166,4 +175,196 @@ object TrinoContext { throw new AssertionError(e) } + def createQueryResults( + queryId: String, + nextUri: URI, + queryHtmlUri: URI, + queryStatus: OperationStatus, + columns: Option[TGetResultSetMetadataResp] = None, + data: Option[TRowSet] = None): QueryResults = { + + val columnList = columns match { + case Some(value) => convertTColumn(value) + case None => null + } + val rowList = data match { + case Some(value) => convertTRowSet(value) + case None => null + } + + new QueryResults( + queryId, + queryHtmlUri, + nextUri, + nextUri, + columnList, + rowList, + StatementStats.builder.setState(queryStatus.state.name()).setQueued(false) + .setElapsedTimeMillis(0).setQueuedTimeMillis(0).build(), + toQueryError(queryStatus), + defaultWarning, + null, + 0L) + } + + def convertTColumn(columns: TGetResultSetMetadataResp): util.List[Column] = { + columns.getSchema.getColumns.asScala.map(c => { + val tp = c.getTypeDesc.getTypes.get(0).getPrimitiveEntry.getType match { + case TTypeId.BOOLEAN_TYPE => ClientStandardTypes.BOOLEAN + case TTypeId.TINYINT_TYPE => ClientStandardTypes.TINYINT + case TTypeId.SMALLINT_TYPE => ClientStandardTypes.SMALLINT + case TTypeId.INT_TYPE => ClientStandardTypes.INTEGER + case TTypeId.BIGINT_TYPE => ClientStandardTypes.BIGINT + case TTypeId.FLOAT_TYPE => ClientStandardTypes.DOUBLE + case TTypeId.DOUBLE_TYPE => ClientStandardTypes.DOUBLE + case TTypeId.STRING_TYPE => ClientStandardTypes.VARCHAR + case TTypeId.TIMESTAMP_TYPE => ClientStandardTypes.TIMESTAMP + case TTypeId.BINARY_TYPE => ClientStandardTypes.VARBINARY + case TTypeId.DECIMAL_TYPE => ClientStandardTypes.DECIMAL + case TTypeId.DATE_TYPE => ClientStandardTypes.DATE + case TTypeId.VARCHAR_TYPE => ClientStandardTypes.VARCHAR + case TTypeId.CHAR_TYPE => ClientStandardTypes.CHAR + case TTypeId.INTERVAL_YEAR_MONTH_TYPE => ClientStandardTypes.INTERVAL_YEAR_TO_MONTH + case TTypeId.INTERVAL_DAY_TIME_TYPE => ClientStandardTypes.TIME_WITH_TIME_ZONE + case TTypeId.TIMESTAMPLOCALTZ_TYPE => ClientStandardTypes.TIMESTAMP_WITH_TIME_ZONE + case _ => ClientStandardTypes.VARCHAR + } + new Column(c.getColumnName, tp, new ClientTypeSignature(tp)) + }).toList.asJava + } + + def convertTRowSet(rowSet: TRowSet): util.List[util.List[Object]] = { + val dataResult = new util.LinkedList[util.List[Object]] + + if (rowSet.getColumns == null) { + return rowSet.getRows.asScala + .map(t => t.getColVals.asScala.map(v => v.getFieldValue.asInstanceOf[Object]).asJava) + .asJava + } + + rowSet.getColumns.asScala.foreach { + case tColumn if tColumn.isSetBoolVal => + val nulls = util.BitSet.valueOf(tColumn.getBoolVal.getNulls) + if (dataResult.isEmpty) { + (1 to tColumn.getBoolVal.getValuesSize).foreach(_ => + dataResult.add(new util.LinkedList[Object]())) + } + + tColumn.getBoolVal.getValues.asScala.zipWithIndex.foreach { + case (_, rowIdx) if nulls.get(rowIdx) => + dataResult.get(rowIdx).add(null) + case (v, rowIdx) => + dataResult.get(rowIdx).add(v) + } + case tColumn if tColumn.isSetByteVal => + val nulls = util.BitSet.valueOf(tColumn.getByteVal.getNulls) + if (dataResult.isEmpty) { + (1 to tColumn.getByteVal.getValuesSize).foreach(_ => + dataResult.add(new util.LinkedList[Object]())) + } + + tColumn.getByteVal.getValues.asScala.zipWithIndex.foreach { + case (_, rowIdx) if nulls.get(rowIdx) => + dataResult.get(rowIdx).add(null) + case (v, rowIdx) => + dataResult.get(rowIdx).add(v) + } + case tColumn if tColumn.isSetI16Val => + val nulls = util.BitSet.valueOf(tColumn.getI16Val.getNulls) + if (dataResult.isEmpty) { + (1 to tColumn.getI16Val.getValuesSize).foreach(_ => + dataResult.add(new util.LinkedList[Object]())) + } + + tColumn.getI16Val.getValues.asScala.zipWithIndex.foreach { + case (_, rowIdx) if nulls.get(rowIdx) => + dataResult.get(rowIdx).add(null) + case (v, rowIdx) => + dataResult.get(rowIdx).add(v) + } + case tColumn if tColumn.isSetI32Val => + val nulls = util.BitSet.valueOf(tColumn.getI32Val.getNulls) + if (dataResult.isEmpty) { + (1 to tColumn.getI32Val.getValuesSize).foreach(_ => + dataResult.add(new util.LinkedList[Object]())) + } + + tColumn.getI32Val.getValues.asScala.zipWithIndex.foreach { + case (_, rowIdx) if nulls.get(rowIdx) => + dataResult.get(rowIdx).add(null) + case (v, rowIdx) => + dataResult.get(rowIdx).add(v) + } + case tColumn if tColumn.isSetI64Val => + val nulls = util.BitSet.valueOf(tColumn.getI64Val.getNulls) + if (dataResult.isEmpty) { + (1 to tColumn.getI64Val.getValuesSize).foreach(_ => + dataResult.add(new util.LinkedList[Object]())) + } + + tColumn.getI64Val.getValues.asScala.zipWithIndex.foreach { + case (_, rowIdx) if nulls.get(rowIdx) => + dataResult.get(rowIdx).add(null) + case (v, rowIdx) => + dataResult.get(rowIdx).add(v) + } + case tColumn if tColumn.isSetDoubleVal => + val nulls = util.BitSet.valueOf(tColumn.getDoubleVal.getNulls) + if (dataResult.isEmpty) { + (1 to tColumn.getDoubleVal.getValuesSize).foreach(_ => + dataResult.add(new util.LinkedList[Object]())) + } + + tColumn.getDoubleVal.getValues.asScala.zipWithIndex.foreach { + case (_, rowIdx) if nulls.get(rowIdx) => + dataResult.get(rowIdx).add(null) + case (v, rowIdx) => + dataResult.get(rowIdx).add(v) + } + case tColumn if tColumn.isSetBinaryVal => + val nulls = util.BitSet.valueOf(tColumn.getBinaryVal.getNulls) + if (dataResult.isEmpty) { + (1 to tColumn.getBinaryVal.getValuesSize).foreach(_ => + dataResult.add(new util.LinkedList[Object]())) + } + + tColumn.getBinaryVal.getValues.asScala.zipWithIndex.foreach { + case (_, rowIdx) if nulls.get(rowIdx) => + dataResult.get(rowIdx).add(null) + case (v, rowIdx) => + dataResult.get(rowIdx).add(v) + } + case tColumn => + val nulls = util.BitSet.valueOf(tColumn.getStringVal.getNulls) + if (dataResult.isEmpty) { + (1 to tColumn.getStringVal.getValuesSize).foreach(_ => + dataResult.add(new util.LinkedList[Object]())) + } + + tColumn.getStringVal.getValues.asScala.zipWithIndex.foreach { + case (_, rowIdx) if nulls.get(rowIdx) => + dataResult.get(rowIdx).add(null) + case (v, rowIdx) => + dataResult.get(rowIdx).add(v) + } + } + dataResult + } + + def toQueryError(queryStatus: OperationStatus): QueryError = { + val exception = queryStatus.exception + if (exception.isEmpty) { + null + } else { + new QueryError( + exception.get.getMessage, + queryStatus.state.name(), + GENERIC_INTERNAL_ERROR_CODE, + GENERIC_INTERNAL_ERROR_NAME, + GENERIC_INTERNAL_ERROR_TYPE, + null, + null) + } + } + } diff --git a/kyuubi-server/src/test/scala/org/apache/kyuubi/server/trino/api/TrinoContextSuite.scala b/kyuubi-server/src/test/scala/org/apache/kyuubi/server/trino/api/TrinoContextSuite.scala index 67a502288..8d7b2bf2c 100644 --- a/kyuubi-server/src/test/scala/org/apache/kyuubi/server/trino/api/TrinoContextSuite.scala +++ b/kyuubi-server/src/test/scala/org/apache/kyuubi/server/trino/api/TrinoContextSuite.scala @@ -17,13 +17,24 @@ package org.apache.kyuubi.server.trino.api +import java.net.URI import java.time.ZoneId +import javax.ws.rs.core.MediaType + +import scala.collection.JavaConverters._ import io.trino.client.ProtocolHeaders.TRINO_HEADERS +import org.apache.hive.service.rpc.thrift.TProtocolVersion.HIVE_CLI_SERVICE_PROTOCOL_V9 +import org.scalatest.concurrent.PatienceConfiguration.Timeout +import org.scalatest.time.SpanSugar.convertIntToGrainOfTime + +import org.apache.kyuubi.{KyuubiFunSuite, RestFrontendTestHelper} +import org.apache.kyuubi.events.KyuubiOperationEvent +import org.apache.kyuubi.operation.{FetchOrientation, OperationHandle} +import org.apache.kyuubi.operation.OperationState.{FINISHED, OperationState} -import org.apache.kyuubi.KyuubiFunSuite +class TrinoContextSuite extends KyuubiFunSuite with RestFrontendTestHelper { -class TrinoContextSuite extends KyuubiFunSuite { import TrinoContext._ test("create trino request context with header") { @@ -67,4 +78,83 @@ class TrinoContextSuite extends KyuubiFunSuite { assert(actual == expectedTrinoContext) } + test("test convert") { + val opHandle = getOpHandle("select 1") + val opHandleStr = opHandle.identifier.toString + checkOpState(opHandleStr, FINISHED) + + val metadataResp = fe.be.getResultSetMetadata(opHandle) + val tRowSet = fe.be.fetchResults(opHandle, FetchOrientation.FETCH_NEXT, 1000, false) + val status = fe.be.getOperationStatus(opHandle) + + val uri = new URI("sfdsfsdfdsf") + val results = TrinoContext + .createQueryResults("/xdfd/xdf", uri, uri, status, Option(metadataResp), Option(tRowSet)) + + print(results.toString) + assert(results.getColumns.get(0).getType.equals("integer")) + assert(results.getData.asScala.last.get(0) == 1) + } + + test("test convert from table") { + initSql("CREATE DATABASE IF NOT EXISTS INIT_DB") + initSql( + "CREATE TABLE IF NOT EXISTS INIT_DB.test(a int, b double, c String," + + "d BOOLEAN,e DATE,f TIMESTAMP,g ARRAY,h DECIMAL," + + "i MAP) USING PARQUET;") + initSql( + "INSERT INTO INIT_DB.test VALUES (1,2.2,'3',true,current_date()," + + "current_timestamp(),array('1','2'),2.0, map('m','p') )") + + val opHandle = getOpHandle("SELECT * FROM INIT_DB.test") + val opHandleStr = opHandle.identifier.toString + checkOpState(opHandleStr, FINISHED) + + val metadataResp = fe.be.getResultSetMetadata(opHandle) + val tRowSet = fe.be.fetchResults(opHandle, FetchOrientation.FETCH_NEXT, 1000, false) + val status = fe.be.getOperationStatus(opHandle) + + val uri = new URI("sfdsfsdfdsf") + val results = TrinoContext + .createQueryResults("/xdfd/xdf", uri, uri, status, Option(metadataResp), Option(tRowSet)) + + print(results.toString) + assert(results.getColumns.get(0).getType.equals("integer")) + assert(results.getData.asScala.last.get(0) != null) + } + + def getOpHandleStr(statement: String = "show tables"): String = { + getOpHandle(statement).identifier.toString + } + + def getOpHandle(statement: String = "show tables"): OperationHandle = { + val sessionHandle = fe.be.openSession( + HIVE_CLI_SERVICE_PROTOCOL_V9, + "admin", + "123456", + "localhost", + Map("testConfig" -> "testValue")) + + if (statement.nonEmpty) { + fe.be.executeStatement(sessionHandle, statement, Map.empty, runAsync = false, 30000) + } else { + fe.be.getCatalogs(sessionHandle) + } + } + + private def checkOpState(opHandleStr: String, state: OperationState): Unit = { + eventually(Timeout(30.seconds)) { + val response = webTarget.path(s"api/v1/operations/$opHandleStr/event") + .request(MediaType.APPLICATION_JSON_TYPE).get() + assert(response.getStatus === 200) + val operationEvent = response.readEntity(classOf[KyuubiOperationEvent]) + assert(operationEvent.state === state.name()) + } + } + + private def initSql(sql: String): Unit = { + val initOpHandle = getOpHandle(sql) + val initOpHandleStr = initOpHandle.identifier.toString + checkOpState(initOpHandleStr, FINISHED) + } } From 301185c65024cf428487ab5cbae8caefae90db8e Mon Sep 17 00:00:00 2001 From: liangbowen Date: Tue, 7 Feb 2023 12:27:45 +0800 Subject: [PATCH 017/760] [KYUUBI #4256] [DOCS] Add PYTHON option to `kyuubi.operation.language` config ### _Why are the changes needed?_ - as pyspark is supported in #3780, exposing PYTHON option for `kyuubi.operation.language` as an experimental feature ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [ ] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4256 from bowenliang123/doc-language-python. Closes #4256 d10c0281 [liangbowen] update description d05cedda [liangbowen] add PYTHON option in kyuubi.operation.language Authored-by: liangbowen Signed-off-by: liangbowen --- docs/deployment/settings.md | 2 +- .../main/scala/org/apache/kyuubi/config/KyuubiConf.scala | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/docs/deployment/settings.md b/docs/deployment/settings.md index 9cd8a681d..7391f4241 100644 --- a/docs/deployment/settings.md +++ b/docs/deployment/settings.md @@ -433,7 +433,7 @@ You can configure the Kyuubi properties in `$KYUUBI_HOME/conf/kyuubi-defaults.co |-----------------------------------------|---------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------|-------| | kyuubi.operation.idle.timeout | PT3H | Operation will be closed when it's not accessed for this duration of time | duration | 1.0.0 | | kyuubi.operation.interrupt.on.cancel | true | When true, all running tasks will be interrupted if one cancels a query. When false, all running tasks will remain until finished. | boolean | 1.2.0 | -| kyuubi.operation.language | SQL | Choose a programing language for the following inputs
  • SQL: (Default) Run all following statements as SQL queries.
  • SCALA: Run all following input a scala codes
| string | 1.5.0 | +| kyuubi.operation.language | SQL | Choose a programing language for the following inputs
  • SQL: (Default) Run all following statements as SQL queries.
  • SCALA: Run all following input as scala codes
  • PYTHON: (Experimental) Run all following input as Python codes with Spark engine
| string | 1.5.0 | | kyuubi.operation.log.dir.root | server_operation_logs | Root directory for query operation log at server-side. | string | 1.4.0 | | kyuubi.operation.plan.only.excludes | ResetCommand,SetCommand,SetNamespaceCommand,UseStatement,SetCatalogAndNamespace | Comma-separated list of query plan names, in the form of simple class names, i.e, for `SET abc=xyz`, the value will be `SetCommand`. For those auxiliary plans, such as `switch databases`, `set properties`, or `create temporary view` etc., which are used for setup evaluating environments for analyzing actual queries, we can use this config to exclude them and let them take effect. See also kyuubi.operation.plan.only.mode. | seq | 1.5.0 | | kyuubi.operation.plan.only.mode | none | Configures the statement performed mode, The value can be 'parse', 'analyze', 'optimize', 'optimize_with_stats', 'physical', 'execution', or 'none', when it is 'none', indicate to the statement will be fully executed, otherwise only way without executing the query. different engines currently support different modes, the Spark engine supports all modes, and the Flink engine supports 'parse', 'physical', and 'execution', other engines do not support planOnly currently. | string | 1.4.0 | diff --git a/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala b/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala index e4c48218a..75f7efedc 100644 --- a/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala +++ b/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala @@ -2160,8 +2160,12 @@ object KyuubiConf { val OPERATION_LANGUAGE: ConfigEntry[String] = buildConf("kyuubi.operation.language") .doc("Choose a programing language for the following inputs" + - "
  • SQL: (Default) Run all following statements as SQL queries.
  • " + - "
  • SCALA: Run all following input a scala codes
") + "
    " + + "
  • SQL: (Default) Run all following statements as SQL queries.
  • " + + "
  • SCALA: Run all following input as scala codes
  • " + + "
  • PYTHON: (Experimental) Run all following input as Python codes with Spark engine" + + "
  • " + + "
") .version("1.5.0") .stringConf .transform(_.toUpperCase(Locale.ROOT)) From 95318d578289249e1380140ec9b19c46c2543235 Mon Sep 17 00:00:00 2001 From: edddddy Date: Tue, 7 Feb 2023 19:04:10 +0800 Subject: [PATCH 018/760] [KYUUBI #4247] `build/mvn` should use the exact version defined in `pom.xml` ### _Why are the changes needed?_ As mentioned in #4247, it seems to be better that using the same maven version with master. Thus, modify the version check logic in `build/mvn` from equal or greater to equal. ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [x] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4261 from edddddy/mvn_ver_cp. Closes #4247 11f19eca1 [1456173400] change the if condition to equal Lead-authored-by: edddddy Co-authored-by: 1456173400 <1456173400@qq.com> Signed-off-by: Cheng Pan --- build/mvn | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/mvn b/build/mvn index d67638ba2..de697f410 100755 --- a/build/mvn +++ b/build/mvn @@ -76,7 +76,7 @@ install_mvn() { fi # See simple version normalization: http://stackoverflow.com/questions/16989598/bash-comparing-version-numbers function version { echo "$@" | awk -F. '{ printf("%03d%03d%03d\n", $1,$2,$3); }'; } - if [ $(version $MVN_DETECTED_VERSION) -lt $(version $MVN_VERSION) ]; then + if [ $(version $MVN_DETECTED_VERSION) -eq $(version $MVN_VERSION) ]; then local APACHE_MIRROR=${APACHE_MIRROR:-'https://archive.apache.org/dist/'} install_app \ From 31077b5763536a8b39089f2c5a54049f9209a94c Mon Sep 17 00:00:00 2001 From: liangbowen Date: Wed, 8 Feb 2023 00:26:53 +0800 Subject: [PATCH 019/760] [KYUUBI #4265] Bump maven-surefire-plugin to 3.0.0-M8 ### _Why are the changes needed?_ - bump maven-surefire-plugin 3.0.0-M7 (released in June 2022) to 3.0.0-M8 (release in Jan 2023), release notes: https://blogs.apache.org/maven/entry/apache-maven-surefire-failsafe-plugin1 ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [x] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4265 from bowenliang123/surefire-3.0.0-M8. Closes #4265 60f9b2da [liangbowen] bump maven-surefire-plugin to 3.0.0-M8 Authored-by: liangbowen Signed-off-by: liangbowen --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 80d210c0c..66853ac0d 100644 --- a/pom.xml +++ b/pom.xml @@ -214,7 +214,7 @@ 1.6.6 1.6.1 4.8.0 - 3.0.0-M7 + 3.0.0-M8 2.2.0 org.scalatest.tags.Slow,org.apache.kyuubi.tags.HudiTest From 0eff3cec535464f08cf6162971b4a80a48536132 Mon Sep 17 00:00:00 2001 From: liangbowen Date: Wed, 8 Feb 2023 22:56:43 +0800 Subject: [PATCH 020/760] [KYUUBI #4270] Register shutdown hook for plugin cleanup ### _Why are the changes needed?_ to fix #4270. - Register shutdown hook for plugin cleanup ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [x] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4272 from bowenliang123/authz-shutdown-hook. Closes #4270 3f376f43 [liangbowen] add log 6a2018f6 [liangbowen] nit 8b7ed86c [liangbowen] register plugin's cleanup shutdownHook Authored-by: liangbowen Signed-off-by: liangbowen --- .../authz/ranger/RangerSparkExtension.scala | 2 +- .../authz/ranger/SparkRangerAdminPlugin.scala | 26 +++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/extensions/spark/kyuubi-spark-authz/src/main/scala/org/apache/kyuubi/plugin/spark/authz/ranger/RangerSparkExtension.scala b/extensions/spark/kyuubi-spark-authz/src/main/scala/org/apache/kyuubi/plugin/spark/authz/ranger/RangerSparkExtension.scala index f4dcb3f9f..5708dfeaf 100644 --- a/extensions/spark/kyuubi-spark-authz/src/main/scala/org/apache/kyuubi/plugin/spark/authz/ranger/RangerSparkExtension.scala +++ b/extensions/spark/kyuubi-spark-authz/src/main/scala/org/apache/kyuubi/plugin/spark/authz/ranger/RangerSparkExtension.scala @@ -36,7 +36,7 @@ import org.apache.kyuubi.plugin.spark.authz.util.{RuleEliminateMarker, RuleElimi * @since 1.6.0 */ class RangerSparkExtension extends (SparkSessionExtensions => Unit) { - SparkRangerAdminPlugin.init() + SparkRangerAdminPlugin.initialize() override def apply(v1: SparkSessionExtensions): Unit = { v1.injectCheckRule(AuthzConfigurationChecker) diff --git a/extensions/spark/kyuubi-spark-authz/src/main/scala/org/apache/kyuubi/plugin/spark/authz/ranger/SparkRangerAdminPlugin.scala b/extensions/spark/kyuubi-spark-authz/src/main/scala/org/apache/kyuubi/plugin/spark/authz/ranger/SparkRangerAdminPlugin.scala index 7ece55fe5..3d46563aa 100644 --- a/extensions/spark/kyuubi-spark-authz/src/main/scala/org/apache/kyuubi/plugin/spark/authz/ranger/SparkRangerAdminPlugin.scala +++ b/extensions/spark/kyuubi-spark-authz/src/main/scala/org/apache/kyuubi/plugin/spark/authz/ranger/SparkRangerAdminPlugin.scala @@ -21,8 +21,10 @@ import scala.collection.JavaConverters._ import scala.collection.mutable.ArrayBuffer import scala.collection.mutable.LinkedHashMap +import org.apache.hadoop.util.ShutdownHookManager import org.apache.ranger.plugin.policyengine.RangerAccessRequest import org.apache.ranger.plugin.service.RangerBasePlugin +import org.slf4j.LoggerFactory import org.apache.kyuubi.plugin.spark.authz.AccessControlException import org.apache.kyuubi.plugin.spark.authz.util.AuthZUtils._ @@ -30,6 +32,7 @@ import org.apache.kyuubi.plugin.spark.authz.util.RangerConfigProvider object SparkRangerAdminPlugin extends RangerBasePlugin("spark", "sparkSql") with RangerConfigProvider { + final private val LOG = LoggerFactory.getLogger(getClass) /** * For a Spark SQL query, it may contain 0 or more privilege objects to verify, e.g. a typical @@ -60,6 +63,29 @@ object SparkRangerAdminPlugin extends RangerBasePlugin("spark", "sparkSql") s"ranger.plugin.$getServiceType.use.usergroups.from.userstore.enabled", false) + /** + * plugin initialization + * with cleanup shutdown hook registered + */ + def initialize(): Unit = { + this.init() + registerCleanupShutdownHook(this) + } + + /** + * register shutdown hook for plugin cleanup + */ + private def registerCleanupShutdownHook(plugin: RangerBasePlugin): Unit = { + ShutdownHookManager.get().addShutdownHook( + () => { + if (plugin != null) { + LOG.info(s"clean up ranger plugin, appId: ${plugin.getAppId}") + this.cleanup() + } + }, + Integer.MAX_VALUE) + } + def getFilterExpr(req: AccessRequest): Option[String] = { val result = evalRowFilterPolicies(req, null) Option(result) From 4070d78fe7a83cdb1ddc14ba4fbcd05050ffafc0 Mon Sep 17 00:00:00 2001 From: runzhliu Date: Wed, 8 Feb 2023 23:43:52 +0800 Subject: [PATCH 021/760] [KYUUBI #4268] Bump Arrow version to 11.0.0 ### _Why are the changes needed?_ to close #4268 . ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [ ] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4273 from runzhliu/patch-2. Closes #4268 5329a842 [runzhliu] bump Arrow version to 11.0.0 Authored-by: runzhliu Signed-off-by: liangbowen --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 66853ac0d..ebf6db655 100644 --- a/pom.xml +++ b/pom.xml @@ -115,7 +115,7 @@ 2.12 2.8.1 - 10.0.1 + 11.0.0 4.9.3 4.3.4 From e5743c8309978cfc2bf430721d0c9d2d46909883 Mon Sep 17 00:00:00 2001 From: Thomas Prelle Date: Thu, 9 Feb 2023 00:06:07 +0800 Subject: [PATCH 022/760] [KYUUBI #4255][AUTHZ] Add authz for describe relation ### _Why are the changes needed?_ During a describe table call, if the table it's a v2 table, it's a DescribeRelation a not a DescribeTableCommand. So DescribeRelation should have same authorization than DescribeTableCommand. Fix https://github.com/apache/kyuubi/issues/4255 ### _How was this patch tested?_ - [x] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [x] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4257 from tprelle/fixDescribeRelationAuthz. Closes #4255 3ffdbc9d [Thomas Prelle] [AUTHZ] Add authz for describe relation Authored-by: Thomas Prelle Signed-off-by: liangbowen --- .../src/main/resources/table_command_spec.json | 14 ++++++++++++++ .../authz/V2CommandsPrivilegesSuite.scala | 18 ++++++++++++++++++ .../plugin/spark/authz/gen/TableCommands.scala | 11 +++++++++++ ...ebergCatalogRangerSparkExtensionSuite.scala | 9 +++++++++ ...TableCatalogRangerSparkExtensionSuite.scala | 8 ++++++++ 5 files changed, 60 insertions(+) diff --git a/extensions/spark/kyuubi-spark-authz/src/main/resources/table_command_spec.json b/extensions/spark/kyuubi-spark-authz/src/main/resources/table_command_spec.json index 3b9b8f24e..af748c278 100644 --- a/extensions/spark/kyuubi-spark-authz/src/main/resources/table_command_spec.json +++ b/extensions/spark/kyuubi-spark-authz/src/main/resources/table_command_spec.json @@ -219,6 +219,20 @@ "fieldName" : "query", "fieldExtractor" : "LogicalPlanQueryExtractor" } ] +}, { + "classname" : "org.apache.spark.sql.catalyst.plans.logical.DescribeRelation", + "tableDescs" : [ { + "fieldName" : "relation", + "fieldExtractor" : "ResolvedTableTableExtractor", + "columnDesc" : null, + "actionTypeDesc" : null, + "tableTypeDesc" : null, + "catalogDesc" : null, + "isInput" : true, + "setCurrentDatabaseIfMissing" : true + } ], + "opType" : "DESCTABLE", + "queryDescs" : [ ] }, { "classname" : "org.apache.spark.sql.catalyst.plans.logical.DropColumns", "tableDescs" : [ { diff --git a/extensions/spark/kyuubi-spark-authz/src/test/scala/org/apache/kyuubi/plugin/spark/authz/V2CommandsPrivilegesSuite.scala b/extensions/spark/kyuubi-spark-authz/src/test/scala/org/apache/kyuubi/plugin/spark/authz/V2CommandsPrivilegesSuite.scala index 9d3e6d42d..dede81426 100644 --- a/extensions/spark/kyuubi-spark-authz/src/test/scala/org/apache/kyuubi/plugin/spark/authz/V2CommandsPrivilegesSuite.scala +++ b/extensions/spark/kyuubi-spark-authz/src/test/scala/org/apache/kyuubi/plugin/spark/authz/V2CommandsPrivilegesSuite.scala @@ -515,6 +515,24 @@ abstract class V2CommandsPrivilegesSuite extends PrivilegesBuilderSuite { assert(accessType === AccessType.UPDATE) } + test("DescribeTable") { + val plan = executePlan(s"DESCRIBE TABLE $catalogTable").analyzed + val (inputs, outputs, operationType) = PrivilegesBuilder.build(plan, spark) + assert(operationType === DESCTABLE) + assert(inputs.size === 1) + val po = inputs.head + assert(po.actionType === PrivilegeObjectActionType.OTHER) + assert(po.privilegeObjectType === PrivilegeObjectType.TABLE_OR_VIEW) + assert(po.catalog === Some(catalogV2)) + assert(po.dbname === namespace) + assert(po.objectName === catalogTableShort) + assert(po.columns.isEmpty) + checkV2TableOwner(po) + val accessType = AccessType(po, operationType, isInput = true) + assert(accessType === AccessType.SELECT) + assert(outputs.size === 0) + } + // with V2AlterTableCommand test("AddColumns") { diff --git a/extensions/spark/kyuubi-spark-authz/src/test/scala/org/apache/kyuubi/plugin/spark/authz/gen/TableCommands.scala b/extensions/spark/kyuubi-spark-authz/src/test/scala/org/apache/kyuubi/plugin/spark/authz/gen/TableCommands.scala index ef981515a..e2fda9162 100644 --- a/extensions/spark/kyuubi-spark-authz/src/test/scala/org/apache/kyuubi/plugin/spark/authz/gen/TableCommands.scala +++ b/extensions/spark/kyuubi-spark-authz/src/test/scala/org/apache/kyuubi/plugin/spark/authz/gen/TableCommands.scala @@ -410,6 +410,16 @@ object TableCommands { TableCommandSpec(cmd, Seq(tableDesc), DESCTABLE) } + val DescribeRelationTable = { + val cmd = "org.apache.spark.sql.catalyst.plans.logical.DescribeRelation" + val tableDesc = TableDesc( + "relation", + classOf[ResolvedTableTableExtractor], + isInput = true, + setCurrentDatabaseIfMissing = true) + TableCommandSpec(cmd, Seq(tableDesc), DESCTABLE) + } + val DropTable = { val cmd = "org.apache.spark.sql.execution.command.DropTableCommand" val tableTypeDesc = @@ -614,6 +624,7 @@ object TableCommands { DeleteFromTable, DescribeColumn, DescribeTable, + DescribeRelationTable, DropTable, DropTableV2, InsertIntoDataSource, diff --git a/extensions/spark/kyuubi-spark-authz/src/test/scala/org/apache/kyuubi/plugin/spark/authz/ranger/IcebergCatalogRangerSparkExtensionSuite.scala b/extensions/spark/kyuubi-spark-authz/src/test/scala/org/apache/kyuubi/plugin/spark/authz/ranger/IcebergCatalogRangerSparkExtensionSuite.scala index a2634bb26..909c26d36 100644 --- a/extensions/spark/kyuubi-spark-authz/src/test/scala/org/apache/kyuubi/plugin/spark/authz/ranger/IcebergCatalogRangerSparkExtensionSuite.scala +++ b/extensions/spark/kyuubi-spark-authz/src/test/scala/org/apache/kyuubi/plugin/spark/authz/ranger/IcebergCatalogRangerSparkExtensionSuite.scala @@ -222,4 +222,13 @@ class IcebergCatalogRangerSparkExtensionSuite extends RangerSparkExtensionSuite }) } } + + test("[KYUUBI #4255] DESCRIBE TABLE") { + assume(isSparkV32OrGreater) + val e1 = intercept[AccessControlException]( + doAs("someone", sql(s"DESCRIBE TABLE $catalogV2.$namespace1.$table1").explain())) + assert(e1.getMessage.contains(s"does not have [select] privilege" + + s" on [$namespace1/$table1]")) + } + } diff --git a/extensions/spark/kyuubi-spark-authz/src/test/scala/org/apache/kyuubi/plugin/spark/authz/ranger/V2JdbcTableCatalogRangerSparkExtensionSuite.scala b/extensions/spark/kyuubi-spark-authz/src/test/scala/org/apache/kyuubi/plugin/spark/authz/ranger/V2JdbcTableCatalogRangerSparkExtensionSuite.scala index 6bdab9d9d..9f980c27a 100644 --- a/extensions/spark/kyuubi-spark-authz/src/test/scala/org/apache/kyuubi/plugin/spark/authz/ranger/V2JdbcTableCatalogRangerSparkExtensionSuite.scala +++ b/extensions/spark/kyuubi-spark-authz/src/test/scala/org/apache/kyuubi/plugin/spark/authz/ranger/V2JdbcTableCatalogRangerSparkExtensionSuite.scala @@ -107,6 +107,14 @@ class V2JdbcTableCatalogRangerSparkExtensionSuite extends RangerSparkExtensionSu s" on [$namespace1/$table1/id]")) } + test("[KYUUBI #4255] DESCRIBE TABLE") { + assume(isSparkV31OrGreater) + val e1 = intercept[AccessControlException]( + doAs("someone", sql(s"DESCRIBE TABLE $catalogV2.$namespace1.$table1").explain())) + assert(e1.getMessage.contains(s"does not have [select] privilege" + + s" on [$namespace1/$table1]")) + } + test("[KYUUBI #3424] CREATE TABLE") { assume(isSparkV31OrGreater) From afcfe9afb5e8df1761dd80b8ed236d11520eed83 Mon Sep 17 00:00:00 2001 From: df_liu Date: Thu, 9 Feb 2023 08:34:47 +0800 Subject: [PATCH 023/760] [KYUUBI #4252] Bump Flink to 1.14.6 ### _Why are the changes needed?_ close #4252 ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [ ] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4277 from df-Liu/fix_4252. Closes #4252 a5969d01 [df_liu] [KYUUBI #4252] Bump Flink 1.14.6 Authored-by: df_liu Signed-off-by: liangbowen --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index ebf6db655..fecc0ad8d 100644 --- a/pom.xml +++ b/pom.xml @@ -2328,7 +2328,7 @@ flink-1.14 - 1.14.5 + 1.14.6 _${scala.binary.version} From 3f5121ab2d5558a7ea8cdba63bf15cd20b4127e3 Mon Sep 17 00:00:00 2001 From: df_liu Date: Thu, 9 Feb 2023 08:35:43 +0800 Subject: [PATCH 024/760] [KYUUBI #4251] Bump Flink to 1.15.3 ### _Why are the changes needed?_ close #4251 ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [ ] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4276 from df-Liu/fix_4251. Closes #4251 eb5565cb [df_liu] [KYUUBI #4251] Bump Flink 1.15.3 Authored-by: df_liu Signed-off-by: liangbowen --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index fecc0ad8d..15d425dbb 100644 --- a/pom.xml +++ b/pom.xml @@ -2336,7 +2336,7 @@ flink-1.15 - 1.15.2 + 1.15.3 From 953155272054137bab1d2e59c910b1ccf614ff56 Mon Sep 17 00:00:00 2001 From: liangbowen Date: Thu, 9 Feb 2023 14:14:55 +0800 Subject: [PATCH 025/760] [KYUUBI #4266] [INFRA] Add Dependency Review step to prevent introducing vulnerable dependencies ### _Why are the changes needed?_ - Add actions/dependency-review-action (https://github.com/actions/dependency-review-action) as new step in the dependency workflow, which fails when future PR is bringing vulnerable dependencies. - configuring `fail-on-severity` to `moderate` level image ### _How was this patch tested?_ - [x] Pass the Dependency Check CI job Closes #4266 from bowenliang123/dependency-review. Closes #4266 63a84b22 [liangbowen] add actions/dependency-review-action to dependency workflow Authored-by: liangbowen Signed-off-by: liangbowen --- .github/workflows/dep.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/dep.yml b/.github/workflows/dep.yml index 5ea4447cc..ae1e3de6f 100644 --- a/.github/workflows/dep.yml +++ b/.github/workflows/dep.yml @@ -23,8 +23,9 @@ on: - master - branch-* paths: - # dependency check happens only pom changes + # when pom or dependency workflow changes - '**/pom.xml' + - '.github/workflows/dep.yml' concurrency: group: dep-${{ github.ref }} @@ -57,3 +58,7 @@ jobs: -pl kyuubi-ctl,kyuubi-server,kyuubi-assembly -am - name: Check dependency list run: build/dependency.sh + - name: Dependency Review + uses: actions/dependency-review-action@v3 + with: + fail-on-severity: moderate From fec6ee6110bea900b9af347801fb4447bb285521 Mon Sep 17 00:00:00 2001 From: Cheng Pan Date: Thu, 9 Feb 2023 19:27:42 +0800 Subject: [PATCH 026/760] [KYUUBI #4285] Do not cancel CI jobs on stable branch ### _Why are the changes needed?_ Since https://github.com/apache/kyuubi/pull/2175, the previous running CI jobs will be canceled on the same branch or PR to save CI resources, this PR keeps the behavior for PR but doesn't cancel the running CI jobs when commit to the stable branch. Ref: https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#example-using-a-fallback-value ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [x] Add screenshots for manual tests if appropriate I pushed an empty commit to verify the behavior does not change in PR, and I need to merge this to master first, then merge another PR immediately to verify the rest behaviors. - [ ] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4285 from pan3793/ci-con. Closes #4285 3aa7f427e [Cheng Pan] empty 4156eb196 [Cheng Pan] Do not cancel CI jobs on stable branch Authored-by: Cheng Pan Signed-off-by: Cheng Pan --- .github/workflows/dep.yml | 2 +- .github/workflows/license.yml | 2 +- .github/workflows/master.yml | 2 +- .github/workflows/style.yml | 2 +- .github/workflows/web-ui.yml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/dep.yml b/.github/workflows/dep.yml index ae1e3de6f..16475ce0f 100644 --- a/.github/workflows/dep.yml +++ b/.github/workflows/dep.yml @@ -28,7 +28,7 @@ on: - '.github/workflows/dep.yml' concurrency: - group: dep-${{ github.ref }} + group: dep-${{ github.head_ref || github.run_id }} cancel-in-progress: true jobs: diff --git a/.github/workflows/license.yml b/.github/workflows/license.yml index 73ef05864..17591dacb 100644 --- a/.github/workflows/license.yml +++ b/.github/workflows/license.yml @@ -26,7 +26,7 @@ on: - branch-* concurrency: - group: lincense-${{ github.ref }} + group: license-${{ github.head_ref || github.run_id }} cancel-in-progress: true jobs: diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index 59bcdbd58..c65226d78 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -28,7 +28,7 @@ on: - branch-* concurrency: - group: test-${{ github.ref }} + group: test-${{ github.head_ref || github.run_id }} cancel-in-progress: true env: diff --git a/.github/workflows/style.yml b/.github/workflows/style.yml index c848a2f8c..f9307e529 100644 --- a/.github/workflows/style.yml +++ b/.github/workflows/style.yml @@ -24,7 +24,7 @@ on: - branch-* concurrency: - group: linter-${{ github.ref }} + group: linter-${{ github.head_ref || github.run_id }} cancel-in-progress: true jobs: diff --git a/.github/workflows/web-ui.yml b/.github/workflows/web-ui.yml index 08c97cfc9..d99078990 100644 --- a/.github/workflows/web-ui.yml +++ b/.github/workflows/web-ui.yml @@ -11,7 +11,7 @@ on: - branch-* concurrency: - group: web-ui-${{ github.ref }} + group: web-ui-${{ github.head_ref || github.run_id }} cancel-in-progress: true jobs: From a31d4490c890dd3b61aa4d5724ffaf15f8b6acda Mon Sep 17 00:00:00 2001 From: Cheng Pan Date: Thu, 9 Feb 2023 19:30:26 +0800 Subject: [PATCH 027/760] [KYUUBI #4271] Clarify incremental collect mode ignore `kyuubi.operation.result.max.rows` ### _Why are the changes needed?_ `kyuubi.operation.result.max.rows` does not take effect on incremental collect mode because of performance concerns, this PR updates the configuration docs to mention that. ### _How was this patch tested?_ - [x] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [ ] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4271 from pan3793/maxrow. Closes #4271 29b290a3e [Cheng Pan] nit 3d2872352 [Cheng Pan] log 277ebb5ff [Cheng Pan] ifx 091511a91 [Cheng Pan] nit f15fb2270 [Cheng Pan] nit 1a1259ef5 [Cheng Pan] nit ba57a2660 [Cheng Pan] fix 0b58d8b1a [Cheng Pan] ut 74b59dcee [Cheng Pan] nit 230defbff [Cheng Pan] Increamental collect mode should respect kyuubi.operation.result.max.rows Authored-by: Cheng Pan Signed-off-by: Cheng Pan --- .../spark/operation/ExecuteStatement.scala | 60 +++++++++---------- .../spark/operation/SparkOperation.scala | 6 +- .../org/apache/kyuubi/config/KyuubiConf.scala | 19 +++--- .../KyuubiOperationPerUserSuite.scala | 39 ++++++------ 4 files changed, 64 insertions(+), 60 deletions(-) diff --git a/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/operation/ExecuteStatement.scala b/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/operation/ExecuteStatement.scala index 2cdc2b500..fac90f7ea 100644 --- a/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/operation/ExecuteStatement.scala +++ b/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/operation/ExecuteStatement.scala @@ -21,7 +21,7 @@ import java.util.concurrent.RejectedExecutionException import scala.collection.JavaConverters._ -import org.apache.spark.sql.{DataFrame, Row} +import org.apache.spark.sql.DataFrame import org.apache.spark.sql.kyuubi.SparkDatasetHelper import org.apache.spark.sql.types._ @@ -70,43 +70,41 @@ class ExecuteStatement( addOperationListener() result = spark.sql(statement) - iter = - if (incrementalCollect) { - info("Execute in incremental collect mode") + val resultMaxRows = spark.conf.getOption(OPERATION_RESULT_MAX_ROWS.key).map(_.toInt) + .getOrElse(session.sessionManager.getConf.get(OPERATION_RESULT_MAX_ROWS)) + iter = if (incrementalCollect) { + if (resultMaxRows > 0) { + warn(s"Ignore ${OPERATION_RESULT_MAX_ROWS.key} on incremental collect mode.") + } + info("Execute in incremental collect mode") + def internalIterator(): Iterator[Any] = if (arrowEnabled) { + SparkDatasetHelper.toArrowBatchRdd(convertComplexType(result)).toLocalIterator + } else { + result.toLocalIterator().asScala + } + new IterableFetchIterator[Any](new Iterable[Any] { + override def iterator: Iterator[Any] = internalIterator() + }) + } else { + val internalArray = if (resultMaxRows <= 0) { + info("Execute in full collect mode") if (arrowEnabled) { - new IterableFetchIterator[Array[Byte]](new Iterable[Array[Byte]] { - override def iterator: Iterator[Array[Byte]] = SparkDatasetHelper.toArrowBatchRdd( - convertComplexType(result)).toLocalIterator - }) + SparkDatasetHelper.toArrowBatchRdd(convertComplexType(result)).collect() } else { - new IterableFetchIterator[Row](new Iterable[Row] { - override def iterator: Iterator[Row] = result.toLocalIterator().asScala - }) + result.collect() } } else { - val resultMaxRows = spark.conf.getOption(OPERATION_RESULT_MAX_ROWS.key).map(_.toInt) - .getOrElse(session.sessionManager.getConf.get(OPERATION_RESULT_MAX_ROWS)) - if (resultMaxRows <= 0) { - info("Execute in full collect mode") - if (arrowEnabled) { - new ArrayFetchIterator( - SparkDatasetHelper.toArrowBatchRdd( - convertComplexType(result)).collect()) - } else { - new ArrayFetchIterator(result.collect()) - } + info(s"Execute with max result rows[$resultMaxRows]") + if (arrowEnabled) { + // this will introduce shuffle and hurt performance + val limitedResult = result.limit(resultMaxRows) + SparkDatasetHelper.toArrowBatchRdd(convertComplexType(limitedResult)).collect() } else { - info(s"Execute with max result rows[$resultMaxRows]") - if (arrowEnabled) { - // this will introduce shuffle and hurt performance - new ArrayFetchIterator( - SparkDatasetHelper.toArrowBatchRdd( - convertComplexType(result.limit(resultMaxRows))).collect()) - } else { - new ArrayFetchIterator(result.take(resultMaxRows)) - } + result.take(resultMaxRows) } } + new ArrayFetchIterator(internalArray) + } setCompiledStateIfNeeded() setState(OperationState.FINISHED) } catch { diff --git a/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/operation/SparkOperation.scala b/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/operation/SparkOperation.scala index 842ff944f..b62ef6745 100644 --- a/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/operation/SparkOperation.scala +++ b/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/operation/SparkOperation.scala @@ -259,13 +259,13 @@ abstract class SparkOperation(session: Session) override def shouldRunAsync: Boolean = false - protected def arrowEnabled(): Boolean = { - resultFormat().equalsIgnoreCase("arrow") && + protected def arrowEnabled: Boolean = { + resultFormat.equalsIgnoreCase("arrow") && // TODO: (fchen) make all operation support arrow getClass.getCanonicalName == classOf[ExecuteStatement].getCanonicalName } - protected def resultFormat(): String = { + protected def resultFormat: String = { // TODO: respect the config of the operation ExecuteStatement, if it was set. spark.conf.get("kyuubi.operation.result.format", "thrift") } diff --git a/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala b/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala index 75f7efedc..b46674d06 100644 --- a/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala +++ b/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala @@ -1645,11 +1645,20 @@ object KyuubiConf { .checkValue(_ >= 1000, "must >= 1s if set") .createOptional + val OPERATION_RESULT_MAX_ROWS: ConfigEntry[Int] = + buildConf("kyuubi.operation.result.max.rows") + .doc("Max rows of Spark query results. Rows exceeding the limit would be ignored. " + + "By setting this value to 0 to disable the max rows limit.") + .version("1.6.0") + .intConf + .createWithDefault(0) + val OPERATION_INCREMENTAL_COLLECT: ConfigEntry[Boolean] = buildConf("kyuubi.operation.incremental.collect") .internal .doc("When true, the executor side result will be sequentially calculated and returned to" + - " the Spark driver side.") + s" the Spark driver side. Note that, ${OPERATION_RESULT_MAX_ROWS.key} will be ignored" + + " on incremental collect mode.") .version("1.4.0") .booleanConf .createWithDefault(false) @@ -1667,14 +1676,6 @@ object KyuubiConf { .transform(_.toLowerCase(Locale.ROOT)) .createWithDefault("thrift") - val OPERATION_RESULT_MAX_ROWS: ConfigEntry[Int] = - buildConf("kyuubi.operation.result.max.rows") - .doc("Max rows of Spark query results. Rows exceeding the limit would be ignored. " + - "By setting this value to 0 to disable the max rows limit.") - .version("1.6.0") - .intConf - .createWithDefault(0) - val SERVER_OPERATION_LOG_DIR_ROOT: ConfigEntry[String] = buildConf("kyuubi.operation.log.dir.root") .doc("Root directory for query operation log at server-side.") diff --git a/kyuubi-server/src/test/scala/org/apache/kyuubi/operation/KyuubiOperationPerUserSuite.scala b/kyuubi-server/src/test/scala/org/apache/kyuubi/operation/KyuubiOperationPerUserSuite.scala index 5e4796a87..2ece2379a 100644 --- a/kyuubi-server/src/test/scala/org/apache/kyuubi/operation/KyuubiOperationPerUserSuite.scala +++ b/kyuubi-server/src/test/scala/org/apache/kyuubi/operation/KyuubiOperationPerUserSuite.scala @@ -166,23 +166,6 @@ class KyuubiOperationPerUserSuite assert(r1 !== r2) } - test("test engine spark result max rows") { - withSessionConf()(Map.empty)(Map(KyuubiConf.OPERATION_RESULT_MAX_ROWS.key -> "1")) { - withJdbcStatement("va") { statement => - statement.executeQuery("create temporary view va as select * from values(1),(2)") - - val resultLimit1 = statement.executeQuery("select * from va") - assert(resultLimit1.next()) - assert(!resultLimit1.next()) - - statement.executeQuery(s"set ${KyuubiConf.OPERATION_RESULT_MAX_ROWS.key}=0") - val resultUnLimit = statement.executeQuery("select * from va") - assert(resultUnLimit.next()) - assert(resultUnLimit.next()) - } - } - } - test("support to interrupt the thrift request if remote engine is broken") { assume(!httpMode) withSessionConf(Map( @@ -227,6 +210,28 @@ class KyuubiOperationPerUserSuite } } + test("max result rows") { + Seq("true", "false").foreach { incremental => + Seq("thrift", "arrow").foreach { resultFormat => + Seq("0", "1").foreach { maxResultRows => + withSessionConf()(Map.empty)(Map( + KyuubiConf.OPERATION_RESULT_FORMAT.key -> resultFormat, + KyuubiConf.OPERATION_RESULT_MAX_ROWS.key -> maxResultRows, + KyuubiConf.OPERATION_INCREMENTAL_COLLECT.key -> incremental)) { + withJdbcStatement("va") { statement => + statement.executeQuery("create temporary view va as select * from values(1),(2)") + val resultLimit = statement.executeQuery("select * from va") + assert(resultLimit.next()) + // always ignore max result rows on incremental collect mode + if (incremental == "true" || maxResultRows == "0") assert(resultLimit.next()) + assert(!resultLimit.next()) + } + } + } + } + } + } + test("scala NPE issue with hdfs jar") { val jarDir = Utils.createTempDir().toFile val udfCode = From 3bb283ecd734135f86c4b6ebf83515b7ee8652c0 Mon Sep 17 00:00:00 2001 From: Cheng Pan Date: Thu, 9 Feb 2023 19:50:07 +0800 Subject: [PATCH 028/760] [KYUUBI #4287] [INFRA] Remove Travis ### _Why are the changes needed?_ As mentioned in https://github.com/apache/kyuubi/issues/4139, we are not able to use Travis for AMR testing in the future. ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [ ] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4287 from pan3793/rm-travis. Closes #4287 efe2a4854 [Cheng Pan] nit c72a8b00c [Cheng Pan] Remove travis Authored-by: Cheng Pan Signed-off-by: Cheng Pan --- .gitattributes | 1 - .github/labeler.yml | 1 - .travis.yml | 71 --------------------------------------------- README.md | 1 - build/Dockerfile | 2 +- 5 files changed, 1 insertion(+), 75 deletions(-) delete mode 100644 .travis.yml diff --git a/.gitattributes b/.gitattributes index b3623c426..6bbad541a 100644 --- a/.gitattributes +++ b/.gitattributes @@ -18,7 +18,6 @@ .github/ export-ignore .idea/ export-ignore .readthedocs.yml export-ignore -.travis.yml export-ignore _config.yml export-ignore codecov.yml export-ignore licenses-binary/ export-ignore diff --git a/.github/labeler.yml b/.github/labeler.yml index a9f79a537..bbc64ed66 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -45,7 +45,6 @@ - ".gitattributes" - ".github/**/*" - ".gitignore" - - ".travis.yml" - "LICENSE" - "LICENSE-binary" - "NOTICE" diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index c09fa9566..000000000 --- a/.travis.yml +++ /dev/null @@ -1,71 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. -# The ASF licenses this file to You under the Apache License, Version 2.0 -# (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -sudo: required -dist: focal -arch: arm64-graviton2 -group: edge -virt: vm -env: SPARK_LOCAL_IP=localhost - -branches: - only: - - master - -language: java - -matrix: - include: - - name: Build Kyuubi common on Linux ARM64 - script: - - ./build/mvn test $MVN_ARGS -pl kyuubi-common,kyuubi-zookeeper,kyuubi-ha,kyuubi-ctl,kyuubi-metrics,kyuubi-hive-beeline,kyuubi-hive-jdbc,extensions/server/kyuubi-server-plugin -am - - name: Build Kyuubi Flink on Linux ARM64 - script: - - ./build/mvn test $MVN_ARGS -pl externals/kyuubi-flink-sql-engine,integration-tests/kyuubi-flink-it - - name: Build Kyuubi Spark on Linux ARM64 - script: - - ./build/mvn test $MVN_ARGS -pl externals/kyuubi-spark-sql-engine - - ./build/mvn test $MVN_ARGS -pl kyuubi-server -DwildcardSuites=org.apache.kyuubi.operation.KyuubiOperationPerUserSuite - - name: Build Kyuubi Trino on Linux ARM64 - script: - - ./build/mvn test $MVN_ARGS -pl externals/kyuubi-trino-engine,integration-tests/kyuubi-trino-it - - name: Build Kyuubi Hive on Linux ARM64 - script: - - ./build/mvn test $MVN_ARGS -pl externals/kyuubi-hive-sql-engine,integration-tests/kyuubi-hive-it - -cache: - directories: - - $HOME/.m2 - -install: - - sudo apt update - - sudo apt install -y openjdk-8-jdk - - export JAVA_HOME="/usr/lib/jvm/java-8-openjdk-${TRAVIS_CPU_ARCH}" - - export PATH="$JAVA_HOME/bin:/usr/share/maven/bin:$PATH" - - ./build/mvn --version - -before_script: - - export MVN_ARGS="-Dmaven.javadoc.skip=true -Drat.skip=true -Dscalastyle.skip=true -Dspotless.check.skip -V -B -ntp -Dorg.slf4j.simpleLogger.defaultLogLevel=warn -Pjdbc-shaded" - - ./build/mvn clean install -DskipTests $MVN_ARGS - - -after_success: - - echo "Travis exited with ${TRAVIS_TEST_RESULT}" - -after_failure: - - echo "Travis exited with ${TRAVIS_TEST_RESULT}" - - for log in `find * -name "unit-tests.log"`; do echo "=========$log========="; grep "ERROR" $log -A 100 -B 5; done diff --git a/README.md b/README.md index b38d69334..16fc794ee 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,6 @@ [![](https://tokei.rs/b1/github.com/apache/kyuubi)](https://github.com/apache/kyuubi) [![codecov](https://codecov.io/gh/apache/kyuubi/branch/master/graph/badge.svg)](https://codecov.io/gh/apache/kyuubi) ![GitHub Workflow Status](https://img.shields.io/github/workflow/status/apache/kyuubi/Kyuubi/master?style=plastic) -[![Travis](https://api.travis-ci.com/apache/kyuubi.svg?branch=master)](https://travis-ci.com/apache/kyuubi) [![Documentation Status](https://readthedocs.org/projects/kyuubi/badge/?version=latest)](https://kyuubi.readthedocs.io/en/master/) ![GitHub top language](https://img.shields.io/github/languages/top/apache/kyuubi) [![Commit activity](https://img.shields.io/github/commit-activity/m/apache/kyuubi)](https://github.com/apache/kyuubi/graphs/commit-activity) diff --git a/build/Dockerfile b/build/Dockerfile index b53b6716e..cd2d5077c 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -37,7 +37,7 @@ ARG MVN_ARG # Pass the environment variable `CI` into container, for internal use only. # -# Continuous integration(aka. CI) services like GitHub Actions, Travis always provide +# Continuous integration(aka. CI) services like GitHub Actions always provide # an environment variable `CI` in runners, and we detect this variable to run some # specific actions, e.g. run `mvn` in batch mode to suppress noisy logs. ARG CI From 5cd33a0e3c56320ad2047f94415957274d0a0ee7 Mon Sep 17 00:00:00 2001 From: df_liu Date: Thu, 9 Feb 2023 20:09:06 +0800 Subject: [PATCH 029/760] [KYUUBI #4252][FOLLOWUP] Bump Flink 1.14.6 ### _Why are the changes needed?_ updated `.github/workflows/master.yml#L161` ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [ ] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4284 from df-Liu/follow_4252. Closes #4252 3b0069418 [df_liu] [KYUUBI #4252][FOLLOWUP] Bump Flink 1.14.6 Authored-by: df_liu Signed-off-by: Cheng Pan --- .github/workflows/master.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index c65226d78..7a58a5d08 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -158,7 +158,7 @@ jobs: include: - java: 8 flink: '1.15' - flink-archive: '-Dflink.archive.mirror=https://archive.apache.org/dist/flink/flink-1.14.5 -Dflink.archive.name=flink-1.14.5-bin-scala_2.12.tgz' + flink-archive: '-Dflink.archive.mirror=https://archive.apache.org/dist/flink/flink-1.14.6 -Dflink.archive.name=flink-1.14.6-bin-scala_2.12.tgz' comment: 'verify-on-flink-1.14-binary' - java: 8 flink: '1.15' From b47f0890e1cdeecbf0a54988e912d6640fdd2720 Mon Sep 17 00:00:00 2001 From: df_liu Date: Thu, 9 Feb 2023 20:10:13 +0800 Subject: [PATCH 030/760] [KYUUBI #4250] Bump Flink to 1.16.1 ### _Why are the changes needed?_ close #4250 ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [ ] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4275 from df-Liu/fix_4250. Closes #4250 723c4b745 [df_liu] [KYUUBI #4250] fix 1.16.1 0e151a408 [df_liu] [KYUUBI #4250] Bump Flink 1.16.1 Authored-by: df_liu Signed-off-by: Cheng Pan --- .github/workflows/master.yml | 2 +- .../kyuubi/engine/flink/operation/FlinkOperationSuite.scala | 1 + pom.xml | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index 7a58a5d08..d5494a42c 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -162,7 +162,7 @@ jobs: comment: 'verify-on-flink-1.14-binary' - java: 8 flink: '1.15' - flink-archive: '-Dflink.archive.mirror=https://archive.apache.org/dist/flink/flink-1.16.0 -Dflink.archive.name=flink-1.16.0-bin-scala_2.12.tgz' + flink-archive: '-Dflink.archive.mirror=https://archive.apache.org/dist/flink/flink-1.16.1 -Dflink.archive.name=flink-1.16.1-bin-scala_2.12.tgz' comment: 'verify-on-flink-1.16-binary' steps: - uses: actions/checkout@v3 diff --git a/externals/kyuubi-flink-sql-engine/src/test/scala/org/apache/kyuubi/engine/flink/operation/FlinkOperationSuite.scala b/externals/kyuubi-flink-sql-engine/src/test/scala/org/apache/kyuubi/engine/flink/operation/FlinkOperationSuite.scala index c75124c39..d0522d3ea 100644 --- a/externals/kyuubi-flink-sql-engine/src/test/scala/org/apache/kyuubi/engine/flink/operation/FlinkOperationSuite.scala +++ b/externals/kyuubi-flink-sql-engine/src/test/scala/org/apache/kyuubi/engine/flink/operation/FlinkOperationSuite.scala @@ -903,6 +903,7 @@ class FlinkOperationSuite extends WithFlinkSQLEngine with HiveJDBCTestHelper { statement.getConnection.setCatalog("cat_a") val changedCatalog = statement.getConnection.getCatalog assert(changedCatalog == "cat_a") + statement.getConnection.setCatalog("default_catalog") assert(statement.execute("drop catalog cat_a")) } } diff --git a/pom.xml b/pom.xml index 15d425dbb..b57324989 100644 --- a/pom.xml +++ b/pom.xml @@ -2344,7 +2344,7 @@ flink-1.16 - 1.16.0 + 1.16.1 From 68cc0e40970453e59351d199396a2c546afb8c08 Mon Sep 17 00:00:00 2001 From: liangbowen Date: Thu, 9 Feb 2023 20:12:52 +0800 Subject: [PATCH 031/760] [KYUUBI #4262] [AUTHZ] Change from DROP to ALTER as required privilege of the source table in AlterTableRenameCommand ### _Why are the changes needed?_ to close #4262 . - change the required privilege of the source table from DROP to ALTER - skip privilege checks of the new target table, as the same way in Ranger's Hive plugin ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [x] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4281 from bowenliang123/4262-renametable. Closes #4262 e7117de9 [liangbowen] change required privilege of the source table from DROP to ALTER Authored-by: liangbowen Signed-off-by: liangbowen --- .../src/main/resources/table_command_spec.json | 17 ----------------- .../plugin/spark/authz/ranger/AccessType.scala | 3 +-- .../spark/authz/PrivilegesBuilderSuite.scala | 7 ++----- .../plugin/spark/authz/gen/TableCommands.scala | 8 ++------ 4 files changed, 5 insertions(+), 30 deletions(-) diff --git a/extensions/spark/kyuubi-spark-authz/src/main/resources/table_command_spec.json b/extensions/spark/kyuubi-spark-authz/src/main/resources/table_command_spec.json index af748c278..d36690bcf 100644 --- a/extensions/spark/kyuubi-spark-authz/src/main/resources/table_command_spec.json +++ b/extensions/spark/kyuubi-spark-authz/src/main/resources/table_command_spec.json @@ -691,23 +691,6 @@ "fieldName" : "oldName", "fieldExtractor" : "TableIdentifierTableExtractor", "columnDesc" : null, - "actionTypeDesc" : { - "fieldName" : null, - "fieldExtractor" : null, - "actionType" : "DELETE" - }, - "tableTypeDesc" : { - "fieldName" : "oldName", - "fieldExtractor" : "TableIdentifierTableTypeExtractor", - "skipTypes" : [ "TEMP_VIEW" ] - }, - "catalogDesc" : null, - "isInput" : false, - "setCurrentDatabaseIfMissing" : false - }, { - "fieldName" : "newName", - "fieldExtractor" : "TableIdentifierTableExtractor", - "columnDesc" : null, "actionTypeDesc" : null, "tableTypeDesc" : { "fieldName" : "oldName", diff --git a/extensions/spark/kyuubi-spark-authz/src/main/scala/org/apache/kyuubi/plugin/spark/authz/ranger/AccessType.scala b/extensions/spark/kyuubi-spark-authz/src/main/scala/org/apache/kyuubi/plugin/spark/authz/ranger/AccessType.scala index 52e3c0176..7d62229ee 100644 --- a/extensions/spark/kyuubi-spark-authz/src/main/scala/org/apache/kyuubi/plugin/spark/authz/ranger/AccessType.scala +++ b/extensions/spark/kyuubi-spark-authz/src/main/scala/org/apache/kyuubi/plugin/spark/authz/ranger/AccessType.scala @@ -35,14 +35,13 @@ object AccessType extends Enumeration { case CREATETABLE | CREATEVIEW | CREATETABLE_AS_SELECT if obj.privilegeObjectType == TABLE_OR_VIEW => if (isInput) SELECT else CREATE - // new table new `CREATE` privilege here and the old table gets `DELETE` via actionType - case ALTERTABLE_RENAME => CREATE case ALTERDATABASE | ALTERDATABASE_LOCATION | ALTERTABLE_ADDCOLS | ALTERTABLE_ADDPARTS | ALTERTABLE_DROPPARTS | ALTERTABLE_LOCATION | + ALTERTABLE_RENAME | ALTERTABLE_PROPERTIES | ALTERTABLE_RENAMECOL | ALTERTABLE_RENAMEPART | diff --git a/extensions/spark/kyuubi-spark-authz/src/test/scala/org/apache/kyuubi/plugin/spark/authz/PrivilegesBuilderSuite.scala b/extensions/spark/kyuubi-spark-authz/src/test/scala/org/apache/kyuubi/plugin/spark/authz/PrivilegesBuilderSuite.scala index 15f58deb3..b014aaaca 100644 --- a/extensions/spark/kyuubi-spark-authz/src/test/scala/org/apache/kyuubi/plugin/spark/authz/PrivilegesBuilderSuite.scala +++ b/extensions/spark/kyuubi-spark-authz/src/test/scala/org/apache/kyuubi/plugin/spark/authz/PrivilegesBuilderSuite.scala @@ -143,7 +143,7 @@ abstract class PrivilegesBuilderSuite extends AnyFunSuite val (in, out, operationType) = PrivilegesBuilder.build(plan, spark) assert(operationType === ALTERTABLE_RENAME) assert(in.isEmpty) - assert(out.size === 2) + assert(out.size === 1) out.foreach { po => assert(po.privilegeObjectType === PrivilegeObjectType.TABLE_OR_VIEW) assert(po.catalog.isEmpty) @@ -151,10 +151,7 @@ abstract class PrivilegesBuilderSuite extends AnyFunSuite assert(Set(oldTableShort, "efg").contains(po.objectName)) assert(po.columns.isEmpty) val accessType = ranger.AccessType(po, operationType, isInput = false) - assert(Set(AccessType.CREATE, AccessType.DROP).contains(accessType)) - if (accessType == AccessType.DROP) { - checkTableOwner(po) - } + assert(accessType == AccessType.ALTER) } } } diff --git a/extensions/spark/kyuubi-spark-authz/src/test/scala/org/apache/kyuubi/plugin/spark/authz/gen/TableCommands.scala b/extensions/spark/kyuubi-spark-authz/src/test/scala/org/apache/kyuubi/plugin/spark/authz/gen/TableCommands.scala index e2fda9162..d24583e76 100644 --- a/extensions/spark/kyuubi-spark-authz/src/test/scala/org/apache/kyuubi/plugin/spark/authz/gen/TableCommands.scala +++ b/extensions/spark/kyuubi-spark-authz/src/test/scala/org/apache/kyuubi/plugin/spark/authz/gen/TableCommands.scala @@ -102,7 +102,6 @@ object TableCommands { val AlterTableRename = { val cmd = "org.apache.spark.sql.execution.command.AlterTableRenameCommand" - val actionTypeDesc = ActionTypeDesc(actionType = Some(DELETE)) val oldTableTableTypeDesc = TableTypeDesc( @@ -112,12 +111,9 @@ object TableCommands { val oldTableD = TableDesc( "oldName", tite, - tableTypeDesc = Some(oldTableTableTypeDesc), - actionTypeDesc = Some(actionTypeDesc)) + tableTypeDesc = Some(oldTableTableTypeDesc)) - val newTableD = - TableDesc("newName", tite, tableTypeDesc = Some(oldTableTableTypeDesc)) - TableCommandSpec(cmd, Seq(oldTableD, newTableD), ALTERTABLE_RENAME) + TableCommandSpec(cmd, Seq(oldTableD), ALTERTABLE_RENAME) } // this is for spark 3.1 or below From cc6f54ed0ce2cce3264c44710947bd461384ab9c Mon Sep 17 00:00:00 2001 From: Cheng Pan Date: Thu, 9 Feb 2023 20:45:02 +0800 Subject: [PATCH 032/760] [KYUUBI #4247][FOLLOWUP] Fix `build/mvn` version comparison ### _Why are the changes needed?_ `build/mvn` should download the maven when the local `mvn` version does not match, this corrects the change in #4261 ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [x] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4291 from pan3793/mvn-version. Closes #4247 42717a73c [Cheng Pan] [KYUUBI #4247][FOLLOWUP] Fix build/mvn version comparison Authored-by: Cheng Pan Signed-off-by: Cheng Pan --- build/mvn | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/mvn b/build/mvn index de697f410..67aa02b4f 100755 --- a/build/mvn +++ b/build/mvn @@ -76,7 +76,7 @@ install_mvn() { fi # See simple version normalization: http://stackoverflow.com/questions/16989598/bash-comparing-version-numbers function version { echo "$@" | awk -F. '{ printf("%03d%03d%03d\n", $1,$2,$3); }'; } - if [ $(version $MVN_DETECTED_VERSION) -eq $(version $MVN_VERSION) ]; then + if [ $(version $MVN_DETECTED_VERSION) -ne $(version $MVN_VERSION) ]; then local APACHE_MIRROR=${APACHE_MIRROR:-'https://archive.apache.org/dist/'} install_app \ From 0bdcfb82163b69d1b9feb683620408bda347434e Mon Sep 17 00:00:00 2001 From: runzhliu Date: Thu, 9 Feb 2023 20:52:11 +0800 Subject: [PATCH 033/760] [KYUUBI #4248] Modify the GitHub Action of setting up the Minikube cluster ### _Why are the changes needed?_ bump the minikube version to 1.29.0 and the kubernetes version to 1.26.1. After ran some tests for the used action manusa/actions-setup-minikubev2.7.2, it turns out that it would have some problems when I bump the minikube version to 1.29.0, please refer to [this link](https://github.com/runzhliu/yum-with-browser/actions/runs/4113082204/jobs/7098775474) for details. In addition, I have tried another minikube action medyagh/setup-minikubemaster, it's good to bump the version for both minikube and Kubernetes, please refer to [this link](https://github.com/runzhliu/yum-with-browser/actions/runs/4112397435/jobs/7097254604#step:5:8). However, I would prefer to [this way](https://github.com/apache/spark/blob/master/.github/workflows/build_and_test.yml#L945-L966) to setup a minikube cluster for the rest Kubernetes related tests, it's also good to set up the cluster as well, please refer to [this link](https://github.com/runzhliu/yum-with-browser/actions/runs/4113018527/jobs/7098637193#step:4:2). ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [ ] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4248 from runzhliu/patch-1. Closes #4248 7d12e0e8 [runzhliu] Update master.yml 52448ae0 [runzhliu] Update master.yml 5fb0b9ec [runzhliu] Update master.yml 3491b33a [runzhliu] Merge branch 'apache:master' into patch-1 64101d95 [runzhliu] bump the minikube version to 1.29.0 and the kubernetes version to 1.26.1 4b278697 [runzhliu] Merge branch 'apache:master' into patch-1 b19e0983 [runzhliu] Update master.yml f6bc18ee [runzhliu] Update master.yml bb133312 [runzhliu] Update master.yml b44ad030 [runzhliu] Update master.yml 7f22810f [runzhliu] Update master.yml 09a6dadf [runzhliu] Update master.yml 60623715 [runzhliu] Update master.yml 0d2c6568 [runzhliu] Update master.yml 5c8b0cc6 [runzhliu] bump the minikube version to 1.29.0 and the kubernetes version to 1.26.1 Authored-by: runzhliu Signed-off-by: liangbowen --- .github/workflows/master.yml | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index d5494a42c..0cd1f3140 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -33,6 +33,8 @@ concurrency: env: MVN_OPT: -Dmaven.javadoc.skip=true -Drat.skip=true -Dscalastyle.skip=true -Dspotless.check.skip -Dorg.slf4j.simpleLogger.defaultLogLevel=warn -Pjdbc-shaded + KUBERNETES_VERSION: v1.26.1 + MINIKUBE_VERSION: v1.29.0 jobs: default: @@ -78,7 +80,7 @@ jobs: - name: Setup Python uses: actions/setup-python@v4 with: - python-version: '3.9' + python-version: '3.9' - name: Build and test Kyuubi and Spark with maven w/o linters run: | TEST_MODULES="dev/kyuubi-codecov" @@ -347,15 +349,17 @@ jobs: file: build/Dockerfile load: true tags: apache/kyuubi:latest - # from https://github.com/marketplace/actions/setup-minikube-kubernetes-cluster - name: Setup Minikube - uses: manusa/actions-setup-minikube@v2.7.2 - with: - minikube version: 'v1.28.0' - kubernetes version: 'v1.25.4' - github token: ${{ secrets.GITHUB_TOKEN }} + run: | + # https://minikube.sigs.k8s.io/docs/start/ + curl -LO https://github.com/kubernetes/minikube/releases/download/${MINIKUBE_VERSION}/minikube-linux-amd64 + sudo install minikube-linux-amd64 /usr/local/bin/minikube + minikube start --cpus 2 --memory 4096 --kubernetes-version=${KUBERNETES_VERSION} --force + # https://minikube.sigs.k8s.io/docs/handbook/pushing/#7-loading-directly-to-in-cluster-container-runtime + minikube image load apache/kyuubi:latest - name: kubectl pre-check run: | + kubectl get nodes kubectl get serviceaccount kubectl create serviceaccount kyuubi kubectl create clusterrolebinding kyuubi-role --clusterrole=edit --serviceaccount=default:kyuubi @@ -394,15 +398,12 @@ jobs: steps: - name: Checkout uses: actions/checkout@v3 - # from https://github.com/marketplace/actions/setup-minikube-kubernetes-cluster - name: Setup Minikube - uses: manusa/actions-setup-minikube@v2.7.2 - with: - minikube version: 'v1.28.0' - kubernetes version: 'v1.25.4' - github token: ${{ secrets.GITHUB_TOKEN }} - driver: docker - start args: '--extra-config=kubeadm.ignore-preflight-errors=NumCPU --force --cpus 2 --memory 4096' + run: | + # https://minikube.sigs.k8s.io/docs/start/ + curl -LO https://github.com/kubernetes/minikube/releases/download/${MINIKUBE_VERSION}/minikube-linux-amd64 + sudo install minikube-linux-amd64 /usr/local/bin/minikube + minikube start --cpus 2 --memory 4096 --kubernetes-version=${KUBERNETES_VERSION} --force # in case: https://spark.apache.org/docs/latest/running-on-kubernetes.html#rbac - name: Create Service Account run: | From 1c7a66ff84a3051bd799c552049f6b50abc73d28 Mon Sep 17 00:00:00 2001 From: Kent Yao Date: Thu, 9 Feb 2023 12:53:47 +0000 Subject: [PATCH 034/760] [KYUUBI #4269] Add a GA Job For Documentation Verification ### _Why are the changes needed?_ Add a GA Job For Documentation Verification ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [ ] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4269 from yaooqinn/docga. Closes #4269 c9b39de6 [Cheng Pan] Update .github/workflows/docs.yml db2d37f8 [Kent Yao] Update .github/workflows/docs.yml c5bc8518 [Kent Yao] trigger ga 6340129b [Kent Yao] trigger ga fafbf847 [Kent Yao] trigger ga 52f23240 [Kent Yao] trigger ga 1e0000a7 [Kent Yao] Add a GA Job For Documentaion Verification Lead-authored-by: Kent Yao Co-authored-by: Cheng Pan Signed-off-by: Cheng Pan --- .github/workflows/docs.yml | 48 ++++++++++++++++++++++++++++++++++++++ docs/requirements.txt | 4 ++-- 2 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/docs.yml diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 000000000..55cb6b8b1 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,48 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +name: Docs + +on: + pull_request: + branches: + - master + +concurrency: + group: docs-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + sphinx: + name: sphinx-build + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 + with: + python-version: '3.9' + cache: 'pip' + cache-dependency-path: docs/requirements.txt + - run: pip install -r docs/requirements.txt + - name: make html + run: make -d --directory docs html + - name: upload html + uses: actions/upload-artifact@v3 + with: + path: | + docs/_build/html/ + !docs/_build/html/_sources/ diff --git a/docs/requirements.txt b/docs/requirements.txt index 8a5ee7e12..00a2eb136 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -22,6 +22,6 @@ markdown==3.3.7 recommonmark==0.7.1 sphinx==4.5.0 sphinx-book-theme==0.3.3 -sphinx-markdown-tables==0.0.15 -sphinx-notfound-page==0.8 +sphinx-markdown-tables==0.0.17 +sphinx-notfound-page==0.8.3 sphinx-togglebutton===0.3.2 From 363e798b7ccb636eb224c5a7caa1eaaa475a0cc9 Mon Sep 17 00:00:00 2001 From: Cheng Pan Date: Thu, 9 Feb 2023 13:18:42 +0000 Subject: [PATCH 035/760] [KYUUBI #4283] [TEST] Fix flaky test in SessionsResourceSuite ### _Why are the changes needed?_ This PR proposes to fix the flaky test ``` - post session exception if failed to open engine session *** FAILED *** ``` which was introduced in #4185 https://github.com/apache/kyuubi/actions/runs/4131523933/jobs/7139249142 ### _How was this patch tested?_ - [x] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [x] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4283 from pan3793/flaky. Closes #4283 f5c70077 [Cheng Pan] [TEST] Fix flaky test in SessionsResourceSuite Authored-by: Cheng Pan Signed-off-by: Cheng Pan --- .../org/apache/kyuubi/server/api/v1/SessionsResourceSuite.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kyuubi-server/src/test/scala/org/apache/kyuubi/server/api/v1/SessionsResourceSuite.scala b/kyuubi-server/src/test/scala/org/apache/kyuubi/server/api/v1/SessionsResourceSuite.scala index 1357245ad..dcd4d5904 100644 --- a/kyuubi-server/src/test/scala/org/apache/kyuubi/server/api/v1/SessionsResourceSuite.scala +++ b/kyuubi-server/src/test/scala/org/apache/kyuubi/server/api/v1/SessionsResourceSuite.scala @@ -296,7 +296,7 @@ class SessionsResourceSuite extends KyuubiFunSuite with RestFrontendTestHelper { response = webTarget.path(s"api/v1/sessions/$sessionHandle").request().get() // will meet json parse exception with response.readEntity(classOf[KyuubiSessionEvent]) val sessionEvent = response.readEntity(classOf[String]) - assert(sessionEvent.contains("SparkException: Master")) + assert(sessionEvent.contains("The last 10 line(s) of log are:")) } } } From 61df9e2cc402e9bd35b4f300790fda2ac8bdc495 Mon Sep 17 00:00:00 2001 From: Cheng Pan Date: Thu, 9 Feb 2023 22:42:50 +0800 Subject: [PATCH 036/760] [KYUUBI #4292] Pin threeten 1.7.0 ### _Why are the changes needed?_ Pin `org.threeten:threeten-extra:1.7.0` for spark master as a workaround for https://github.com/ThreeTen/threeten-extra/issues/226 ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [ ] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4292 from pan3793/threeten. Closes #4292 8c1a24fd5 [Cheng Pan] Pin threeten 1.7.0 Authored-by: Cheng Pan Signed-off-by: Cheng Pan --- pom.xml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/pom.xml b/pom.xml index b57324989..fc282c43e 100644 --- a/pom.xml +++ b/pom.xml @@ -2309,8 +2309,19 @@ spark-master 3.4.0-SNAPSHOT + + 1.7.0 org.scalatest.tags.Slow,org.apache.kyuubi.tags.DeltaTest,org.apache.kyuubi.tags.IcebergTest,org.apache.kyuubi.tags.HudiTest,org.apache.kyuubi.tags.PySparkTest + + + + org.threeten + threeten-extra + ${threeten.version} + + + From afc912c25d9b7c3a778bc02c404c689df35a1dbb Mon Sep 17 00:00:00 2001 From: Cheng Pan Date: Fri, 10 Feb 2023 05:16:45 +0800 Subject: [PATCH 037/760] [KYUUBI #4253] Change default Flink version to 1.16 ### _Why are the changes needed?_ This PR updates `pom.xml` and CI workflow to change the default Flink version to 1.16, close #4253 ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [x] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4289 from pan3793/flink-1.16. Closes #4253 dc3885a38 [Cheng Pan] [KYUUBI #4253] Change default Flink version to 1.16 Authored-by: Cheng Pan Signed-off-by: Cheng Pan --- .github/workflows/master.yml | 8 ++++---- pom.xml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index 0cd1f3140..fd0505999 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -159,13 +159,13 @@ jobs: comment: [ "normal" ] include: - java: 8 - flink: '1.15' + flink: '1.16' flink-archive: '-Dflink.archive.mirror=https://archive.apache.org/dist/flink/flink-1.14.6 -Dflink.archive.name=flink-1.14.6-bin-scala_2.12.tgz' comment: 'verify-on-flink-1.14-binary' - java: 8 - flink: '1.15' - flink-archive: '-Dflink.archive.mirror=https://archive.apache.org/dist/flink/flink-1.16.1 -Dflink.archive.name=flink-1.16.1-bin-scala_2.12.tgz' - comment: 'verify-on-flink-1.16-binary' + flink: '1.16' + flink-archive: '-Dflink.archive.mirror=https://archive.apache.org/dist/flink/flink-1.15.3 -Dflink.archive.name=flink-1.15.3-bin-scala_2.12.tgz' + comment: 'verify-on-flink-1.15-binary' steps: - uses: actions/checkout@v3 - name: Tune Runner VM diff --git a/pom.xml b/pom.xml index fc282c43e..d292fe9e2 100644 --- a/pom.xml +++ b/pom.xml @@ -134,7 +134,7 @@ 2.4.4 0.9.3 0.62.2 - 1.15.2 + 1.16.1 flink-${flink.version}-bin-scala_${scala.binary.version}.tgz ${apache.archive.dist}/flink/flink-${flink.version} From 673e8e1ce7aa5b993e9797ff8a43340c825ac5e9 Mon Sep 17 00:00:00 2001 From: runzhliu Date: Fri, 10 Feb 2023 07:39:22 +0800 Subject: [PATCH 038/760] [KYUUBI #4286] Bump Jackson from 2.14.1 to 2.14.2 ### _Why are the changes needed?_ - Jackson 2.14.2 release note: https://github.com/FasterXML/jackson/wiki/Jackson-Release-2.14.2 ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [ ] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4286 from runzhliu/patch-4. Closes #4286 6902e2ec [runzhliu] Update dependencyList 3cde2004 [runzhliu] Merge branch 'apache:master' into patch-4 8ac6eccd [runzhliu] bump the jackson maven dependecy version to 2.14.2 Authored-by: runzhliu Signed-off-by: liangbowen --- dev/dependencyList | 18 +++++++++--------- pom.xml | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/dev/dependencyList b/dev/dependencyList index 10933b9fa..2035b95de 100644 --- a/dev/dependencyList +++ b/dev/dependencyList @@ -65,16 +65,16 @@ httpclient/4.5.14//httpclient-4.5.14.jar httpcore/4.4.16//httpcore-4.4.16.jar httpmime/4.5.14//httpmime-4.5.14.jar j2objc-annotations/1.3//j2objc-annotations-1.3.jar -jackson-annotations/2.14.1//jackson-annotations-2.14.1.jar -jackson-core/2.14.1//jackson-core-2.14.1.jar -jackson-databind/2.14.1//jackson-databind-2.14.1.jar -jackson-dataformat-yaml/2.14.1//jackson-dataformat-yaml-2.14.1.jar +jackson-annotations/2.14.2//jackson-annotations-2.14.2.jar +jackson-core/2.14.2//jackson-core-2.14.2.jar +jackson-databind/2.14.2//jackson-databind-2.14.2.jar +jackson-dataformat-yaml/2.14.2//jackson-dataformat-yaml-2.14.2.jar jackson-datatype-jdk8/2.12.3//jackson-datatype-jdk8-2.12.3.jar -jackson-datatype-jsr310/2.14.1//jackson-datatype-jsr310-2.14.1.jar -jackson-jaxrs-base/2.14.1//jackson-jaxrs-base-2.14.1.jar -jackson-jaxrs-json-provider/2.14.1//jackson-jaxrs-json-provider-2.14.1.jar -jackson-module-jaxb-annotations/2.14.1//jackson-module-jaxb-annotations-2.14.1.jar -jackson-module-scala_2.12/2.14.1//jackson-module-scala_2.12-2.14.1.jar +jackson-datatype-jsr310/2.14.2//jackson-datatype-jsr310-2.14.2.jar +jackson-jaxrs-base/2.14.2//jackson-jaxrs-base-2.14.2.jar +jackson-jaxrs-json-provider/2.14.2//jackson-jaxrs-json-provider-2.14.2.jar +jackson-module-jaxb-annotations/2.14.2//jackson-module-jaxb-annotations-2.14.2.jar +jackson-module-scala_2.12/2.14.2//jackson-module-scala_2.12-2.14.2.jar jakarta.annotation-api/1.3.5//jakarta.annotation-api-1.3.5.jar jakarta.inject/2.6.1//jakarta.inject-2.6.1.jar jakarta.servlet-api/4.0.4//jakarta.servlet-api-4.0.4.jar diff --git a/pom.xml b/pom.xml index d292fe9e2..e4ca73d69 100644 --- a/pom.xml +++ b/pom.xml @@ -158,7 +158,7 @@ 4.4.16 0.12.0 1.1.0 - 2.14.1 + 2.14.2 4.0.4 2.3.2 1.2.2 From 8d269b83d370e9940c3c9a39c3e3209d9e2dfee6 Mon Sep 17 00:00:00 2001 From: Cheng Pan Date: Fri, 10 Feb 2023 12:23:48 +0800 Subject: [PATCH 039/760] [KYUUBI #4297] Daily publish master snapshot docker image ### _Why are the changes needed?_ This PR proposes to change the publish `master-snapshot` image from every commit of the master branch to the daily scheduled, it saves GA resources. ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [ ] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4297 from pan3793/docker-nightly. Closes #4297 1424287cb [Cheng Pan] nit 32cffe8ab [Cheng Pan] Daily publish master snapshot docker image Authored-by: Cheng Pan Signed-off-by: Cheng Pan --- ...-image.yml => publish-snapshot-docker.yml} | 13 +++---- ...napshot.yml => publish-snapshot-nexus.yml} | 36 +++++++++---------- 2 files changed, 22 insertions(+), 27 deletions(-) rename .github/workflows/{docker-image.yml => publish-snapshot-docker.yml} (86%) rename .github/workflows/{publish-snapshot.yml => publish-snapshot-nexus.yml} (69%) diff --git a/.github/workflows/docker-image.yml b/.github/workflows/publish-snapshot-docker.yml similarity index 86% rename from .github/workflows/docker-image.yml rename to .github/workflows/publish-snapshot-docker.yml index b403e46b5..5c9c04d27 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/publish-snapshot-docker.yml @@ -15,22 +15,17 @@ # limitations under the License. # -name: Publish Docker image +name: Publish Snapshot Docker Image on: - push: - branches: - - master + schedule: + - cron: '0 0 * * *' jobs: push_to_registry: - name: Push Docker image to Docker Hub + name: Push Snapshot Docker Image to Docker Hub if: ${{ startsWith(github.repository, 'apache/') }} runs-on: ubuntu-22.04 - concurrency: - # this group should be global unique - group: push-docker-image - cancel-in-progress: true steps: - name: Checkout uses: actions/checkout@v3 diff --git a/.github/workflows/publish-snapshot.yml b/.github/workflows/publish-snapshot-nexus.yml similarity index 69% rename from .github/workflows/publish-snapshot.yml rename to .github/workflows/publish-snapshot-nexus.yml index acd04bfab..0d4222b04 100644 --- a/.github/workflows/publish-snapshot.yml +++ b/.github/workflows/publish-snapshot-nexus.yml @@ -15,11 +15,11 @@ # limitations under the License. # -name: Publish Snapshot +name: Publish Snapshot Nexus on: schedule: - - cron: '0 0 * * *' + - cron: '0 0 * * *' jobs: publish-snapshot: @@ -41,19 +41,19 @@ jobs: - branch: branch-1.6 profiles: -Pflink-provided,spark-provided,hive-provided,spark-3.3 steps: - - name: Checkout repository - uses: actions/checkout@v3 - with: - ref: ${{ matrix.branch }} - - name: Setup JDK 8 - uses: actions/setup-java@v3 - with: - distribution: temurin - java-version: 8 - cache: 'maven' - check-latest: false - - name: Publish snapshot - ${{ matrix.branch }} - env: - ASF_USERNAME: ${{ secrets.NEXUS_USER }} - ASF_PASSWORD: ${{ secrets.NEXUS_PW }} - run: ./build/mvn clean deploy -s ./build/release/asf-settings.xml -DskipTests ${{ matrix.profiles }} + - name: Checkout repository + uses: actions/checkout@v3 + with: + ref: ${{ matrix.branch }} + - name: Setup JDK 8 + uses: actions/setup-java@v3 + with: + distribution: temurin + java-version: 8 + cache: 'maven' + check-latest: false + - name: Publish Snapshot Jar to Nexus - ${{ matrix.branch }} + env: + ASF_USERNAME: ${{ secrets.NEXUS_USER }} + ASF_PASSWORD: ${{ secrets.NEXUS_PW }} + run: build/mvn clean deploy -s build/release/asf-settings.xml -DskipTests ${{ matrix.profiles }} From 5318585550bd1e0921e95fea10637d3fe4b04ea3 Mon Sep 17 00:00:00 2001 From: Cheng Pan Date: Fri, 10 Feb 2023 12:25:57 +0800 Subject: [PATCH 040/760] [KYUUBI #4288] Use `eclipse-temurin:8-jdk-focal` as default base image ### _Why are the changes needed?_ eclipse-temurin is the successor for openjdk, see https://github.com/apache/spark/pull/37705 "The core change is: the OS of base image changes debian-bullseye to ubuntu-focal (based on debian bullseye)." ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [ ] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4288 from pan3793/image. Closes #4288 b7b619018 [Cheng Pan] Use eclipse-temurin:8-jdk-focal as default base image Authored-by: Cheng Pan Signed-off-by: Cheng Pan --- build/Dockerfile | 10 ++++++---- docker/Dockerfile | 7 ++++--- tools/spark-block-cleaner/kubernetes/docker/Dockerfile | 3 ++- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/build/Dockerfile b/build/Dockerfile index cd2d5077c..8ecc6c8b7 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -29,9 +29,9 @@ # Declare the BASE_IMAGE argument in the first line, for more detail # see: https://github.com/moby/moby/issues/38379 -ARG BASE_IMAGE=openjdk:8-jdk +ARG BASE_IMAGE=eclipse-temurin:8-jdk-focal -FROM maven:3.6-jdk-8 as builder +FROM eclipse-temurin:8-jdk-focal as builder ARG MVN_ARG @@ -48,7 +48,8 @@ WORKDIR /workspace/kyuubi RUN apt-get update && \ DEBIAN_FRONTEND=noninteractive \ - apt-get install -y python3 && \ + apt-get install -y bash python3 && \ + ln -snf /bin/bash /bin/sh && \ ./build/dist ${MVN_ARG} && \ mv /workspace/kyuubi/dist /opt/kyuubi && \ # Removing stuff saves time because docker creates a temporary layer @@ -71,7 +72,8 @@ COPY --from=builder /opt/kyuubi ${KYUUBI_HOME} RUN set -ex && \ apt-get update && \ DEBIAN_FRONTEND=noninteractive \ - apt install -y bash tini libc6 libpam-modules krb5-user libnss3 procps && \ + apt-get install -y bash tini libc6 libpam-modules krb5-user libnss3 procps && \ + ln -snf /bin/bash /bin/sh && \ useradd -u ${kyuubi_uid} -g root kyuubi && \ mkdir -p ${KYUUBI_HOME} ${KYUUBI_LOG_DIR} ${KYUUBI_PID_DIR} ${KYUUBI_WORK_DIR_ROOT} && \ chmod ug+rw -R ${KYUUBI_HOME} && \ diff --git a/docker/Dockerfile b/docker/Dockerfile index 588f99b1f..190500825 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -24,7 +24,7 @@ # -t the target repo and tag name # more options can be found with -h -ARG BASE_IMAGE=openjdk:8-jre-slim +ARG BASE_IMAGE=eclipse-temurin:8-jdk-focal ARG spark_provided="spark_builtin" FROM ${BASE_IMAGE} as builder_spark_provided @@ -34,7 +34,7 @@ ONBUILD ENV SPARK_HOME ${spark_home_in_docker} FROM ${BASE_IMAGE} as builder_spark_builtin ONBUILD ENV SPARK_HOME /opt/spark -ONBUILD RUN mkdir -p ${SPARK_HOME} +ONBUILD RUN mkdir -p ${SPARK_HOME} ONBUILD COPY spark-binary ${SPARK_HOME} FROM builder_${spark_provided} @@ -50,7 +50,8 @@ ENV KYUUBI_WORK_DIR_ROOT ${KYUUBI_HOME}/work RUN set -ex && \ sed -i 's/http:\/\/deb.\(.*\)/https:\/\/deb.\1/g' /etc/apt/sources.list && \ apt-get update && \ - apt install -y bash tini libc6 libpam-modules krb5-user libnss3 procps && \ + apt-get install -y bash tini libc6 libpam-modules krb5-user libnss3 procps && \ + ln -snf /bin/bash /bin/sh && \ useradd -u ${kyuubi_uid} -g root kyuubi -d /home/kyuubi -m && \ mkdir -p ${KYUUBI_HOME} ${KYUUBI_LOG_DIR} ${KYUUBI_PID_DIR} ${KYUUBI_WORK_DIR_ROOT} && \ rm -rf /var/cache/apt/* diff --git a/tools/spark-block-cleaner/kubernetes/docker/Dockerfile b/tools/spark-block-cleaner/kubernetes/docker/Dockerfile index d039dc355..95a7b2cf8 100644 --- a/tools/spark-block-cleaner/kubernetes/docker/Dockerfile +++ b/tools/spark-block-cleaner/kubernetes/docker/Dockerfile @@ -14,10 +14,11 @@ # See the License for the specific language governing permissions and # limitations under the License. # -FROM openjdk:8-jre-slim +FROM eclipse-temurin:8-jdk-focal RUN apt-get update && \ apt install -y tini && \ + rm -rf /var/cache/apt/* && \ mkdir /data && \ mkdir -p /opt/block-cleaner && \ mkdir -p /log/cleanerLog From 547e5ca6176d386f5d8a8b9aadb9c285450f0e37 Mon Sep 17 00:00:00 2001 From: Cheng Pan Date: Fri, 10 Feb 2023 13:04:24 +0800 Subject: [PATCH 041/760] [KYUUBI #4145] Change lock and polling seq_num path on service discovery ### _Why are the changes needed?_ This PR proposes to change the paths of distributed lock and seq_num(used for POLLING engine pool select policy) on the Service Discovery component. The reason is that namespace `${serverSpace}_${KYUUBI_VERSION}_${shareLevel}_${engineType}/` should be dedicated to engine registration, we'd better use the separated namespace for other functionalities. - lock path ``` # before ${serverSpace}_${shareLevel}_${engineType}/lock/${user}/${subdomain} # after ${serverSpace}_${KYUUBI_VERSION}_${shareLevel}_${engineType}_lock/${user}/${subdomain} ``` - seq_num ``` # before ${serverSpace}_${KYUUBI_VERSION}_${shareLevel}_${engineType}/seq_num/${user}/${poolName} # after ${serverSpace}_${KYUUBI_VERSION}_${shareLevel}_${engineType}_seqNum/${user}/${poolName} ``` ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [x] [Run test](https://kyuubi.apache.org/docs/latest/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4145 from pan3793/namespace. Closes #4145 c912b3f66 [Cheng Pan] name 3326b9b95 [Cheng Pan] name 10083db0b [Cheng Pan] Change lock and polloing seq_num path on service discovery Authored-by: Cheng Pan Signed-off-by: Cheng Pan --- .../org/apache/kyuubi/config/KyuubiConf.scala | 2 +- .../ha/client/etcd/EtcdDiscoveryClient.scala | 40 +++++++++---------- .../zookeeper/ZookeeperDiscoveryClient.scala | 40 +++++++++---------- .../org/apache/kyuubi/engine/EngineRef.scala | 10 ++--- .../apache/kyuubi/engine/EngineRefTests.scala | 2 +- 5 files changed, 46 insertions(+), 48 deletions(-) diff --git a/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala b/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala index b46674d06..01bd46bd3 100644 --- a/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala +++ b/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala @@ -1807,7 +1807,7 @@ object KyuubiConf { .intConf .createWithDefault(-1) - val ENGINE_POOL_BALANCE_POLICY: ConfigEntry[String] = + val ENGINE_POOL_SELECT_POLICY: ConfigEntry[String] = buildConf("kyuubi.engine.pool.selectPolicy") .doc("The select policy of an engine from the corresponding engine pool engine for " + "a session.
    " + diff --git a/kyuubi-ha/src/main/scala/org/apache/kyuubi/ha/client/etcd/EtcdDiscoveryClient.scala b/kyuubi-ha/src/main/scala/org/apache/kyuubi/ha/client/etcd/EtcdDiscoveryClient.scala index ad3a0550c..80a70f2f2 100644 --- a/kyuubi-ha/src/main/scala/org/apache/kyuubi/ha/client/etcd/EtcdDiscoveryClient.scala +++ b/kyuubi-ha/src/main/scala/org/apache/kyuubi/ha/client/etcd/EtcdDiscoveryClient.scala @@ -90,7 +90,7 @@ class EtcdDiscoveryClient(conf: KyuubiConf) extends DiscoveryClient { } } - def createClient(): Unit = { + override def createClient(): Unit = { client = buildClient() kvClient = client.getKVClient() lockClient = client.getLockClient() @@ -99,13 +99,13 @@ class EtcdDiscoveryClient(conf: KyuubiConf) extends DiscoveryClient { leaseTTL = conf.get(HighAvailabilityConf.HA_ETCD_LEASE_TIMEOUT) / 1000 } - def closeClient(): Unit = { + override def closeClient(): Unit = { if (client != null) { client.close() } } - def create(path: String, mode: String, createParent: Boolean = true): String = { + override def create(path: String, mode: String, createParent: Boolean = true): String = { // createParent can not effect here mode match { case "PERSISTENT" => kvClient.put( @@ -116,7 +116,7 @@ class EtcdDiscoveryClient(conf: KyuubiConf) extends DiscoveryClient { path } - def getData(path: String): Array[Byte] = { + override def getData(path: String): Array[Byte] = { val response = kvClient.get(ByteSequence.from(path.getBytes())).get() if (response.getKvs.isEmpty) { throw new KyuubiException(s"Key[$path] not exists in ETCD, please check it.") @@ -125,12 +125,12 @@ class EtcdDiscoveryClient(conf: KyuubiConf) extends DiscoveryClient { } } - def setData(path: String, data: Array[Byte]): Boolean = { + override def setData(path: String, data: Array[Byte]): Boolean = { val response = kvClient.put(ByteSequence.from(path.getBytes), ByteSequence.from(data)).get() response != null } - def getChildren(path: String): List[String] = { + override def getChildren(path: String): List[String] = { val kvs = kvClient.get( ByteSequence.from(path.getBytes()), GetOption.newBuilder().isPrefix(true).build()).get().getKvs @@ -142,25 +142,25 @@ class EtcdDiscoveryClient(conf: KyuubiConf) extends DiscoveryClient { } } - def pathExists(path: String): Boolean = { + override def pathExists(path: String): Boolean = { !pathNonExists(path) } - def pathNonExists(path: String): Boolean = { + override def pathNonExists(path: String): Boolean = { kvClient.get(ByteSequence.from(path.getBytes())).get().getKvs.isEmpty } - def delete(path: String, deleteChildren: Boolean = false): Unit = { + override def delete(path: String, deleteChildren: Boolean = false): Unit = { kvClient.delete( ByteSequence.from(path.getBytes()), DeleteOption.newBuilder().isPrefix(deleteChildren).build()).get() } - def monitorState(serviceDiscovery: ServiceDiscovery): Unit = { + override def monitorState(serviceDiscovery: ServiceDiscovery): Unit = { // not need with etcd } - def tryWithLock[T]( + override def tryWithLock[T]( lockPath: String, timeout: Long)(f: => T): T = { // the default unit is millis, covert to seconds. @@ -195,7 +195,7 @@ class EtcdDiscoveryClient(conf: KyuubiConf) extends DiscoveryClient { } } - def getServerHost(namespace: String): Option[(String, Int)] = { + override def getServerHost(namespace: String): Option[(String, Int)] = { // TODO: use last one because to avoid touching some maybe-crashed engines // We need a big improvement here. getServiceNodesInfo(namespace, Some(1), silent = true) match { @@ -204,7 +204,7 @@ class EtcdDiscoveryClient(conf: KyuubiConf) extends DiscoveryClient { } } - def getEngineByRefId( + override def getEngineByRefId( namespace: String, engineRefId: String): Option[(String, Int)] = { getServiceNodesInfo(namespace, silent = true) @@ -212,7 +212,7 @@ class EtcdDiscoveryClient(conf: KyuubiConf) extends DiscoveryClient { .map(data => (data.host, data.port)) } - def getServiceNodesInfo( + override def getServiceNodesInfo( namespace: String, sizeOpt: Option[Int] = None, silent: Boolean = false): Seq[ServiceNodeInfo] = { @@ -241,7 +241,7 @@ class EtcdDiscoveryClient(conf: KyuubiConf) extends DiscoveryClient { } } - def registerService( + override def registerService( conf: KyuubiConf, namespace: String, serviceDiscovery: ServiceDiscovery, @@ -267,7 +267,7 @@ class EtcdDiscoveryClient(conf: KyuubiConf) extends DiscoveryClient { } } - def deregisterService(): Unit = { + override def deregisterService(): Unit = { // close the EPHEMERAL_SEQUENTIAL node in etcd if (serviceNode != null) { if (serviceNode.lease != LEASE_NULL_VALUE) { @@ -278,7 +278,7 @@ class EtcdDiscoveryClient(conf: KyuubiConf) extends DiscoveryClient { } } - def postDeregisterService(namespace: String): Boolean = { + override def postDeregisterService(namespace: String): Boolean = { if (namespace != null) { delete(DiscoveryPaths.makePath(null, namespace), true) true @@ -287,7 +287,7 @@ class EtcdDiscoveryClient(conf: KyuubiConf) extends DiscoveryClient { } } - def createAndGetServiceNode( + override def createAndGetServiceNode( conf: KyuubiConf, namespace: String, instance: String, @@ -297,7 +297,7 @@ class EtcdDiscoveryClient(conf: KyuubiConf) extends DiscoveryClient { } @VisibleForTesting - def startSecretNode( + override def startSecretNode( createMode: String, basePath: String, initData: String, @@ -307,7 +307,7 @@ class EtcdDiscoveryClient(conf: KyuubiConf) extends DiscoveryClient { ByteSequence.from(initData.getBytes())).get() } - def getAndIncrement(path: String, delta: Int = 1): Int = { + override def getAndIncrement(path: String, delta: Int = 1): Int = { val lockPath = s"${path}_tmp_for_lock" tryWithLock(lockPath, 60 * 1000) { if (pathNonExists(path)) { diff --git a/kyuubi-ha/src/main/scala/org/apache/kyuubi/ha/client/zookeeper/ZookeeperDiscoveryClient.scala b/kyuubi-ha/src/main/scala/org/apache/kyuubi/ha/client/zookeeper/ZookeeperDiscoveryClient.scala index 1315cf029..daa27047e 100644 --- a/kyuubi-ha/src/main/scala/org/apache/kyuubi/ha/client/zookeeper/ZookeeperDiscoveryClient.scala +++ b/kyuubi-ha/src/main/scala/org/apache/kyuubi/ha/client/zookeeper/ZookeeperDiscoveryClient.scala @@ -66,17 +66,17 @@ class ZookeeperDiscoveryClient(conf: KyuubiConf) extends DiscoveryClient { @volatile private var serviceNode: PersistentNode = _ private var watcher: DeRegisterWatcher = _ - def createClient(): Unit = { + override def createClient(): Unit = { zkClient.start() } - def closeClient(): Unit = { + override def closeClient(): Unit = { if (zkClient != null) { zkClient.close() } } - def create(path: String, mode: String, createParent: Boolean = true): String = { + override def create(path: String, mode: String, createParent: Boolean = true): String = { val builder = if (createParent) zkClient.create().creatingParentsIfNeeded() else zkClient.create() builder @@ -84,27 +84,27 @@ class ZookeeperDiscoveryClient(conf: KyuubiConf) extends DiscoveryClient { .forPath(path) } - def getData(path: String): Array[Byte] = { + override def getData(path: String): Array[Byte] = { zkClient.getData.forPath(path) } - def setData(path: String, data: Array[Byte]): Boolean = { + override def setData(path: String, data: Array[Byte]): Boolean = { zkClient.setData().forPath(path, data) != null } - def getChildren(path: String): List[String] = { + override def getChildren(path: String): List[String] = { zkClient.getChildren.forPath(path).asScala.toList } - def pathExists(path: String): Boolean = { + override def pathExists(path: String): Boolean = { zkClient.checkExists().forPath(path) != null } - def pathNonExists(path: String): Boolean = { + override def pathNonExists(path: String): Boolean = { zkClient.checkExists().forPath(path) == null } - def delete(path: String, deleteChildren: Boolean = false): Unit = { + override def delete(path: String, deleteChildren: Boolean = false): Unit = { if (deleteChildren) { zkClient.delete().deletingChildrenIfNeeded().forPath(path) } else { @@ -112,7 +112,7 @@ class ZookeeperDiscoveryClient(conf: KyuubiConf) extends DiscoveryClient { } } - def monitorState(serviceDiscovery: ServiceDiscovery): Unit = { + override def monitorState(serviceDiscovery: ServiceDiscovery): Unit = { zkClient .getConnectionStateListenable.addListener(new ConnectionStateListener { private val isConnected = new AtomicBoolean(false) @@ -141,7 +141,7 @@ class ZookeeperDiscoveryClient(conf: KyuubiConf) extends DiscoveryClient { }) } - def tryWithLock[T](lockPath: String, timeout: Long)(f: => T): T = { + override def tryWithLock[T](lockPath: String, timeout: Long)(f: => T): T = { var lock: InterProcessSemaphoreMutex = null try { try { @@ -189,7 +189,7 @@ class ZookeeperDiscoveryClient(conf: KyuubiConf) extends DiscoveryClient { } } - def getServerHost(namespace: String): Option[(String, Int)] = { + override def getServerHost(namespace: String): Option[(String, Int)] = { // TODO: use last one because to avoid touching some maybe-crashed engines // We need a big improvement here. getServiceNodesInfo(namespace, Some(1), silent = true) match { @@ -198,7 +198,7 @@ class ZookeeperDiscoveryClient(conf: KyuubiConf) extends DiscoveryClient { } } - def getEngineByRefId( + override def getEngineByRefId( namespace: String, engineRefId: String): Option[(String, Int)] = { getServiceNodesInfo(namespace, silent = true) @@ -206,7 +206,7 @@ class ZookeeperDiscoveryClient(conf: KyuubiConf) extends DiscoveryClient { .map(data => (data.host, data.port)) } - def getServiceNodesInfo( + override def getServiceNodesInfo( namespace: String, sizeOpt: Option[Int] = None, silent: Boolean = false): Seq[ServiceNodeInfo] = { @@ -235,7 +235,7 @@ class ZookeeperDiscoveryClient(conf: KyuubiConf) extends DiscoveryClient { } } - def registerService( + override def registerService( conf: KyuubiConf, namespace: String, serviceDiscovery: ServiceDiscovery, @@ -254,7 +254,7 @@ class ZookeeperDiscoveryClient(conf: KyuubiConf) extends DiscoveryClient { watchNode() } - def deregisterService(): Unit = { + override def deregisterService(): Unit = { // close the EPHEMERAL_SEQUENTIAL node in zk if (serviceNode != null) { try { @@ -268,7 +268,7 @@ class ZookeeperDiscoveryClient(conf: KyuubiConf) extends DiscoveryClient { } } - def postDeregisterService(namespace: String): Boolean = { + override def postDeregisterService(namespace: String): Boolean = { if (namespace != null) { try { delete(namespace, true) @@ -283,7 +283,7 @@ class ZookeeperDiscoveryClient(conf: KyuubiConf) extends DiscoveryClient { } } - def createAndGetServiceNode( + override def createAndGetServiceNode( conf: KyuubiConf, namespace: String, instance: String, @@ -293,7 +293,7 @@ class ZookeeperDiscoveryClient(conf: KyuubiConf) extends DiscoveryClient { } @VisibleForTesting - def startSecretNode( + override def startSecretNode( createMode: String, basePath: String, initData: String, @@ -307,7 +307,7 @@ class ZookeeperDiscoveryClient(conf: KyuubiConf) extends DiscoveryClient { secretNode.start() } - def getAndIncrement(path: String, delta: Int = 1): Int = { + override def getAndIncrement(path: String, delta: Int = 1): Int = { val dai = new DistributedAtomicInteger(zkClient, path, new RetryForever(1000)) var atomicVal: AtomicValue[Integer] = null do { diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/engine/EngineRef.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/engine/EngineRef.scala index 565f41ff2..84b7707e8 100644 --- a/kyuubi-server/src/main/scala/org/apache/kyuubi/engine/EngineRef.scala +++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/engine/EngineRef.scala @@ -74,7 +74,7 @@ private[kyuubi] class EngineRef( private val enginePoolIgnoreSubdomain: Boolean = conf.get(ENGINE_POOL_IGNORE_SUBDOMAIN) - private val enginePoolBalancePolicy: String = conf.get(ENGINE_POOL_BALANCE_POLICY) + private val enginePoolSelectPolicy: String = conf.get(ENGINE_POOL_SELECT_POLICY) // In case the multi kyuubi instances have the small gap of timeout, here we add // a small amount of time for timeout @@ -97,12 +97,11 @@ private[kyuubi] class EngineRef( warn(s"Request engine pool size($clientPoolSize) exceeds, fallback to " + s"system threshold $poolThreshold") } - val seqNum = enginePoolBalancePolicy match { + val seqNum = enginePoolSelectPolicy match { case "POLLING" => val snPath = DiscoveryPaths.makePath( - s"${serverSpace}_${KYUUBI_VERSION}_${shareLevel}_$engineType", - "seq_num", + s"${serverSpace}_${KYUUBI_VERSION}_${shareLevel}_${engineType}_seqNum", appUser, clientPoolName) DiscoveryClientProvider.withDiscoveryClient(conf) { client => @@ -159,8 +158,7 @@ private[kyuubi] class EngineRef( case _ => val lockPath = DiscoveryPaths.makePath( - s"${serverSpace}_${shareLevel}_$engineType", - "lock", + s"${serverSpace}_${KYUUBI_VERSION}_${shareLevel}_${engineType}_lock", appUser, subdomain) discoveryClient.tryWithLock( diff --git a/kyuubi-server/src/test/scala/org/apache/kyuubi/engine/EngineRefTests.scala b/kyuubi-server/src/test/scala/org/apache/kyuubi/engine/EngineRefTests.scala index 5d8ae3177..5ca8723f5 100644 --- a/kyuubi-server/src/test/scala/org/apache/kyuubi/engine/EngineRefTests.scala +++ b/kyuubi-server/src/test/scala/org/apache/kyuubi/engine/EngineRefTests.scala @@ -204,7 +204,7 @@ trait EngineRefTests extends KyuubiFunSuite { conf.set(ENGINE_POOL_NAME, pool_name) conf.set(HighAvailabilityConf.HA_NAMESPACE, "engine_test") conf.set(HighAvailabilityConf.HA_ADDRESSES, getConnectString()) - conf.set(ENGINE_POOL_BALANCE_POLICY, "POLLING") + conf.set(ENGINE_POOL_SELECT_POLICY, "POLLING") (0 until (10)).foreach { i => val engine7 = new EngineRef(conf, user, "grp", id, null) val engineNumber = Integer.parseInt(engine7.subdomain.substring(pool_name.length + 1)) From 255b110392bf03be60033b2701b9e49125f6d74f Mon Sep 17 00:00:00 2001 From: Fu Chen Date: Fri, 10 Feb 2023 13:05:17 +0800 Subject: [PATCH 042/760] [KYUUBI #4296] Bump Spark in spark-master profile from 3.4.0-SNAPSHOT to 3.5.0-SNAPSHOT ### _Why are the changes needed?_ https://github.com/apache/spark/pull/39733 ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [ ] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4296 from cfmcgrady/spark-3.5.0-SNAPSHOT. Closes #4296 e16e3f1b0 [Fu Chen] bump spark from 3.4.0-SNAPSHOT to 3.5.0-SNAPSHOT Authored-by: Fu Chen Signed-off-by: Cheng Pan --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e4ca73d69..9afc0cf1d 100644 --- a/pom.xml +++ b/pom.xml @@ -2308,7 +2308,7 @@ spark-master - 3.4.0-SNAPSHOT + 3.5.0-SNAPSHOT 1.7.0 org.scalatest.tags.Slow,org.apache.kyuubi.tags.DeltaTest,org.apache.kyuubi.tags.IcebergTest,org.apache.kyuubi.tags.HudiTest,org.apache.kyuubi.tags.PySparkTest From 708d982eb90f2b3e915d3e9ec9fda10932469d9a Mon Sep 17 00:00:00 2001 From: zwangsheng <2213335496@qq.com> Date: Fri, 10 Feb 2023 14:28:14 +0800 Subject: [PATCH 043/760] [KYUUBI #4184][SUBTASK-1] Refactor docker-image-tool.sh ### _Why are the changes needed?_ See more in #4184 This subtask remove build kyuubi docker image from dev mode(source code). In follow subtask, will add feature build kyuubi docker image from source code. ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [ ] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4217 from zwangsheng/tool/docker-image-tool. Closes #4184 34c866025 [zwangsheng] Add +x premission for docker-image-tool 97b404db7 [zwangsheng] Copy download spark binary 6f267e221 [zwangsheng] Refactor docker-image-tool.sh Authored-by: zwangsheng <2213335496@qq.com> Signed-off-by: Cheng Pan --- bin/docker-image-tool.sh | 71 +++++++++++++--------------------------- 1 file changed, 22 insertions(+), 49 deletions(-) diff --git a/bin/docker-image-tool.sh b/bin/docker-image-tool.sh index e9e4338b5..f3efc8bf5 100755 --- a/bin/docker-image-tool.sh +++ b/bin/docker-image-tool.sh @@ -27,19 +27,21 @@ function error { if [ -z "${KYUUBI_HOME}" ]; then KYUUBI_HOME="$(cd "`dirname "$0"`"/..; pwd)" fi - -CTX_DIR="$KYUUBI_HOME/target/tmp/docker" +KYUUBI_IMAGE_NAME="kyuubi" function is_dev_build { [ ! -f "$KYUUBI_HOME/RELEASE" ] } -function cleanup_ctx_dir { - if is_dev_build; then - rm -rf "$CTX_DIR" - fi -} -trap cleanup_ctx_dir EXIT +if is_dev_build; then + cat < Date: Fri, 10 Feb 2023 15:25:49 +0800 Subject: [PATCH 044/760] Bump 1.8.0-SNAPSHOT --- dev/kyuubi-codecov/pom.xml | 2 +- dev/kyuubi-tpcds/pom.xml | 2 +- extensions/server/kyuubi-server-plugin/pom.xml | 2 +- extensions/spark/kyuubi-extension-spark-3-1/pom.xml | 2 +- extensions/spark/kyuubi-extension-spark-3-2/pom.xml | 2 +- extensions/spark/kyuubi-extension-spark-3-3/pom.xml | 2 +- extensions/spark/kyuubi-extension-spark-common/pom.xml | 2 +- extensions/spark/kyuubi-extension-spark-jdbc-dialect/pom.xml | 2 +- extensions/spark/kyuubi-spark-authz/pom.xml | 2 +- extensions/spark/kyuubi-spark-connector-common/pom.xml | 2 +- extensions/spark/kyuubi-spark-connector-hive/pom.xml | 2 +- extensions/spark/kyuubi-spark-connector-kudu/pom.xml | 2 +- extensions/spark/kyuubi-spark-connector-tpcds/pom.xml | 2 +- extensions/spark/kyuubi-spark-connector-tpch/pom.xml | 2 +- extensions/spark/kyuubi-spark-lineage/pom.xml | 2 +- externals/kyuubi-download/pom.xml | 2 +- externals/kyuubi-flink-sql-engine/pom.xml | 2 +- externals/kyuubi-hive-sql-engine/pom.xml | 2 +- externals/kyuubi-jdbc-engine/pom.xml | 2 +- externals/kyuubi-spark-sql-engine/pom.xml | 2 +- externals/kyuubi-trino-engine/pom.xml | 2 +- integration-tests/kyuubi-flink-it/pom.xml | 2 +- integration-tests/kyuubi-hive-it/pom.xml | 2 +- integration-tests/kyuubi-jdbc-it/pom.xml | 2 +- integration-tests/kyuubi-kubernetes-it/pom.xml | 2 +- integration-tests/kyuubi-trino-it/pom.xml | 2 +- integration-tests/kyuubi-zookeeper-it/pom.xml | 2 +- integration-tests/pom.xml | 2 +- kyuubi-assembly/pom.xml | 2 +- kyuubi-common/pom.xml | 2 +- kyuubi-ctl/pom.xml | 2 +- kyuubi-events/pom.xml | 2 +- kyuubi-ha/pom.xml | 2 +- kyuubi-hive-beeline/pom.xml | 2 +- kyuubi-hive-jdbc-shaded/pom.xml | 2 +- kyuubi-hive-jdbc/pom.xml | 2 +- kyuubi-metrics/pom.xml | 2 +- kyuubi-rest-client/pom.xml | 2 +- kyuubi-server/pom.xml | 2 +- kyuubi-zookeeper/pom.xml | 2 +- pom.xml | 2 +- tools/spark-block-cleaner/pom.xml | 2 +- 42 files changed, 42 insertions(+), 42 deletions(-) diff --git a/dev/kyuubi-codecov/pom.xml b/dev/kyuubi-codecov/pom.xml index 1d1dcb574..ba15ec0f8 100644 --- a/dev/kyuubi-codecov/pom.xml +++ b/dev/kyuubi-codecov/pom.xml @@ -21,7 +21,7 @@ org.apache.kyuubi kyuubi-parent - 1.7.0-SNAPSHOT + 1.8.0-SNAPSHOT ../../pom.xml diff --git a/dev/kyuubi-tpcds/pom.xml b/dev/kyuubi-tpcds/pom.xml index 2921cbe8b..1bc69f9f2 100644 --- a/dev/kyuubi-tpcds/pom.xml +++ b/dev/kyuubi-tpcds/pom.xml @@ -21,7 +21,7 @@ org.apache.kyuubi kyuubi-parent - 1.7.0-SNAPSHOT + 1.8.0-SNAPSHOT ../../pom.xml diff --git a/extensions/server/kyuubi-server-plugin/pom.xml b/extensions/server/kyuubi-server-plugin/pom.xml index b7dfe0ae8..799f27c46 100644 --- a/extensions/server/kyuubi-server-plugin/pom.xml +++ b/extensions/server/kyuubi-server-plugin/pom.xml @@ -21,7 +21,7 @@ org.apache.kyuubi kyuubi-parent - 1.7.0-SNAPSHOT + 1.8.0-SNAPSHOT ../../../pom.xml diff --git a/extensions/spark/kyuubi-extension-spark-3-1/pom.xml b/extensions/spark/kyuubi-extension-spark-3-1/pom.xml index 5bd4b2fd5..9f218f9d0 100644 --- a/extensions/spark/kyuubi-extension-spark-3-1/pom.xml +++ b/extensions/spark/kyuubi-extension-spark-3-1/pom.xml @@ -21,7 +21,7 @@ org.apache.kyuubi kyuubi-parent - 1.7.0-SNAPSHOT + 1.8.0-SNAPSHOT ../../../pom.xml diff --git a/extensions/spark/kyuubi-extension-spark-3-2/pom.xml b/extensions/spark/kyuubi-extension-spark-3-2/pom.xml index daab162b7..a80040aca 100644 --- a/extensions/spark/kyuubi-extension-spark-3-2/pom.xml +++ b/extensions/spark/kyuubi-extension-spark-3-2/pom.xml @@ -21,7 +21,7 @@ org.apache.kyuubi kyuubi-parent - 1.7.0-SNAPSHOT + 1.8.0-SNAPSHOT ../../../pom.xml diff --git a/extensions/spark/kyuubi-extension-spark-3-3/pom.xml b/extensions/spark/kyuubi-extension-spark-3-3/pom.xml index cc8291213..ca729a781 100644 --- a/extensions/spark/kyuubi-extension-spark-3-3/pom.xml +++ b/extensions/spark/kyuubi-extension-spark-3-3/pom.xml @@ -21,7 +21,7 @@ org.apache.kyuubi kyuubi-parent - 1.7.0-SNAPSHOT + 1.8.0-SNAPSHOT ../../../pom.xml diff --git a/extensions/spark/kyuubi-extension-spark-common/pom.xml b/extensions/spark/kyuubi-extension-spark-common/pom.xml index 2c587fd78..6d4bd1443 100644 --- a/extensions/spark/kyuubi-extension-spark-common/pom.xml +++ b/extensions/spark/kyuubi-extension-spark-common/pom.xml @@ -21,7 +21,7 @@ org.apache.kyuubi kyuubi-parent - 1.7.0-SNAPSHOT + 1.8.0-SNAPSHOT ../../../pom.xml diff --git a/extensions/spark/kyuubi-extension-spark-jdbc-dialect/pom.xml b/extensions/spark/kyuubi-extension-spark-jdbc-dialect/pom.xml index 5588805e9..48c4c4379 100644 --- a/extensions/spark/kyuubi-extension-spark-jdbc-dialect/pom.xml +++ b/extensions/spark/kyuubi-extension-spark-jdbc-dialect/pom.xml @@ -21,7 +21,7 @@ org.apache.kyuubi kyuubi-parent - 1.7.0-SNAPSHOT + 1.8.0-SNAPSHOT ../../../pom.xml diff --git a/extensions/spark/kyuubi-spark-authz/pom.xml b/extensions/spark/kyuubi-spark-authz/pom.xml index d537e5a1c..84f76e49e 100644 --- a/extensions/spark/kyuubi-spark-authz/pom.xml +++ b/extensions/spark/kyuubi-spark-authz/pom.xml @@ -21,7 +21,7 @@ org.apache.kyuubi kyuubi-parent - 1.7.0-SNAPSHOT + 1.8.0-SNAPSHOT ../../../pom.xml diff --git a/extensions/spark/kyuubi-spark-connector-common/pom.xml b/extensions/spark/kyuubi-spark-connector-common/pom.xml index e9fa8fcb4..1cba0ccdd 100644 --- a/extensions/spark/kyuubi-spark-connector-common/pom.xml +++ b/extensions/spark/kyuubi-spark-connector-common/pom.xml @@ -21,7 +21,7 @@ org.apache.kyuubi kyuubi-parent - 1.7.0-SNAPSHOT + 1.8.0-SNAPSHOT ../../../pom.xml diff --git a/extensions/spark/kyuubi-spark-connector-hive/pom.xml b/extensions/spark/kyuubi-spark-connector-hive/pom.xml index a97dfa053..37e3d8409 100644 --- a/extensions/spark/kyuubi-spark-connector-hive/pom.xml +++ b/extensions/spark/kyuubi-spark-connector-hive/pom.xml @@ -21,7 +21,7 @@ org.apache.kyuubi kyuubi-parent - 1.7.0-SNAPSHOT + 1.8.0-SNAPSHOT ../../../pom.xml diff --git a/extensions/spark/kyuubi-spark-connector-kudu/pom.xml b/extensions/spark/kyuubi-spark-connector-kudu/pom.xml index f3d667fce..97356cd93 100644 --- a/extensions/spark/kyuubi-spark-connector-kudu/pom.xml +++ b/extensions/spark/kyuubi-spark-connector-kudu/pom.xml @@ -21,7 +21,7 @@ org.apache.kyuubi kyuubi-parent - 1.7.0-SNAPSHOT + 1.8.0-SNAPSHOT ../../../pom.xml diff --git a/extensions/spark/kyuubi-spark-connector-tpcds/pom.xml b/extensions/spark/kyuubi-spark-connector-tpcds/pom.xml index 53557aea3..e9b867739 100644 --- a/extensions/spark/kyuubi-spark-connector-tpcds/pom.xml +++ b/extensions/spark/kyuubi-spark-connector-tpcds/pom.xml @@ -21,7 +21,7 @@ org.apache.kyuubi kyuubi-parent - 1.7.0-SNAPSHOT + 1.8.0-SNAPSHOT ../../../pom.xml diff --git a/extensions/spark/kyuubi-spark-connector-tpch/pom.xml b/extensions/spark/kyuubi-spark-connector-tpch/pom.xml index 4625cdf1a..5b418e200 100644 --- a/extensions/spark/kyuubi-spark-connector-tpch/pom.xml +++ b/extensions/spark/kyuubi-spark-connector-tpch/pom.xml @@ -21,7 +21,7 @@ org.apache.kyuubi kyuubi-parent - 1.7.0-SNAPSHOT + 1.8.0-SNAPSHOT ../../../pom.xml diff --git a/extensions/spark/kyuubi-spark-lineage/pom.xml b/extensions/spark/kyuubi-spark-lineage/pom.xml index 74c05299d..c8f9b30bd 100644 --- a/extensions/spark/kyuubi-spark-lineage/pom.xml +++ b/extensions/spark/kyuubi-spark-lineage/pom.xml @@ -21,7 +21,7 @@ org.apache.kyuubi kyuubi-parent - 1.7.0-SNAPSHOT + 1.8.0-SNAPSHOT ../../../pom.xml diff --git a/externals/kyuubi-download/pom.xml b/externals/kyuubi-download/pom.xml index b0479f7ed..ce9ba2014 100644 --- a/externals/kyuubi-download/pom.xml +++ b/externals/kyuubi-download/pom.xml @@ -21,7 +21,7 @@ org.apache.kyuubi kyuubi-parent - 1.7.0-SNAPSHOT + 1.8.0-SNAPSHOT ../../pom.xml diff --git a/externals/kyuubi-flink-sql-engine/pom.xml b/externals/kyuubi-flink-sql-engine/pom.xml index c93993607..4d7167c1c 100644 --- a/externals/kyuubi-flink-sql-engine/pom.xml +++ b/externals/kyuubi-flink-sql-engine/pom.xml @@ -21,7 +21,7 @@ org.apache.kyuubi kyuubi-parent - 1.7.0-SNAPSHOT + 1.8.0-SNAPSHOT ../../pom.xml diff --git a/externals/kyuubi-hive-sql-engine/pom.xml b/externals/kyuubi-hive-sql-engine/pom.xml index 1dbc31947..0319d3dd2 100644 --- a/externals/kyuubi-hive-sql-engine/pom.xml +++ b/externals/kyuubi-hive-sql-engine/pom.xml @@ -21,7 +21,7 @@ org.apache.kyuubi kyuubi-parent - 1.7.0-SNAPSHOT + 1.8.0-SNAPSHOT ../../pom.xml diff --git a/externals/kyuubi-jdbc-engine/pom.xml b/externals/kyuubi-jdbc-engine/pom.xml index 8853cec64..4bcc4fb60 100644 --- a/externals/kyuubi-jdbc-engine/pom.xml +++ b/externals/kyuubi-jdbc-engine/pom.xml @@ -21,7 +21,7 @@ org.apache.kyuubi kyuubi-parent - 1.7.0-SNAPSHOT + 1.8.0-SNAPSHOT ../../pom.xml diff --git a/externals/kyuubi-spark-sql-engine/pom.xml b/externals/kyuubi-spark-sql-engine/pom.xml index 3d3259c80..5b227cb5e 100644 --- a/externals/kyuubi-spark-sql-engine/pom.xml +++ b/externals/kyuubi-spark-sql-engine/pom.xml @@ -21,7 +21,7 @@ org.apache.kyuubi kyuubi-parent - 1.7.0-SNAPSHOT + 1.8.0-SNAPSHOT ../../pom.xml diff --git a/externals/kyuubi-trino-engine/pom.xml b/externals/kyuubi-trino-engine/pom.xml index 7e2f67370..7aea8f33a 100644 --- a/externals/kyuubi-trino-engine/pom.xml +++ b/externals/kyuubi-trino-engine/pom.xml @@ -21,7 +21,7 @@ org.apache.kyuubi kyuubi-parent - 1.7.0-SNAPSHOT + 1.8.0-SNAPSHOT ../../pom.xml diff --git a/integration-tests/kyuubi-flink-it/pom.xml b/integration-tests/kyuubi-flink-it/pom.xml index 7f9a84a85..8ccc14e5e 100644 --- a/integration-tests/kyuubi-flink-it/pom.xml +++ b/integration-tests/kyuubi-flink-it/pom.xml @@ -21,7 +21,7 @@ org.apache.kyuubi integration-tests - 1.7.0-SNAPSHOT + 1.8.0-SNAPSHOT ../pom.xml diff --git a/integration-tests/kyuubi-hive-it/pom.xml b/integration-tests/kyuubi-hive-it/pom.xml index 8b9813a2b..ff9a6b35e 100644 --- a/integration-tests/kyuubi-hive-it/pom.xml +++ b/integration-tests/kyuubi-hive-it/pom.xml @@ -21,7 +21,7 @@ org.apache.kyuubi integration-tests - 1.7.0-SNAPSHOT + 1.8.0-SNAPSHOT ../pom.xml diff --git a/integration-tests/kyuubi-jdbc-it/pom.xml b/integration-tests/kyuubi-jdbc-it/pom.xml index 0aef12fb3..2d95de78e 100644 --- a/integration-tests/kyuubi-jdbc-it/pom.xml +++ b/integration-tests/kyuubi-jdbc-it/pom.xml @@ -21,7 +21,7 @@ org.apache.kyuubi integration-tests - 1.7.0-SNAPSHOT + 1.8.0-SNAPSHOT ../pom.xml diff --git a/integration-tests/kyuubi-kubernetes-it/pom.xml b/integration-tests/kyuubi-kubernetes-it/pom.xml index cb04e73c1..ef56a770f 100644 --- a/integration-tests/kyuubi-kubernetes-it/pom.xml +++ b/integration-tests/kyuubi-kubernetes-it/pom.xml @@ -22,7 +22,7 @@ org.apache.kyuubi integration-tests - 1.7.0-SNAPSHOT + 1.8.0-SNAPSHOT ../pom.xml 4.0.0 diff --git a/integration-tests/kyuubi-trino-it/pom.xml b/integration-tests/kyuubi-trino-it/pom.xml index e62e58d1d..107d621b0 100644 --- a/integration-tests/kyuubi-trino-it/pom.xml +++ b/integration-tests/kyuubi-trino-it/pom.xml @@ -21,7 +21,7 @@ org.apache.kyuubi integration-tests - 1.7.0-SNAPSHOT + 1.8.0-SNAPSHOT ../pom.xml diff --git a/integration-tests/kyuubi-zookeeper-it/pom.xml b/integration-tests/kyuubi-zookeeper-it/pom.xml index eaeff5898..bded1585b 100644 --- a/integration-tests/kyuubi-zookeeper-it/pom.xml +++ b/integration-tests/kyuubi-zookeeper-it/pom.xml @@ -21,7 +21,7 @@ org.apache.kyuubi integration-tests - 1.7.0-SNAPSHOT + 1.8.0-SNAPSHOT ../pom.xml diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml index 4e3431afb..b6a48daae 100644 --- a/integration-tests/pom.xml +++ b/integration-tests/pom.xml @@ -21,7 +21,7 @@ org.apache.kyuubi kyuubi-parent - 1.7.0-SNAPSHOT + 1.8.0-SNAPSHOT integration-tests diff --git a/kyuubi-assembly/pom.xml b/kyuubi-assembly/pom.xml index 725126f84..0524470a2 100644 --- a/kyuubi-assembly/pom.xml +++ b/kyuubi-assembly/pom.xml @@ -22,7 +22,7 @@ org.apache.kyuubi kyuubi-parent - 1.7.0-SNAPSHOT + 1.8.0-SNAPSHOT ../pom.xml diff --git a/kyuubi-common/pom.xml b/kyuubi-common/pom.xml index 61ae4c0e7..d62761d72 100644 --- a/kyuubi-common/pom.xml +++ b/kyuubi-common/pom.xml @@ -21,7 +21,7 @@ org.apache.kyuubi kyuubi-parent - 1.7.0-SNAPSHOT + 1.8.0-SNAPSHOT ../pom.xml diff --git a/kyuubi-ctl/pom.xml b/kyuubi-ctl/pom.xml index aa1e8f2e4..eb4060ffd 100644 --- a/kyuubi-ctl/pom.xml +++ b/kyuubi-ctl/pom.xml @@ -21,7 +21,7 @@ org.apache.kyuubi kyuubi-parent - 1.7.0-SNAPSHOT + 1.8.0-SNAPSHOT ../pom.xml diff --git a/kyuubi-events/pom.xml b/kyuubi-events/pom.xml index a8030eb83..b97e9dffb 100644 --- a/kyuubi-events/pom.xml +++ b/kyuubi-events/pom.xml @@ -21,7 +21,7 @@ org.apache.kyuubi kyuubi-parent - 1.7.0-SNAPSHOT + 1.8.0-SNAPSHOT ../pom.xml diff --git a/kyuubi-ha/pom.xml b/kyuubi-ha/pom.xml index 8d7246eff..b4605b6a1 100644 --- a/kyuubi-ha/pom.xml +++ b/kyuubi-ha/pom.xml @@ -21,7 +21,7 @@ org.apache.kyuubi kyuubi-parent - 1.7.0-SNAPSHOT + 1.8.0-SNAPSHOT ../pom.xml diff --git a/kyuubi-hive-beeline/pom.xml b/kyuubi-hive-beeline/pom.xml index 76753b38d..151616243 100644 --- a/kyuubi-hive-beeline/pom.xml +++ b/kyuubi-hive-beeline/pom.xml @@ -21,7 +21,7 @@ org.apache.kyuubi kyuubi-parent - 1.7.0-SNAPSHOT + 1.8.0-SNAPSHOT kyuubi-hive-beeline diff --git a/kyuubi-hive-jdbc-shaded/pom.xml b/kyuubi-hive-jdbc-shaded/pom.xml index 0bfe88922..1a6f258b0 100644 --- a/kyuubi-hive-jdbc-shaded/pom.xml +++ b/kyuubi-hive-jdbc-shaded/pom.xml @@ -21,7 +21,7 @@ org.apache.kyuubi kyuubi-parent - 1.7.0-SNAPSHOT + 1.8.0-SNAPSHOT kyuubi-hive-jdbc-shaded diff --git a/kyuubi-hive-jdbc/pom.xml b/kyuubi-hive-jdbc/pom.xml index 4d9648e75..7b45c27bb 100644 --- a/kyuubi-hive-jdbc/pom.xml +++ b/kyuubi-hive-jdbc/pom.xml @@ -21,7 +21,7 @@ org.apache.kyuubi kyuubi-parent - 1.7.0-SNAPSHOT + 1.8.0-SNAPSHOT kyuubi-hive-jdbc diff --git a/kyuubi-metrics/pom.xml b/kyuubi-metrics/pom.xml index b8ba40f47..2edeb73c7 100644 --- a/kyuubi-metrics/pom.xml +++ b/kyuubi-metrics/pom.xml @@ -21,7 +21,7 @@ org.apache.kyuubi kyuubi-parent - 1.7.0-SNAPSHOT + 1.8.0-SNAPSHOT kyuubi-metrics_2.12 diff --git a/kyuubi-rest-client/pom.xml b/kyuubi-rest-client/pom.xml index 28b1670f1..0af949125 100644 --- a/kyuubi-rest-client/pom.xml +++ b/kyuubi-rest-client/pom.xml @@ -21,7 +21,7 @@ org.apache.kyuubi kyuubi-parent - 1.7.0-SNAPSHOT + 1.8.0-SNAPSHOT kyuubi-rest-client diff --git a/kyuubi-server/pom.xml b/kyuubi-server/pom.xml index 4dd89e0e6..f14d2baa3 100644 --- a/kyuubi-server/pom.xml +++ b/kyuubi-server/pom.xml @@ -21,7 +21,7 @@ org.apache.kyuubi kyuubi-parent - 1.7.0-SNAPSHOT + 1.8.0-SNAPSHOT kyuubi-server_2.12 diff --git a/kyuubi-zookeeper/pom.xml b/kyuubi-zookeeper/pom.xml index f97f4c0eb..cd3e017cd 100644 --- a/kyuubi-zookeeper/pom.xml +++ b/kyuubi-zookeeper/pom.xml @@ -21,7 +21,7 @@ org.apache.kyuubi kyuubi-parent - 1.7.0-SNAPSHOT + 1.8.0-SNAPSHOT kyuubi-zookeeper_2.12 diff --git a/pom.xml b/pom.xml index 9afc0cf1d..6aae60a8f 100644 --- a/pom.xml +++ b/pom.xml @@ -27,7 +27,7 @@ org.apache.kyuubi kyuubi-parent - 1.7.0-SNAPSHOT + 1.8.0-SNAPSHOT pom Kyuubi Project Parent diff --git a/tools/spark-block-cleaner/pom.xml b/tools/spark-block-cleaner/pom.xml index e42f1cd96..02e682c3a 100644 --- a/tools/spark-block-cleaner/pom.xml +++ b/tools/spark-block-cleaner/pom.xml @@ -21,7 +21,7 @@ org.apache.kyuubi kyuubi-parent - 1.7.0-SNAPSHOT + 1.8.0-SNAPSHOT ../../pom.xml From d862272645dab08878c8f99158977b622227e784 Mon Sep 17 00:00:00 2001 From: liangbowen Date: Fri, 10 Feb 2023 16:19:45 +0800 Subject: [PATCH 045/760] [KYUUBI #4274] [INFRA] Introduce `mvnd` to speed up CI jobs of Dependency, Licence and Style Check ### _Why are the changes needed?_ #### Motivation: - speed up maven building and testing with `mvnd` (https://github.com/apache/maven-mvnd). `mvnd` itself embeds exactly the same distribution of maven with more improvements and features on the client side and compilation server in daemon mode. - also inspired by custom github action for mvnd installing in Apache Camel (https://github.com/apache/camel/blob/main/.github/actions/install-mvnd/action.yml) #### Changes in this PR: - introducing `mvnd` by adding `build/mvnd` script - use `mvnd` version `0.9.0` which embeds `maven` 3.8.7. The maven version embedded in `mvnd` is also check and guaranteed the same as maven version required in pom by `build/mvnd` script. - use mvnd in CI jobs of Depedency Check and Style Check #### Comparision - CI jobs (both with maven dependencies cached in local repo) Job | with `build/mvn` | with `build/mvnd` --- | --- | --- Dependency Check | 9min1sec;[see log](https://github.com/apache/kyuubi/actions/runs/3966175262/jobs/6796688187) | 6min 46 sec ; [see log](https://github.com/apache/kyuubi/actions/runs/4124518481/jobs/7123867015) Style Check | 10min32sec ; [see log](https://github.com/apache/kyuubi/actions/runs/4122613274/jobs/7119603428) | 7min50s ; [see log](https://github.com/apache/kyuubi/actions/runs/4125013599/jobs/7125006946) Licence Check | 32s ; [see log](https://github.com/apache/kyuubi/actions/runs/4130799867/jobs/7137835062) | 21s ; [see log](https://github.com/apache/kyuubi/actions/runs/4130923126/jobs/7138088114) - building entire maven project on local machine (skipping test running and style checking with `fast` profile) `build/mvnd clean install -Pfast` Pasted Graphic `build/mvn clean install -Pfast` Pasted Graphic 1 ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [x] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4274 from bowenliang123/ci-mvnd. Closes #4274 380c1c9ea [liangbowen] update cache key a93bba7b7 [liangbowen] style bc8da133e [Bowen Liang] Merge branch 'master' into ci-mvnd 97becc035 [liangbowen] add `build/maven-mvnd-*/**` to rat-excludes 5b61f50b9 [liangbowen] apply mvnd to license check 7be181d0f [liangbowen] typo in build/mvnd c52861df8 [liangbowen] update f6d0eb28c [liangbowen] introduce mvnd Lead-authored-by: liangbowen Co-authored-by: Bowen Liang Signed-off-by: Cheng Pan --- .github/actions/setup-mvnd/action.yaml | 32 ++++++ .github/workflows/dep.yml | 5 +- .github/workflows/license.yml | 4 +- .github/workflows/style.yml | 16 +-- .gitignore | 1 + .rat-excludes | 1 + build/mvnd | 134 +++++++++++++++++++++++++ pom.xml | 1 + 8 files changed, 185 insertions(+), 9 deletions(-) create mode 100644 .github/actions/setup-mvnd/action.yaml create mode 100755 build/mvnd diff --git a/.github/actions/setup-mvnd/action.yaml b/.github/actions/setup-mvnd/action.yaml new file mode 100644 index 000000000..d7497e332 --- /dev/null +++ b/.github/actions/setup-mvnd/action.yaml @@ -0,0 +1,32 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +name: 'setup-mvnd' +description: 'Setup the maven daemon' +runs: + using: composite + steps: + - name: Cache Mvnd + uses: actions/cache@v3 + with: + path: | + build/maven-mvnd-* + build/apache-maven-* + key: setup-mvnd-${{ runner.os }}-mvnd + - name: Check Mvnd + run: build/mvnd -v + shell: bash diff --git a/.github/workflows/dep.yml b/.github/workflows/dep.yml index 16475ce0f..dc94e4ea7 100644 --- a/.github/workflows/dep.yml +++ b/.github/workflows/dep.yml @@ -26,6 +26,7 @@ on: # when pom or dependency workflow changes - '**/pom.xml' - '.github/workflows/dep.yml' + - .github/actions/setup-mvnd/*.yaml concurrency: group: dep-${{ github.head_ref || github.run_id }} @@ -44,11 +45,13 @@ jobs: java-version: 8 cache: 'maven' check-latest: false + - name: Setup Mvnd + uses: ./.github/actions/setup-mvnd - name: build env: MAVEN_OPTS: -Dorg.slf4j.simpleLogger.defaultLogLevel=error run: >- - build/mvn clean install + build/mvnd clean install -Pflink-provided,spark-provided,hive-provided -Dmaven.javadoc.skip=true -Drat.skip=true diff --git a/.github/workflows/license.yml b/.github/workflows/license.yml index 17591dacb..a490def91 100644 --- a/.github/workflows/license.yml +++ b/.github/workflows/license.yml @@ -42,8 +42,10 @@ jobs: java-version: 8 cache: 'maven' check-latest: false + - name: Setup Mvnd + uses: ./.github/actions/setup-mvnd - run: >- - build/mvn org.apache.rat:apache-rat-plugin:check + build/mvnd org.apache.rat:apache-rat-plugin:check -Ptpcds -Pspark-block-cleaner -Pkubernetes-it -Pspark-3.1 -Pspark-3.2 -Pspark-3.3 - name: Upload rat report diff --git a/.github/workflows/style.yml b/.github/workflows/style.yml index f9307e529..9ff484824 100644 --- a/.github/workflows/style.yml +++ b/.github/workflows/style.yml @@ -45,14 +45,16 @@ jobs: java-version: 8 cache: 'maven' check-latest: false + - name: Setup Mvnd + uses: ./.github/actions/setup-mvnd - name: Setup Python 3 uses: actions/setup-python@v4 with: python-version: '3.9' cache: 'pip' - - name: Check kyuubi modules avaliable + - name: Check kyuubi modules available id: modules-check - run: build/mvn dependency:resolve -DincludeGroupIds="org.apache.kyuubi" -DincludeScope="compile" -DexcludeTransitive=true ${{ matrix.profiles }} + run: build/mvnd dependency:resolve -DincludeGroupIds="org.apache.kyuubi" -DincludeScope="compile" -DexcludeTransitive=true ${{ matrix.profiles }} continue-on-error: true - name: Install @@ -61,13 +63,13 @@ jobs: if: steps.modules-check.conclusion == 'success' && steps.modules-check.outcome == 'failure' run: | MVN_OPT="-DskipTests -Dorg.slf4j.simpleLogger.defaultLogLevel=warn -Dmaven.javadoc.skip=true -Drat.skip=true -Dscalastyle.skip=true -Dspotless.check.skip" - build/mvn clean install ${MVN_OPT} -Pflink-provided,hive-provided,spark-provided,spark-block-cleaner,spark-3.2,tpcds - build/mvn clean install ${MVN_OPT} -pl extensions/spark/kyuubi-extension-spark-3-1 -Pspark-3.1 - build/mvn clean install ${MVN_OPT} -pl extensions/spark/kyuubi-extension-spark-3-3,extensions/spark/kyuubi-spark-connector-kudu,extensions/spark/kyuubi-spark-connector-hive -Pspark-3.3 + build/mvnd clean install ${MVN_OPT} -Pflink-provided,hive-provided,spark-provided,spark-block-cleaner,spark-3.2,tpcds + build/mvnd clean install ${MVN_OPT} -pl extensions/spark/kyuubi-extension-spark-3-1 -Pspark-3.1 + build/mvnd clean install ${MVN_OPT} -pl extensions/spark/kyuubi-extension-spark-3-3,extensions/spark/kyuubi-spark-connector-kudu,extensions/spark/kyuubi-spark-connector-hive -Pspark-3.3 - name: Scalastyle with maven id: scalastyle-check - run: build/mvn scalastyle:check ${{ matrix.profiles }} + run: build/mvnd scalastyle:check ${{ matrix.profiles }} - name: Print scalastyle error report if: failure() && steps.scalastyle-check.outcome != 'success' run: >- @@ -81,7 +83,7 @@ jobs: run: | SPOTLESS_BLACK_VERSION=$(build/mvn help:evaluate -Dexpression=spotless.python.black.version -q -DforceStdout) pip install black==$SPOTLESS_BLACK_VERSION - build/mvn spotless:check ${{ matrix.profiles }} -Pspotless-python + build/mvnd spotless:check ${{ matrix.profiles }} -Pspotless-python - name: setup npm uses: actions/setup-node@v3 with: diff --git a/.gitignore b/.gitignore index bbdd246c4..bb2df4fd0 100644 --- a/.gitignore +++ b/.gitignore @@ -40,6 +40,7 @@ .scala_dependencies .settings build/apache-maven* +build/maven-mvnd* build/release/tmp build/scala* build/test diff --git a/.rat-excludes b/.rat-excludes index 86c38ec99..2906e7cea 100644 --- a/.rat-excludes +++ b/.rat-excludes @@ -32,6 +32,7 @@ NOTICE* docs/** build/apache-maven-*/** +build/maven-mvnd-*/** build/scala-*/** **/**/operation_logs/**/** **/**/server_operation_logs/**/** diff --git a/build/mvnd b/build/mvnd new file mode 100755 index 000000000..493ee43ad --- /dev/null +++ b/build/mvnd @@ -0,0 +1,134 @@ +#!/usr/bin/env bash + +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# Determine the current working directory +_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# Preserve the calling directory +_CALLING_DIR="$(pwd)" +# Options used during compilation +_COMPILE_JVM_OPTS="-Xms2g -Xmx2g -XX:ReservedCodeCacheSize=1g -Xss128m" + +if [ "$CI" ]; then + export MAVEN_CLI_OPTS="--no-transfer-progress --errors --fail-fast" +fi + +# Installs any application tarball given a URL, the expected tarball name, +# and, optionally, a checkable binary path to determine if the binary has +# already been installed +## Arg1 - URL +## Arg2 - Tarball Name +## Arg3 - Checkable Binary +install_app() { + local remote_tarball="$1/$2" + local local_tarball="${_DIR}/$2" + local binary="${_DIR}/$3" + + # setup `curl` and `wget` silent options if we're running on Jenkins + local curl_opts="-L" + local wget_opts="" + curl_opts="--progress-bar ${curl_opts}" + wget_opts="--progress=bar:force ${wget_opts}" + + if [ -z "$3" ] || [ ! -f "$binary" ]; then + # check if we already have the tarball + # check if we have curl installed + # download application + rm -f "$local_tarball" + [ ! -f "${local_tarball}" ] && [ "$(command -v curl)" ] && \ + echo "exec: curl ${curl_opts} ${remote_tarball}" 1>&2 && \ + curl ${curl_opts} "${remote_tarball}" > "${local_tarball}" + # if the file still doesn't exist, lets try `wget` and cross our fingers + [ ! -f "${local_tarball}" ] && [ "$(command -v wget)" ] && \ + echo "exec: wget ${wget_opts} ${remote_tarball}" 1>&2 && \ + wget ${wget_opts} -O "${local_tarball}" "${remote_tarball}" + # if both were unsuccessful, exit + [ ! -f "${local_tarball}" ] && \ + echo -n "ERROR: Cannot download $2 with cURL or wget; " && \ + echo "please install manually and try again." && \ + exit 2 + cd "${_DIR}" && tar -xzf "$2" + rm -rf "$local_tarball" + fi +} + +function get_os_type() { + local unameOsOut=$(uname -s) + local osType + case "${unameOsOut}" in + Linux*) osType=linux ;; + Darwin*) osType=darwin ;; + CYGWIN*) osType=windows ;; + MINGW*) osType=windows ;; + *) osType="UNKNOWN:${unameOsOut}" ;; + esac + echo "$osType" +} + +function get_os_arch() { + local unameArchOut="$(uname -m)" + local arch + case "${unameArchOut}" in + x86_64*) arch=amd64 ;; + arm64*) arch=aarch64 ;; + *) arch="UNKNOWN:${unameOsOut}" ;; + esac + echo "$arch" +} + +# Determine the Mvnd version from the root pom.xml file and +# install mvnd under the build/ folder if needed. +function install_mvnd() { + local MVND_VERSION=$(grep "" "${_DIR}/../pom.xml" | head -n1 | awk -F '[<>]' '{print $3}') + local MVN_VERSION=$(grep "" "${_DIR}/../pom.xml" | head -n1 | awk -F '[<>]' '{print $3}') + MVND_BIN="$(command -v mvnd)" + if [ "$MVND_BIN" ]; then + local MVND_DETECTED_VERSION="$(mvnd -v 2>&1 | grep '(mvnd)' | awk '{print $5}')" + local MVN_DETECTED_VERSION="$(mvnd -v 2>&1 | grep 'Apache Maven' | awk 'NR==2 {print $3}')" + fi + # See simple version normalization: http://stackoverflow.com/questions/16989598/bash-comparing-version-numbers + function version { echo "$@" | awk -F. '{ printf("%03d%03d%03d\n", $1,$2,$3); }'; } + + if [ $(version $MVND_DETECTED_VERSION) -ne $(version $MVND_VERSION) ]; then + local APACHE_MIRROR=${APACHE_MIRROR:-'https://downloads.apache.org'} + local OS_TYPE=$(get_os_type) + local ARCH=$(get_os_arch) + + install_app \ + "${APACHE_MIRROR}/maven/mvnd/${MVND_VERSION}" \ + "maven-mvnd-${MVND_VERSION}-${OS_TYPE}-${ARCH}.tar.gz" \ + "maven-mvnd-${MVND_VERSION}-${OS_TYPE}-${ARCH}/bin/mvnd" + + MVND_BIN="${_DIR}/maven-mvnd-${MVND_VERSION}-${OS_TYPE}-${ARCH}/bin/mvnd" + else + if [ "$(version $MVN_DETECTED_VERSION)" -ne "$(version $MVN_VERSION)" ]; then + echo "Mvnd $MVND_DETECTED_VERSION embedded maven version $MVN_DETECTED_VERSION is not equivalent to $MVN_VERSION required in pom." + exit 1 + fi + fi +} + +install_mvnd + +cd "${_CALLING_DIR}" + +# Set any `mvn` options if not already present +export MAVEN_OPTS=${MAVEN_OPTS:-"$_COMPILE_JVM_OPTS"} + +echo "Using \`mvnd\` from path: $MVND_BIN" 1>&2 +${MVND_BIN} $MAVEN_CLI_OPTS "$@" diff --git a/pom.xml b/pom.xml index 6aae60a8f..b761ef158 100644 --- a/pom.xml +++ b/pom.xml @@ -109,6 +109,7 @@ 1.8 3.8.7 + 0.9.0 ${java.version} ${java.version} 2.12.17 From 7cd206642c962b233a86edfe17f0bb0c99e34c70 Mon Sep 17 00:00:00 2001 From: Cheng Pan Date: Fri, 10 Feb 2023 16:22:19 +0800 Subject: [PATCH 046/760] [KYUUBI #4302] Change log level from debug to info for creating work dir MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### _Why are the changes needed?_ The message is not noisy and is helpful when failing to create, e.g. no permission. ``` 2023-02-10 14:30:32.731 INFO org.apache.kyuubi.operation.LaunchEngine: Processing simba's query[d51a4def-d3d7-454b-a63d-28e27a1e3c3f]: RUNNING_STATE -> ERROR_STATE, time taken: 0.1 seconds Error: org.apache.kyuubi.KyuubiSQLException: Error operating LaunchEngine: java.io.IOException: 权限不够 at java.io.UnixFileSystem.createFileExclusively(Native Method) at java.io.File.createNewFile(File.java:1012) at org.apache.kyuubi.engine.ProcBuilder.$anonfun$engineLog$6(ProcBuilder.scala:187) at scala.Option.getOrElse(Option.scala:189) at org.apache.kyuubi.engine.ProcBuilder.engineLog(ProcBuilder.scala:184) at org.apache.kyuubi.engine.ProcBuilder.engineLog$(ProcBuilder.scala:159) at org.apache.kyuubi.engine.spark.SparkProcessBuilder.engineLog$lzycompute(SparkProcessBuilder.scala:36) at org.apache.kyuubi.engine.spark.SparkProcessBuilder.engineLog(SparkProcessBuilder.scala:36) at org.apache.kyuubi.engine.ProcBuilder.processBuilder(ProcBuilder.scala:141) at org.apache.kyuubi.engine.ProcBuilder.processBuilder$(ProcBuilder.scala:135) at org.apache.kyuubi.engine.spark.SparkProcessBuilder.processBuilder$lzycompute(SparkProcessBuilder.scala:36) at org.apache.kyuubi.engine.spark.SparkProcessBuilder.processBuilder(SparkProcessBuilder.scala:36) at org.apache.kyuubi.engine.ProcBuilder.start(ProcBuilder.scala:198) at org.apache.kyuubi.engine.ProcBuilder.start$(ProcBuilder.scala:197) at org.apache.kyuubi.engine.spark.SparkProcessBuilder.start(SparkProcessBuilder.scala:36) at org.apache.kyuubi.engine.EngineRef.$anonfun$create$1(EngineRef.scala:194) at org.apache.kyuubi.ha.client.zookeeper.ZookeeperDiscoveryClient.tryWithLock(ZookeeperDiscoveryClient.scala:174) at org.apache.kyuubi.engine.EngineRef.tryWithLock(EngineRef.scala:160) at org.apache.kyuubi.engine.EngineRef.create(EngineRef.scala:165) at org.apache.kyuubi.engine.EngineRef.$anonfun$getOrCreate$1(EngineRef.scala:260) at scala.Option.getOrElse(Option.scala:189) at org.apache.kyuubi.engine.EngineRef.getOrCreate(EngineRef.scala:260) at org.apache.kyuubi.session.KyuubiSessionImpl.$anonfun$openEngineSession$1(KyuubiSessionImpl.scala:120) at org.apache.kyuubi.session.KyuubiSessionImpl.$anonfun$openEngineSession$1$adapted(KyuubiSessionImpl.scala:113) at org.apache.kyuubi.ha.client.DiscoveryClientProvider$.withDiscoveryClient(DiscoveryClientProvider.scala:36) at org.apache.kyuubi.session.KyuubiSessionImpl.openEngineSession(KyuubiSessionImpl.scala:113) at org.apache.kyuubi.operation.LaunchEngine.$anonfun$runInternal$2(LaunchEngine.scala:49) at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) at java.util.concurrent.FutureTask.run(FutureTask.java:266) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at java.lang.Thread.run(Thread.java:748) at org.apache.kyuubi.KyuubiSQLException$.apply(KyuubiSQLException.scala:69) at org.apache.kyuubi.operation.KyuubiOperation$$anonfun$onError$1.applyOrElse(KyuubiOperation.scala:75) at org.apache.kyuubi.operation.KyuubiOperation$$anonfun$onError$1.applyOrElse(KyuubiOperation.scala:56) at scala.runtime.AbstractPartialFunction.apply(AbstractPartialFunction.scala:38) at org.apache.kyuubi.operation.LaunchEngine.$anonfun$runInternal$2(LaunchEngine.scala:51) at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) at java.util.concurrent.FutureTask.run(FutureTask.java:266) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at java.lang.Thread.run(Thread.java:748) Caused by: java.io.IOException: 权限不够 at java.io.UnixFileSystem.createFileExclusively(Native Method) at java.io.File.createNewFile(File.java:1012) at org.apache.kyuubi.engine.ProcBuilder.$anonfun$engineLog$6(ProcBuilder.scala:187) at scala.Option.getOrElse(Option.scala:189) at org.apache.kyuubi.engine.ProcBuilder.engineLog(ProcBuilder.scala:184) at org.apache.kyuubi.engine.ProcBuilder.engineLog$(ProcBuilder.scala:159) at org.apache.kyuubi.engine.spark.SparkProcessBuilder.engineLog$lzycompute(SparkProcessBuilder.scala:36) at org.apache.kyuubi.engine.spark.SparkProcessBuilder.engineLog(SparkProcessBuilder.scala:36) at org.apache.kyuubi.engine.ProcBuilder.processBuilder(ProcBuilder.scala:141) at org.apache.kyuubi.engine.ProcBuilder.processBuilder$(ProcBuilder.scala:135) at org.apache.kyuubi.engine.spark.SparkProcessBuilder.processBuilder$lzycompute(SparkProcessBuilder.scala:36) at org.apache.kyuubi.engine.spark.SparkProcessBuilder.processBuilder(SparkProcessBuilder.scala:36) at org.apache.kyuubi.engine.ProcBuilder.start(ProcBuilder.scala:198) at org.apache.kyuubi.engine.ProcBuilder.start$(ProcBuilder.scala:197) at org.apache.kyuubi.engine.spark.SparkProcessBuilder.start(SparkProcessBuilder.scala:36) at org.apache.kyuubi.engine.EngineRef.$anonfun$create$1(EngineRef.scala:194) at org.apache.kyuubi.ha.client.zookeeper.ZookeeperDiscoveryClient.tryWithLock(ZookeeperDiscoveryClient.scala:174) at org.apache.kyuubi.engine.EngineRef.tryWithLock(EngineRef.scala:160) at org.apache.kyuubi.engine.EngineRef.create(EngineRef.scala:165) at org.apache.kyuubi.engine.EngineRef.$anonfun$getOrCreate$1(EngineRef.scala:260) at scala.Option.getOrElse(Option.scala:189) at org.apache.kyuubi.engine.EngineRef.getOrCreate(EngineRef.scala:260) at org.apache.kyuubi.session.KyuubiSessionImpl.$anonfun$openEngineSession$1(KyuubiSessionImpl.scala:120) at org.apache.kyuubi.session.KyuubiSessionImpl.$anonfun$openEngineSession$1$adapted(KyuubiSessionImpl.scala:113) at org.apache.kyuubi.ha.client.DiscoveryClientProvider$.withDiscoveryClient(DiscoveryClientProvider.scala:36) at org.apache.kyuubi.session.KyuubiSessionImpl.openEngineSession(KyuubiSessionImpl.scala:113) at org.apache.kyuubi.operation.LaunchEngine.$anonfun$runInternal$2(LaunchEngine.scala:49) ... 5 more (state=,code=0) ``` ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [ ] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4302 from pan3793/info. Closes #4302 24c8ab39d [Cheng Pan] Change log level from debug to info for creating work dir Authored-by: Cheng Pan Signed-off-by: Cheng Pan --- .../src/main/scala/org/apache/kyuubi/engine/ProcBuilder.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/engine/ProcBuilder.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/engine/ProcBuilder.scala index a119d971d..4c7330b4d 100644 --- a/kyuubi-server/src/main/scala/org/apache/kyuubi/engine/ProcBuilder.scala +++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/engine/ProcBuilder.scala @@ -118,7 +118,7 @@ trait ProcBuilder { env.get("KYUUBI_WORK_DIR_ROOT").map { root => val workingRoot = Paths.get(root).toAbsolutePath if (!Files.exists(workingRoot)) { - debug(s"Creating KYUUBI_WORK_DIR_ROOT at $workingRoot") + info(s"Creating KYUUBI_WORK_DIR_ROOT at $workingRoot") Files.createDirectories(workingRoot) } if (Files.isDirectory(workingRoot)) { @@ -127,7 +127,7 @@ trait ProcBuilder { }.map { rootAbs => val working = Paths.get(rootAbs, proxyUser) if (!Files.exists(working)) { - debug(s"Creating $proxyUser's working directory at $working") + info(s"Creating $proxyUser's working directory at $working") Files.createDirectories(working) } if (Files.isDirectory(working)) { From e545df50487592e15f348c50c4a0e2416fa66511 Mon Sep 17 00:00:00 2001 From: runzhliu Date: Sat, 11 Feb 2023 08:54:07 +0800 Subject: [PATCH 047/760] [KYUUBI #4299] Bump JUnit to 4.13.2 ### _Why are the changes needed?_ bump JUnit4 from 4.13.1 to 4.13.2 release note: https://github.com/junit-team/junit4/blob/HEAD/doc/ReleaseNotes4.13.2.md nothing new after running `build/dependency.sh --replace` ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [ ] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4310 from runzhliu/patch-6. Closes #4299 8857d254 [runzhliu] Merge branch 'apache:master' into patch-6 c2893632 [runzhliu] Bump Junit4 from 4.13.1 to 4.13.2 Authored-by: runzhliu Signed-off-by: liangbowen --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index b761ef158..c7a0fdb3a 100644 --- a/pom.xml +++ b/pom.xml @@ -166,7 +166,7 @@ 2.38 9.4.50.v20221201 0.9.94 - 4.13.1 + 4.13.2 5.12.1 1.15.0 6.0.5 From de15dbe28a99fe29e77cf8d7e2bac5d8f8e90eb1 Mon Sep 17 00:00:00 2001 From: runzhliu Date: Sat, 11 Feb 2023 09:07:05 +0800 Subject: [PATCH 048/760] [KYUUBI #4299] Bump MySQL JDBC driver to 8.0.32 ### _Why are the changes needed?_ bump Mysql jdbc driver to 8.0.32 release: https://github.com/mysql/mysql-connector-j/releases/tag/8.0.32 Mysql jdbc driver used in test scope only nothing new after running build/dependency.sh --replace ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [ ] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4309 from runzhliu/patch-7. Closes #4299 a9e43e91 [runzhliu] Merge branch 'apache:master' into patch-7 53ce3a37 [runzhliu] Merge branch 'apache:master' into patch-7 d3f02cd6 [runzhliu] Bump Mysql jdbc driver from 8.0.31 to 8.0.32 Authored-by: runzhliu Signed-off-by: liangbowen --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index c7a0fdb3a..a15b4b590 100644 --- a/pom.xml +++ b/pom.xml @@ -171,7 +171,7 @@ 1.15.0 6.0.5 2.19.0 - 8.0.31 + 8.0.32 4.1.87.Final 1.10.1 6.0.0 From 7f86611b316a0bac3c336c549673aabd22f9c2c4 Mon Sep 17 00:00:00 2001 From: dnskr Date: Sun, 12 Feb 2023 18:42:36 +0800 Subject: [PATCH 049/760] [KYUUBI #4313] [K8S][HELM] Comment license in helm chart NOTES ### _Why are the changes needed?_ The changes are needed to hide license text printed after chart installed. Before changes: ``` $ helm install kyuubi ${KYUUBI_HOME}/charts/kyuubi -n kyuubi --create-namespace NAME: kyuubi LAST DEPLOYED: Sat Feb 11 20:35:52 2023 NAMESPACE: kyuubi STATUS: deployed REVISION: 1 TEST SUITE: None NOTES: # # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # The chart has been installed! In order to check the release status, use: helm status kyuubi -n kyuubi or for more detailed info helm get all kyuubi -n kyuubi ************************ ******* Services ******* ************************ THRIFT_BINARY: - To access kyuubi-thrift-binary service within the cluster, use the following URL: kyuubi-thrift-binary.kyuubi.svc.cluster.local - To access kyuubi-thrift-binary service from outside the cluster for debugging, run the following command: kubectl port-forward svc/kyuubi-thrift-binary 10009:10009 -n kyuubi and use 127.0.0.1:10009 ``` After changes: ``` $ helm install kyuubi ${KYUUBI_HOME}/charts/kyuubi -n kyuubi --create-namespace NAME: kyuubi LAST DEPLOYED: Sat Feb 11 20:37:45 2023 NAMESPACE: kyuubi STATUS: deployed REVISION: 1 TEST SUITE: None NOTES: The chart has been installed! In order to check the release status, use: helm status kyuubi -n kyuubi or for more detailed info helm get all kyuubi -n kyuubi ************************ ******* Services ******* ************************ THRIFT_BINARY: - To access kyuubi-thrift-binary service within the cluster, use the following URL: kyuubi-thrift-binary.kyuubi.svc.cluster.local - To access kyuubi-thrift-binary service from outside the cluster for debugging, run the following command: kubectl port-forward svc/kyuubi-thrift-binary 10009:10009 -n kyuubi and use 127.0.0.1:10009 ``` ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [ ] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4313 from dnskr/comment_license_in_helm_chart_notes. Closes #4313 7cc76639b [dnskr] [K8S][HELM] Comment license in helm chart NOTES Authored-by: dnskr Signed-off-by: Cheng Pan --- charts/kyuubi/templates/NOTES.txt | 32 +++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/charts/kyuubi/templates/NOTES.txt b/charts/kyuubi/templates/NOTES.txt index be29b8048..0da72d0eb 100644 --- a/charts/kyuubi/templates/NOTES.txt +++ b/charts/kyuubi/templates/NOTES.txt @@ -1,19 +1,19 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. -# The ASF licenses this file to You under the Apache License, Version 2.0 -# (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# +{{/* + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/}} The chart has been installed! From b6ebd5e8c52438ca5987a10020dd0729ed257a30 Mon Sep 17 00:00:00 2001 From: hongkunyoo Date: Mon, 13 Feb 2023 00:37:51 +0800 Subject: [PATCH 050/760] [KYUUBI #4307] Small missing `kubectl` Maybe `kubectl` command is missing? Closes #4307 from hongkunyoo/patch-1. Closes #4307 549357af7 [hongkunyoo] Small missing `kubectl` Authored-by: hongkunyoo Signed-off-by: Cheng Pan --- docs/quick_start/quick_start_with_helm.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/quick_start/quick_start_with_helm.md b/docs/quick_start/quick_start_with_helm.md index 99c787841..be3adb75c 100644 --- a/docs/quick_start/quick_start_with_helm.md +++ b/docs/quick_start/quick_start_with_helm.md @@ -30,7 +30,7 @@ Please go to [Install Helm](https://helm.sh/docs/intro/install/) page to get and #### [Optional] Create namespace on kubernetes ```bash -create ns kyuubi +kubectl create ns kyuubi ``` #### Get kyuubi started From 68b70ca1d2a969c6c1664bb5e51e986fe5d474dc Mon Sep 17 00:00:00 2001 From: dnskr Date: Mon, 13 Feb 2023 00:40:52 +0800 Subject: [PATCH 051/760] [KYUUBI #4314] [DOCS][HELM] Refine helm chart docs ### _Why are the changes needed?_ The changes are needed to make doc page clearer and a bit nicer, and reflect latest changes in the chart. ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [ ] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request This patch had conflicts when merged, resolved by Committer: Cheng Pan Closes #4314 from dnskr/update_helm_chart_doc_page. Closes #4314 d7c18fd53 [dnskr] [DOCS][HELM] Refine helm chart docs Authored-by: dnskr Signed-off-by: Cheng Pan --- docs/quick_start/quick_start_with_helm.md | 131 +++++++++++----------- 1 file changed, 66 insertions(+), 65 deletions(-) diff --git a/docs/quick_start/quick_start_with_helm.md b/docs/quick_start/quick_start_with_helm.md index be3adb75c..a2de54445 100644 --- a/docs/quick_start/quick_start_with_helm.md +++ b/docs/quick_start/quick_start_with_helm.md @@ -15,105 +15,106 @@ - limitations under the License. --> -# Getting Started With Kyuubi on kubernetes +# Getting Started With Kyuubi on Kubernetes -## Running kyuubi with helm +## Running Kyuubi with Helm -[Helm](https://helm.sh/) is the package manager for Kubernetes,it can be used to find, share, and use software built for Kubernetes. +[Helm](https://helm.sh/) is the package manager for Kubernetes, it can be used to find, share, and use software built for Kubernetes. -### Get helm and Install +### Install Helm -Please go to [Install Helm](https://helm.sh/docs/intro/install/) page to get and install an appropriate release version for yourself. +Please go to [Installing Helm](https://helm.sh/docs/intro/install/) page to get and install an appropriate release version for yourself. ### Get Kyuubi Started -#### [Optional] Create namespace on kubernetes +#### Install the chart -```bash -kubectl create ns kyuubi +```shell +helm install kyuubi ${KYUUBI_HOME}/charts/kyuubi -n kyuubi --create-namespace ``` -#### Get kyuubi started +It will print release info with notes, including the ways to get Kyuubi accessed within Kubernetes cluster and exposed externally depending on the configuration provided. -```bash -helm install kyuubi-helm ${KYUUBI_HOME}/charts/kyuubi -n ${namespace_name} -``` - -It will print variables and the way to get kyuubi expose ip and port. - -```bash -NAME: kyuubi-helm -LAST DEPLOYED: Wed Oct 20 15:22:47 2021 +```shell +NAME: kyuubi +LAST DEPLOYED: Sat Feb 11 20:59:00 2023 NAMESPACE: kyuubi STATUS: deployed REVISION: 1 TEST SUITE: None NOTES: -Get kyuubi expose URL by running these commands: - export NODE_PORT=$(kubectl get --namespace kyuubi -o jsonpath="{.spec.ports[0].nodePort}" services kyuubi-svc) - export NODE_IP=$(kubectl get nodes --namespace kyuubi -o jsonpath="{.items[0].status.addresses[0].address}") - echo $NODE_IP:$NODE_PORT +The chart has been installed! + +In order to check the release status, use: + helm status kyuubi -n kyuubi + or for more detailed info + helm get all kyuubi -n kyuubi + +************************ +******* Services ******* +************************ +THRIFT_BINARY: +- To access kyuubi-thrift-binary service within the cluster, use the following URL: + kyuubi-thrift-binary.kyuubi.svc.cluster.local +- To access kyuubi-thrift-binary service from outside the cluster for debugging, run the following command: + kubectl port-forward svc/kyuubi-thrift-binary 10009:10009 -n kyuubi + and use 127.0.0.1:10009 ``` -#### Using hive beeline +#### Uninstall the chart -[Using Hive Beeline](./quick_start.html#using-hive-beeline) to opening a connection. +```shell +helm uninstall kyuubi -n kyuubi +``` -#### Remove kyuubi +#### Configure chart release -```bash -helm uninstall kyuubi-helm -n ${namespace_name} -``` +Specify configuration properties using `--set` flag. +For example, to install the chart with `replicaCount` set to `1`, use the following command: -#### Edit server config +```shell +helm install kyuubi ${KYUUBI_HOME}/charts/kyuubi -n kyuubi --create-namespace --set replicaCount=1 +``` -Modify `values.yaml` under `${KYUUBI_HOME}/docker/helm`: +Also, custom values file can be used to override default property values. For example, create `myvalues.yaml` to specify `replicaCount` and `resources`: ```yaml -# Kyuubi server numbers -replicaCount: 2 - -image: - repository: apache/kyuubi - pullPolicy: Always - # Overrides the image tag whose default is the chart appVersion. - tag: "master-snapshot" - -server: - bind: - host: 0.0.0.0 - port: 10009 - conf: - mountPath: /opt/kyuubi/conf - -service: - type: NodePort - # The default port limit of kubernetes is 30000-32767 - # to change: - # vim kube-apiserver.yaml (usually under path: /etc/kubernetes/manifests/) - # add or change line 'service-node-port-range=1-32767' under kube-apiserver - port: 30009 +replicaCount: 1 + +resources: + requests: + cpu: 2 + memory: 4Gi + limits: + cpu: 4 + memory: 10Gi +``` + +and use it to override default chart values with `-f` flag: + +```shell +helm install kyuubi ${KYUUBI_HOME}/charts/kyuubi -n kyuubi --create-namespace -f myvalues.yaml ``` -#### Get server log +#### Access logs -List all server pods: +List all pods in the release namespace: -```bash -kubectl get po -n ${namespace_name} +```shell +kubectl get pod -n kyuubi ``` -The server pods will print: +Find Kyuubi pods: -```text -NAME READY STATUS RESTARTS AGE -kyuubi-server-585d8944c5-m7j5s 1/1 Running 0 30m -kyuubi-server-32sdsa1245-2d2sj 1/1 Running 0 30m +```shell +NAME READY STATUS RESTARTS AGE +kyuubi-5b6d496c98-kbhws 1/1 Running 0 38m +kyuubi-5b6d496c98-lqldk 1/1 Running 0 38m ``` -then, use pod name to get logs: +Then, use pod name to get logs: -```bash -kubectl -n ${namespace_name} logs kyuubi-server-585d8944c5-m7j5s +```shell +kubectl logs kyuubi-5b6d496c98-kbhws -n kyuubi ``` From 3b0137ae7840458cfed10e65de9a6841d8e26197 Mon Sep 17 00:00:00 2001 From: liangbowen Date: Mon, 13 Feb 2023 13:15:18 +0800 Subject: [PATCH 052/760] [KYUUBI #4308] [DOCS] Make README more welcoming MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### _Why are the changes needed?_   - Make Readme more welcoming to readers, in - Elegancy: clear, center aligned Kyuubi logo in front - Maturity: always good to see numbers of contributors, lines and PRs - Guidance: quick starting point to project homepage and documentation for next steps - Sociability: showing star number of the project Preview link: https://github.com/bowenliang123/incubator-kyuubi/blob/readme-welcome/README.md Before: image After: image ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [x] Add screenshots for manual tests if appropriate - [ ] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4308 from bowenliang123/readme-welcome. Closes #4308 35fafffb [Bowen Liang] add links for badges 55721d17 [liangbowen] remove licence badge and add total lines badge 42971575 [Bowen Liang] Update README.md 5909b1d6 [liangbowen] make Readme more welcoming Lead-authored-by: liangbowen Co-authored-by: Bowen Liang Signed-off-by: liangbowen --- README.md | 48 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 32 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 16fc794ee..0b46abf85 100644 --- a/README.md +++ b/README.md @@ -15,27 +15,43 @@ - limitations under the License. --> -# Apache Kyuubi - -Kyuubi logo +

    + Kyuubi logo +

    + +

    + + + + + + + + + + + + + + + +

    +

    + Project + - + Documentation + - + Who's using +

    -[![License](https://img.shields.io/badge/license-Apache%202-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0.html) -[![Release](https://img.shields.io/github/v/release/apache/kyuubi?label=release)](https://github.com/apache/kyuubi/releases) -[![](https://tokei.rs/b1/github.com/apache/kyuubi)](https://github.com/apache/kyuubi) -[![codecov](https://codecov.io/gh/apache/kyuubi/branch/master/graph/badge.svg)](https://codecov.io/gh/apache/kyuubi) -![GitHub Workflow Status](https://img.shields.io/github/workflow/status/apache/kyuubi/Kyuubi/master?style=plastic) -[![Documentation Status](https://readthedocs.org/projects/kyuubi/badge/?version=latest)](https://kyuubi.readthedocs.io/en/master/) -![GitHub top language](https://img.shields.io/github/languages/top/apache/kyuubi) -[![Commit activity](https://img.shields.io/github/commit-activity/m/apache/kyuubi)](https://github.com/apache/kyuubi/graphs/commit-activity) -[![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/apache/kyuubi.svg)](http://isitmaintained.com/project/apache/kyuubi "Average time to resolve an issue") -[![Percentage of issues still open](http://isitmaintained.com/badge/open/apache/kyuubi.svg)](http://isitmaintained.com/project/apache/kyuubi "Percentage of issues still open") +# Apache Kyuubi +Apache Kyuubi™ is a distributed and multi-tenant gateway to provide serverless +SQL on data warehouses and lakehouses. + ## What is Kyuubi? -Apache Kyuubi™ is a distributed and multi-tenant gateway to provide serverless -SQL on data warehouses and lakehouses. - Kyuubi provides a pure SQL gateway through Thrift JDBC/ODBC interface for end-users to manipulate large-scale data with pre-programmed and extensible Spark SQL engines. This "out-of-the-box" model minimizes the barriers and costs for end-users to use Spark at the client side. At the server-side, Kyuubi server and engines' multi-tenant architecture provides the administrators a way to achieve computing resource isolation, data security, high availability, high client concurrency, etc. ![](./docs/imgs/kyuubi_positioning.png) From 41f08059f081c14bd88f9f733935370c9896b20a Mon Sep 17 00:00:00 2001 From: odone Date: Mon, 13 Feb 2023 19:28:14 +0800 Subject: [PATCH 053/760] [KYUUBI #3935] Support use Trino client to submit SQL ### _Why are the changes needed?_ Close #3935 ### _How was this patch tested?_ - [x] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [x] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4232 from iodone/kyuubi-3935. Closes #3935 936ea1f8 [odone] address e7bd01a1 [odone] support trino client connect kyuubi trino server 9ea8b6af [odone] [WIP] trion request/response implementation Authored-by: odone Signed-off-by: ulyssesyou --- dev/dependencyList | 2 +- .../service/AbstractBackendService.scala | 7 +- .../kyuubi/service/BackendService.scala | 4 +- kyuubi-server/pom.xml | 10 + .../kyuubi/server/BackendServiceMetric.scala | 6 +- .../api/KyuubiTrinoOperationTranslator.scala | 12 +- .../kyuubi/server/trino/api/Query.scala | 206 ++++++++++++++++++ .../server/trino/api/TrinoContext.scala | 17 +- ...per.scala => TrinoScalaObjectMapper.scala} | 11 +- .../server/trino/api/TrinoServerConfig.scala | 2 +- .../trino/api/v1/StatementResource.scala | 145 ++++++++++-- .../kyuubi/RestFrontendTestHelper.scala | 4 +- .../apache/kyuubi/TrinoClientTestHelper.scala | 80 ------- .../kyuubi/TrinoRestFrontendTestHelper.scala | 40 ++++ .../trino/api/TrinoClientApiSuite.scala | 145 ++++++++++++ .../server/trino/api/TrinoContextSuite.scala | 4 +- .../trino/api/v1/StatementResourceSuite.scala | 94 +++++++- pom.xml | 6 + 18 files changed, 661 insertions(+), 134 deletions(-) create mode 100644 kyuubi-server/src/main/scala/org/apache/kyuubi/server/trino/api/Query.scala rename kyuubi-server/src/main/scala/org/apache/kyuubi/server/trino/api/{KyuubiScalaObjectMapper.scala => TrinoScalaObjectMapper.scala} (73%) delete mode 100644 kyuubi-server/src/test/scala/org/apache/kyuubi/TrinoClientTestHelper.scala create mode 100644 kyuubi-server/src/test/scala/org/apache/kyuubi/TrinoRestFrontendTestHelper.scala create mode 100644 kyuubi-server/src/test/scala/org/apache/kyuubi/server/trino/api/TrinoClientApiSuite.scala diff --git a/dev/dependencyList b/dev/dependencyList index 2035b95de..9813bb562 100644 --- a/dev/dependencyList +++ b/dev/dependencyList @@ -69,7 +69,7 @@ jackson-annotations/2.14.2//jackson-annotations-2.14.2.jar jackson-core/2.14.2//jackson-core-2.14.2.jar jackson-databind/2.14.2//jackson-databind-2.14.2.jar jackson-dataformat-yaml/2.14.2//jackson-dataformat-yaml-2.14.2.jar -jackson-datatype-jdk8/2.12.3//jackson-datatype-jdk8-2.12.3.jar +jackson-datatype-jdk8/2.14.2//jackson-datatype-jdk8-2.14.2.jar jackson-datatype-jsr310/2.14.2//jackson-datatype-jsr310-2.14.2.jar jackson-jaxrs-base/2.14.2//jackson-jaxrs-base-2.14.2.jar jackson-jaxrs-json-provider/2.14.2//jackson-jaxrs-json-provider-2.14.2.jar diff --git a/kyuubi-common/src/main/scala/org/apache/kyuubi/service/AbstractBackendService.scala b/kyuubi-common/src/main/scala/org/apache/kyuubi/service/AbstractBackendService.scala index e7c2d8365..b9e254508 100644 --- a/kyuubi-common/src/main/scala/org/apache/kyuubi/service/AbstractBackendService.scala +++ b/kyuubi-common/src/main/scala/org/apache/kyuubi/service/AbstractBackendService.scala @@ -156,11 +156,14 @@ abstract class AbstractBackendService(name: String) queryId } - override def getOperationStatus(operationHandle: OperationHandle): OperationStatus = { + override def getOperationStatus( + operationHandle: OperationHandle, + maxWait: Option[Long]): OperationStatus = { val operation = sessionManager.operationManager.getOperation(operationHandle) if (operation.shouldRunAsync) { try { - operation.getBackgroundHandle.get(timeout, TimeUnit.MILLISECONDS) + val waitTime = maxWait.getOrElse(timeout) + operation.getBackgroundHandle.get(waitTime, TimeUnit.MILLISECONDS) } catch { case e: TimeoutException => debug(s"$operationHandle: Long polling timed out, ${e.getMessage}") diff --git a/kyuubi-common/src/main/scala/org/apache/kyuubi/service/BackendService.scala b/kyuubi-common/src/main/scala/org/apache/kyuubi/service/BackendService.scala index e18411566..968a94197 100644 --- a/kyuubi-common/src/main/scala/org/apache/kyuubi/service/BackendService.scala +++ b/kyuubi-common/src/main/scala/org/apache/kyuubi/service/BackendService.scala @@ -91,7 +91,9 @@ trait BackendService { foreignTable: String): OperationHandle def getQueryId(operationHandle: OperationHandle): String - def getOperationStatus(operationHandle: OperationHandle): OperationStatus + def getOperationStatus( + operationHandle: OperationHandle, + maxWait: Option[Long] = None): OperationStatus def cancelOperation(operationHandle: OperationHandle): Unit def closeOperation(operationHandle: OperationHandle): Unit def getResultSetMetadata(operationHandle: OperationHandle): TGetResultSetMetadataResp diff --git a/kyuubi-server/pom.xml b/kyuubi-server/pom.xml index f14d2baa3..53b8a9601 100644 --- a/kyuubi-server/pom.xml +++ b/kyuubi-server/pom.xml @@ -221,6 +221,16 @@ jersey-media-multipart + + com.fasterxml.jackson.core + jackson-databind + + + + com.fasterxml.jackson.datatype + jackson-datatype-jdk8 + + com.zaxxer HikariCP diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/BackendServiceMetric.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/BackendServiceMetric.scala index d8b664163..68bf11d7f 100644 --- a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/BackendServiceMetric.scala +++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/BackendServiceMetric.scala @@ -152,9 +152,11 @@ trait BackendServiceMetric extends BackendService { } } - abstract override def getOperationStatus(operationHandle: OperationHandle): OperationStatus = { + abstract override def getOperationStatus( + operationHandle: OperationHandle, + maxWait: Option[Long] = None): OperationStatus = { MetricsSystem.timerTracing(MetricsConstants.BS_GET_OPERATION_STATUS) { - super.getOperationStatus(operationHandle) + super.getOperationStatus(operationHandle, maxWait) } } diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/trino/api/KyuubiTrinoOperationTranslator.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/trino/api/KyuubiTrinoOperationTranslator.scala index 6ec9fc1c8..5eba9c327 100644 --- a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/trino/api/KyuubiTrinoOperationTranslator.scala +++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/trino/api/KyuubiTrinoOperationTranslator.scala @@ -19,10 +19,9 @@ package org.apache.kyuubi.server.trino.api import scala.collection.JavaConverters._ -import org.apache.hive.service.rpc.thrift.TProtocolVersion - import org.apache.kyuubi.operation.OperationHandle import org.apache.kyuubi.service.BackendService +import org.apache.kyuubi.session.SessionHandle import org.apache.kyuubi.sql.parser.trino.KyuubiTrinoFeParser import org.apache.kyuubi.sql.plan.PassThroughNode import org.apache.kyuubi.sql.plan.trino.{GetCatalogs, GetColumns, GetSchemas, GetTables, GetTableTypes, GetTypeInfo} @@ -32,17 +31,10 @@ class KyuubiTrinoOperationTranslator(backendService: BackendService) { def transform( statement: String, - user: String, - ipAddress: String, + sessionHandle: SessionHandle, configs: Map[String, String], runAsync: Boolean, queryTimeout: Long): OperationHandle = { - val sessionHandle = backendService.openSession( - TProtocolVersion.HIVE_CLI_SERVICE_PROTOCOL_V11, - user, - "", - ipAddress, - configs) parser.parsePlan(statement) match { case GetSchemas(catalogName, schemaPattern) => backendService.getSchemas(sessionHandle, catalogName, schemaPattern) diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/trino/api/Query.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/trino/api/Query.scala new file mode 100644 index 000000000..c8c9e2fd6 --- /dev/null +++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/trino/api/Query.scala @@ -0,0 +1,206 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.kyuubi.server.trino.api + +import java.net.URI +import java.security.SecureRandom +import java.util.Objects.requireNonNull +import java.util.UUID +import java.util.concurrent.atomic.AtomicLong +import javax.ws.rs.WebApplicationException +import javax.ws.rs.core.{Response, UriInfo} + +import Slug.Context.{EXECUTING_QUERY, QUEUED_QUERY} +import com.google.common.hash.Hashing +import io.trino.client.QueryResults +import org.apache.hive.service.rpc.thrift.TProtocolVersion + +import org.apache.kyuubi.operation.{FetchOrientation, OperationHandle} +import org.apache.kyuubi.operation.OperationState.{FINISHED, INITIALIZED, OperationState, PENDING} +import org.apache.kyuubi.service.BackendService +import org.apache.kyuubi.session.SessionHandle + +case class Query( + queryId: QueryId, + context: TrinoContext, + be: BackendService) { + + private val QUEUED_QUERY_PATH = "/v1/statement/queued/" + private val EXECUTING_QUERY_PATH = "/v1/statement/executing" + + private val slug: Slug = Slug.createNewWithUUID(queryId.getQueryId) + private val lastToken = new AtomicLong + + private val defaultMaxRows = 1000 + private val defaultFetchOrientation = FetchOrientation.withName("FETCH_NEXT") + + def getQueryResults(token: Long, uriInfo: UriInfo, maxWait: Long = 0): QueryResults = { + val status = + be.getOperationStatus(queryId.operationHandle, Some(maxWait)) + val nextUri = if (status.exception.isEmpty) { + getNextUri(token + 1, uriInfo, toSlugContext(status.state)) + } else null + val queryHtmlUri = uriInfo.getRequestUriBuilder + .replacePath("ui/query.html").replaceQuery(queryId.getQueryId).build() + + status.state match { + case FINISHED => + val metaData = be.getResultSetMetadata(queryId.operationHandle) + val resultSet = be.fetchResults( + queryId.operationHandle, + defaultFetchOrientation, + defaultMaxRows, + false) + TrinoContext.createQueryResults( + queryId.getQueryId, + nextUri, + queryHtmlUri, + status, + Option(metaData), + Option(resultSet)) + case _ => + TrinoContext.createQueryResults( + queryId.getQueryId, + nextUri, + queryHtmlUri, + status) + } + } + + def getLastToken: Long = this.lastToken.get() + + def getSlug: Slug = this.slug + + def cancel: Unit = clear + + private def clear = { + be.closeOperation(queryId.operationHandle) + context.session.get("sessionId").foreach { id => + be.closeSession(SessionHandle.fromUUID(id)) + } + } + + private def setToken(token: Long): Unit = { + val lastToken = this.lastToken.get + if (token != lastToken && token != lastToken + 1) { + throw new WebApplicationException(Response.Status.GONE) + } + this.lastToken.compareAndSet(lastToken, token) + } + + private def getNextUri(token: Long, uriInfo: UriInfo, slugContext: Slug.Context.Context): URI = { + val path = slugContext match { + case QUEUED_QUERY => QUEUED_QUERY_PATH + case EXECUTING_QUERY => EXECUTING_QUERY_PATH + } + + uriInfo.getBaseUriBuilder.replacePath(path) + .path(queryId.getQueryId) + .path(slug.makeSlug(slugContext, token)) + .path(String.valueOf(token)) + .replaceQuery("") + .build() + } + + private def toSlugContext(state: OperationState): Slug.Context.Context = { + state match { + case INITIALIZED | PENDING => Slug.Context.QUEUED_QUERY + case _ => Slug.Context.EXECUTING_QUERY + } + } + +} + +object Query { + + def apply( + statement: String, + context: TrinoContext, + translator: KyuubiTrinoOperationTranslator, + backendService: BackendService, + queryTimeout: Long = 0): Query = { + + val sessionHandle = createSession(context, backendService) + val operationHandle = translator.transform( + statement, + sessionHandle, + context.session, + true, + queryTimeout) + val newSessionProperties = + context.session + ("sessionId" -> sessionHandle.identifier.toString) + val updatedContext = context.copy(session = newSessionProperties) + Query(QueryId(operationHandle), updatedContext, backendService) + } + + def apply(id: String, context: TrinoContext, backendService: BackendService): Query = { + Query(QueryId(id), context, backendService) + } + + private def createSession( + context: TrinoContext, + backendService: BackendService): SessionHandle = { + backendService.openSession( + TProtocolVersion.HIVE_CLI_SERVICE_PROTOCOL_V11, + context.user, + "", + context.remoteUserAddress.getOrElse(""), + context.session) + } + +} + +case class QueryId(operationHandle: OperationHandle) { + def getQueryId: String = operationHandle.identifier.toString +} + +object QueryId { + def apply(id: String): QueryId = QueryId(OperationHandle(id)) +} + +object Slug { + + object Context extends Enumeration { + type Context = Value + val QUEUED_QUERY, EXECUTING_QUERY = Value + } + + private val RANDOM = new SecureRandom + + def createNew: Slug = { + val randomBytes = new Array[Byte](16) + RANDOM.nextBytes(randomBytes) + new Slug(randomBytes) + } + + def createNewWithUUID(uuid: String): Slug = { + val uuidBytes = UUID.fromString(uuid).toString.getBytes("UTF-8") + new Slug(uuidBytes) + } +} + +case class Slug(slugKey: Array[Byte]) { + val hmac = Hashing.hmacSha1(requireNonNull(slugKey, "slugKey is null")) + + def makeSlug(context: Slug.Context.Context, token: Long): String = { + "y" + hmac.newHasher.putInt(context.id).putLong(token).hash.toString + } + + def isValid(context: Slug.Context.Context, slug: String, token: Long): Boolean = + makeSlug(context, token) == slug +} diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/trino/api/TrinoContext.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/trino/api/TrinoContext.scala index 4a0736ddb..9e7713904 100644 --- a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/trino/api/TrinoContext.scala +++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/trino/api/TrinoContext.scala @@ -28,6 +28,7 @@ import io.trino.client.{ClientStandardTypes, ClientTypeSignature, Column, QueryE import io.trino.client.ProtocolHeaders.TRINO_HEADERS import org.apache.hive.service.rpc.thrift.{TGetResultSetMetadataResp, TRowSet, TTypeId} +import org.apache.kyuubi.operation.OperationState.FINISHED import org.apache.kyuubi.operation.OperationStatus /** @@ -58,6 +59,7 @@ case class TrinoContext( source: Option[String] = None, catalog: Option[String] = None, schema: Option[String] = None, + remoteUserAddress: Option[String] = None, language: Option[String] = None, traceToken: Option[String] = None, clientInfo: Option[String] = None, @@ -72,10 +74,11 @@ object TrinoContext { private val GENERIC_INTERNAL_ERROR_NAME = "GENERIC_INTERNAL_ERROR_NAME" private val GENERIC_INTERNAL_ERROR_TYPE = "INTERNAL_ERROR" - def apply(headers: HttpHeaders): TrinoContext = { - apply(headers.getRequestHeaders.asScala.toMap.map { + def apply(headers: HttpHeaders, remoteAddress: Option[String]): TrinoContext = { + val context = apply(headers.getRequestHeaders.asScala.toMap.map { case (k, v) => (k, v.asScala.toList) }) + context.copy(remoteUserAddress = remoteAddress) } def apply(headers: Map[String, List[String]]): TrinoContext = { @@ -134,7 +137,6 @@ object TrinoContext { } } - // TODO: Building response with TrinoContext and other information def buildTrinoResponse(qr: QueryResults, trinoContext: TrinoContext): Response = { val responseBuilder = Response.ok(qr) @@ -156,8 +158,6 @@ object TrinoContext { responseBuilder.header(TRINO_HEADERS.responseDeallocatedPrepare, urlEncode(v)) } - responseBuilder.header(TRINO_HEADERS.responseClearSession, s"responseClearSession") - responseBuilder.header(TRINO_HEADERS.responseClearTransactionId, "false") responseBuilder.build() } @@ -192,11 +192,16 @@ object TrinoContext { case None => null } + val updatedNextUri = queryStatus.state match { + case FINISHED if rowList == null || rowList.isEmpty || rowList.get(0).isEmpty => null + case _ => nextUri + } + new QueryResults( queryId, queryHtmlUri, nextUri, - nextUri, + updatedNextUri, columnList, rowList, StatementStats.builder.setState(queryStatus.state.name()).setQueued(false) diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/trino/api/KyuubiScalaObjectMapper.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/trino/api/TrinoScalaObjectMapper.scala similarity index 73% rename from kyuubi-server/src/main/scala/org/apache/kyuubi/server/trino/api/KyuubiScalaObjectMapper.scala rename to kyuubi-server/src/main/scala/org/apache/kyuubi/server/trino/api/TrinoScalaObjectMapper.scala index 915b109b7..f6055927a 100644 --- a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/trino/api/KyuubiScalaObjectMapper.scala +++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/trino/api/TrinoScalaObjectMapper.scala @@ -19,11 +19,14 @@ package org.apache.kyuubi.server.trino.api import javax.ws.rs.ext.ContextResolver -import com.fasterxml.jackson.databind.ObjectMapper -import com.fasterxml.jackson.module.scala.DefaultScalaModule +import com.fasterxml.jackson.databind.{DeserializationFeature, ObjectMapper} +import com.fasterxml.jackson.datatype.jdk8.Jdk8Module -class KyuubiScalaObjectMapper extends ContextResolver[ObjectMapper] { - private val mapper = new ObjectMapper().registerModule(DefaultScalaModule) +class TrinoScalaObjectMapper extends ContextResolver[ObjectMapper] { + + private lazy val mapper = new ObjectMapper() + .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) + .registerModule(new Jdk8Module) override def getContext(aClass: Class[_]): ObjectMapper = mapper } diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/trino/api/TrinoServerConfig.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/trino/api/TrinoServerConfig.scala index d1f7de336..298e60c9c 100644 --- a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/trino/api/TrinoServerConfig.scala +++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/trino/api/TrinoServerConfig.scala @@ -21,6 +21,6 @@ import org.glassfish.jersey.server.ResourceConfig class TrinoServerConfig extends ResourceConfig { packages("org.apache.kyuubi.server.trino.api.v1") - register(classOf[KyuubiScalaObjectMapper]) + register(classOf[TrinoScalaObjectMapper]) register(classOf[RestExceptionMapper]) } diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/trino/api/v1/StatementResource.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/trino/api/v1/StatementResource.scala index 122b39ccd..ab783f8ac 100644 --- a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/trino/api/v1/StatementResource.scala +++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/trino/api/v1/StatementResource.scala @@ -18,16 +18,24 @@ package org.apache.kyuubi.server.trino.api.v1 import javax.ws.rs._ -import javax.ws.rs.core.{Context, HttpHeaders, MediaType} +import javax.ws.rs.core.{Context, HttpHeaders, MediaType, Response, UriInfo} +import javax.ws.rs.core.MediaType.TEXT_PLAIN_TYPE +import javax.ws.rs.core.Response.Status.{BAD_REQUEST, NOT_FOUND} +import scala.util.Try +import scala.util.control.NonFatal + +import io.airlift.units.Duration import io.swagger.v3.oas.annotations.media.{Content, Schema} import io.swagger.v3.oas.annotations.responses.ApiResponse import io.swagger.v3.oas.annotations.tags.Tag import io.trino.client.QueryResults import org.apache.kyuubi.Logging -import org.apache.kyuubi.server.trino.api.{ApiRequestContext, KyuubiTrinoOperationTranslator} +import org.apache.kyuubi.server.trino.api.{ApiRequestContext, KyuubiTrinoOperationTranslator, Query, QueryId, Slug, TrinoContext} +import org.apache.kyuubi.server.trino.api.Slug.Context.{EXECUTING_QUERY, QUEUED_QUERY} import org.apache.kyuubi.server.trino.api.v1.dto.Ok +import org.apache.kyuubi.service.BackendService @Tag(name = "Statement") @Produces(Array(MediaType.APPLICATION_JSON)) @@ -50,11 +58,32 @@ private[v1] class StatementResource extends ApiRequestContext with Logging { schema = new Schema(implementation = classOf[QueryResults]))), description = "Create a query") - @GET + @POST @Path("/") @Consumes(Array(MediaType.TEXT_PLAIN)) - def query(statement: String, @Context headers: HttpHeaders): QueryResults = { - throw new UnsupportedOperationException + def query( + statement: String, + @Context headers: HttpHeaders, + @Context uriInfo: UriInfo): Response = { + if (statement == null || statement.isEmpty) { + throw badRequest(BAD_REQUEST, "SQL statement is empty") + } + + val remoteAddr = Option(httpRequest.getRemoteAddr) + val trinoContext = TrinoContext(headers, remoteAddr) + + try { + val query = Query(statement, trinoContext, translator, fe.be) + val qr = query.getQueryResults(query.getLastToken, uriInfo) + TrinoContext.buildTrinoResponse(qr, query.context) + } catch { + case e: Exception => + val errorMsg = + s"Error submitting sql" + e.printStackTrace() + error(errorMsg, e) + throw badRequest(BAD_REQUEST, errorMsg) + } } @ApiResponse( @@ -65,11 +94,31 @@ private[v1] class StatementResource extends ApiRequestContext with Logging { @GET @Path("/queued/{queryId}/{slug}/{token}") def getQueuedStatementStatus( - @Context headers: HttpHeaders, @PathParam("queryId") queryId: String, @PathParam("slug") slug: String, - @PathParam("token") token: Long): QueryResults = { - throw new UnsupportedOperationException + @PathParam("token") token: Long, + @QueryParam("maxWait") maxWait: Duration, + @Context headers: HttpHeaders, + @Context uriInfo: UriInfo): Response = { + + val remoteAddr = Option(httpRequest.getRemoteAddr) + val trinoContext = TrinoContext(headers, remoteAddr) + val waitTime = if (maxWait == null) 0 else maxWait.toMillis + getQuery(fe.be, trinoContext, QueryId(queryId), slug, token, QUEUED_QUERY) + .flatMap(query => + Try(TrinoContext.buildTrinoResponse( + query.getQueryResults( + token, + uriInfo, + waitTime), + query.context))) + .recover { + case NonFatal(e) => + val errorMsg = + s"Error executing for query id $queryId" + error(errorMsg, e) + throw badRequest(NOT_FOUND, "Query not found") + }.get } @ApiResponse( @@ -80,11 +129,28 @@ private[v1] class StatementResource extends ApiRequestContext with Logging { @GET @Path("/executing/{queryId}/{slug}/{token}") def getExecutingStatementStatus( - @Context headers: HttpHeaders, @PathParam("queryId") queryId: String, @PathParam("slug") slug: String, - @PathParam("token") token: Long): QueryResults = { - throw new UnsupportedOperationException + @PathParam("token") token: Long, + @QueryParam("maxWait") maxWait: Duration, + @Context headers: HttpHeaders, + @Context uriInfo: UriInfo): Response = { + + val remoteAddr = Option(httpRequest.getRemoteAddr) + val trinoContext = TrinoContext(headers, remoteAddr) + val waitTime = if (maxWait == null) 0 else maxWait.toMillis + getQuery(fe.be, trinoContext, QueryId(queryId), slug, token, EXECUTING_QUERY) + .flatMap(query => + Try(TrinoContext.buildTrinoResponse( + query.getQueryResults(token, uriInfo, waitTime), + query.context))) + .recover { + case NonFatal(e) => + val errorMsg = + s"Error executing for query id $queryId" + error(errorMsg, e) + throw badRequest(NOT_FOUND, "Query not found") + }.get } @ApiResponse( @@ -95,11 +161,23 @@ private[v1] class StatementResource extends ApiRequestContext with Logging { @DELETE @Path("/queued/{queryId}/{slug}/{token}") def cancelQueuedStatement( - @Context headers: HttpHeaders, @PathParam("queryId") queryId: String, @PathParam("slug") slug: String, - @PathParam("token") token: Long): QueryResults = { - throw new UnsupportedOperationException + @PathParam("token") token: Long, + @Context headers: HttpHeaders): Response = { + + val remoteAddr = Option(httpRequest.getRemoteAddr) + val trinoContext = TrinoContext(headers, remoteAddr) + getQuery(fe.be, trinoContext, QueryId(queryId), slug, token, QUEUED_QUERY) + .flatMap(query => Try(query.cancel)) + .recover { + case NonFatal(e) => + val errorMsg = + s"Error executing for query id $queryId" + error(errorMsg, e) + throw badRequest(NOT_FOUND, "Query not found") + }.get + Response.noContent.build } @ApiResponse( @@ -110,11 +188,44 @@ private[v1] class StatementResource extends ApiRequestContext with Logging { @DELETE @Path("/executing/{queryId}/{slug}/{token}") def cancelExecutingStatementStatus( - @Context headers: HttpHeaders, @PathParam("queryId") queryId: String, @PathParam("slug") slug: String, - @PathParam("token") token: Long): QueryResults = { - throw new UnsupportedOperationException + @PathParam("token") token: Long, + @Context headers: HttpHeaders): Response = { + + val remoteAddr = Option(httpRequest.getRemoteAddr) + val trinoContext = TrinoContext(headers, remoteAddr) + getQuery(fe.be, trinoContext, QueryId(queryId), slug, token, EXECUTING_QUERY) + .flatMap(query => Try(query.cancel)) + .recover { + case NonFatal(e) => + val errorMsg = + s"Error executing for query id $queryId" + error(errorMsg, e) + throw badRequest(NOT_FOUND, "Query not found") + }.get + + Response.noContent.build } + private def getQuery( + be: BackendService, + context: TrinoContext, + queryId: QueryId, + slug: String, + token: Long, + slugContext: Slug.Context.Context): Try[Query] = { + + Try(be.sessionManager.operationManager.getOperation(queryId.operationHandle)).map { _ => + Query(queryId, context, be) + }.filter(_.getSlug.isValid(slugContext, slug, token)) + } + + private def badRequest(status: Response.Status, message: String) = + new WebApplicationException( + Response.status(status) + .`type`(TEXT_PLAIN_TYPE) + .entity(message) + .build) + } diff --git a/kyuubi-server/src/test/scala/org/apache/kyuubi/RestFrontendTestHelper.scala b/kyuubi-server/src/test/scala/org/apache/kyuubi/RestFrontendTestHelper.scala index b22783771..fafdcf4a7 100644 --- a/kyuubi-server/src/test/scala/org/apache/kyuubi/RestFrontendTestHelper.scala +++ b/kyuubi-server/src/test/scala/org/apache/kyuubi/RestFrontendTestHelper.scala @@ -37,7 +37,7 @@ import org.apache.kyuubi.service.AbstractFrontendService object RestFrontendTestHelper { - private class RestApiBaseSuite extends JerseyTest { + class RestApiBaseSuite extends JerseyTest { override def configure: Application = new ResourceConfig(getClass) .register(classOf[MultiPartFeature]) @@ -58,7 +58,7 @@ trait RestFrontendTestHelper extends WithKyuubiServer { override protected val frontendProtocols: Seq[FrontendProtocol] = FrontendProtocols.REST :: Nil - private val restApiBaseSuite = new RestApiBaseSuite + protected val restApiBaseSuite: JerseyTest = new RestApiBaseSuite override def beforeAll(): Unit = { super.beforeAll() diff --git a/kyuubi-server/src/test/scala/org/apache/kyuubi/TrinoClientTestHelper.scala b/kyuubi-server/src/test/scala/org/apache/kyuubi/TrinoClientTestHelper.scala deleted file mode 100644 index c0b3949f4..000000000 --- a/kyuubi-server/src/test/scala/org/apache/kyuubi/TrinoClientTestHelper.scala +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.kyuubi - -import java.net.URI -import java.time.ZoneId -import java.util.{Locale, Optional} -import java.util.concurrent.TimeUnit - -import scala.collection.JavaConverters._ - -import io.airlift.units.Duration -import io.trino.client.{ClientSelectedRole, ClientSession, StatementClient, StatementClientFactory} -import okhttp3.OkHttpClient - -trait TrinoClientTestHelper extends RestFrontendTestHelper { - - override def afterAll(): Unit = { - super.afterAll() - } - - private val httpClient = new OkHttpClient.Builder().build() - - protected val clientSession = createClientSession(baseUri: URI) - - def getTrinoStatementClient(sql: String): StatementClient = { - StatementClientFactory.newStatementClient(httpClient, clientSession, sql) - } - - def createClientSession(connectUrl: URI): ClientSession = { - new ClientSession( - connectUrl, - "kyuubi_test", - Optional.of("test_user"), - "kyuubi", - Optional.of("test_token_tracing"), - Set[String]().asJava, - "test_client_info", - "test_catalog", - "test_schema", - "test_path", - ZoneId.systemDefault(), - Locale.getDefault, - Map[String, String]( - "test_resource_key0" -> "test_resource_value0", - "test_resource_key1" -> "test_resource_value1").asJava, - Map[String, String]( - "test_property_key0" -> "test_property_value0", - "test_property_key1" -> "test_propert_value1").asJava, - Map[String, String]( - "test_statement_key0" -> "select 1", - "test_statement_key1" -> "select 2").asJava, - Map[String, ClientSelectedRole]( - "test_role_key0" -> ClientSelectedRole.valueOf("ROLE"), - "test_role_key2" -> ClientSelectedRole.valueOf("ALL")).asJava, - Map[String, String]( - "test_credentials_key0" -> "test_credentials_value0", - "test_credentials_key1" -> "test_credentials_value1").asJava, - "test_transaction_id", - new Duration(2, TimeUnit.MINUTES), - true) - - } - -} diff --git a/kyuubi-server/src/test/scala/org/apache/kyuubi/TrinoRestFrontendTestHelper.scala b/kyuubi-server/src/test/scala/org/apache/kyuubi/TrinoRestFrontendTestHelper.scala new file mode 100644 index 000000000..1ff00e64f --- /dev/null +++ b/kyuubi-server/src/test/scala/org/apache/kyuubi/TrinoRestFrontendTestHelper.scala @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.kyuubi + +import org.glassfish.jersey.client.ClientConfig +import org.glassfish.jersey.test.JerseyTest + +import org.apache.kyuubi.config.KyuubiConf.FrontendProtocols +import org.apache.kyuubi.config.KyuubiConf.FrontendProtocols.FrontendProtocol +import org.apache.kyuubi.server.trino.api.TrinoScalaObjectMapper + +trait TrinoRestFrontendTestHelper extends RestFrontendTestHelper { + + private class TrinoRestBaseSuite extends RestFrontendTestHelper.RestApiBaseSuite { + override def configureClient(config: ClientConfig): Unit = { + config.register(classOf[TrinoScalaObjectMapper]) + } + } + + override protected val frontendProtocols: Seq[FrontendProtocol] = + FrontendProtocols.TRINO :: Nil + + override protected val restApiBaseSuite: JerseyTest = new TrinoRestBaseSuite + +} diff --git a/kyuubi-server/src/test/scala/org/apache/kyuubi/server/trino/api/TrinoClientApiSuite.scala b/kyuubi-server/src/test/scala/org/apache/kyuubi/server/trino/api/TrinoClientApiSuite.scala new file mode 100644 index 000000000..c88b5c940 --- /dev/null +++ b/kyuubi-server/src/test/scala/org/apache/kyuubi/server/trino/api/TrinoClientApiSuite.scala @@ -0,0 +1,145 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.kyuubi.server.trino.api + +import java.net.URI +import java.time.ZoneId +import java.util.{Collections, Locale, Optional} +import java.util.concurrent.TimeUnit +import java.util.concurrent.atomic.AtomicReference + +import scala.annotation.tailrec +import scala.collection.JavaConverters._ + +import com.google.common.base.Verify +import io.airlift.units.Duration +import io.trino.client.{ClientSession, StatementClient, StatementClientFactory} +import okhttp3.OkHttpClient + +import org.apache.kyuubi.{KyuubiFunSuite, KyuubiSQLException, TrinoRestFrontendTestHelper} + +class TrinoClientApiSuite extends KyuubiFunSuite with TrinoRestFrontendTestHelper { + + private val httpClient = + new OkHttpClient.Builder() + .readTimeout(5, TimeUnit.MINUTES) + .build() + private lazy val clientSession = + new AtomicReference[ClientSession](createTestClientSession(baseUri)) + + test("submit query with trino client api") { + val trino = getTrinoStatementClient("select 1") + val result = execute(trino) + val sessionId = trino.getSetSessionProperties.asScala.get("sessionId") + assert(result == List(List(1))) + + updateClientSession(trino) + + val trino1 = getTrinoStatementClient("select 2") + val result1 = execute(trino1) + val sessionId1 = trino1.getSetSessionProperties.asScala.get("sessionId") + assert(result1 == List(List(2))) + assert(sessionId != sessionId1) + + trino.close() + } + + private def updateClientSession(trino: StatementClient): Unit = { + val session = clientSession.get + + var builder = ClientSession.builder(session) + // update catalog and schema + if (trino.getSetCatalog.isPresent || trino.getSetSchema.isPresent) { + builder = builder + .withCatalog(trino.getSetCatalog.orElse(session.getCatalog)) + .withSchema(trino.getSetSchema.orElse(session.getSchema)) + } + + // update path if present + if (trino.getSetPath.isPresent) { + builder = builder.withPath(trino.getSetPath.get) + } + + // update session properties if present + if (!trino.getSetSessionProperties.isEmpty || !trino.getResetSessionProperties.isEmpty) { + val properties = session.getProperties.asScala.clone() + properties ++= trino.getSetSessionProperties.asScala + properties --= trino.getResetSessionProperties.asScala + builder = builder.withProperties(properties.asJava) + } + clientSession.set(builder.build()) + } + + private def execute(trino: StatementClient): List[List[Any]] = { + @tailrec + def getData(trino: StatementClient): (Boolean, List[List[Any]]) = { + if (trino.isRunning) { + val data = trino.currentData().getData() + trino.advance() + if (data != null) { + (true, data.asScala.toList.map(_.asScala.toList)) + } else { + getData(trino) + } + } else { + Verify.verify(trino.isFinished) + val finalStatus = trino.finalStatusInfo() + if (finalStatus.getError() != null) { + throw KyuubiSQLException( + s"Query ${finalStatus.getId} failed: ${finalStatus.getError.getMessage}") + } + (false, List[List[Any]]()) + } + } + Iterator.continually(getData(trino)).takeWhile(_._1).flatMap(_._2).toList + } + + private def getTrinoStatementClient(sql: String): StatementClient = { + StatementClientFactory.newStatementClient(httpClient, clientSession.get, sql) + } + + private def createTestClientSession(connectUrl: URI): ClientSession = { + new ClientSession( + connectUrl, + "kyuubi_test", + Optional.of("test_user"), + "kyuubi", + Optional.of("test_token_tracing"), + Set[String]().asJava, + "test_client_info", + "test_catalog", + "test_schema", + null, + ZoneId.systemDefault(), + Locale.getDefault, + Collections.emptyMap(), + Map[String, String]( + "test_property_key0" -> "test_property_value0", + "test_property_key1" -> "test_propert_value1").asJava, + Map[String, String]( + "test_statement_key0" -> "select 1", + "test_statement_key1" -> "select 2").asJava, + Collections.emptyMap(), + Collections.emptyMap(), + null, + new Duration(2, TimeUnit.MINUTES), + true) + + } + +} diff --git a/kyuubi-server/src/test/scala/org/apache/kyuubi/server/trino/api/TrinoContextSuite.scala b/kyuubi-server/src/test/scala/org/apache/kyuubi/server/trino/api/TrinoContextSuite.scala index 8d7b2bf2c..87c8eda96 100644 --- a/kyuubi-server/src/test/scala/org/apache/kyuubi/server/trino/api/TrinoContextSuite.scala +++ b/kyuubi-server/src/test/scala/org/apache/kyuubi/server/trino/api/TrinoContextSuite.scala @@ -85,7 +85,7 @@ class TrinoContextSuite extends KyuubiFunSuite with RestFrontendTestHelper { val metadataResp = fe.be.getResultSetMetadata(opHandle) val tRowSet = fe.be.fetchResults(opHandle, FetchOrientation.FETCH_NEXT, 1000, false) - val status = fe.be.getOperationStatus(opHandle) + val status = fe.be.getOperationStatus(opHandle, Some(0)) val uri = new URI("sfdsfsdfdsf") val results = TrinoContext @@ -112,7 +112,7 @@ class TrinoContextSuite extends KyuubiFunSuite with RestFrontendTestHelper { val metadataResp = fe.be.getResultSetMetadata(opHandle) val tRowSet = fe.be.fetchResults(opHandle, FetchOrientation.FETCH_NEXT, 1000, false) - val status = fe.be.getOperationStatus(opHandle) + val status = fe.be.getOperationStatus(opHandle, Some(0)) val uri = new URI("sfdsfsdfdsf") val results = TrinoContext diff --git a/kyuubi-server/src/test/scala/org/apache/kyuubi/server/trino/api/v1/StatementResourceSuite.scala b/kyuubi-server/src/test/scala/org/apache/kyuubi/server/trino/api/v1/StatementResourceSuite.scala index b60c7c67a..adbf389c9 100644 --- a/kyuubi-server/src/test/scala/org/apache/kyuubi/server/trino/api/v1/StatementResourceSuite.scala +++ b/kyuubi-server/src/test/scala/org/apache/kyuubi/server/trino/api/v1/StatementResourceSuite.scala @@ -17,15 +17,27 @@ package org.apache.kyuubi.server.trino.api.v1 -import org.apache.kyuubi.{KyuubiFunSuite, RestFrontendTestHelper} -import org.apache.kyuubi.config.KyuubiConf.FrontendProtocols -import org.apache.kyuubi.config.KyuubiConf.FrontendProtocols.FrontendProtocol +import javax.ws.rs.client.Entity +import javax.ws.rs.core.{MediaType, Response} + +import scala.collection.JavaConverters._ + +import io.trino.client.{QueryError, QueryResults} +import io.trino.client.ProtocolHeaders.TRINO_HEADERS + +import org.apache.kyuubi.{KyuubiFunSuite, KyuubiSQLException, TrinoRestFrontendTestHelper} +import org.apache.kyuubi.operation.{OperationHandle, OperationState} +import org.apache.kyuubi.server.trino.api.TrinoContext import org.apache.kyuubi.server.trino.api.v1.dto.Ok +import org.apache.kyuubi.session.SessionHandle -class StatementResourceSuite extends KyuubiFunSuite with RestFrontendTestHelper { +class StatementResourceSuite extends KyuubiFunSuite with TrinoRestFrontendTestHelper { - override protected val frontendProtocols: Seq[FrontendProtocol] = - FrontendProtocols.TRINO :: Nil + case class TrinoResponse( + response: Option[Response] = None, + queryError: Option[QueryError] = None, + data: List[List[Any]] = List[List[Any]](), + isEnd: Boolean = false) test("statement test") { val response = webTarget.path("v1/statement/test").request().get() @@ -33,4 +45,74 @@ class StatementResourceSuite extends KyuubiFunSuite with RestFrontendTestHelper assert(result == new Ok("trino server is running")) } + test("statement submit for query error") { + + val response = webTarget.path("v1/statement") + .request().post(Entity.entity("select a", MediaType.TEXT_PLAIN_TYPE)) + + val trinoResponseIter = Iterator.iterate(TrinoResponse(response = Option(response)))(getData) + val isErr = trinoResponseIter.takeWhile(_.isEnd == false).exists { t => + t.queryError != None && t.response == None + } + assert(isErr == true) + } + + test("statement submit and get result") { + val response = webTarget.path("v1/statement") + .request().post(Entity.entity("select 1", MediaType.TEXT_PLAIN_TYPE)) + + val trinoResponseIter = Iterator.iterate(TrinoResponse(response = Option(response)))(getData) + val dataSet = trinoResponseIter + .takeWhile(_.isEnd == false) + .map(_.data) + .flatten.toList + assert(dataSet == List(List(1))) + } + + test("query cancel") { + val response = webTarget.path("v1/statement") + .request().post(Entity.entity("select 1", MediaType.TEXT_PLAIN_TYPE)) + val qr = response.readEntity(classOf[QueryResults]) + val sessionManager = fe.be.sessionManager + val sessionHandle = + response.getStringHeaders.get(TRINO_HEADERS.responseSetSession).asScala + .map(_.split("=")) + .find { + case Array("sessionId", _) => true + } + .map { + case Array(_, value) => SessionHandle.fromUUID(TrinoContext.urlDecode(value)) + }.get + sessionManager.getSession(sessionHandle) + val operationHandle = OperationHandle(qr.getId) + val operation = sessionManager.operationManager.getOperation(operationHandle) + assert(response.getStatus == 200) + val path = qr.getNextUri.getPath + val nextResponse = webTarget.path(path).request().header( + TRINO_HEADERS.requestSession(), + s"sessionId=${TrinoContext.urlEncode(sessionHandle.identifier.toString)}").delete() + assert(nextResponse.getStatus == 204) + assert(operation.getStatus.state == OperationState.CLOSED) + val exception = intercept[KyuubiSQLException](sessionManager.getSession(sessionHandle)) + assert(exception.getMessage === s"Invalid $sessionHandle") + + } + + private def getData(current: TrinoResponse): TrinoResponse = { + current.response.map { response => + assert(response.getStatus == 200) + val qr = response.readEntity(classOf[QueryResults]) + val nextData = Option(qr.getData) + .map(_.asScala.toList.map(_.asScala.toList)) + .getOrElse(List[List[Any]]()) + val nextResponse = Option(qr.getNextUri).map { + uri => + val path = uri.getPath + val headers = response.getHeaders + webTarget.path(path).request().headers(headers).get() + } + TrinoResponse(nextResponse, Option(qr.getError), nextData) + }.getOrElse(TrinoResponse(isEnd = true)) + } + } diff --git a/pom.xml b/pom.xml index a15b4b590..06fba720d 100644 --- a/pom.xml +++ b/pom.xml @@ -778,6 +778,12 @@ ${jackson.version} + + com.fasterxml.jackson.datatype + jackson-datatype-jdk8 + ${jackson.version} + + com.fasterxml.jackson.jaxrs jackson-jaxrs-base From 161dd3fa8d4eeea693bfd835d0de08026d6f82fa Mon Sep 17 00:00:00 2001 From: Cheng Pan Date: Mon, 13 Feb 2023 19:37:50 +0800 Subject: [PATCH 054/760] [KYUUBI #4317] Upload `kyuubi-spark-batch-submit.log.*` on CI failure ### _Why are the changes needed?_ ``` ... 07:43:26.520 KyuubiSessionManager-exec-pool: Thread-779 INFO ProcBuilder: Logging to /home/runner/work/kyuubi/kyuubi/kyuubi-server/target/work/runner/kyuubi-spark-batch-submit.log.0 ... ``` ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [ ] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4317 from pan3793/ci-log. Closes #4317 dbde5306 [Cheng Pan] Upload kyuubi-spark-batch-submit.log on CI failure Authored-by: Cheng Pan Signed-off-by: ulyssesyou --- .github/workflows/master.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index fd0505999..ed7403fbc 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -102,6 +102,7 @@ jobs: path: | **/target/unit-tests.log **/kyuubi-spark-sql-engine.log* + **/kyuubi-spark-batch-submit.log* authz: name: Kyuubi-AuthZ and Spark Test From 10adbdc1855b8059fe2e7e72cf814aad2af63b20 Mon Sep 17 00:00:00 2001 From: liangbowen Date: Mon, 13 Feb 2023 20:11:20 +0800 Subject: [PATCH 055/760] [KYUUBI #4315] [INFRA] Check modules available before build step in dependency workflow ### _Why are the changes needed?_ - Check whether modules are available by running `mvnd dependency:resolve`. if true, skip the `build` step of `mvnd install`. - Reducing the time cost of dependency workflow from ~ 10 min to ~4 min ([see log](https://github.com/apache/kyuubi/actions/runs/4162071562/jobs/7200786535)). ### _How was this patch tested?_ - [x] Pass CI workflows Closes #4315 from bowenliang123/dep-check-first. Closes #4315 7c20ea24 [liangbowen] nit 65397868 [liangbowen] add validate to bring back enforcer check 1c035136 [liangbowen] add validate to bring back enforcer check d4e703de [liangbowen] update dep.yml 8f8d0457 [liangbowen] update dep.yml e7adfacf [liangbowen] update c88eff98 [liangbowen] check modules available before build Authored-by: liangbowen Signed-off-by: liangbowen --- .github/workflows/dep.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/workflows/dep.yml b/.github/workflows/dep.yml index dc94e4ea7..ebda6b47e 100644 --- a/.github/workflows/dep.yml +++ b/.github/workflows/dep.yml @@ -47,9 +47,18 @@ jobs: check-latest: false - name: Setup Mvnd uses: ./.github/actions/setup-mvnd + - name: Check kyuubi modules available + id: modules-check + run: >- + build/mvnd dependency:resolve validate + -DincludeGroupIds="org.apache.kyuubi" -DincludeScope="compile" + -Pfast -Denforcer.skip=false + -pl kyuubi-ctl,kyuubi-server,kyuubi-assembly -am + continue-on-error: true - name: build env: MAVEN_OPTS: -Dorg.slf4j.simpleLogger.defaultLogLevel=error + if: steps.modules-check.conclusion == 'success' && steps.modules-check.outcome == 'failure' run: >- build/mvnd clean install -Pflink-provided,spark-provided,hive-provided From bb3e06a035d76c4bc9448e5430fbdaa31f8c74ed Mon Sep 17 00:00:00 2001 From: liangbowen Date: Tue, 14 Feb 2023 02:23:32 +0800 Subject: [PATCH 056/760] [KYUUBI #4312] [DOCS] Include `**/README.md` in markdown style check ### _Why are the changes needed?_ - Include `**/README.md` markdown files in spotless style check ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [x] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4312 from bowenliang123/reformat-readme. Closes #4312 1fda1bdeb [Bowen Liang] Merge branch 'master' into reformat-readme 2ca8b4c81 [liangbowen] merge master 876f52a4c [liangbowen] include `**/README.md` in spotless style check Lead-authored-by: liangbowen Co-authored-by: Bowen Liang Signed-off-by: Cheng Pan --- README.md | 41 ++++++-------- dev/kyuubi-tpcds/README.md | 53 ++++++++++--------- docker/playground/README.md | 3 +- extensions/README.md | 36 ++++++------- extensions/spark/kyuubi-spark-authz/README.md | 32 +++++------ .../spark/kyuubi-spark-lineage/README.md | 33 ++++++------ kyuubi-hive-beeline/README.md | 1 + kyuubi-hive-jdbc/README.md | 2 +- kyuubi-server/web-ui/README.md | 2 +- pom.xml | 1 + 10 files changed, 101 insertions(+), 103 deletions(-) diff --git a/README.md b/README.md index 0b46abf85..e54f6fac0 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,19 @@ +- Licensed to the Apache Software Foundation (ASF) under one or more +- contributor license agreements. See the NOTICE file distributed with +- this work for additional information regarding copyright ownership. +- The ASF licenses this file to You under the Apache License, Version 2.0 +- (the "License"); you may not use this file except in compliance with +- the License. You may obtain a copy of the License at +- +- http://www.apache.org/licenses/LICENSE-2.0 +- +- Unless required by applicable law or agreed to in writing, software +- distributed under the License is distributed on an "AS IS" BASIS, +- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- See the License for the specific language governing permissions and +- limitations under the License. +-->

    Kyuubi logo @@ -45,6 +45,7 @@

    # Apache Kyuubi + Apache Kyuubi™ is a distributed and multi-tenant gateway to provide serverless SQL on data warehouses and lakehouses. @@ -60,12 +61,10 @@ Kyuubi provides a pure SQL gateway through Thrift JDBC/ODBC interface for end-us - [x] Multi-tenant Spark Support - [x] Running Spark in a serverless way - ### Target Users Kyuubi's goal is to make it easy and efficient for `anyone` to use Spark(maybe other engines soon) and facilitate users to handle big data like ordinary data. Here, `anyone` means that users do not need to have a Spark technical background but a human language, SQL only. Sometimes, SQL skills are unnecessary when integrating Kyuubi with Apache Superset, which supports rich visualizations and dashboards. - In typical big data production environments with Kyuubi, there should be system administrators and end-users. - System administrators: A small group consists of Spark experts responsible for Kyuubi deployment, configuration, and tuning. @@ -73,7 +72,6 @@ In typical big data production environments with Kyuubi, there should be system Additionally, the Kyuubi community will continuously optimize the whole system with various features, such as History-Based Optimizer, Auto-tuning, Materialized View, SQL Dialects, Functions, e.t.c. - ### Usage scenarios #### Port workloads from HiveServer2 to Spark SQL @@ -86,7 +84,6 @@ HiveServer2 can identify and authenticate a caller, and then if the caller also Kyuubi extends the use of STS in a multi-tenant model based on a unified interface and relies on the concept of multi-tenancy to interact with cluster managers to finally gain the ability of resources sharing/isolation and data security. The loosely coupled architecture of the Kyuubi server and engine dramatically improves the client concurrency and service stability of the service itself. - #### DataLake/LakeHouse Support The vision of Kyuubi is to unify the portal and become an easy-to-use data lake management platform. Different kinds of workloads, such as ETL processing and BI analytics, can be supported by one platform, using one copy of data, with one SQL interface. @@ -95,25 +92,19 @@ The vision of Kyuubi is to unify the portal and become an easy-to-use data lake - Multiple Catalogs support - SQL Standard Authorization support for DataLake(coming) - #### Cloud Native Support Kyuubi can deploy its engines on different kinds of Cluster Managers, such as, Hadoop YARN, Kubernetes, etc. - ![](./docs/imgs/kyuubi_migrating_yarn_to_k8s.png) - ### The Kyuubi Ecosystem(present and future) - The figure below shows our vision for the Kyuubi Ecosystem. Some of them have been realized, some in development, and others would not be possible without your help. ![](./docs/imgs/kyuubi_ecosystem.drawio.png) - - ## Online Documentation Since Kyuubi 1.3.0-incubating, the Kyuubi online documentation is hosted by [https://kyuubi.apache.org/](https://kyuubi.apache.org/). diff --git a/dev/kyuubi-tpcds/README.md b/dev/kyuubi-tpcds/README.md index adffb6726..a9a6487aa 100644 --- a/dev/kyuubi-tpcds/README.md +++ b/dev/kyuubi-tpcds/README.md @@ -1,21 +1,22 @@ +- Licensed to the Apache Software Foundation (ASF) under one or more +- contributor license agreements. See the NOTICE file distributed with +- this work for additional information regarding copyright ownership. +- The ASF licenses this file to You under the Apache License, Version 2.0 +- (the "License"); you may not use this file except in compliance with +- the License. You may obtain a copy of the License at +- +- http://www.apache.org/licenses/LICENSE-2.0 +- +- Unless required by applicable law or agreed to in writing, software +- distributed under the License is distributed on an "AS IS" BASIS, +- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- See the License for the specific language governing permissions and +- limitations under the License. +--> # Introduction + This module includes TPC-DS data generator and benchmark tool. # How to use @@ -27,12 +28,12 @@ package jar with following command: Support options: -| key | default | description | -|--------------|-----------------|-----------------------------------| -| db | default | the database to write data | -| scaleFactor | 1 | the scale factor of TPC-DS | -| format | parquet | the format of table to store data | -| parallel | scaleFactor * 2 | the parallelism of Spark job | +| key | default | description | +|-------------|-----------------|-----------------------------------| +| db | default | the database to write data | +| scaleFactor | 1 | the scale factor of TPC-DS | +| format | parquet | the format of table to store data | +| parallel | scaleFactor * 2 | the parallelism of Spark job | Example: the following command to generate 10GB data with new database `tpcds_sf10`. @@ -47,7 +48,7 @@ $SPARK_HOME/bin/spark-submit \ Support options: -| key | default | description | +| key | default | description | |-------------|------------------------|---------------------------------------------------------------| | db | none(required) | the TPC-DS database | | benchmark | tpcds-v2.4-benchmark | the name of application | @@ -65,6 +66,7 @@ $SPARK_HOME/bin/spark-submit \ ``` We also support run one of the TPC-DS query: + ```shell $SPARK_HOME/bin/spark-submit \ --class org.apache.kyuubi.tpcds.benchmark.RunBenchmark \ @@ -73,6 +75,7 @@ $SPARK_HOME/bin/spark-submit \ The result of TPC-DS benchmark like: -| name | minTimeMs | maxTimeMs | avgTimeMs | stdDev | stdDevPercent | -|---------|-----------|-------------|------------|----------|----------------| -| q1-v2.4 | 50.522384 | 868.010383 | 323.398267 | 471.6482 | 145.8413108576 | +| name | minTimeMs | maxTimeMs | avgTimeMs | stdDev | stdDevPercent | +|---------|-----------|------------|------------|----------|----------------| +| q1-v2.4 | 50.522384 | 868.010383 | 323.398267 | 471.6482 | 145.8413108576 | + diff --git a/docker/playground/README.md b/docker/playground/README.md index d9e227c2c..66dca2af0 100644 --- a/docker/playground/README.md +++ b/docker/playground/README.md @@ -1,5 +1,5 @@ Playground -=== +========== ## For Users @@ -45,3 +45,4 @@ Kyuubi supply some built-in dataset, after Kyuubi started, you can run the follo 1. Build images `docker/playground/build-image.sh`; 2. Optional to use `buildx` to build and publish cross-platform images `BUILDX=1 docker/playground/build-image.sh`; + diff --git a/extensions/README.md b/extensions/README.md index 92eac9097..5725f0f9b 100644 --- a/extensions/README.md +++ b/extensions/README.md @@ -1,25 +1,24 @@ - +- Licensed to the Apache Software Foundation (ASF) under one or more +- contributor license agreements. See the NOTICE file distributed with +- this work for additional information regarding copyright ownership. +- The ASF licenses this file to You under the Apache License, Version 2.0 +- (the "License"); you may not use this file except in compliance with +- the License. You may obtain a copy of the License at +- +- http://www.apache.org/licenses/LICENSE-2.0 +- +- Unless required by applicable law or agreed to in writing, software +- distributed under the License is distributed on an "AS IS" BASIS, +- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- See the License for the specific language governing permissions and +- limitations under the License. +--> + # For developers This folder contains plugins/extension for kyuubi server and different engine types. - - ext - kyuubi-server - spark @@ -27,4 +26,5 @@ This folder contains plugins/extension for kyuubi server and different engine ty - trino - hive - others - - ... \ No newline at end of file + - ... + diff --git a/extensions/spark/kyuubi-spark-authz/README.md b/extensions/spark/kyuubi-spark-authz/README.md index c257e30e1..554797ee0 100644 --- a/extensions/spark/kyuubi-spark-authz/README.md +++ b/extensions/spark/kyuubi-spark-authz/README.md @@ -1,19 +1,19 @@ +- Licensed to the Apache Software Foundation (ASF) under one or more +- contributor license agreements. See the NOTICE file distributed with +- this work for additional information regarding copyright ownership. +- The ASF licenses this file to You under the Apache License, Version 2.0 +- (the "License"); you may not use this file except in compliance with +- the License. You may obtain a copy of the License at +- +- http://www.apache.org/licenses/LICENSE-2.0 +- +- Unless required by applicable law or agreed to in writing, software +- distributed under the License is distributed on an "AS IS" BASIS, +- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- See the License for the specific language governing permissions and +- limitations under the License. +--> # Kyuubi Spark AuthZ Extension @@ -29,7 +29,6 @@ build/mvn clean package -pl :kyuubi-spark-authz_2.12 -Dspark.version=3.2.1 -Dranger.version=2.3.0 ``` - ### Supported Apache Spark Versions `-Dspark.version=` @@ -54,3 +53,4 @@ build/mvn clean package -pl :kyuubi-spark-authz_2.12 -Dspark.version=3.2.1 -Dran - [x] 1.0.x - [x] 0.7.x - [x] 0.6.x + diff --git a/extensions/spark/kyuubi-spark-lineage/README.md b/extensions/spark/kyuubi-spark-lineage/README.md index e70acc015..34f2733b4 100644 --- a/extensions/spark/kyuubi-spark-lineage/README.md +++ b/extensions/spark/kyuubi-spark-lineage/README.md @@ -1,26 +1,26 @@ +- Licensed to the Apache Software Foundation (ASF) under one or more +- contributor license agreements. See the NOTICE file distributed with +- this work for additional information regarding copyright ownership. +- The ASF licenses this file to You under the Apache License, Version 2.0 +- (the "License"); you may not use this file except in compliance with +- the License. You may obtain a copy of the License at +- +- http://www.apache.org/licenses/LICENSE-2.0 +- +- Unless required by applicable law or agreed to in writing, software +- distributed under the License is distributed on an "AS IS" BASIS, +- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- See the License for the specific language governing permissions and +- limitations under the License. +--> # Kyuubi Spark Listener Extension ## Functions - [x] All `listener` extensions can be implemented in this module, like `QueryExecutionListener` and `ExtraListener` -- [x] Add `SparkOperationLineageQueryExecutionListener` to extends spark `QueryExecutionListener` +- [x] Add `SparkOperationLineageQueryExecutionListener` to extends spark `QueryExecutionListener` - [x] SQL lineage parsing will be triggered after SQL execution and will be written to the json logger file ## Build @@ -37,3 +37,4 @@ build/mvn clean package -pl :kyuubi-spark-lineage_2.12 -Dspark.version=3.2.1 - [x] 3.3.x (default) - [x] 3.2.x - [x] 3.1.x + diff --git a/kyuubi-hive-beeline/README.md b/kyuubi-hive-beeline/README.md index ec4f86fd7..161acb99b 100644 --- a/kyuubi-hive-beeline/README.md +++ b/kyuubi-hive-beeline/README.md @@ -3,3 +3,4 @@ Aiming to make a better supported beeline for Kyuubi - Support to show launch engine log when getting KyuubiConnection(Done, available since v1.4.0-incubating) + diff --git a/kyuubi-hive-jdbc/README.md b/kyuubi-hive-jdbc/README.md index 3210e76ac..10a0522dc 100644 --- a/kyuubi-hive-jdbc/README.md +++ b/kyuubi-hive-jdbc/README.md @@ -1,9 +1,9 @@ # Kyuubi Hive JDBC Module - Aiming to make a better supported client for Kyuubi and Spark - Add catalog to getTables meta function for DataLakes (DONE, broken in v1.3.0-incubating, fixed in v1.3.1-incubating) - Deploy to maven central (DONE, available since v1.3.0-incubating) - Create shaded jar (DONE, available since v1.4.0-incubating) - Remove Hive dependencies (DONE, available since v1.6.0-incubating) + diff --git a/kyuubi-server/web-ui/README.md b/kyuubi-server/web-ui/README.md index cc5654b23..6d808ecde 100644 --- a/kyuubi-server/web-ui/README.md +++ b/kyuubi-server/web-ui/README.md @@ -17,7 +17,6 @@ npm install To do this you can change the VITE_APP_DEV_WEB_URL parameter variable as the service url in `.env.development` in the project root directory, such as http://127.0. 0.1:8090 - ```shell npm run dev ``` @@ -56,3 +55,4 @@ pnpm run build # Code Format pnpm run prettier ``` + diff --git a/pom.xml b/pom.xml index 06fba720d..8bffc4f8b 100644 --- a/pom.xml +++ b/pom.xml @@ -2076,6 +2076,7 @@ + **/README.md docs/**/*.md From 4de0a697ae700d5c6d6122f740437f38d66715dd Mon Sep 17 00:00:00 2001 From: liangbowen Date: Tue, 14 Feb 2023 10:48:59 +0800 Subject: [PATCH 057/760] [KYUUBI #4312] [FOLLOWUP] Fix overmatched README pattern for markdown styling ### _Why are the changes needed?_ - fix the README files' over-matched pattern mentioned in https://github.com/apache/kyuubi/pull/4312/files#r1105223855 ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [x] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4319 from bowenliang123/readme-match-pattern. Closes #4312 3a8266e6 [liangbowen] update 189695be [liangbowen] fix overmatching pattern for README file Authored-by: liangbowen Signed-off-by: liangbowen --- pom.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 8bffc4f8b..644119ece 100644 --- a/pom.xml +++ b/pom.xml @@ -2076,8 +2076,9 @@ - **/README.md docs/**/*.md + */README.md + docker/playground/README.md ${flexmark.version} From 16d4735c92f7fde9b31e1831647bab2e058a5906 Mon Sep 17 00:00:00 2001 From: Yikf Date: Tue, 14 Feb 2023 13:30:25 +0800 Subject: [PATCH 058/760] [KYUUBI #4322] MySQL url configuration joiner should use `&` instead of `;` ### _Why are the changes needed?_ The mysql url configuration joiner should use `&` instead of `;` ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [ ] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4322 from Yikf/mysql-fe-test. Closes #4322 8d289e793 [Yikf] The configuration joiner should use `&` instead of `;` Authored-by: Yikf Signed-off-by: Cheng Pan --- .../org/apache/kyuubi/server/mysql/MySQLJDBCTestHelper.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kyuubi-server/src/test/scala/org/apache/kyuubi/server/mysql/MySQLJDBCTestHelper.scala b/kyuubi-server/src/test/scala/org/apache/kyuubi/server/mysql/MySQLJDBCTestHelper.scala index a703b6b15..e6df1fb20 100644 --- a/kyuubi-server/src/test/scala/org/apache/kyuubi/server/mysql/MySQLJDBCTestHelper.scala +++ b/kyuubi-server/src/test/scala/org/apache/kyuubi/server/mysql/MySQLJDBCTestHelper.scala @@ -42,7 +42,7 @@ trait MySQLJDBCTestHelper extends JDBCTestHelper { if (jdbcConfigs.isEmpty) { "" } else { - "?" + jdbcConfigs.map(kv => kv._1 + "=" + kv._2).mkString(";") + "?" + jdbcConfigs.map(kv => kv._1 + "=" + kv._2).mkString("&") } jdbcUrl + jdbcConfStr } From 02deaf4756fef953d706f171d87c5d0dd760f264 Mon Sep 17 00:00:00 2001 From: Yikf Date: Tue, 14 Feb 2023 13:34:55 +0800 Subject: [PATCH 059/760] [KYUUBI #4320] Support GetPrimaryKeys for Trino Fe ### _Why are the changes needed?_ Support GetPrimaryKeys for Trino Fe, close https://github.com/apache/kyuubi/issues/4320 ### _How was this patch tested?_ - [x] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [x] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4321 from Yikf/primaryKeys. Closes #4320 3690a2c7 [Yikf] Support GetPrimaryKeys for Trino Fe Authored-by: Yikf Signed-off-by: ulyssesyou --- .../apache/kyuubi/sql/KyuubiTrinoFeBaseLexer.g4 | 6 ++++++ .../kyuubi/sql/KyuubiTrinoFeBaseParser.g4 | 7 +++++++ .../api/KyuubiTrinoOperationTranslator.scala | 7 ++++++- .../parser/trino/KyuubiTrinoFeAstBuilder.scala | 6 +++++- .../sql/plan/trino/TrinoFeOperations.scala | 4 ++++ .../parser/trino/KyuubiTrinoFeParserSuite.scala | 17 ++++++++++++++++- 6 files changed, 44 insertions(+), 3 deletions(-) diff --git a/kyuubi-server/src/main/antlr4/org/apache/kyuubi/sql/KyuubiTrinoFeBaseLexer.g4 b/kyuubi-server/src/main/antlr4/org/apache/kyuubi/sql/KyuubiTrinoFeBaseLexer.g4 index 0b9543a43..0cc02de64 100644 --- a/kyuubi-server/src/main/antlr4/org/apache/kyuubi/sql/KyuubiTrinoFeBaseLexer.g4 +++ b/kyuubi-server/src/main/antlr4/org/apache/kyuubi/sql/KyuubiTrinoFeBaseLexer.g4 @@ -97,6 +97,12 @@ SCOPE_TABLE: 'SCOPE_TABLE'; SOURCE_DATA_TYPE: 'SOURCE_DATA_TYPE'; IS_AUTOINCREMENT: 'IS_AUTOINCREMENT'; IS_GENERATEDCOLUMN: 'IS_GENERATEDCOLUMN'; +VARCHAR: 'VARCHAR'; +SMALLINT: 'SMALLINT'; +CAST: 'CAST'; +AS: 'AS'; +KEY_SEQ: 'KEY_SEQ'; +PK_NAME: 'PK_NAME'; fragment SEARCH_STRING_ESCAPE: '\'' '\\' '\''; diff --git a/kyuubi-server/src/main/antlr4/org/apache/kyuubi/sql/KyuubiTrinoFeBaseParser.g4 b/kyuubi-server/src/main/antlr4/org/apache/kyuubi/sql/KyuubiTrinoFeBaseParser.g4 index 590c4378d..6af00af5d 100644 --- a/kyuubi-server/src/main/antlr4/org/apache/kyuubi/sql/KyuubiTrinoFeBaseParser.g4 +++ b/kyuubi-server/src/main/antlr4/org/apache/kyuubi/sql/KyuubiTrinoFeBaseParser.g4 @@ -47,6 +47,13 @@ statement SOURCE_DATA_TYPE COMMA IS_AUTOINCREMENT COMMA IS_GENERATEDCOLUMN FROM SYSTEM_JDBC_COLUMNS (WHERE tableCatalogFilter? AND? tableSchemaFilter? AND? tableNameFilter? AND? colNameFilter?)? ORDER BY TABLE_CAT COMMA TABLE_SCHEM COMMA TABLE_NAME COMMA ORDINAL_POSITION #getColumns + | SELECT CAST LEFT_PAREN NULL AS VARCHAR RIGHT_PAREN TABLE_CAT COMMA + CAST LEFT_PAREN NULL AS VARCHAR RIGHT_PAREN TABLE_SCHEM COMMA + CAST LEFT_PAREN NULL AS VARCHAR RIGHT_PAREN TABLE_NAME COMMA + CAST LEFT_PAREN NULL AS VARCHAR RIGHT_PAREN COLUMN_NAME COMMA + CAST LEFT_PAREN NULL AS SMALLINT RIGHT_PAREN KEY_SEQ COMMA + CAST LEFT_PAREN NULL AS VARCHAR RIGHT_PAREN PK_NAME + WHERE FALSE #getPrimaryKeys | .*? #passThrough ; diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/trino/api/KyuubiTrinoOperationTranslator.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/trino/api/KyuubiTrinoOperationTranslator.scala index 5eba9c327..c78cb351e 100644 --- a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/trino/api/KyuubiTrinoOperationTranslator.scala +++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/trino/api/KyuubiTrinoOperationTranslator.scala @@ -24,7 +24,7 @@ import org.apache.kyuubi.service.BackendService import org.apache.kyuubi.session.SessionHandle import org.apache.kyuubi.sql.parser.trino.KyuubiTrinoFeParser import org.apache.kyuubi.sql.plan.PassThroughNode -import org.apache.kyuubi.sql.plan.trino.{GetCatalogs, GetColumns, GetSchemas, GetTables, GetTableTypes, GetTypeInfo} +import org.apache.kyuubi.sql.plan.trino.{GetCatalogs, GetColumns, GetPrimaryKeys, GetSchemas, GetTables, GetTableTypes, GetTypeInfo} class KyuubiTrinoOperationTranslator(backendService: BackendService) { lazy val parser = new KyuubiTrinoFeParser() @@ -60,6 +60,11 @@ class KyuubiTrinoOperationTranslator(backendService: BackendService) { schemaPattern, tableNamePattern, colNamePattern) + case GetPrimaryKeys() => + val operationHandle = backendService.getPrimaryKeys(sessionHandle, null, null, null) + // The trino implementation always returns empty. + operationHandle.setHasResultSet(false) + operationHandle case PassThroughNode() => backendService.executeStatement(sessionHandle, statement, configs, runAsync, queryTimeout) } diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/sql/parser/trino/KyuubiTrinoFeAstBuilder.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/sql/parser/trino/KyuubiTrinoFeAstBuilder.scala index c5ae97199..061985c1c 100644 --- a/kyuubi-server/src/main/scala/org/apache/kyuubi/sql/parser/trino/KyuubiTrinoFeAstBuilder.scala +++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/sql/parser/trino/KyuubiTrinoFeAstBuilder.scala @@ -25,7 +25,7 @@ import org.apache.kyuubi.sql.KyuubiTrinoFeBaseParser._ import org.apache.kyuubi.sql.KyuubiTrinoFeBaseParserBaseVisitor import org.apache.kyuubi.sql.parser.KyuubiParser.unescapeSQLString import org.apache.kyuubi.sql.plan.{KyuubiTreeNode, PassThroughNode} -import org.apache.kyuubi.sql.plan.trino.{GetCatalogs, GetColumns, GetSchemas, GetTables, GetTableTypes, GetTypeInfo} +import org.apache.kyuubi.sql.plan.trino.{GetCatalogs, GetColumns, GetPrimaryKeys, GetSchemas, GetTables, GetTableTypes, GetTypeInfo} class KyuubiTrinoFeAstBuilder extends KyuubiTrinoFeBaseParserBaseVisitor[AnyRef] { @@ -92,6 +92,10 @@ class KyuubiTrinoFeAstBuilder extends KyuubiTrinoFeBaseParserBaseVisitor[AnyRef] GetColumns(catalog, schemaPattern, tableNamePattern, colNamePattern) } + override def visitGetPrimaryKeys(ctx: GetPrimaryKeysContext): KyuubiTreeNode = { + GetPrimaryKeys() + } + override def visitNullCatalog(ctx: NullCatalogContext): AnyRef = { null } diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/sql/plan/trino/TrinoFeOperations.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/sql/plan/trino/TrinoFeOperations.scala index 85e6f168b..6136995ab 100644 --- a/kyuubi-server/src/main/scala/org/apache/kyuubi/sql/plan/trino/TrinoFeOperations.scala +++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/sql/plan/trino/TrinoFeOperations.scala @@ -55,3 +55,7 @@ case class GetColumns( colNamePattern: String) extends KyuubiTreeNode { override def name(): String = "Get Columns" } + +case class GetPrimaryKeys() extends KyuubiTreeNode { + override def name(): String = "Get Primary Keys" +} diff --git a/kyuubi-server/src/test/scala/org/apache/kyuubi/parser/trino/KyuubiTrinoFeParserSuite.scala b/kyuubi-server/src/test/scala/org/apache/kyuubi/parser/trino/KyuubiTrinoFeParserSuite.scala index 3f5cf70b5..bbced0b61 100644 --- a/kyuubi-server/src/test/scala/org/apache/kyuubi/parser/trino/KyuubiTrinoFeParserSuite.scala +++ b/kyuubi-server/src/test/scala/org/apache/kyuubi/parser/trino/KyuubiTrinoFeParserSuite.scala @@ -20,7 +20,7 @@ package org.apache.kyuubi.parser.trino import org.apache.kyuubi.KyuubiFunSuite import org.apache.kyuubi.sql.parser.trino.KyuubiTrinoFeParser import org.apache.kyuubi.sql.plan.{KyuubiTreeNode, PassThroughNode} -import org.apache.kyuubi.sql.plan.trino.{GetCatalogs, GetColumns, GetSchemas, GetTables, GetTableTypes, GetTypeInfo} +import org.apache.kyuubi.sql.plan.trino.{GetCatalogs, GetColumns, GetPrimaryKeys, GetSchemas, GetTables, GetTableTypes, GetTypeInfo} class KyuubiTrinoFeParserSuite extends KyuubiFunSuite { val parser = new KyuubiTrinoFeParser() @@ -354,4 +354,19 @@ class KyuubiTrinoFeParserSuite extends KyuubiFunSuite { tableName = "%aa", colName = "%bb") } + + test("Support GetPrimaryKeys for Trino Fe") { + val kyuubiTreeNode = parse( + """ + | SELECT CAST(NULL AS varchar) TABLE_CAT, + | CAST(NULL AS varchar) TABLE_SCHEM, + | CAST(NULL AS varchar) TABLE_NAME, + | CAST(NULL AS varchar) COLUMN_NAME, + | CAST(NULL AS smallint) KEY_SEQ, + | CAST(NULL AS varchar) PK_NAME + | WHERE false + |""".stripMargin) + + assert(kyuubiTreeNode.isInstanceOf[GetPrimaryKeys]) + } } From 9ce5ef62390a8dc40043bee1cfd0943ea24e35fd Mon Sep 17 00:00:00 2001 From: ulysses-you Date: Tue, 14 Feb 2023 14:57:00 +0800 Subject: [PATCH 060/760] [KYUUBI #4324] Add Publish docker image step ### _Why are the changes needed?_ Add Publish docker image step. The step details is maintained by docker repo. ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [ ] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4324 from ulysses-you/release. Closes #4324 0f6a6646 [ulysses-you] Add Publish docker image step Authored-by: ulysses-you Signed-off-by: ulyssesyou --- docs/community/release.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/community/release.md b/docs/community/release.md index 5d3a00b03..ffbdda9c1 100644 --- a/docs/community/release.md +++ b/docs/community/release.md @@ -49,6 +49,8 @@ The release process consists of several steps: 6. If necessary, fix any issues and go back to step 3. 7. Finalize the release 8. Promote the release +9. Remove the dist repo directories for deprecated release candidates +10. Publish docker image ## Decide to release @@ -280,3 +282,6 @@ svn delete https://dist.apache.org/repos/dist/dev/kyuubi/{RELEASE_TAG} \ --message "Remove deprecated Apache Kyuubi ${RELEASE_TAG}" ``` +## Publish docker image + +See steps in `https://github.com/apache/kyuubi-docker/blob/master/release/release_guide.md` From 763c088d8197b168894b3e5d84aefc248fd3273b Mon Sep 17 00:00:00 2001 From: ulysses-you Date: Tue, 14 Feb 2023 15:33:36 +0800 Subject: [PATCH 061/760] [KYUUBI #4323] Improve trino session context ### _Why are the changes needed?_ This pr improves the trino session context: 1. always reuse the kyuubi session if session id exists, so we can restore the session context for next query 2. transform trino client information to kyuubi session, e.g. trino request source (trino-cli) ### _How was this patch tested?_ - [x] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [x] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4323 from ulysses-you/trino-session. Closes #4323 59804bad [ulysses-you] style fcf540a6 [ulysses-you] Improve trino session context Authored-by: ulysses-you Signed-off-by: ulyssesyou --- .../kyuubi/server/trino/api/Query.scala | 54 ++++++++++++++----- .../server/trino/api/TrinoContext.scala | 20 ++++--- .../trino/api/v1/StatementResource.scala | 7 +-- .../trino/api/TrinoClientApiSuite.scala | 18 +++++-- .../trino/api/v1/StatementResourceSuite.scala | 8 +-- 5 files changed, 74 insertions(+), 33 deletions(-) diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/trino/api/Query.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/trino/api/Query.scala index c8c9e2fd6..925875579 100644 --- a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/trino/api/Query.scala +++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/trino/api/Query.scala @@ -25,6 +25,8 @@ import java.util.concurrent.atomic.AtomicLong import javax.ws.rs.WebApplicationException import javax.ws.rs.core.{Response, UriInfo} +import scala.collection.mutable + import Slug.Context.{EXECUTING_QUERY, QUEUED_QUERY} import com.google.common.hash.Hashing import io.trino.client.QueryResults @@ -32,6 +34,7 @@ import org.apache.hive.service.rpc.thrift.TProtocolVersion import org.apache.kyuubi.operation.{FetchOrientation, OperationHandle} import org.apache.kyuubi.operation.OperationState.{FINISHED, INITIALIZED, OperationState, PENDING} +import org.apache.kyuubi.server.trino.api.Query.KYUUBI_SESSION_ID import org.apache.kyuubi.service.BackendService import org.apache.kyuubi.session.SessionHandle @@ -90,7 +93,7 @@ case class Query( private def clear = { be.closeOperation(queryId.operationHandle) - context.session.get("sessionId").foreach { id => + context.session.get(KYUUBI_SESSION_ID).foreach { id => be.closeSession(SessionHandle.fromUUID(id)) } } @@ -128,23 +131,24 @@ case class Query( object Query { + val KYUUBI_SESSION_ID = "kyuubi.session.id" + def apply( statement: String, context: TrinoContext, translator: KyuubiTrinoOperationTranslator, backendService: BackendService, queryTimeout: Long = 0): Query = { - - val sessionHandle = createSession(context, backendService) + val sessionHandle = getOrCreateSession(context, backendService) val operationHandle = translator.transform( statement, sessionHandle, context.session, true, queryTimeout) - val newSessionProperties = - context.session + ("sessionId" -> sessionHandle.identifier.toString) - val updatedContext = context.copy(session = newSessionProperties) + val sessionWithId = + context.session + (KYUUBI_SESSION_ID -> sessionHandle.identifier.toString) + val updatedContext = context.copy(session = sessionWithId) Query(QueryId(operationHandle), updatedContext, backendService) } @@ -152,15 +156,39 @@ object Query { Query(QueryId(id), context, backendService) } - private def createSession( + private def getOrCreateSession( context: TrinoContext, backendService: BackendService): SessionHandle = { - backendService.openSession( - TProtocolVersion.HIVE_CLI_SERVICE_PROTOCOL_V11, - context.user, - "", - context.remoteUserAddress.getOrElse(""), - context.session) + context.session.get(KYUUBI_SESSION_ID).map(SessionHandle.fromUUID).getOrElse { + // transform Trino information to session and engine as far as possible. + val trinoInfo = new mutable.HashMap[String, String]() + context.clientInfo.foreach { info => + trinoInfo.put("trino.client.info", info) + } + context.source.foreach { source => + trinoInfo.put("trino.request.source", source) + } + context.traceToken.foreach { traceToken => + trinoInfo.put("trino.trace.token", traceToken) + } + context.timeZone.foreach { timeZone => + trinoInfo.put("trino.time.zone", timeZone) + } + context.language.foreach { language => + trinoInfo.put("trino.language", language) + } + if (context.clientTags.nonEmpty) { + trinoInfo.put("trino.client.info", context.clientTags.mkString(",")) + } + + val newSessionConfigs = context.session ++ trinoInfo + backendService.openSession( + TProtocolVersion.HIVE_CLI_SERVICE_PROTOCOL_V11, + context.user, + "", + context.remoteUserAddress.getOrElse(""), + newSessionConfigs) + } } } diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/trino/api/TrinoContext.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/trino/api/TrinoContext.scala index 9e7713904..0c7911a46 100644 --- a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/trino/api/TrinoContext.scala +++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/trino/api/TrinoContext.scala @@ -30,7 +30,9 @@ import org.apache.hive.service.rpc.thrift.{TGetResultSetMetadataResp, TRowSet, T import org.apache.kyuubi.operation.OperationState.FINISHED import org.apache.kyuubi.operation.OperationStatus +import org.apache.kyuubi.server.trino.api.Query.KYUUBI_SESSION_ID +// TODO: Support replace `preparedStatement` for Trino-jdbc /** * The description and functionality of trino request * and response's context @@ -140,15 +142,17 @@ object TrinoContext { def buildTrinoResponse(qr: QueryResults, trinoContext: TrinoContext): Response = { val responseBuilder = Response.ok(qr) - trinoContext.catalog.foreach( - responseBuilder.header(TRINO_HEADERS.responseSetCatalog, _)) - trinoContext.schema.foreach( - responseBuilder.header(TRINO_HEADERS.responseSetSchema, _)) + // Note, We have injected kyuubi session id to session context so that the next query can find + // the previous session to restore the query context. + // It's hard to follow the Trino style that set all context to http headers. + // Because we do not know the context at server side. e.g. `set k=v`, `use database`. + // We also can not inject other session context into header before we supporting to map + // query result to session context. + require(trinoContext.session.contains(KYUUBI_SESSION_ID), s"$KYUUBI_SESSION_ID must be set.") + responseBuilder.header( + TRINO_HEADERS.responseSetSession, + s"$KYUUBI_SESSION_ID=${urlEncode(trinoContext.session(KYUUBI_SESSION_ID))}") - trinoContext.session.foreach { - case (k, v) => - responseBuilder.header(TRINO_HEADERS.responseSetSession, s"${k}=${urlEncode(v)}") - } trinoContext.preparedStatement.foreach { case (k, v) => responseBuilder.header(TRINO_HEADERS.responseAddedPrepare, s"${k}=${urlEncode(v)}") diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/trino/api/v1/StatementResource.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/trino/api/v1/StatementResource.scala index ab783f8ac..ee23c61f3 100644 --- a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/trino/api/v1/StatementResource.scala +++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/trino/api/v1/StatementResource.scala @@ -215,9 +215,10 @@ private[v1] class StatementResource extends ApiRequestContext with Logging { slug: String, token: Long, slugContext: Slug.Context.Context): Try[Query] = { - - Try(be.sessionManager.operationManager.getOperation(queryId.operationHandle)).map { _ => - Query(queryId, context, be) + Try(be.sessionManager.operationManager.getOperation(queryId.operationHandle)).map { op => + val sessionWithId = context.session ++ + Map(Query.KYUUBI_SESSION_ID -> op.getSession.handle.identifier.toString) + Query(queryId, context.copy(session = sessionWithId), be) }.filter(_.getSlug.isValid(slugContext, slug, token)) } diff --git a/kyuubi-server/src/test/scala/org/apache/kyuubi/server/trino/api/TrinoClientApiSuite.scala b/kyuubi-server/src/test/scala/org/apache/kyuubi/server/trino/api/TrinoClientApiSuite.scala index c88b5c940..13e10a112 100644 --- a/kyuubi-server/src/test/scala/org/apache/kyuubi/server/trino/api/TrinoClientApiSuite.scala +++ b/kyuubi-server/src/test/scala/org/apache/kyuubi/server/trino/api/TrinoClientApiSuite.scala @@ -45,16 +45,24 @@ class TrinoClientApiSuite extends KyuubiFunSuite with TrinoRestFrontendTestHelpe test("submit query with trino client api") { val trino = getTrinoStatementClient("select 1") val result = execute(trino) - val sessionId = trino.getSetSessionProperties.asScala.get("sessionId") + val sessionId = trino.getSetSessionProperties.asScala.get(Query.KYUUBI_SESSION_ID) assert(result == List(List(1))) updateClientSession(trino) - val trino1 = getTrinoStatementClient("select 2") + val trino1 = getTrinoStatementClient("set k=v") val result1 = execute(trino1) - val sessionId1 = trino1.getSetSessionProperties.asScala.get("sessionId") - assert(result1 == List(List(2))) - assert(sessionId != sessionId1) + val sessionId1 = trino1.getSetSessionProperties.asScala.get(Query.KYUUBI_SESSION_ID) + assert(result1 == List(List("k", "v"))) + assert(sessionId == sessionId1) + + updateClientSession(trino) + + val trino2 = getTrinoStatementClient("set k") + val result2 = execute(trino2) + val sessionId2 = trino2.getSetSessionProperties.asScala.get(Query.KYUUBI_SESSION_ID) + assert(result2 == List(List("k", "v"))) + assert(sessionId == sessionId2) trino.close() } diff --git a/kyuubi-server/src/test/scala/org/apache/kyuubi/server/trino/api/v1/StatementResourceSuite.scala b/kyuubi-server/src/test/scala/org/apache/kyuubi/server/trino/api/v1/StatementResourceSuite.scala index adbf389c9..5740f6d38 100644 --- a/kyuubi-server/src/test/scala/org/apache/kyuubi/server/trino/api/v1/StatementResourceSuite.scala +++ b/kyuubi-server/src/test/scala/org/apache/kyuubi/server/trino/api/v1/StatementResourceSuite.scala @@ -27,7 +27,7 @@ import io.trino.client.ProtocolHeaders.TRINO_HEADERS import org.apache.kyuubi.{KyuubiFunSuite, KyuubiSQLException, TrinoRestFrontendTestHelper} import org.apache.kyuubi.operation.{OperationHandle, OperationState} -import org.apache.kyuubi.server.trino.api.TrinoContext +import org.apache.kyuubi.server.trino.api.{Query, TrinoContext} import org.apache.kyuubi.server.trino.api.v1.dto.Ok import org.apache.kyuubi.session.SessionHandle @@ -78,7 +78,7 @@ class StatementResourceSuite extends KyuubiFunSuite with TrinoRestFrontendTestHe response.getStringHeaders.get(TRINO_HEADERS.responseSetSession).asScala .map(_.split("=")) .find { - case Array("sessionId", _) => true + case Array(Query.KYUUBI_SESSION_ID, _) => true } .map { case Array(_, value) => SessionHandle.fromUUID(TrinoContext.urlDecode(value)) @@ -90,12 +90,12 @@ class StatementResourceSuite extends KyuubiFunSuite with TrinoRestFrontendTestHe val path = qr.getNextUri.getPath val nextResponse = webTarget.path(path).request().header( TRINO_HEADERS.requestSession(), - s"sessionId=${TrinoContext.urlEncode(sessionHandle.identifier.toString)}").delete() + s"${Query.KYUUBI_SESSION_ID}=${TrinoContext.urlEncode(sessionHandle.identifier.toString)}") + .delete() assert(nextResponse.getStatus == 204) assert(operation.getStatus.state == OperationState.CLOSED) val exception = intercept[KyuubiSQLException](sessionManager.getSession(sessionHandle)) assert(exception.getMessage === s"Invalid $sessionHandle") - } private def getData(current: TrinoResponse): TrinoResponse = { From 8fe794709bb2a3e28baa6dccf89f62b1dac5c285 Mon Sep 17 00:00:00 2001 From: Cheng Pan Date: Tue, 14 Feb 2023 10:54:01 +0000 Subject: [PATCH 062/760] [KYUUBI #4316] Fix returned Timestamp values may lose precision MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### _Why are the changes needed?_ This PR proposes to use `org.apache.spark.sql.execution#toHiveString` to replace `org.apache.kyuubi.engine.spark.schema#toHiveString` to get consistent result w/ `spark-sql` and `STS`. Because of [SPARK-32006](https://issues.apache.org/jira/browse/SPARK-32006), it only works w/ Spark 3.1 and above. The patch takes effects on both thrift and arrow result format. ### _How was this patch tested?_ - [x] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [x] Add screenshots for manual tests if appropriate ``` ➜ ~ beeline -u 'jdbc:hive2://0.0.0.0:10009/default' Connecting to jdbc:hive2://0.0.0.0:10009/default Connected to: Spark SQL (version 3.3.1) Driver: Hive JDBC (version 2.3.9) Transaction isolation: TRANSACTION_REPEATABLE_READ Beeline version 2.3.9 by Apache Hive 0: jdbc:hive2://0.0.0.0:10009/default> select to_timestamp('2023-02-08 22:17:33.123456789'); +----------------------------------------------+ | to_timestamp(2023-02-08 22:17:33.123456789) | +----------------------------------------------+ | 2023-02-08 22:17:33.123456 | +----------------------------------------------+ 1 row selected (0.415 seconds) ``` - [x] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4318 from pan3793/hive-string. Closes #4316 ba9016f6 [Cheng Pan] nit 8be774b4 [Cheng Pan] nit bd696fe3 [Cheng Pan] nit b5cf051c [Cheng Pan] fix dd6b7021 [Cheng Pan] test 63edd34d [Cheng Pan] nit 37cc70af [Cheng Pan] Fix python ut c66ad22d [Cheng Pan] [KYUUBI #4316] Fix returned Timestamp values may lose precision 41d94445 [Cheng Pan] Revert "[KYUUBI #3958] Fix Spark session timezone format" Authored-by: Cheng Pan Signed-off-by: Cheng Pan --- .../spark/operation/ExecutePython.scala | 14 ++-- .../spark/operation/SparkOperation.scala | 51 +++++++----- .../kyuubi/engine/spark/schema/RowSet.scala | 82 +++---------------- .../spark/sql/kyuubi/SparkDatasetHelper.scala | 10 +-- .../engine/spark/schema/RowSetSuite.scala | 31 ++++--- .../org/apache/kyuubi/util/RowSetUtils.scala | 64 +++------------ .../kyuubi/operation/SparkDataTypeTests.scala | 35 +++++++- .../hive/arrow/ArrowColumnarBatchRow.java | 16 +++- .../engine/spark/SparkSqlEngineSuite.scala | 4 +- 9 files changed, 123 insertions(+), 184 deletions(-) diff --git a/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/operation/ExecutePython.scala b/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/operation/ExecutePython.scala index e48ff6e5b..d2627fd99 100644 --- a/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/operation/ExecutePython.scala +++ b/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/operation/ExecutePython.scala @@ -88,9 +88,9 @@ class ExecutePython( val output = response.map(_.content.getOutput()).getOrElse("") val ename = response.map(_.content.getEname()).getOrElse("") val evalue = response.map(_.content.getEvalue()).getOrElse("") - val traceback = response.map(_.content.getTraceback()).getOrElse(Array.empty) + val traceback = response.map(_.content.getTraceback()).getOrElse(Seq.empty) iter = - new ArrayFetchIterator[Row](Array(Row(output, status, ename, evalue, Row(traceback: _*)))) + new ArrayFetchIterator[Row](Array(Row(output, status, ename, evalue, traceback))) setState(OperationState.FINISHED) } else { throw KyuubiSQLException(s"Interpret error:\n$statement\n $response") @@ -210,7 +210,7 @@ case class SessionPythonWorker( stdin.flush() val pythonResponse = Option(stdout.readLine()).map(ExecutePython.fromJson[PythonResponse](_)) // throw exception if internal python code fail - if (internal && pythonResponse.map(_.content.status) != Some(PythonResponse.OK_STATUS)) { + if (internal && !pythonResponse.map(_.content.status).contains(PythonResponse.OK_STATUS)) { throw KyuubiSQLException(s"Internal python code $code failure: $pythonResponse") } pythonResponse @@ -328,7 +328,7 @@ object ExecutePython extends Logging { } // for test - def defaultSparkHome(): String = { + def defaultSparkHome: String = { val homeDirFilter: FilenameFilter = (dir: File, name: String) => dir.isDirectory && name.contains("spark-") && !name.contains("-engine") // get from kyuubi-server/../externals/kyuubi-download/target @@ -418,7 +418,7 @@ case class PythonResponseContent( data: Map[String, String], ename: String, evalue: String, - traceback: Array[String], + traceback: Seq[String], status: String) { def getOutput(): String = { Option(data) @@ -431,7 +431,7 @@ case class PythonResponseContent( def getEvalue(): String = { Option(evalue).getOrElse("") } - def getTraceback(): Array[String] = { - Option(traceback).getOrElse(Array.empty) + def getTraceback(): Seq[String] = { + Option(traceback).getOrElse(Seq.empty) } } diff --git a/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/operation/SparkOperation.scala b/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/operation/SparkOperation.scala index b62ef6745..06884534d 100644 --- a/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/operation/SparkOperation.scala +++ b/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/operation/SparkOperation.scala @@ -24,6 +24,7 @@ import org.apache.hive.service.rpc.thrift.{TGetResultSetMetadataResp, TProgressU import org.apache.spark.kyuubi.{SparkProgressMonitor, SQLOperationListener} import org.apache.spark.kyuubi.SparkUtilsHelper.redact import org.apache.spark.sql.{DataFrame, Row, SparkSession} +import org.apache.spark.sql.internal.SQLConf import org.apache.spark.sql.types.StructType import org.apache.kyuubi.{KyuubiSQLException, Utils} @@ -135,27 +136,35 @@ abstract class SparkOperation(session: Session) spark.sparkContext.setLocalProperty protected def withLocalProperties[T](f: => T): T = { - try { - spark.sparkContext.setJobGroup(statementId, redactedStatement, forceCancel) - spark.sparkContext.setLocalProperty(KYUUBI_SESSION_USER_KEY, session.user) - spark.sparkContext.setLocalProperty(KYUUBI_STATEMENT_ID_KEY, statementId) - schedulerPool match { - case Some(pool) => - spark.sparkContext.setLocalProperty(SPARK_SCHEDULER_POOL_KEY, pool) - case None => - } - if (isSessionUserSignEnabled) { - setSessionUserSign() - } + SQLConf.withExistingConf(spark.sessionState.conf) { + val originalSession = SparkSession.getActiveSession + try { + SparkSession.setActiveSession(spark) + spark.sparkContext.setJobGroup(statementId, redactedStatement, forceCancel) + spark.sparkContext.setLocalProperty(KYUUBI_SESSION_USER_KEY, session.user) + spark.sparkContext.setLocalProperty(KYUUBI_STATEMENT_ID_KEY, statementId) + schedulerPool match { + case Some(pool) => + spark.sparkContext.setLocalProperty(SPARK_SCHEDULER_POOL_KEY, pool) + case None => + } + if (isSessionUserSignEnabled) { + setSessionUserSign() + } - f - } finally { - spark.sparkContext.setLocalProperty(SPARK_SCHEDULER_POOL_KEY, null) - spark.sparkContext.setLocalProperty(KYUUBI_SESSION_USER_KEY, null) - spark.sparkContext.setLocalProperty(KYUUBI_STATEMENT_ID_KEY, null) - spark.sparkContext.clearJobGroup() - if (isSessionUserSignEnabled) { - clearSessionUserSign() + f + } finally { + spark.sparkContext.setLocalProperty(SPARK_SCHEDULER_POOL_KEY, null) + spark.sparkContext.setLocalProperty(KYUUBI_SESSION_USER_KEY, null) + spark.sparkContext.setLocalProperty(KYUUBI_STATEMENT_ID_KEY, null) + spark.sparkContext.clearJobGroup() + if (isSessionUserSignEnabled) { + clearSessionUserSign() + } + originalSession match { + case Some(session) => SparkSession.setActiveSession(session) + case None => SparkSession.clearActiveSession() + } } } } @@ -246,7 +255,7 @@ abstract class SparkOperation(session: Session) } else { val taken = iter.take(rowSetSize) RowSet.toTRowSet( - taken.toList.asInstanceOf[List[Row]], + taken.toSeq.asInstanceOf[Seq[Row]], resultSchema, getProtocolVersion, timeZone) diff --git a/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/schema/RowSet.scala b/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/schema/RowSet.scala index 8cc88156b..7be70403d 100644 --- a/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/schema/RowSet.scala +++ b/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/schema/RowSet.scala @@ -18,22 +18,25 @@ package org.apache.kyuubi.engine.spark.schema import java.nio.ByteBuffer -import java.nio.charset.StandardCharsets -import java.sql.Timestamp -import java.time._ -import java.util.Date +import java.time.ZoneId import scala.collection.JavaConverters._ import org.apache.hive.service.rpc.thrift._ import org.apache.spark.sql.Row +import org.apache.spark.sql.execution.HiveResult import org.apache.spark.sql.types._ -import org.apache.kyuubi.engine.spark.schema.SchemaHelper.TIMESTAMP_NTZ import org.apache.kyuubi.util.RowSetUtils._ object RowSet { + def toHiveString(valueAndType: (Any, DataType), nested: Boolean = false): String = { + // compatible w/ Spark 3.1 and above + val timeFormatters = HiveResult.getTimeFormatters + HiveResult.toHiveString(valueAndType, nested, timeFormatters) + } + def toTRowSet( bytes: Array[Byte], protocolVersion: TProtocolVersion): TRowSet = { @@ -68,9 +71,9 @@ object RowSet { } def toRowBasedSet(rows: Seq[Row], schema: StructType, timeZone: ZoneId): TRowSet = { - var i = 0 val rowSize = rows.length val tRows = new java.util.ArrayList[TRow](rowSize) + var i = 0 while (i < rowSize) { val row = rows(i) val tRow = new TRow() @@ -151,13 +154,7 @@ object RowSet { while (i < rowSize) { val row = rows(i) nulls.set(i, row.isNullAt(ordinal)) - val value = - if (row.isNullAt(ordinal)) { - "" - } else { - toHiveString((row.get(ordinal), typ), timeZone) - } - values.add(value) + values.add(toHiveString(row.get(ordinal) -> typ)) i += 1 } TColumn.stringVal(new TStringColumn(values, nulls)) @@ -238,69 +235,12 @@ object RowSet { case _ => val tStrValue = new TStringValue if (!row.isNullAt(ordinal)) { - tStrValue.setValue( - toHiveString((row.get(ordinal), types(ordinal).dataType), timeZone)) + tStrValue.setValue(toHiveString(row.get(ordinal) -> types(ordinal).dataType)) } TColumnValue.stringVal(tStrValue) } } - /** - * A simpler impl of Spark's toHiveString - */ - def toHiveString(dataWithType: (Any, DataType), timeZone: ZoneId): String = { - dataWithType match { - case (null, _) => - // Only match nulls in nested type values - "null" - - case (d: Date, DateType) => - formatDate(d) - - case (ld: LocalDate, DateType) => - formatLocalDate(ld) - - case (t: Timestamp, TimestampType) => - formatTimestamp(t, Option(timeZone)) - - case (t: LocalDateTime, ntz) if ntz.getClass.getSimpleName.equals(TIMESTAMP_NTZ) => - formatLocalDateTime(t) - - case (i: Instant, TimestampType) => - formatInstant(i, Option(timeZone)) - - case (bin: Array[Byte], BinaryType) => - new String(bin, StandardCharsets.UTF_8) - - case (decimal: java.math.BigDecimal, DecimalType()) => - decimal.toPlainString - - case (s: String, StringType) => - // Only match string in nested type values - "\"" + s + "\"" - - case (d: Duration, _) => toDayTimeIntervalString(d) - - case (p: Period, _) => toYearMonthIntervalString(p) - - case (seq: scala.collection.Seq[_], ArrayType(typ, _)) => - seq.map(v => (v, typ)).map(e => toHiveString(e, timeZone)).mkString("[", ",", "]") - - case (m: Map[_, _], MapType(kType, vType, _)) => - m.map { case (key, value) => - toHiveString((key, kType), timeZone) + ":" + toHiveString((value, vType), timeZone) - }.toSeq.sorted.mkString("{", ",", "}") - - case (struct: Row, StructType(fields)) => - struct.toSeq.zip(fields).map { case (v, t) => - s""""${t.name}":${toHiveString((v, t.dataType), timeZone)}""" - }.mkString("{", ",", "}") - - case (other, _) => - other.toString - } - } - private def toTColumn(data: Array[Byte]): TColumn = { val values = new java.util.ArrayList[ByteBuffer](1) values.add(ByteBuffer.wrap(data)) diff --git a/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/spark/sql/kyuubi/SparkDatasetHelper.scala b/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/spark/sql/kyuubi/SparkDatasetHelper.scala index 23f7df213..46c3bce4d 100644 --- a/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/spark/sql/kyuubi/SparkDatasetHelper.scala +++ b/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/spark/sql/kyuubi/SparkDatasetHelper.scala @@ -22,7 +22,7 @@ import java.time.ZoneId import org.apache.spark.rdd.RDD import org.apache.spark.sql.{DataFrame, Dataset, Row} import org.apache.spark.sql.functions._ -import org.apache.spark.sql.types.{ArrayType, DataType, MapType, StructField, StructType} +import org.apache.spark.sql.types._ import org.apache.kyuubi.engine.spark.schema.RowSet @@ -41,11 +41,11 @@ object SparkDatasetHelper { val dt = DataType.fromDDL(schemaDDL) dt match { case StructType(Array(StructField(_, st: StructType, _, _))) => - RowSet.toHiveString((row, st), timeZone) + RowSet.toHiveString((row, st), nested = true) case StructType(Array(StructField(_, at: ArrayType, _, _))) => - RowSet.toHiveString((row.toSeq.head, at), timeZone) + RowSet.toHiveString((row.toSeq.head, at), nested = true) case StructType(Array(StructField(_, mt: MapType, _, _))) => - RowSet.toHiveString((row.toSeq.head, mt), timeZone) + RowSet.toHiveString((row.toSeq.head, mt), nested = true) case _ => throw new UnsupportedOperationException } @@ -54,7 +54,7 @@ object SparkDatasetHelper { val cols = df.schema.map { case sf @ StructField(name, _: StructType, _, _) => toHiveStringUDF(quotedCol(name), lit(sf.toDDL)).as(name) - case sf @ StructField(name, (_: MapType | _: ArrayType), _, _) => + case sf @ StructField(name, _: MapType | _: ArrayType, _, _) => toHiveStringUDF(struct(quotedCol(name)), lit(sf.toDDL)).as(name) case StructField(name, _, _, _) => quotedCol(name) } diff --git a/externals/kyuubi-spark-sql-engine/src/test/scala/org/apache/kyuubi/engine/spark/schema/RowSetSuite.scala b/externals/kyuubi-spark-sql-engine/src/test/scala/org/apache/kyuubi/engine/spark/schema/RowSetSuite.scala index 803eea3e6..a999563ea 100644 --- a/externals/kyuubi-spark-sql-engine/src/test/scala/org/apache/kyuubi/engine/spark/schema/RowSetSuite.scala +++ b/externals/kyuubi-spark-sql-engine/src/test/scala/org/apache/kyuubi/engine/spark/schema/RowSetSuite.scala @@ -30,7 +30,6 @@ import org.apache.spark.sql.types._ import org.apache.spark.unsafe.types.CalendarInterval import org.apache.kyuubi.KyuubiFunSuite -import org.apache.kyuubi.engine.spark.schema.RowSet.toHiveString class RowSetSuite extends KyuubiFunSuite { @@ -159,22 +158,22 @@ class RowSetSuite extends KyuubiFunSuite { val decCol = cols.next().getStringVal decCol.getValues.asScala.zipWithIndex.foreach { - case (b, 11) => assert(b.isEmpty) + case (b, 11) => assert(b === "NULL") case (b, i) => assert(b === s"$i.$i") } val dateCol = cols.next().getStringVal dateCol.getValues.asScala.zipWithIndex.foreach { - case (b, 11) => assert(b.isEmpty) + case (b, 11) => assert(b === "NULL") case (b, i) => - assert(b === toHiveString((Date.valueOf(s"2018-11-${i + 1}"), DateType), zoneId)) + assert(b === RowSet.toHiveString(Date.valueOf(s"2018-11-${i + 1}") -> DateType)) } val tsCol = cols.next().getStringVal tsCol.getValues.asScala.zipWithIndex.foreach { - case (b, 11) => assert(b.isEmpty) + case (b, 11) => assert(b === "NULL") case (b, i) => assert(b === - toHiveString((Timestamp.valueOf(s"2018-11-17 13:33:33.$i"), TimestampType), zoneId)) + RowSet.toHiveString(Timestamp.valueOf(s"2018-11-17 13:33:33.$i") -> TimestampType)) } val binCol = cols.next().getBinaryVal @@ -185,23 +184,21 @@ class RowSetSuite extends KyuubiFunSuite { val arrCol = cols.next().getStringVal arrCol.getValues.asScala.zipWithIndex.foreach { - case (b, 11) => assert(b === "") - case (b, i) => assert(b === toHiveString( - (Array.fill(i)(java.lang.Double.valueOf(s"$i.$i")).toSeq, ArrayType(DoubleType)), - zoneId)) + case (b, 11) => assert(b === "NULL") + case (b, i) => assert(b === RowSet.toHiveString( + Array.fill(i)(java.lang.Double.valueOf(s"$i.$i")).toSeq -> ArrayType(DoubleType))) } val mapCol = cols.next().getStringVal mapCol.getValues.asScala.zipWithIndex.foreach { - case (b, 11) => assert(b === "") - case (b, i) => assert(b === toHiveString( - (Map(i -> java.lang.Double.valueOf(s"$i.$i")), MapType(IntegerType, DoubleType)), - zoneId)) + case (b, 11) => assert(b === "NULL") + case (b, i) => assert(b === RowSet.toHiveString( + Map(i -> java.lang.Double.valueOf(s"$i.$i")) -> MapType(IntegerType, DoubleType))) } val intervalCol = cols.next().getStringVal intervalCol.getValues.asScala.zipWithIndex.foreach { - case (b, 11) => assert(b === "") + case (b, 11) => assert(b === "NULL") case (b, i) => assert(b === new CalendarInterval(i, i, i).toString) } } @@ -237,7 +234,7 @@ class RowSetSuite extends KyuubiFunSuite { assert(r6.get(9).getStringVal.getValue === "2018-11-06") val r7 = iter.next().getColVals - assert(r7.get(10).getStringVal.getValue === "2018-11-17 13:33:33.600") + assert(r7.get(10).getStringVal.getValue === "2018-11-17 13:33:33.6") assert(r7.get(11).getStringVal.getValue === new String( Array.fill[Byte](6)(6.toByte), StandardCharsets.UTF_8)) @@ -245,7 +242,7 @@ class RowSetSuite extends KyuubiFunSuite { val r8 = iter.next().getColVals assert(r8.get(12).getStringVal.getValue === Array.fill(7)(7.7d).mkString("[", ",", "]")) assert(r8.get(13).getStringVal.getValue === - toHiveString((Map(7 -> 7.7d), MapType(IntegerType, DoubleType)), zoneId)) + RowSet.toHiveString(Map(7 -> 7.7d) -> MapType(IntegerType, DoubleType))) val r9 = iter.next().getColVals assert(r9.get(14).getStringVal.getValue === new CalendarInterval(8, 8, 8).toString) diff --git a/kyuubi-common/src/main/scala/org/apache/kyuubi/util/RowSetUtils.scala b/kyuubi-common/src/main/scala/org/apache/kyuubi/util/RowSetUtils.scala index 82417a730..fca79c0f2 100644 --- a/kyuubi-common/src/main/scala/org/apache/kyuubi/util/RowSetUtils.scala +++ b/kyuubi-common/src/main/scala/org/apache/kyuubi/util/RowSetUtils.scala @@ -18,14 +18,11 @@ package org.apache.kyuubi.util import java.nio.ByteBuffer -import java.sql.Timestamp -import java.time.{Duration, Instant, LocalDate, LocalDateTime, Period, ZoneId} +import java.time.{Instant, LocalDate, LocalDateTime, ZoneId} import java.time.chrono.IsoChronology -import java.time.format.DateTimeFormatter import java.time.format.DateTimeFormatterBuilder import java.time.temporal.ChronoField -import java.util.{Date, Locale, TimeZone} -import java.util.concurrent.TimeUnit +import java.util.{Date, Locale} import scala.language.implicitConversions @@ -37,24 +34,18 @@ private[kyuubi] object RowSetUtils { final private val SECOND_PER_HOUR: Long = SECOND_PER_MINUTE * 60L final private val SECOND_PER_DAY: Long = SECOND_PER_HOUR * 24L - private lazy val dateFormatter = { - createDateTimeFormatterBuilder().appendPattern("yyyy-MM-dd") - .toFormatter(Locale.US) - .withChronology(IsoChronology.INSTANCE) - } + private lazy val dateFormatter = createDateTimeFormatterBuilder() + .appendPattern("yyyy-MM-dd") + .toFormatter(Locale.US) + .withChronology(IsoChronology.INSTANCE) private lazy val legacyDateFormatter = FastDateFormat.getInstance("yyyy-MM-dd", Locale.US) - private lazy val timestampFormatter: DateTimeFormatter = { - createDateTimeFormatterBuilder().appendPattern("yyyy-MM-dd HH:mm:ss") - .appendFraction(ChronoField.NANO_OF_SECOND, 0, 9, true) - .toFormatter(Locale.US) - .withChronology(IsoChronology.INSTANCE) - } - - private lazy val legacyTimestampFormatter = { - FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss.SSS", Locale.US) - } + private lazy val timestampFormatter = createDateTimeFormatterBuilder() + .appendPattern("yyyy-MM-dd HH:mm:ss") + .appendFraction(ChronoField.NANO_OF_SECOND, 0, 9, true) + .toFormatter(Locale.US) + .withChronology(IsoChronology.INSTANCE) private def createDateTimeFormatterBuilder(): DateTimeFormatterBuilder = { new DateTimeFormatterBuilder().parseCaseInsensitive() @@ -77,40 +68,7 @@ private[kyuubi] object RowSetUtils { .getOrElse(timestampFormatter.format(i)) } - def formatTimestamp(t: Timestamp, timeZone: Option[ZoneId] = None): String = { - timeZone.map(zoneId => { - FastDateFormat.getInstance( - legacyTimestampFormatter.getPattern, - TimeZone.getTimeZone(zoneId), - legacyTimestampFormatter.getLocale) - .format(t) - }).getOrElse(legacyTimestampFormatter.format(t)) - } - implicit def bitSetToBuffer(bitSet: java.util.BitSet): ByteBuffer = { ByteBuffer.wrap(bitSet.toByteArray) } - - def toDayTimeIntervalString(d: Duration): String = { - var rest = d.getSeconds - var sign = "" - if (d.getSeconds < 0) { - sign = "-" - rest = -rest - } - val days = TimeUnit.SECONDS.toDays(rest) - rest %= SECOND_PER_DAY - val hours = TimeUnit.SECONDS.toHours(rest) - rest %= SECOND_PER_HOUR - val minutes = TimeUnit.SECONDS.toMinutes(rest) - val seconds = rest % SECOND_PER_MINUTE - f"$sign$days $hours%02d:$minutes%02d:$seconds%02d.${d.getNano}%09d" - } - - def toYearMonthIntervalString(d: Period): String = { - val years = d.getYears - val months = d.getMonths - val sign = if (years < 0 || months < 0) "-" else "" - s"$sign${Math.abs(years)}-${Math.abs(months)}" - } } diff --git a/kyuubi-common/src/test/scala/org/apache/kyuubi/operation/SparkDataTypeTests.scala b/kyuubi-common/src/test/scala/org/apache/kyuubi/operation/SparkDataTypeTests.scala index 688167703..3164ae496 100644 --- a/kyuubi-common/src/test/scala/org/apache/kyuubi/operation/SparkDataTypeTests.scala +++ b/kyuubi-common/src/test/scala/org/apache/kyuubi/operation/SparkDataTypeTests.scala @@ -159,9 +159,10 @@ trait SparkDataTypeTests extends HiveJDBCTestHelper { } } - test("execute statement - select timestamp") { + test("execute statement - select timestamp - second") { withJdbcStatement() { statement => - val resultSet = statement.executeQuery("SELECT TIMESTAMP '2018-11-17 13:33:33' AS col") + val resultSet = statement.executeQuery( + "SELECT TIMESTAMP '2018-11-17 13:33:33' AS col") assert(resultSet.next()) assert(resultSet.getTimestamp("col") === Timestamp.valueOf("2018-11-17 13:33:33")) val metaData = resultSet.getMetaData @@ -171,13 +172,39 @@ trait SparkDataTypeTests extends HiveJDBCTestHelper { } } + test("execute statement - select timestamp - millisecond") { + withJdbcStatement() { statement => + val resultSet = statement.executeQuery( + "SELECT TIMESTAMP '2018-11-17 13:33:33.12345' AS col") + assert(resultSet.next()) + assert(resultSet.getTimestamp("col") === Timestamp.valueOf("2018-11-17 13:33:33.12345")) + val metaData = resultSet.getMetaData + assert(metaData.getColumnType(1) === java.sql.Types.TIMESTAMP) + assert(metaData.getPrecision(1) === 29) + assert(metaData.getScale(1) === 9) + } + } + + test("execute statement - select timestamp - overflow") { + withJdbcStatement() { statement => + val resultSet = statement.executeQuery( + "SELECT TIMESTAMP '2018-11-17 13:33:33.1234567' AS col") + assert(resultSet.next()) + assert(resultSet.getTimestamp("col") === Timestamp.valueOf("2018-11-17 13:33:33.123456")) + val metaData = resultSet.getMetaData + assert(metaData.getColumnType(1) === java.sql.Types.TIMESTAMP) + assert(metaData.getPrecision(1) === 29) + assert(metaData.getScale(1) === 9) + } + } + test("execute statement - select timestamp_ntz") { assume(SPARK_ENGINE_VERSION >= "3.4") withJdbcStatement() { statement => val resultSet = statement.executeQuery( - "SELECT make_timestamp_ntz(2022, 03, 24, 18, 08, 31.800) AS col") + "SELECT make_timestamp_ntz(2022, 03, 24, 18, 08, 31.8888) AS col") assert(resultSet.next()) - assert(resultSet.getTimestamp("col") === Timestamp.valueOf("2022-03-24 18:08:31.800")) + assert(resultSet.getTimestamp("col") === Timestamp.valueOf("2022-03-24 18:08:31.8888")) val metaData = resultSet.getMetaData assert(metaData.getColumnType(1) === java.sql.Types.TIMESTAMP) assert(metaData.getPrecision(1) === 29) diff --git a/kyuubi-hive-jdbc/src/main/java/org/apache/kyuubi/jdbc/hive/arrow/ArrowColumnarBatchRow.java b/kyuubi-hive-jdbc/src/main/java/org/apache/kyuubi/jdbc/hive/arrow/ArrowColumnarBatchRow.java index 20ed55a1d..fa914ce5d 100644 --- a/kyuubi-hive-jdbc/src/main/java/org/apache/kyuubi/jdbc/hive/arrow/ArrowColumnarBatchRow.java +++ b/kyuubi-hive-jdbc/src/main/java/org/apache/kyuubi/jdbc/hive/arrow/ArrowColumnarBatchRow.java @@ -105,6 +105,10 @@ public Object getMap(int ordinal) { } public Object get(int ordinal, TTypeId dataType) { + long seconds; + long milliseconds; + long microseconds; + int nanos; switch (dataType) { case BOOLEAN_TYPE: return getBoolean(ordinal); @@ -127,13 +131,17 @@ public Object get(int ordinal, TTypeId dataType) { case STRING_TYPE: return getString(ordinal); case TIMESTAMP_TYPE: - return new Timestamp(getLong(ordinal) / 1000); + microseconds = getLong(ordinal); + nanos = (int) (microseconds % 1000000) * 1000; + Timestamp timestamp = new Timestamp(microseconds / 1000); + timestamp.setNanos(nanos); + return timestamp; case DATE_TYPE: return DateUtils.internalToDate(getInt(ordinal)); case INTERVAL_DAY_TIME_TYPE: - long microseconds = getLong(ordinal); - long seconds = microseconds / 1000000; - int nanos = (int) (microseconds % 1000000) * 1000; + microseconds = getLong(ordinal); + seconds = microseconds / 1000000; + nanos = (int) (microseconds % 1000000) * 1000; return new HiveIntervalDayTime(seconds, nanos); case INTERVAL_YEAR_MONTH_TYPE: return new HiveIntervalYearMonth(getInt(ordinal)); diff --git a/kyuubi-server/src/test/scala/org/apache/kyuubi/engine/spark/SparkSqlEngineSuite.scala b/kyuubi-server/src/test/scala/org/apache/kyuubi/engine/spark/SparkSqlEngineSuite.scala index 1e35d2f1d..9ab627413 100644 --- a/kyuubi-server/src/test/scala/org/apache/kyuubi/engine/spark/SparkSqlEngineSuite.scala +++ b/kyuubi-server/src/test/scala/org/apache/kyuubi/engine/spark/SparkSqlEngineSuite.scala @@ -139,13 +139,13 @@ class SparkSqlEngineSuite extends WithKyuubiServer with HiveJDBCTestHelper { val utcResultSet = statement.executeQuery("select from_utc_timestamp(from_unixtime(" + "1670404535000/1000,'yyyy-MM-dd HH:mm:ss'),'GMT+08:00')") assert(utcResultSet.next()) - assert(utcResultSet.getString(1) == "2022-12-07 17:15:35.0") + assert(utcResultSet.getString(1) === "2022-12-07 17:15:35.0") val setGMT8ResultSet = statement.executeQuery("set spark.sql.session.timeZone=GMT+8") assert(setGMT8ResultSet.next()) val gmt8ResultSet = statement.executeQuery("select from_utc_timestamp(from_unixtime(" + "1670404535000/1000,'yyyy-MM-dd HH:mm:ss'),'GMT+08:00')") assert(gmt8ResultSet.next()) - assert(gmt8ResultSet.getString(1) == "2022-12-08 01:15:35.0") + assert(gmt8ResultSet.getString(1) === "2022-12-08 01:15:35.0") } } From 536944cb70f7bebf8d067e95cc7bb4ca4ad69d2c Mon Sep 17 00:00:00 2001 From: Fu Chen Date: Wed, 15 Feb 2023 10:26:22 +0800 Subject: [PATCH 063/760] [KYUUBI #4329] Fix REST API 415 HTTP error in Swagger UI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### _Why are the changes needed?_ ![截屏2023-02-14 下午6 22 36](https://user-images.githubusercontent.com/8537877/218708645-60f7f8b2-c5f3-4f02-a1ee-2c1711b78b88.png) the curl command before this PR: ```shell curl -X 'POST' \ 'http://${ip}:10099/api/v1/sessions/f07cca06-026b-442c-8dfa-181694d2b21e/operations/statement' \ -H 'accept: application/json' \ -H 'Content-Type: */*' \ -d '{ "statement": "string", "runAsync": true, "queryTimeout": 3000 }' ``` After this PR: ```shell curl -X 'POST' \ 'http://${ip}:10099/api/v1/sessions/f07cca06-026b-442c-8dfa-181694d2b21e/operations/statement' \ -H 'accept: application/json' \ -H 'Content-Type: application/json' \ -d '{ "statement": "string", "runAsync": true, "queryTimeout": 3000 }' ``` ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [x] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4329 from cfmcgrady/swagger-ui-415. Closes #4329 bbeb920de [Fu Chen] fix swagger ui 415 error Authored-by: Fu Chen Signed-off-by: Fu Chen --- .../org/apache/kyuubi/server/api/v1/OperationsResource.scala | 1 + .../org/apache/kyuubi/server/api/v1/SessionsResource.scala | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/OperationsResource.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/OperationsResource.scala index b0d0607ce..99f1afdc3 100644 --- a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/OperationsResource.scala +++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/OperationsResource.scala @@ -37,6 +37,7 @@ import org.apache.kyuubi.server.api.ApiRequestContext @Tag(name = "Operation") @Produces(Array(MediaType.APPLICATION_JSON)) +@Consumes(Array(MediaType.APPLICATION_JSON)) private[v1] class OperationsResource extends ApiRequestContext with Logging { @ApiResponse( diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/SessionsResource.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/SessionsResource.scala index 04caa97f9..dd4a8c3a7 100644 --- a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/SessionsResource.scala +++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/SessionsResource.scala @@ -41,6 +41,7 @@ import org.apache.kyuubi.session.SessionHandle @Tag(name = "Session") @Produces(Array(MediaType.APPLICATION_JSON)) +@Consumes(Array(MediaType.APPLICATION_JSON)) private[v1] class SessionsResource extends ApiRequestContext with Logging { implicit def toSessionHandle(str: String): SessionHandle = SessionHandle.fromUUID(str) private def sessionManager = fe.be.sessionManager @@ -138,7 +139,6 @@ private[v1] class SessionsResource extends ApiRequestContext with Logging { content = Array(new Content(mediaType = MediaType.APPLICATION_JSON)), description = "Open(create) a session") @POST - @Consumes(Array(MediaType.APPLICATION_JSON)) def openSession(request: SessionOpenRequest): dto.SessionHandle = { val userName = fe.getSessionUser(request.getConfigs.asScala.toMap) val ipAddress = fe.getIpAddress From a17ccdf27a5c18b8193233c9c02c80ae5d55d08d Mon Sep 17 00:00:00 2001 From: zwangsheng <2213335496@qq.com> Date: Wed, 15 Feb 2023 05:30:04 +0000 Subject: [PATCH 064/760] [KYUUBI #4334] [REST] Rest client should catch `NoHttpResponse Exception` and retry ### _Why are the changes needed?_ [NoHttpResponseException](https://hc.apache.org/httpclient-legacy/exception-handling.html) > In some circumstances, usually when under heavy load, the web server may be able to receive requests but unable to process them. A lack of sufficient resources like worker threads is a good example. This may cause the server to drop the connection to the client without giving any response. HttpClient throws NoHttpResponseException when it encounters such a condition. In most cases it is safe to retry a method that failed with NoHttpResponseException. In case of Kyuubi Server is overloaded and does not have enough resources to allocate threads to handle the corresponding request, the request will be dropped and no response will be returned, Kyuubi Rest Client should catch this exception and retry. ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [ ] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4334 from zwangsheng/rest_client/retry_with_no_http_response. Closes #4334 d526e4ab [zwangsheng] Remove Unrelated Code 708f2496 [zwangsheng] [REST] Retry should catch NoHttpResponse Exception and retry Authored-by: zwangsheng <2213335496@qq.com> Signed-off-by: Cheng Pan --- .../src/main/java/org/apache/kyuubi/client/RestClient.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/kyuubi-rest-client/src/main/java/org/apache/kyuubi/client/RestClient.java b/kyuubi-rest-client/src/main/java/org/apache/kyuubi/client/RestClient.java index fa3544726..20e57b963 100644 --- a/kyuubi-rest-client/src/main/java/org/apache/kyuubi/client/RestClient.java +++ b/kyuubi-rest-client/src/main/java/org/apache/kyuubi/client/RestClient.java @@ -27,6 +27,7 @@ import org.apache.commons.lang3.StringUtils; import org.apache.http.HttpEntity; import org.apache.http.HttpHeaders; +import org.apache.http.NoHttpResponseException; import org.apache.http.client.HttpResponseException; import org.apache.http.client.ResponseHandler; import org.apache.http.client.methods.HttpUriRequest; @@ -164,7 +165,7 @@ private String doRequest(URI uri, String authHeader, RequestBuilder requestBuild response = httpclient.execute(httpRequest, responseHandler); LOG.debug("Response: {}", response); - } catch (ConnectException | ConnectTimeoutException e) { + } catch (ConnectException | ConnectTimeoutException | NoHttpResponseException e) { // net exception can be retried by connecting to other Kyuubi server throw new RetryableKyuubiRestException("Api request failed for " + uri.toString(), e); } catch (KyuubiRestException rethrow) { From b79ca8c4ab26d826a87599c0599f4d124754ce4b Mon Sep 17 00:00:00 2001 From: wxmimperio Date: Wed, 15 Feb 2023 11:04:08 +0000 Subject: [PATCH 065/760] [KYUUBI #4311] Fix the wrong parsing of jvm parameters in jdbc url. ### _Why are the changes needed?_ The wrong parsing of jvm parameters in jdbc url. For example: ```shell jdbc:kyuubi://127.0.0.1:2181/;serviceDiscoveryMode=zooKeeper;zooKeeperNamespace=kyuubi;user=tom?spark.driver.memory=8G;spark.driver.extraJavaOptions=-Xss256m -XX:+PrintGCDetails -XX:HeapDumpPath=/heap.hprof ``` Now because the regex `([^;]*)=([^;]*);?` only matches `=`, resulting in wrong parsing: ```shell ``` The correct parsing should be: ```shell ``` This PR change `org.apache.kyuubi.jdbc.hive.Utils` and added unit test in `org.apache.kyuubi.jdbc.hive.UtilsTest`. ### _How was this patch tested?_ - [x] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [x] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4311 from imperio-wxm/master. Closes #4311 d9e124ca [liangbowen] fix percent encoding problem 31ae63e0 [liangbowen] change `com.sun.jndi.toolkit.url.UrlUtil` to `java.net.URLEncoder` f8271ec2 [wxm] Fix the wrong parsing of jvm parameters in jdbc url. 2e93df57 [wxmimperio] Merge branch 'apache:master' into master 4c236447 [wxmimperio] Merge branch 'apache:master' into master 3e0b7764 [wxmimperio] Scala repl output log level adjusted to debug Lead-authored-by: wxmimperio Co-authored-by: liangbowen Co-authored-by: wxm Signed-off-by: Cheng Pan --- .../org/apache/kyuubi/jdbc/hive/Utils.java | 13 +++- .../apache/kyuubi/jdbc/hive/UtilsTest.java | 75 +++++++++++++++++-- 2 files changed, 79 insertions(+), 9 deletions(-) diff --git a/kyuubi-hive-jdbc/src/main/java/org/apache/kyuubi/jdbc/hive/Utils.java b/kyuubi-hive-jdbc/src/main/java/org/apache/kyuubi/jdbc/hive/Utils.java index c5b197f13..59cc67f9d 100644 --- a/kyuubi-hive-jdbc/src/main/java/org/apache/kyuubi/jdbc/hive/Utils.java +++ b/kyuubi-hive-jdbc/src/main/java/org/apache/kyuubi/jdbc/hive/Utils.java @@ -26,6 +26,7 @@ import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.apache.commons.lang3.StringUtils; import org.apache.hive.service.rpc.thrift.TStatus; import org.apache.hive.service.rpc.thrift.TStatusCode; import org.slf4j.Logger; @@ -193,12 +194,20 @@ public static JdbcConnectionParams extractURLComponents(String uri, Properties i } } + Pattern confPattern = Pattern.compile("([^;]*)([^;]*);?"); + // parse hive conf settings String confStr = jdbcURI.getQuery(); if (confStr != null) { - Matcher confMatcher = pattern.matcher(confStr); + Matcher confMatcher = confPattern.matcher(confStr); while (confMatcher.find()) { - connParams.getHiveConfs().put(confMatcher.group(1), confMatcher.group(2)); + String connParam = confMatcher.group(1); + if (StringUtils.isNotBlank(connParam) && connParam.contains("=")) { + int symbolIndex = connParam.indexOf('='); + connParams + .getHiveConfs() + .put(connParam.substring(0, symbolIndex), connParam.substring(symbolIndex + 1)); + } } } diff --git a/kyuubi-hive-jdbc/src/test/java/org/apache/kyuubi/jdbc/hive/UtilsTest.java b/kyuubi-hive-jdbc/src/test/java/org/apache/kyuubi/jdbc/hive/UtilsTest.java index c890c8731..e0594dde0 100644 --- a/kyuubi-hive-jdbc/src/test/java/org/apache/kyuubi/jdbc/hive/UtilsTest.java +++ b/kyuubi-hive-jdbc/src/test/java/org/apache/kyuubi/jdbc/hive/UtilsTest.java @@ -21,8 +21,13 @@ import static org.apache.kyuubi.jdbc.hive.Utils.extractURLComponents; import static org.junit.Assert.assertEquals; +import com.google.common.collect.ImmutableMap; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Collection; +import java.util.Map; import java.util.Properties; import org.junit.Test; import org.junit.runner.RunWith; @@ -35,23 +40,76 @@ public class UtilsTest { private String expectedPort; private String expectedCatalog; private String expectedDb; + private Map expectedHiveConf; private String uri; @Parameterized.Parameters - public static Collection data() { + public static Collection data() throws UnsupportedEncodingException { return Arrays.asList( - new String[][] { - {"localhost", "10009", null, "db", "jdbc:hive2:///db;k1=v1?k2=v2#k3=v3"}, - {"localhost", "10009", null, "default", "jdbc:hive2:///"}, - {"localhost", "10009", null, "default", "jdbc:kyuubi://"}, - {"localhost", "10009", null, "default", "jdbc:hive2://"}, - {"hostname", "10018", null, "db", "jdbc:hive2://hostname:10018/db;k1=v1?k2=v2#k3=v3"}, + new Object[][] { + { + "localhost", + "10009", + null, + "db", + new ImmutableMap.Builder().put("k2", "v2").build(), + "jdbc:hive2:///db;k1=v1?k2=v2#k3=v3" + }, + { + "localhost", + "10009", + null, + "default", + new ImmutableMap.Builder().build(), + "jdbc:hive2:///" + }, + { + "localhost", + "10009", + null, + "default", + new ImmutableMap.Builder().build(), + "jdbc:kyuubi://" + }, + { + "localhost", + "10009", + null, + "default", + new ImmutableMap.Builder().build(), + "jdbc:hive2://" + }, + { + "hostname", + "10018", + null, + "db", + new ImmutableMap.Builder().put("k2", "v2").build(), + "jdbc:hive2://hostname:10018/db;k1=v1?k2=v2#k3=v3" + }, { "hostname", "10018", "catalog", "db", + new ImmutableMap.Builder().put("k2", "v2").build(), "jdbc:hive2://hostname:10018/catalog/db;k1=v1?k2=v2#k3=v3" + }, + { + "hostname", + "10018", + "catalog", + "db", + new ImmutableMap.Builder() + .put("k2", "v2") + .put("k3", "-Xmx2g -XX:+PrintGCDetails -XX:HeapDumpPath=/heap.hprof") + .build(), + "jdbc:hive2://hostname:10018/catalog/db;k1=v1?" + + URLEncoder.encode( + "k2=v2;k3=-Xmx2g -XX:+PrintGCDetails -XX:HeapDumpPath=/heap.hprof", + StandardCharsets.UTF_8.toString()) + .replaceAll("\\+", "%20") + + "#k4=v4" } }); } @@ -61,11 +119,13 @@ public UtilsTest( String expectedPort, String expectedCatalog, String expectedDb, + Map expectedHiveConf, String uri) { this.expectedHost = expectedHost; this.expectedPort = expectedPort; this.expectedCatalog = expectedCatalog; this.expectedDb = expectedDb; + this.expectedHiveConf = expectedHiveConf; this.uri = uri; } @@ -76,5 +136,6 @@ public void testExtractURLComponents() throws JdbcUriParseException { assertEquals(Integer.parseInt(expectedPort), jdbcConnectionParams1.getPort()); assertEquals(expectedCatalog, jdbcConnectionParams1.getCatalogName()); assertEquals(expectedDb, jdbcConnectionParams1.getDbName()); + assertEquals(expectedHiveConf, jdbcConnectionParams1.getHiveConfs()); } } From 9b2ff854868fbc597be24cf9273045ab9901d068 Mon Sep 17 00:00:00 2001 From: Luning Wang Date: Thu, 16 Feb 2023 14:00:32 +0800 Subject: [PATCH 066/760] [KYUUBI #3085][DOCS] Add Hive FTS Connector ### _Why are the changes needed?_ Supply a FTS connector doc in Hive ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [x] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4337 from a49a/hive-fts-doc. Closes #3085 7ec5b73f [Luning Wang] [KYUUBI #3085][DOCS] Add Hive FTS Connector Authored-by: Luning Wang Signed-off-by: Cheng Pan --- docs/connector/hive/flink_table_store.rst | 101 ++++++++++++++++++++++ docs/connector/hive/index.rst | 1 + 2 files changed, 102 insertions(+) create mode 100644 docs/connector/hive/flink_table_store.rst diff --git a/docs/connector/hive/flink_table_store.rst b/docs/connector/hive/flink_table_store.rst new file mode 100644 index 000000000..893262189 --- /dev/null +++ b/docs/connector/hive/flink_table_store.rst @@ -0,0 +1,101 @@ +.. Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + +.. http://www.apache.org/licenses/LICENSE-2.0 + +.. Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +`Flink Table Store`_ +========== + +Flink Table Store is a unified storage to build dynamic tables for both streaming and batch processing in Flink, +supporting high-speed data ingestion and timely data query. + +.. tip:: + This article assumes that you have mastered the basic knowledge and operation of `Flink Table Store`_. + For the knowledge about Flink Table Store not mentioned in this article, + you can obtain it from its `Official Documentation`_. + +By using Kyuubi, we can run SQL queries towards Flink Table Store which is more +convenient, easy to understand, and easy to expand than directly using +Hive to manipulate Flink Table Store. + +Flink Table Store Integration +------------------- + +To enable the integration of kyuubi flink sql engine and Flink Table Store, you need to: + +- Referencing the Flink Table Store :ref:`dependencies` +- Setting the environment variable :ref:`configurations` + +.. _hive-flink-table-store-deps: + +Dependencies +************ + +The **classpath** of kyuubi hive sql engine with Iceberg supported consists of + +1. kyuubi-hive-sql-engine-\ |release|\ _2.12.jar, the engine jar deployed with Kyuubi distributions +2. a copy of hive distribution +3. flink-table-store-hive-connector-_.jar (example: flink-table-store-hive-connector-0.4.0_3.1.jar), which can be found in the `Installation Table Store in Hive`_ + +In order to make the Hive packages visible for the runtime classpath of engines, we can use one of these methods: + +1. You can create an auxlib folder under the root directory of Hive, and copy flink-table-store-hive-connector-0.4.0_3.1.jar into auxlib. +2. Execute ADD JAR statement in the Kyuubi to add dependencies to Hive’s auxiliary classpath. For example: + +.. code-block:: sql + + ADD JAR /path/to/flink-table-store-hive-connector-0.4.0_3.1.jar; + +.. warning:: + The second method is not recommended. If you’re using the MR execution engine and running a join statement, you may be faced with the exception + ``org.apache.hive.com.esotericsoftware.kryo.kryoexception: unable to find class.`` + +.. warning:: + Please mind the compatibility of different Flink Table Store and Hive versions, which can be confirmed on the page of `Flink Table Store multi engine support`_. + +.. _hive-flink-table-store-conf: + +Configurations +************** + +If you are using HDFS, make sure that the environment variable HADOOP_HOME or HADOOP_CONF_DIR is set. + +Flink Table Store Operations +------------------ + +Flink Table Store only supports only reading table store tables through Hive. +A common scenario is to write data with Flink and read data with Hive. +You can follow this document `Flink Table Store Quick Start`_ to write data to a table store table +and then use Kyuubi Hive SQL engine to query the table with the following SQL ``SELECT`` statement. + +Taking ``Query Data`` as an example, + +.. code-block:: sql + + SELECT a, b FROM test_table ORDER BY a; + +Taking ``Query External Table`` as an example, + +.. code-block:: sql + + CREATE EXTERNAL TABLE external_test_table + STORED BY 'org.apache.flink.table.store.hive.TableStoreHiveStorageHandler' + LOCATION '/path/to/table/store/warehouse/default.db/test_table'; + + SELECT a, b FROM test_table ORDER BY a; + +.. _Flink Table Store: https://nightlies.apache.org/flink/flink-table-store-docs-stable/ +.. _Flink Table Store Quick Start: https://nightlies.apache.org/flink/flink-table-store-docs-stable/docs/try-table-store/quick-start/ +.. _Official Documentation: https://nightlies.apache.org/flink/flink-table-store-docs-release-0.4/docs/engines/hive/ +.. _Installation Table Store in Hive: https://nightlies.apache.org/flink/flink-table-store-docs-release-0.4/docs/engines/hive/#installation +.. _Flink Table Store multi engine support: https://nightlies.apache.org/flink/flink-table-store-docs-stable/docs/engines/overview/ diff --git a/docs/connector/hive/index.rst b/docs/connector/hive/index.rst index 2b2b863a6..961e1bc8b 100644 --- a/docs/connector/hive/index.rst +++ b/docs/connector/hive/index.rst @@ -19,4 +19,5 @@ Connectors for Hive SQL Query Engine .. toctree:: :maxdepth: 2 + flink_table_store iceberg From 89fe835b93ea26d1c2ce5d9991a284449d16caa2 Mon Sep 17 00:00:00 2001 From: Cheng Pan Date: Thu, 16 Feb 2023 15:05:53 +0800 Subject: [PATCH 067/760] [KYUUBI #4336] Avoid listing all schemas for Spark session catalog on schema pruning ### _Why are the changes needed?_ Some DBMS tools like DBeaver and HUE will call thrift meta api for listing catalogs, databases, and tables. The current implementation of `CatalogShim_v3_0#getSchemas` will call `listAllNamespaces` first and do schema pruning on the Spark driver, which may cause "permission denied" exception when HMS has permission control, like the ranger plugin. This PR proposes to call HMS API(through v1 session catalog) directly for `spark_catalog`, to suppress the above issue. ``` 2023-02-15 20:02:13.048 ERROR org.apache.kyuubi.server.KyuubiTBinaryFrontendService: Error getting schemas: org.apache.kyuubi.KyuubiSQLException: Error operating GetSchemas: org.apache.spark.sql.AnalysisException: org.apache.hadoop.hive.ql.metadata.HiveException: MetaException(message:Permission denied: user [user1] does not have [SELECT] privilege on [userdb1]) at org.apache.spark.sql.hive.HiveExternalCatalog.withClient(HiveExternalCatalog.scala:134) at org.apache.spark.sql.hive.HiveExternalCatalog.databaseExists(HiveExternalCatalog.scala:249) at org.apache.spark.sql.catalyst.catalog.ExternalCatalogWithListener.databaseExists(ExternalCatalogWithListener.scala:69) at org.apache.spark.sql.catalyst.catalog.SessionCatalog.databaseExists(SessionCatalog.scala:294) at org.apache.spark.sql.execution.datasources.v2.V2SessionCatalog.listNamespaces(V2SessionCatalog.scala:212) at org.apache.kyuubi.engine.spark.shim.CatalogShim_v3_0.$anonfun$listAllNamespaces$1(CatalogShim_v3_0.scala:74) at org.apache.kyuubi.engine.spark.shim.CatalogShim_v3_0.$anonfun$listAllNamespaces$1$adapted(CatalogShim_v3_0.scala:73) at scala.collection.TraversableLike.$anonfun$flatMap$1(TraversableLike.scala:293) at scala.collection.IndexedSeqOptimized.foreach(IndexedSeqOptimized.scala:36) at scala.collection.IndexedSeqOptimized.foreach$(IndexedSeqOptimized.scala:33) at scala.collection.mutable.ArrayOps$ofRef.foreach(ArrayOps.scala:198) at scala.collection.TraversableLike.flatMap(TraversableLike.scala:293) at scala.collection.TraversableLike.flatMap$(TraversableLike.scala:290) at scala.collection.mutable.ArrayOps$ofRef.flatMap(ArrayOps.scala:198) at org.apache.kyuubi.engine.spark.shim.CatalogShim_v3_0.listAllNamespaces(CatalogShim_v3_0.scala:73) at org.apache.kyuubi.engine.spark.shim.CatalogShim_v3_0.listAllNamespaces(CatalogShim_v3_0.scala:90) at org.apache.kyuubi.engine.spark.shim.CatalogShim_v3_0.getSchemasWithPattern(CatalogShim_v3_0.scala:118) at org.apache.kyuubi.engine.spark.shim.CatalogShim_v3_0.getSchemas(CatalogShim_v3_0.scala:133) at org.apache.kyuubi.engine.spark.operation.GetSchemas.runInternal(GetSchemas.scala:43) at org.apache.kyuubi.operation.AbstractOperation.run(AbstractOperation.scala:164) at org.apache.kyuubi.session.AbstractSession.runOperation(AbstractSession.scala:99) at org.apache.kyuubi.engine.spark.session.SparkSessionImpl.runOperation(SparkSessionImpl.scala:78) at org.apache.kyuubi.session.AbstractSession.getSchemas(AbstractSession.scala:150) at org.apache.kyuubi.service.AbstractBackendService.getSchemas(AbstractBackendService.scala:83) at org.apache.kyuubi.service.TFrontendService.GetSchemas(TFrontendService.scala:294) at org.apache.kyuubi.shade.org.apache.hive.service.rpc.thrift.TCLIService$Processor$GetSchemas.getResult(TCLIService.java:1617) at org.apache.kyuubi.shade.org.apache.hive.service.rpc.thrift.TCLIService$Processor$GetSchemas.getResult(TCLIService.java:1602) at org.apache.kyuubi.shade.org.apache.thrift.ProcessFunction.process(ProcessFunction.java:39) at org.apache.kyuubi.shade.org.apache.thrift.TBaseProcessor.process(TBaseProcessor.java:39) at org.apache.kyuubi.service.authentication.TSetIpAddressProcessor.process(TSetIpAddressProcessor.scala:36) at org.apache.kyuubi.shade.org.apache.thrift.server.TThreadPoolServer$WorkerProcess.run(TThreadPoolServer.java:286) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at java.lang.Thread.run(Thread.java:750) ``` ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [ ] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4336 from pan3793/list-schemas. Closes #4336 9ece864c [Cheng Pan] fix f71587e9 [Cheng Pan] Avoid listing all schemas for Spark session catalog on schema prunning Authored-by: Cheng Pan Signed-off-by: Cheng Pan --- .../kyuubi/engine/spark/shim/CatalogShim_v2_4.scala | 2 +- .../kyuubi/engine/spark/shim/CatalogShim_v3_0.scala | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/shim/CatalogShim_v2_4.scala b/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/shim/CatalogShim_v2_4.scala index 3478abc66..0f6195acf 100644 --- a/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/shim/CatalogShim_v2_4.scala +++ b/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/shim/CatalogShim_v2_4.scala @@ -41,7 +41,7 @@ class CatalogShim_v2_4 extends SparkCatalogShim { catalogName: String, schemaPattern: String): Seq[Row] = { (spark.sessionState.catalog.listDatabases(schemaPattern) ++ - getGlobalTempViewManager(spark, schemaPattern)).map(Row(_, "")) + getGlobalTempViewManager(spark, schemaPattern)).map(Row(_, SparkCatalogShim.SESSION_CATALOG)) } def setCurrentDatabase(spark: SparkSession, databaseName: String): Unit = { diff --git a/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/shim/CatalogShim_v3_0.scala b/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/shim/CatalogShim_v3_0.scala index 50e641b59..a663ba636 100644 --- a/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/shim/CatalogShim_v3_0.scala +++ b/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/shim/CatalogShim_v3_0.scala @@ -129,13 +129,12 @@ class CatalogShim_v3_0 extends CatalogShim_v2_4 { spark: SparkSession, catalogName: String, schemaPattern: String): Seq[Row] = { - val catalog = getCatalog(spark, catalogName) - var schemas = getSchemasWithPattern(catalog, schemaPattern) if (catalogName == SparkCatalogShim.SESSION_CATALOG) { - val viewMgr = getGlobalTempViewManager(spark, schemaPattern) - schemas = schemas ++ viewMgr + super.getSchemas(spark, catalogName, schemaPattern) + } else { + val catalog = getCatalog(spark, catalogName) + getSchemasWithPattern(catalog, schemaPattern).map(Row(_, catalog.name)) } - schemas.map(Row(_, catalog.name)) } override def setCurrentDatabase(spark: SparkSession, databaseName: String): Unit = { From 70f7c09b6a7a9ee84490a0c259287663e46d3451 Mon Sep 17 00:00:00 2001 From: Kent Yao Date: Thu, 16 Feb 2023 16:27:19 +0800 Subject: [PATCH 068/760] [KYUUBI #4340] Bump up markdown version from 3.3.7 to 3.4.1 ### _Why are the changes needed?_ Bump up markdown version from 3.3.7 to 3.4.1, the sphinx-markdown-tables has been upgraded to 0.0.17 in which the regression is resolved. It now conflicts with markdown 3.3.7 ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [x] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4340 from yaooqinn/markdown. Closes #4340 da950a74 [Kent Yao] Bump up markdown version form 3.3.7 to 3.4.1 Authored-by: Kent Yao Signed-off-by: Cheng Pan --- docs/requirements.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index 00a2eb136..ecc8116e7 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -17,8 +17,7 @@ # under the License. # -# we shall bypass markdown-3.4.1, see details in KYUUBI-3126 -markdown==3.3.7 +markdown==3.4.1 recommonmark==0.7.1 sphinx==4.5.0 sphinx-book-theme==0.3.3 From c489e29697c73e3eec96aa3ff602e40f9a2c798a Mon Sep 17 00:00:00 2001 From: senmiaoliu Date: Thu, 16 Feb 2023 17:05:18 +0800 Subject: [PATCH 069/760] [KYUUBI #4305][Bug] Backport HIVE-15820: comment at the head of beeline -e ### _Why are the changes needed?_ close [#4305](https://github.com/apache/kyuubi/issues/4305) ### manual tests ```sql bin/beeline -u jdbc:hive2://X:10009 -e " --asd select 1 as a " ``` ![image](https://user-images.githubusercontent.com/18713676/218910222-b829d447-e5b7-4d80-842b-2ddd4f47a26d.png) ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [x] Add screenshots for manual tests if appropriate - [ ] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4333 from lsm1/fix/beeline_comment_header. Closes #4305 f932d4ead [senmiaoliu] reformat 9a071fb40 [senmiaoliu] added multi line ut 425c53631 [senmiaoliu] added ut d4dc21a61 [senmiaoliu] comment at the head of beeline -e Authored-by: senmiaoliu Signed-off-by: Fu Chen --- .../java/org/apache/hive/beeline/KyuubiBeeLine.java | 6 ++++++ .../org/apache/hive/beeline/KyuubiCommands.java | 6 ++++-- .../org/apache/hive/beeline/KyuubiBeeLineTest.java | 13 +++++++++++++ 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/KyuubiBeeLine.java b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/KyuubiBeeLine.java index 7ca767148..92b96ccb4 100644 --- a/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/KyuubiBeeLine.java +++ b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/KyuubiBeeLine.java @@ -29,6 +29,7 @@ import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; +import org.apache.hive.common.util.HiveStringUtils; public class KyuubiBeeLine extends BeeLine { public static final String KYUUBI_BEELINE_DEFAULT_JDBC_DRIVER = @@ -192,4 +193,9 @@ int initArgs(String[] args) { } return code; } + + @Override + boolean dispatch(String line) { + return super.dispatch(HiveStringUtils.removeComments(line)); + } } diff --git a/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/KyuubiCommands.java b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/KyuubiCommands.java index aaa32739a..1a15638f1 100644 --- a/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/KyuubiCommands.java +++ b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/KyuubiCommands.java @@ -23,6 +23,7 @@ import java.sql.*; import java.util.*; import org.apache.hive.beeline.logs.KyuubiBeelineInPlaceUpdateStream; +import org.apache.hive.common.util.HiveStringUtils; import org.apache.kyuubi.jdbc.hive.KyuubiStatement; import org.apache.kyuubi.jdbc.hive.Utils; import org.apache.kyuubi.jdbc.hive.logs.InPlaceUpdateStream; @@ -499,7 +500,7 @@ public boolean connect(Properties props) throws IOException { @Override public String handleMultiLineCmd(String line) throws IOException { - int[] startQuote = {-1}; + line = HiveStringUtils.removeComments(line); Character mask = (System.getProperty("jline.terminal", "").equals("jline.UnsupportedTerminal")) ? null @@ -530,7 +531,8 @@ public String handleMultiLineCmd(String line) throws IOException { if (extra == null) { // it happens when using -f and the line of cmds does not end with ; break; } - if (!extra.isEmpty()) { + extra = HiveStringUtils.removeComments(extra); + if (extra != null && !extra.isEmpty()) { line += "\n" + extra; } } diff --git a/kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/KyuubiBeeLineTest.java b/kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/KyuubiBeeLineTest.java index b144c95c6..d571d9362 100644 --- a/kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/KyuubiBeeLineTest.java +++ b/kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/KyuubiBeeLineTest.java @@ -29,4 +29,17 @@ public void testKyuubiBeelineWithoutArgs() { int result = kyuubiBeeLine.initArgs(new String[0]); assertEquals(0, result); } + + @Test + public void testKyuubiBeelineComment() { + KyuubiBeeLine kyuubiBeeLine = new KyuubiBeeLine(); + int result = kyuubiBeeLine.initArgsFromCliVars(new String[] {"-e", "--comment show database;"}); + assertEquals(0, result); + result = kyuubiBeeLine.initArgsFromCliVars(new String[] {"-e", "--comment\n show database;"}); + assertEquals(1, result); + result = + kyuubiBeeLine.initArgsFromCliVars( + new String[] {"-e", "--comment line 1 \n --comment line 2 \n show database;"}); + assertEquals(1, result); + } } From 6688b3dacf80ac519f6a0d56f8168d15166f5916 Mon Sep 17 00:00:00 2001 From: ulysses-you Date: Thu, 16 Feb 2023 17:53:55 +0800 Subject: [PATCH 070/760] [KYUUBI #4328] Make Trino jdbc driver work ### _Why are the changes needed?_ according to `io.trino.jdbc.ColumnInfo`, there are some type requring signature parameter. - varchar(n) - char(n) - decimal(precision, scale) It failed with trino jdbc now image ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [x] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4328 from ulysses-you/fix-signature. Closes #4328 aede5cec [ulysses-you] nit ae1a7968 [ulysses-you] fix test 8ecdb346 [ulysses-you] Make Trino jdbc driver work Authored-by: ulysses-you Signed-off-by: ulyssesyou --- .github/workflows/master.yml | 6 +- .../it/trino/server/TrinoFrontendSuite.scala | 61 ++++++ .../kyuubi/operation/HiveMetadataTests.scala | 188 +++++++++++++++++- .../kyuubi/operation/SparkMetadataTests.scala | 187 ----------------- .../kyuubi/client/api/v1/dto/VersionInfo.java | 7 +- .../server/trino/api/TrinoContext.scala | 128 +++++++++--- .../trino/api/TrinoScalaObjectMapper.scala | 12 +- .../trino/api/v1/StatementResource.scala | 3 +- .../kyuubi/server/trino/api/v1/dto/Ok.java | 11 +- 9 files changed, 383 insertions(+), 220 deletions(-) create mode 100644 integration-tests/kyuubi-trino-it/src/test/scala/org/apache/kyuubi/it/trino/server/TrinoFrontendSuite.scala diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index ed7403fbc..26d231297 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -294,9 +294,9 @@ jobs: check-latest: false - name: Build and test Trino with maven w/o linters run: | - TEST_MODULES="externals/kyuubi-trino-engine,integration-tests/kyuubi-trino-it" - ./build/mvn ${MVN_OPT} -pl ${TEST_MODULES} -am clean install -DskipTests - ./build/mvn ${MVN_OPT} -pl ${TEST_MODULES} test + TEST_MODULES="kyuubi-server,externals/kyuubi-trino-engine,externals/kyuubi-spark-sql-engine,externals/kyuubi-download,integration-tests/kyuubi-trino-it" + ./build/mvn ${MVN_OPT} -pl ${TEST_MODULES} -am -Pflink-provided -Phive-provided clean install -DskipTests + ./build/mvn -Dmaven.javadoc.skip=true -Drat.skip=true -Dscalastyle.skip=true -Dspotless.check.skip -pl ${TEST_MODULES} -am -Pflink-provided -Phive-provided test -Dtest=none -DwildcardSuites=org.apache.kyuubi.it.trino.operation.TrinoOperationSuite,org.apache.kyuubi.it.trino.server.TrinoFrontendSuite - name: Upload test logs if: failure() uses: actions/upload-artifact@v3 diff --git a/integration-tests/kyuubi-trino-it/src/test/scala/org/apache/kyuubi/it/trino/server/TrinoFrontendSuite.scala b/integration-tests/kyuubi-trino-it/src/test/scala/org/apache/kyuubi/it/trino/server/TrinoFrontendSuite.scala new file mode 100644 index 000000000..bd8bf3eda --- /dev/null +++ b/integration-tests/kyuubi-trino-it/src/test/scala/org/apache/kyuubi/it/trino/server/TrinoFrontendSuite.scala @@ -0,0 +1,61 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.kyuubi.it.trino.server + +import scala.util.control.NonFatal + +import org.apache.kyuubi.WithKyuubiServer +import org.apache.kyuubi.config.KyuubiConf +import org.apache.kyuubi.operation.SparkMetadataTests + +/** + * This test is for Trino jdbc driver with Kyuubi Server and Spark engine: + * + * ------------------------------------------------------------- + * | JDBC | + * | Trino-driver ----> Kyuubi Server --> Spark Engine | + * | | + * ------------------------------------------------------------- + */ +class TrinoFrontendSuite extends WithKyuubiServer with SparkMetadataTests { + // TODO: Add more test cases + + override protected val conf: KyuubiConf = { + KyuubiConf().set(KyuubiConf.FRONTEND_PROTOCOLS, Seq("TRINO")) + } + override protected def jdbcUrl: String = { + s"jdbc:trino://${server.frontendServices.head.connectionUrl}/;" + } + + // trino jdbc driver requires enable SSL if specify password + override protected val password: String = "" + + override def beforeAll(): Unit = { + super.beforeAll() + + // eagerly start spark engine before running test, it's a workaround for trino jdbc driver + // since it does not support changing http connect timeout + try { + withJdbcStatement() { statement => + statement.execute("SELECT 1") + } + } catch { + case NonFatal(e) => + } + } +} diff --git a/kyuubi-common/src/test/scala/org/apache/kyuubi/operation/HiveMetadataTests.scala b/kyuubi-common/src/test/scala/org/apache/kyuubi/operation/HiveMetadataTests.scala index fe1f5f47b..aad31d5b8 100644 --- a/kyuubi-common/src/test/scala/org/apache/kyuubi/operation/HiveMetadataTests.scala +++ b/kyuubi-common/src/test/scala/org/apache/kyuubi/operation/HiveMetadataTests.scala @@ -17,7 +17,11 @@ package org.apache.kyuubi.operation -import org.apache.kyuubi.Utils +import java.sql.{DatabaseMetaData, ResultSet, SQLException, SQLFeatureNotSupportedException} + +import scala.util.Random + +import org.apache.kyuubi.{KYUUBI_VERSION, KyuubiSQLException, Utils} import org.apache.kyuubi.operation.meta.ResultSetSchemaConstant._ // For `hive` external catalog only @@ -98,4 +102,186 @@ trait HiveMetadataTests extends SparkMetadataTests { statement.execute(s"DROP VIEW IF EXISTS ${schemas(3)}.$view_global_test") } } + + test("audit Kyuubi Hive JDBC connection common MetaData") { + withJdbcStatement() { statement => + val metaData = statement.getConnection.getMetaData + Seq( + () => metaData.allProceduresAreCallable(), + () => metaData.getURL, + () => metaData.getUserName, + () => metaData.isReadOnly, + () => metaData.nullsAreSortedHigh, + () => metaData.nullsAreSortedLow, + () => metaData.nullsAreSortedAtStart(), + () => metaData.nullsAreSortedAtEnd(), + () => metaData.usesLocalFiles(), + () => metaData.usesLocalFilePerTable(), + () => metaData.supportsMixedCaseIdentifiers(), + () => metaData.supportsMixedCaseQuotedIdentifiers(), + () => metaData.storesUpperCaseIdentifiers(), + () => metaData.storesUpperCaseQuotedIdentifiers(), + () => metaData.storesLowerCaseIdentifiers(), + () => metaData.storesLowerCaseQuotedIdentifiers(), + () => metaData.storesMixedCaseIdentifiers(), + () => metaData.storesMixedCaseQuotedIdentifiers(), + () => metaData.nullPlusNonNullIsNull, + () => metaData.supportsConvert, + () => metaData.supportsTableCorrelationNames, + () => metaData.supportsDifferentTableCorrelationNames, + () => metaData.supportsExpressionsInOrderBy(), + () => metaData.supportsOrderByUnrelated, + () => metaData.supportsGroupByUnrelated, + () => metaData.supportsGroupByBeyondSelect, + () => metaData.supportsLikeEscapeClause, + () => metaData.supportsMultipleTransactions, + () => metaData.supportsMinimumSQLGrammar, + () => metaData.supportsCoreSQLGrammar, + () => metaData.supportsExtendedSQLGrammar, + () => metaData.supportsANSI92EntryLevelSQL, + () => metaData.supportsANSI92IntermediateSQL, + () => metaData.supportsANSI92FullSQL, + () => metaData.supportsIntegrityEnhancementFacility, + () => metaData.isCatalogAtStart, + () => metaData.supportsSubqueriesInComparisons, + () => metaData.supportsSubqueriesInExists, + () => metaData.supportsSubqueriesInIns, + () => metaData.supportsSubqueriesInQuantifieds, + // Spark support this, see https://issues.apache.org/jira/browse/SPARK-18455 + () => metaData.supportsCorrelatedSubqueries, + () => metaData.supportsOpenCursorsAcrossCommit, + () => metaData.supportsOpenCursorsAcrossRollback, + () => metaData.supportsOpenStatementsAcrossCommit, + () => metaData.supportsOpenStatementsAcrossRollback, + () => metaData.getMaxBinaryLiteralLength, + () => metaData.getMaxCharLiteralLength, + () => metaData.getMaxColumnsInGroupBy, + () => metaData.getMaxColumnsInIndex, + () => metaData.getMaxColumnsInOrderBy, + () => metaData.getMaxColumnsInSelect, + () => metaData.getMaxColumnsInTable, + () => metaData.getMaxConnections, + () => metaData.getMaxCursorNameLength, + () => metaData.getMaxIndexLength, + () => metaData.getMaxSchemaNameLength, + () => metaData.getMaxProcedureNameLength, + () => metaData.getMaxCatalogNameLength, + () => metaData.getMaxRowSize, + () => metaData.doesMaxRowSizeIncludeBlobs, + () => metaData.getMaxStatementLength, + () => metaData.getMaxStatements, + () => metaData.getMaxTableNameLength, + () => metaData.getMaxTablesInSelect, + () => metaData.getMaxUserNameLength, + () => metaData.supportsTransactionIsolationLevel(1), + () => metaData.supportsDataDefinitionAndDataManipulationTransactions, + () => metaData.supportsDataManipulationTransactionsOnly, + () => metaData.dataDefinitionCausesTransactionCommit, + () => metaData.dataDefinitionIgnoredInTransactions, + () => metaData.getColumnPrivileges("", "%", "%", "%"), + () => metaData.getTablePrivileges("", "%", "%"), + () => metaData.getBestRowIdentifier("", "%", "%", 0, true), + () => metaData.getVersionColumns("", "%", "%"), + () => metaData.getExportedKeys("", "default", ""), + () => metaData.supportsResultSetConcurrency(ResultSet.TYPE_FORWARD_ONLY, 2), + () => metaData.ownUpdatesAreVisible(ResultSet.TYPE_FORWARD_ONLY), + () => metaData.ownDeletesAreVisible(ResultSet.TYPE_FORWARD_ONLY), + () => metaData.ownInsertsAreVisible(ResultSet.TYPE_FORWARD_ONLY), + () => metaData.othersUpdatesAreVisible(ResultSet.TYPE_FORWARD_ONLY), + () => metaData.othersDeletesAreVisible(ResultSet.TYPE_FORWARD_ONLY), + () => metaData.othersInsertsAreVisible(ResultSet.TYPE_FORWARD_ONLY), + () => metaData.updatesAreDetected(ResultSet.TYPE_FORWARD_ONLY), + () => metaData.deletesAreDetected(ResultSet.TYPE_FORWARD_ONLY), + () => metaData.insertsAreDetected(ResultSet.TYPE_FORWARD_ONLY), + () => metaData.supportsNamedParameters(), + () => metaData.supportsMultipleOpenResults, + () => metaData.supportsGetGeneratedKeys, + () => metaData.getSuperTypes("", "%", "%"), + () => metaData.getSuperTables("", "%", "%"), + () => metaData.getAttributes("", "%", "%", "%"), + () => metaData.getResultSetHoldability, + () => metaData.locatorsUpdateCopy, + () => metaData.supportsStatementPooling, + () => metaData.getRowIdLifetime, + () => metaData.supportsStoredFunctionsUsingCallSyntax, + () => metaData.autoCommitFailureClosesAllResultSets, + () => metaData.getFunctionColumns("", "%", "%", "%"), + () => metaData.getPseudoColumns("", "%", "%", "%"), + () => metaData.generatedKeyAlwaysReturned).foreach { func => + val e = intercept[SQLFeatureNotSupportedException](func()) + assert(e.getMessage === "Method not supported") + } + + assert(metaData.allTablesAreSelectable) + assert(metaData.getClientInfoProperties.next) + assert(metaData.getDriverName === "Kyuubi Project Hive JDBC Client" || + metaData.getDriverName === "Kyuubi Project Hive JDBC Shaded Client") + assert(metaData.getDriverVersion === KYUUBI_VERSION) + assert( + metaData.getIdentifierQuoteString === " ", + "This method returns a space \" \" if identifier quoting is not supported") + assert(metaData.getNumericFunctions === "") + assert(metaData.getStringFunctions === "") + assert(metaData.getSystemFunctions === "") + assert(metaData.getTimeDateFunctions === "") + assert(metaData.getSearchStringEscape === "\\") + assert(metaData.getExtraNameCharacters === "") + assert(metaData.supportsAlterTableWithAddColumn()) + assert(!metaData.supportsAlterTableWithDropColumn()) + assert(metaData.supportsColumnAliasing()) + assert(metaData.supportsGroupBy) + assert(!metaData.supportsMultipleResultSets) + assert(!metaData.supportsNonNullableColumns) + assert(metaData.supportsOuterJoins) + assert(metaData.supportsFullOuterJoins) + assert(metaData.supportsLimitedOuterJoins) + assert(metaData.getSchemaTerm === "database") + assert(metaData.getProcedureTerm === "UDF") + assert(metaData.getCatalogTerm === "catalog") + assert(metaData.getCatalogSeparator === ".") + assert(metaData.supportsSchemasInDataManipulation) + assert(!metaData.supportsSchemasInProcedureCalls) + assert(metaData.supportsSchemasInTableDefinitions) + assert(!metaData.supportsSchemasInIndexDefinitions) + assert(!metaData.supportsSchemasInPrivilegeDefinitions) + assert(metaData.supportsCatalogsInDataManipulation) + assert(metaData.supportsCatalogsInProcedureCalls) + assert(metaData.supportsCatalogsInTableDefinitions) + assert(metaData.supportsCatalogsInIndexDefinitions) + assert(metaData.supportsCatalogsInPrivilegeDefinitions) + assert(!metaData.supportsPositionedDelete) + assert(!metaData.supportsPositionedUpdate) + assert(!metaData.supportsSelectForUpdate) + assert(!metaData.supportsStoredProcedures) + // This is actually supported, but hive jdbc package return false + assert(!metaData.supportsUnion) + assert(metaData.supportsUnionAll) + assert(metaData.getMaxColumnNameLength === 128) + assert(metaData.getDefaultTransactionIsolation === java.sql.Connection.TRANSACTION_NONE) + assert(!metaData.supportsTransactions) + assert(!metaData.getProcedureColumns("", "%", "%", "%").next()) + val e1 = intercept[SQLException] { + metaData.getPrimaryKeys("", "default", "src").next() + } + assert(e1.getMessage.contains(KyuubiSQLException.featureNotSupported().getMessage)) + assert(!metaData.getImportedKeys("", "default", "").next()) + + val e2 = intercept[SQLException] { + metaData.getCrossReference("", "default", "src", "", "default", "src2").next() + } + assert(e2.getMessage.contains(KyuubiSQLException.featureNotSupported().getMessage)) + assert(!metaData.getIndexInfo("", "default", "src", true, true).next()) + + assert(metaData.supportsResultSetType(new Random().nextInt())) + assert(!metaData.supportsBatchUpdates) + assert(!metaData.getUDTs(",", "%", "%", null).next()) + assert(!metaData.supportsSavepoints) + assert(!metaData.supportsResultSetHoldability(ResultSet.HOLD_CURSORS_OVER_COMMIT)) + assert(metaData.getJDBCMajorVersion === 3) + assert(metaData.getJDBCMinorVersion === 0) + assert(metaData.getSQLStateType === DatabaseMetaData.sqlStateSQL) + assert(metaData.getMaxLogicalLobSize === 0) + assert(!metaData.supportsRefCursors) + } + } } diff --git a/kyuubi-common/src/test/scala/org/apache/kyuubi/operation/SparkMetadataTests.scala b/kyuubi-common/src/test/scala/org/apache/kyuubi/operation/SparkMetadataTests.scala index 4faf5bba4..97099ce47 100644 --- a/kyuubi-common/src/test/scala/org/apache/kyuubi/operation/SparkMetadataTests.scala +++ b/kyuubi-common/src/test/scala/org/apache/kyuubi/operation/SparkMetadataTests.scala @@ -17,11 +17,6 @@ package org.apache.kyuubi.operation -import java.sql.{DatabaseMetaData, ResultSet, SQLException, SQLFeatureNotSupportedException} - -import scala.util.Random - -import org.apache.kyuubi.{KYUUBI_VERSION, KyuubiSQLException} import org.apache.kyuubi.operation.meta.ResultSetSchemaConstant._ // For both `in-memory` and `hive` external catalog @@ -292,186 +287,4 @@ trait SparkMetadataTests extends HiveJDBCTestHelper { assert(typeInfo.getInt(NUM_PREC_RADIX) === 0) } } - - test("audit Kyuubi Hive JDBC connection common MetaData") { - withJdbcStatement() { statement => - val metaData = statement.getConnection.getMetaData - Seq( - () => metaData.allProceduresAreCallable(), - () => metaData.getURL, - () => metaData.getUserName, - () => metaData.isReadOnly, - () => metaData.nullsAreSortedHigh, - () => metaData.nullsAreSortedLow, - () => metaData.nullsAreSortedAtStart(), - () => metaData.nullsAreSortedAtEnd(), - () => metaData.usesLocalFiles(), - () => metaData.usesLocalFilePerTable(), - () => metaData.supportsMixedCaseIdentifiers(), - () => metaData.supportsMixedCaseQuotedIdentifiers(), - () => metaData.storesUpperCaseIdentifiers(), - () => metaData.storesUpperCaseQuotedIdentifiers(), - () => metaData.storesLowerCaseIdentifiers(), - () => metaData.storesLowerCaseQuotedIdentifiers(), - () => metaData.storesMixedCaseIdentifiers(), - () => metaData.storesMixedCaseQuotedIdentifiers(), - () => metaData.nullPlusNonNullIsNull, - () => metaData.supportsConvert, - () => metaData.supportsTableCorrelationNames, - () => metaData.supportsDifferentTableCorrelationNames, - () => metaData.supportsExpressionsInOrderBy(), - () => metaData.supportsOrderByUnrelated, - () => metaData.supportsGroupByUnrelated, - () => metaData.supportsGroupByBeyondSelect, - () => metaData.supportsLikeEscapeClause, - () => metaData.supportsMultipleTransactions, - () => metaData.supportsMinimumSQLGrammar, - () => metaData.supportsCoreSQLGrammar, - () => metaData.supportsExtendedSQLGrammar, - () => metaData.supportsANSI92EntryLevelSQL, - () => metaData.supportsANSI92IntermediateSQL, - () => metaData.supportsANSI92FullSQL, - () => metaData.supportsIntegrityEnhancementFacility, - () => metaData.isCatalogAtStart, - () => metaData.supportsSubqueriesInComparisons, - () => metaData.supportsSubqueriesInExists, - () => metaData.supportsSubqueriesInIns, - () => metaData.supportsSubqueriesInQuantifieds, - // Spark support this, see https://issues.apache.org/jira/browse/SPARK-18455 - () => metaData.supportsCorrelatedSubqueries, - () => metaData.supportsOpenCursorsAcrossCommit, - () => metaData.supportsOpenCursorsAcrossRollback, - () => metaData.supportsOpenStatementsAcrossCommit, - () => metaData.supportsOpenStatementsAcrossRollback, - () => metaData.getMaxBinaryLiteralLength, - () => metaData.getMaxCharLiteralLength, - () => metaData.getMaxColumnsInGroupBy, - () => metaData.getMaxColumnsInIndex, - () => metaData.getMaxColumnsInOrderBy, - () => metaData.getMaxColumnsInSelect, - () => metaData.getMaxColumnsInTable, - () => metaData.getMaxConnections, - () => metaData.getMaxCursorNameLength, - () => metaData.getMaxIndexLength, - () => metaData.getMaxSchemaNameLength, - () => metaData.getMaxProcedureNameLength, - () => metaData.getMaxCatalogNameLength, - () => metaData.getMaxRowSize, - () => metaData.doesMaxRowSizeIncludeBlobs, - () => metaData.getMaxStatementLength, - () => metaData.getMaxStatements, - () => metaData.getMaxTableNameLength, - () => metaData.getMaxTablesInSelect, - () => metaData.getMaxUserNameLength, - () => metaData.supportsTransactionIsolationLevel(1), - () => metaData.supportsDataDefinitionAndDataManipulationTransactions, - () => metaData.supportsDataManipulationTransactionsOnly, - () => metaData.dataDefinitionCausesTransactionCommit, - () => metaData.dataDefinitionIgnoredInTransactions, - () => metaData.getColumnPrivileges("", "%", "%", "%"), - () => metaData.getTablePrivileges("", "%", "%"), - () => metaData.getBestRowIdentifier("", "%", "%", 0, true), - () => metaData.getVersionColumns("", "%", "%"), - () => metaData.getExportedKeys("", "default", ""), - () => metaData.supportsResultSetConcurrency(ResultSet.TYPE_FORWARD_ONLY, 2), - () => metaData.ownUpdatesAreVisible(ResultSet.TYPE_FORWARD_ONLY), - () => metaData.ownDeletesAreVisible(ResultSet.TYPE_FORWARD_ONLY), - () => metaData.ownInsertsAreVisible(ResultSet.TYPE_FORWARD_ONLY), - () => metaData.othersUpdatesAreVisible(ResultSet.TYPE_FORWARD_ONLY), - () => metaData.othersDeletesAreVisible(ResultSet.TYPE_FORWARD_ONLY), - () => metaData.othersInsertsAreVisible(ResultSet.TYPE_FORWARD_ONLY), - () => metaData.updatesAreDetected(ResultSet.TYPE_FORWARD_ONLY), - () => metaData.deletesAreDetected(ResultSet.TYPE_FORWARD_ONLY), - () => metaData.insertsAreDetected(ResultSet.TYPE_FORWARD_ONLY), - () => metaData.supportsNamedParameters(), - () => metaData.supportsMultipleOpenResults, - () => metaData.supportsGetGeneratedKeys, - () => metaData.getSuperTypes("", "%", "%"), - () => metaData.getSuperTables("", "%", "%"), - () => metaData.getAttributes("", "%", "%", "%"), - () => metaData.getResultSetHoldability, - () => metaData.locatorsUpdateCopy, - () => metaData.supportsStatementPooling, - () => metaData.getRowIdLifetime, - () => metaData.supportsStoredFunctionsUsingCallSyntax, - () => metaData.autoCommitFailureClosesAllResultSets, - () => metaData.getFunctionColumns("", "%", "%", "%"), - () => metaData.getPseudoColumns("", "%", "%", "%"), - () => metaData.generatedKeyAlwaysReturned).foreach { func => - val e = intercept[SQLFeatureNotSupportedException](func()) - assert(e.getMessage === "Method not supported") - } - - assert(metaData.allTablesAreSelectable) - assert(metaData.getClientInfoProperties.next) - assert(metaData.getDriverName === "Kyuubi Project Hive JDBC Client" || - metaData.getDriverName === "Kyuubi Project Hive JDBC Shaded Client") - assert(metaData.getDriverVersion === KYUUBI_VERSION) - assert( - metaData.getIdentifierQuoteString === " ", - "This method returns a space \" \" if identifier quoting is not supported") - assert(metaData.getNumericFunctions === "") - assert(metaData.getStringFunctions === "") - assert(metaData.getSystemFunctions === "") - assert(metaData.getTimeDateFunctions === "") - assert(metaData.getSearchStringEscape === "\\") - assert(metaData.getExtraNameCharacters === "") - assert(metaData.supportsAlterTableWithAddColumn()) - assert(!metaData.supportsAlterTableWithDropColumn()) - assert(metaData.supportsColumnAliasing()) - assert(metaData.supportsGroupBy) - assert(!metaData.supportsMultipleResultSets) - assert(!metaData.supportsNonNullableColumns) - assert(metaData.supportsOuterJoins) - assert(metaData.supportsFullOuterJoins) - assert(metaData.supportsLimitedOuterJoins) - assert(metaData.getSchemaTerm === "database") - assert(metaData.getProcedureTerm === "UDF") - assert(metaData.getCatalogTerm === "catalog") - assert(metaData.getCatalogSeparator === ".") - assert(metaData.supportsSchemasInDataManipulation) - assert(!metaData.supportsSchemasInProcedureCalls) - assert(metaData.supportsSchemasInTableDefinitions) - assert(!metaData.supportsSchemasInIndexDefinitions) - assert(!metaData.supportsSchemasInPrivilegeDefinitions) - assert(metaData.supportsCatalogsInDataManipulation) - assert(metaData.supportsCatalogsInProcedureCalls) - assert(metaData.supportsCatalogsInTableDefinitions) - assert(metaData.supportsCatalogsInIndexDefinitions) - assert(metaData.supportsCatalogsInPrivilegeDefinitions) - assert(!metaData.supportsPositionedDelete) - assert(!metaData.supportsPositionedUpdate) - assert(!metaData.supportsSelectForUpdate) - assert(!metaData.supportsStoredProcedures) - // This is actually supported, but hive jdbc package return false - assert(!metaData.supportsUnion) - assert(metaData.supportsUnionAll) - assert(metaData.getMaxColumnNameLength === 128) - assert(metaData.getDefaultTransactionIsolation === java.sql.Connection.TRANSACTION_NONE) - assert(!metaData.supportsTransactions) - assert(!metaData.getProcedureColumns("", "%", "%", "%").next()) - val e1 = intercept[SQLException] { - metaData.getPrimaryKeys("", "default", "src").next() - } - assert(e1.getMessage.contains(KyuubiSQLException.featureNotSupported().getMessage)) - assert(!metaData.getImportedKeys("", "default", "").next()) - - val e2 = intercept[SQLException] { - metaData.getCrossReference("", "default", "src", "", "default", "src2").next() - } - assert(e2.getMessage.contains(KyuubiSQLException.featureNotSupported().getMessage)) - assert(!metaData.getIndexInfo("", "default", "src", true, true).next()) - - assert(metaData.supportsResultSetType(new Random().nextInt())) - assert(!metaData.supportsBatchUpdates) - assert(!metaData.getUDTs(",", "%", "%", null).next()) - assert(!metaData.supportsSavepoints) - assert(!metaData.supportsResultSetHoldability(ResultSet.HOLD_CURSORS_OVER_COMMIT)) - assert(metaData.getJDBCMajorVersion === 3) - assert(metaData.getJDBCMinorVersion === 0) - assert(metaData.getSQLStateType === DatabaseMetaData.sqlStateSQL) - assert(metaData.getMaxLogicalLobSize === 0) - assert(!metaData.supportsRefCursors) - } - } } diff --git a/kyuubi-rest-client/src/main/java/org/apache/kyuubi/client/api/v1/dto/VersionInfo.java b/kyuubi-rest-client/src/main/java/org/apache/kyuubi/client/api/v1/dto/VersionInfo.java index 427272f41..5749c4e32 100644 --- a/kyuubi-rest-client/src/main/java/org/apache/kyuubi/client/api/v1/dto/VersionInfo.java +++ b/kyuubi-rest-client/src/main/java/org/apache/kyuubi/client/api/v1/dto/VersionInfo.java @@ -17,6 +17,8 @@ package org.apache.kyuubi.client.api.v1.dto; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; import java.util.Objects; import org.apache.commons.lang3.builder.ReflectionToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; @@ -26,10 +28,13 @@ public class VersionInfo { public VersionInfo() {} - public VersionInfo(String version) { + // Explicitly specifies JsonProperty to be compatible if disable auto detect feature + @JsonCreator + public VersionInfo(@JsonProperty("version") String version) { this.version = version; } + @JsonProperty public String getVersion() { return version; } diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/trino/api/TrinoContext.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/trino/api/TrinoContext.scala index 0c7911a46..8c85f31d7 100644 --- a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/trino/api/TrinoContext.scala +++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/trino/api/TrinoContext.scala @@ -20,13 +20,15 @@ package org.apache.kyuubi.server.trino.api import java.io.UnsupportedEncodingException import java.net.{URI, URLDecoder, URLEncoder} import java.util +import java.util.Optional import javax.ws.rs.core.{HttpHeaders, Response} import scala.collection.JavaConverters._ -import io.trino.client.{ClientStandardTypes, ClientTypeSignature, Column, QueryError, QueryResults, StatementStats, Warning} +import com.google.common.collect.ImmutableList +import io.trino.client.{ClientStandardTypes, ClientTypeSignature, ClientTypeSignatureParameter, Column, NamedClientTypeSignature, QueryError, QueryResults, RowFieldName, StatementStats, Warning} import io.trino.client.ProtocolHeaders.TRINO_HEADERS -import org.apache.hive.service.rpc.thrift.{TGetResultSetMetadataResp, TRowSet, TTypeId} +import org.apache.hive.service.rpc.thrift.{TCLIServiceConstants, TGetResultSetMetadataResp, TRowSet, TTypeEntry, TTypeId} import org.apache.kyuubi.operation.OperationState.FINISHED import org.apache.kyuubi.operation.OperationStatus @@ -216,32 +218,110 @@ object TrinoContext { 0L) } - def convertTColumn(columns: TGetResultSetMetadataResp): util.List[Column] = { + private def convertTColumn(columns: TGetResultSetMetadataResp): util.List[Column] = { columns.getSchema.getColumns.asScala.map(c => { - val tp = c.getTypeDesc.getTypes.get(0).getPrimitiveEntry.getType match { - case TTypeId.BOOLEAN_TYPE => ClientStandardTypes.BOOLEAN - case TTypeId.TINYINT_TYPE => ClientStandardTypes.TINYINT - case TTypeId.SMALLINT_TYPE => ClientStandardTypes.SMALLINT - case TTypeId.INT_TYPE => ClientStandardTypes.INTEGER - case TTypeId.BIGINT_TYPE => ClientStandardTypes.BIGINT - case TTypeId.FLOAT_TYPE => ClientStandardTypes.DOUBLE - case TTypeId.DOUBLE_TYPE => ClientStandardTypes.DOUBLE - case TTypeId.STRING_TYPE => ClientStandardTypes.VARCHAR - case TTypeId.TIMESTAMP_TYPE => ClientStandardTypes.TIMESTAMP - case TTypeId.BINARY_TYPE => ClientStandardTypes.VARBINARY - case TTypeId.DECIMAL_TYPE => ClientStandardTypes.DECIMAL - case TTypeId.DATE_TYPE => ClientStandardTypes.DATE - case TTypeId.VARCHAR_TYPE => ClientStandardTypes.VARCHAR - case TTypeId.CHAR_TYPE => ClientStandardTypes.CHAR - case TTypeId.INTERVAL_YEAR_MONTH_TYPE => ClientStandardTypes.INTERVAL_YEAR_TO_MONTH - case TTypeId.INTERVAL_DAY_TIME_TYPE => ClientStandardTypes.TIME_WITH_TIME_ZONE - case TTypeId.TIMESTAMPLOCALTZ_TYPE => ClientStandardTypes.TIMESTAMP_WITH_TIME_ZONE - case _ => ClientStandardTypes.VARCHAR - } - new Column(c.getColumnName, tp, new ClientTypeSignature(tp)) + val (tp, arguments) = toClientTypeSignature(c.getTypeDesc.getTypes.get(0)) + new Column(c.getColumnName, tp, new ClientTypeSignature(tp, arguments)) }).toList.asJava } + private def toClientTypeSignature( + entry: TTypeEntry): (String, util.List[ClientTypeSignatureParameter]) = { + // according to `io.trino.jdbc.ColumnInfo` + if (entry.isSetPrimitiveEntry) { + entry.getPrimitiveEntry.getType match { + case TTypeId.BOOLEAN_TYPE => + (ClientStandardTypes.BOOLEAN, ImmutableList.of[ClientTypeSignatureParameter]) + case TTypeId.TINYINT_TYPE => + (ClientStandardTypes.TINYINT, ImmutableList.of[ClientTypeSignatureParameter]) + case TTypeId.SMALLINT_TYPE => + (ClientStandardTypes.SMALLINT, ImmutableList.of[ClientTypeSignatureParameter]) + case TTypeId.INT_TYPE => + (ClientStandardTypes.INTEGER, ImmutableList.of[ClientTypeSignatureParameter]) + case TTypeId.BIGINT_TYPE => + (ClientStandardTypes.BIGINT, ImmutableList.of[ClientTypeSignatureParameter]) + case TTypeId.FLOAT_TYPE => + (ClientStandardTypes.DOUBLE, ImmutableList.of[ClientTypeSignatureParameter]) + case TTypeId.DOUBLE_TYPE => + (ClientStandardTypes.DOUBLE, ImmutableList.of[ClientTypeSignatureParameter]) + case TTypeId.DATE_TYPE => + (ClientStandardTypes.DATE, ImmutableList.of[ClientTypeSignatureParameter]) + case TTypeId.TIMESTAMP_TYPE => + (ClientStandardTypes.TIMESTAMP, ImmutableList.of[ClientTypeSignatureParameter]) + case TTypeId.BINARY_TYPE => + (ClientStandardTypes.VARBINARY, ImmutableList.of[ClientTypeSignatureParameter]) + case TTypeId.DECIMAL_TYPE => + val map = entry.getPrimitiveEntry.getTypeQualifiers.getQualifiers + val precision = Option(map.get(TCLIServiceConstants.PRECISION)).map(_.getI32Value) + .getOrElse(38) + val scale = Option(map.get(TCLIServiceConstants.SCALE)).map(_.getI32Value) + .getOrElse(18) + ( + ClientStandardTypes.DECIMAL, + ImmutableList.of( + ClientTypeSignatureParameter.ofLong(precision), + ClientTypeSignatureParameter.ofLong(scale))) + case TTypeId.STRING_TYPE => + ( + ClientStandardTypes.VARCHAR, + varcharSignatureParameter) + case TTypeId.VARCHAR_TYPE => + ( + ClientStandardTypes.VARCHAR, + varcharSignatureParameter) + case TTypeId.CHAR_TYPE => + (ClientStandardTypes.CHAR, ImmutableList.of(ClientTypeSignatureParameter.ofLong(65536))) + case TTypeId.INTERVAL_YEAR_MONTH_TYPE => + ( + ClientStandardTypes.INTERVAL_YEAR_TO_MONTH, + ImmutableList.of[ClientTypeSignatureParameter]) + case TTypeId.INTERVAL_DAY_TIME_TYPE => + (ClientStandardTypes.TIME_WITH_TIME_ZONE, ImmutableList.of[ClientTypeSignatureParameter]) + case TTypeId.TIMESTAMPLOCALTZ_TYPE => + ( + ClientStandardTypes.TIMESTAMP_WITH_TIME_ZONE, + ImmutableList.of[ClientTypeSignatureParameter]) + case _ => + ( + ClientStandardTypes.VARCHAR, + varcharSignatureParameter) + } + } else if (entry.isSetArrayEntry) { + // thrift does not support nested types. + // it's quite hard to follow the hive way, so always return varchar + // TODO: make complex data type more accurate + ( + ClientStandardTypes.ARRAY, + ImmutableList.of(ClientTypeSignatureParameter.ofType( + new ClientTypeSignature(ClientStandardTypes.VARCHAR, varcharSignatureParameter)))) + } else if (entry.isSetMapEntry) { + ( + ClientStandardTypes.MAP, + ImmutableList.of( + ClientTypeSignatureParameter.ofType( + new ClientTypeSignature(ClientStandardTypes.VARCHAR, varcharSignatureParameter)), + ClientTypeSignatureParameter.ofType( + new ClientTypeSignature(ClientStandardTypes.VARCHAR, varcharSignatureParameter)))) + } else if (entry.isSetStructEntry) { + val parameters = entry.getStructEntry.getNameToTypePtr.asScala.map { case (k, v) => + ClientTypeSignatureParameter.ofNamedType( + new NamedClientTypeSignature( + Optional.of(new RowFieldName(k)), + new ClientTypeSignature(ClientStandardTypes.VARCHAR, varcharSignatureParameter))) + } + ( + ClientStandardTypes.ROW, + ImmutableList.copyOf(parameters.toArray)) + } else { + throw new UnsupportedOperationException(s"Do not support type: $entry") + } + } + + private def varcharSignatureParameter: util.List[ClientTypeSignatureParameter] = { + ImmutableList.of(ClientTypeSignatureParameter.ofLong( + ClientTypeSignature.VARCHAR_UNBOUNDED_LENGTH)) + } + def convertTRowSet(rowSet: TRowSet): util.List[util.List[Object]] = { val dataResult = new util.LinkedList[util.List[Object]] diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/trino/api/TrinoScalaObjectMapper.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/trino/api/TrinoScalaObjectMapper.scala index f6055927a..33091e338 100644 --- a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/trino/api/TrinoScalaObjectMapper.scala +++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/trino/api/TrinoScalaObjectMapper.scala @@ -19,13 +19,23 @@ package org.apache.kyuubi.server.trino.api import javax.ws.rs.ext.ContextResolver -import com.fasterxml.jackson.databind.{DeserializationFeature, ObjectMapper} +import com.fasterxml.jackson.databind.{DeserializationFeature, MapperFeature, ObjectMapper} import com.fasterxml.jackson.datatype.jdk8.Jdk8Module class TrinoScalaObjectMapper extends ContextResolver[ObjectMapper] { + // refer `io.trino.client.JsonCodec` private lazy val mapper = new ObjectMapper() .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) + .disable(MapperFeature.AUTO_DETECT_CREATORS) + .disable(MapperFeature.AUTO_DETECT_FIELDS) + .disable(MapperFeature.AUTO_DETECT_SETTERS) + .disable(MapperFeature.AUTO_DETECT_GETTERS) + .disable(MapperFeature.AUTO_DETECT_IS_GETTERS) + .disable(MapperFeature.USE_GETTERS_AS_SETTERS) + .disable(MapperFeature.CAN_OVERRIDE_ACCESS_MODIFIERS) + .disable(MapperFeature.INFER_PROPERTY_MUTATORS) + .disable(MapperFeature.ALLOW_FINAL_FIELDS_AS_MUTATORS) .registerModule(new Jdk8Module) override def getContext(aClass: Class[_]): ObjectMapper = mapper diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/trino/api/v1/StatementResource.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/trino/api/v1/StatementResource.scala index ee23c61f3..e051dbb23 100644 --- a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/trino/api/v1/StatementResource.scala +++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/trino/api/v1/StatementResource.scala @@ -80,9 +80,8 @@ private[v1] class StatementResource extends ApiRequestContext with Logging { case e: Exception => val errorMsg = s"Error submitting sql" - e.printStackTrace() error(errorMsg, e) - throw badRequest(BAD_REQUEST, errorMsg) + throw badRequest(BAD_REQUEST, errorMsg + "\n" + e.getMessage) } } diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/trino/api/v1/dto/Ok.java b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/trino/api/v1/dto/Ok.java index 50d04609f..982baa2ef 100644 --- a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/trino/api/v1/dto/Ok.java +++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/trino/api/v1/dto/Ok.java @@ -20,6 +20,9 @@ import java.util.Objects; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; import org.apache.commons.lang3.builder.ReflectionToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; @@ -28,10 +31,16 @@ public class Ok { public Ok() {} - public Ok(String content) { + /** + * Follow Trino way that explicitly specifies the json property since we disable the jackson + * auto detect feature. See {@link org.apache.kyuubi.server.trino.api.TrinoScalaObjectMapper} + */ + @JsonCreator + public Ok(@JsonProperty("content") String content) { this.content = content; } + @JsonProperty public String getContent() { return content; } From 6e5f87d6b4e421c7089875ecc957c9dac0356002 Mon Sep 17 00:00:00 2001 From: fwang12 Date: Fri, 17 Feb 2023 10:12:09 +0800 Subject: [PATCH 071/760] [KYUUBI #4344] Expose exec pool work queue size metrics ### _Why are the changes needed?_ It can help to know the backend pressure if the exec pool is full. ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [x] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4344 from turboFei/wait_queue. Closes #4344 161e3808a [fwang12] nit 6d122e238 [fwang12] save 55a4b499d [fwang12] version 668ff8bfe [fwang12] save 9f56b98a8 [fwang12] save a401771ec [fwang12] wait Authored-by: fwang12 Signed-off-by: fwang12 --- docs/monitor/metrics.md | 1 + .../scala/org/apache/spark/ui/EnginePage.scala | 4 ++++ .../apache/kyuubi/session/SessionManager.scala | 5 +++++ .../kyuubi/metrics/MetricsConstants.scala | 1 + .../client/api/v1/dto/ExecPoolStatistic.java | 17 ++++++++++++++--- .../kyuubi/server/api/v1/SessionsResource.scala | 3 ++- .../kyuubi/session/KyuubiSessionManager.scala | 1 + 7 files changed, 28 insertions(+), 4 deletions(-) diff --git a/docs/monitor/metrics.md b/docs/monitor/metrics.md index 1d1fa326a..f128fd1a4 100644 --- a/docs/monitor/metrics.md +++ b/docs/monitor/metrics.md @@ -44,6 +44,7 @@ These metrics include: |--------------------------------------------------|----------------------------------------|-----------|-------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `kyuubi.exec.pool.threads.alive` | | gauge | 1.2.0 |
    threads keepAlive in the backend executive thread pool
    | | `kyuubi.exec.pool.threads.active` | | gauge | 1.2.0 |
    threads active in the backend executive thread pool
    | +| `kyuubi.exec.pool.work_queue.size` | | gauge | 1.7.0 |
    work queue size in the backend executive thread pool
    | | `kyuubi.connection.total` | | counter | 1.2.0 |
    cumulative connection count
    | | `kyuubi.connection.total` | `${sessionType}` | counter | 1.7.0 |
    cumulative connection count with session type `${sessionType}`
    | | `kyuubi.connection.opened` | | gauge | 1.2.0 |
    current active connection count
    | diff --git a/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/spark/ui/EnginePage.scala b/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/spark/ui/EnginePage.scala index 0aba0c7c5..a2a2931f4 100644 --- a/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/spark/ui/EnginePage.scala +++ b/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/spark/ui/EnginePage.scala @@ -84,6 +84,10 @@ case class EnginePage(parent: EngineTab) extends WebUIPage("") { Background execution pool threads active: {engine.backendService.sessionManager.getActiveCount} +
  • + Background execution pool work queue size: + {engine.backendService.sessionManager.getWorkQueueSize} +
  • }.getOrElse(Seq.empty) }
diff --git a/kyuubi-common/src/main/scala/org/apache/kyuubi/session/SessionManager.scala b/kyuubi-common/src/main/scala/org/apache/kyuubi/session/SessionManager.scala index 662ac3e58..f8e77dd63 100644 --- a/kyuubi-common/src/main/scala/org/apache/kyuubi/session/SessionManager.scala +++ b/kyuubi-common/src/main/scala/org/apache/kyuubi/session/SessionManager.scala @@ -172,6 +172,11 @@ abstract class SessionManager(name: String) extends CompositeService(name) { execPool.getActiveCount } + def getWorkQueueSize: Int = { + assert(execPool != null) + execPool.getQueue.size() + } + private var _confRestrictList: Set[String] = _ private var _confIgnoreList: Set[String] = _ private var _batchConfIgnoreList: Set[String] = _ diff --git a/kyuubi-metrics/src/main/scala/org/apache/kyuubi/metrics/MetricsConstants.scala b/kyuubi-metrics/src/main/scala/org/apache/kyuubi/metrics/MetricsConstants.scala index 62c67266f..e97fd28ea 100644 --- a/kyuubi-metrics/src/main/scala/org/apache/kyuubi/metrics/MetricsConstants.scala +++ b/kyuubi-metrics/src/main/scala/org/apache/kyuubi/metrics/MetricsConstants.scala @@ -29,6 +29,7 @@ object MetricsConstants { final val EXEC_POOL_ALIVE: String = KYUUBI + "exec.pool.threads.alive" final val EXEC_POOL_ACTIVE: String = KYUUBI + "exec.pool.threads.active" + final val EXEC_POOL_WORK_QUEUE_SIZE: String = KYUUBI + "exec.pool.work_queue.size" final private val CONN = KYUUBI + "connection." final private val THRIFT_HTTP_CONN = KYUUBI + "thrift.http.connection." diff --git a/kyuubi-rest-client/src/main/java/org/apache/kyuubi/client/api/v1/dto/ExecPoolStatistic.java b/kyuubi-rest-client/src/main/java/org/apache/kyuubi/client/api/v1/dto/ExecPoolStatistic.java index ee8a9f007..a40811f92 100644 --- a/kyuubi-rest-client/src/main/java/org/apache/kyuubi/client/api/v1/dto/ExecPoolStatistic.java +++ b/kyuubi-rest-client/src/main/java/org/apache/kyuubi/client/api/v1/dto/ExecPoolStatistic.java @@ -24,12 +24,14 @@ public class ExecPoolStatistic { private int execPoolSize; private int execPoolActiveCount; + private int execPoolWorkQueueSize; public ExecPoolStatistic() {} - public ExecPoolStatistic(int execPoolSize, int execPoolActiveCount) { + public ExecPoolStatistic(int execPoolSize, int execPoolActiveCount, int execPoolWorkQueueSize) { this.execPoolSize = execPoolSize; this.execPoolActiveCount = execPoolActiveCount; + this.execPoolWorkQueueSize = execPoolWorkQueueSize; } public int getExecPoolSize() { @@ -48,18 +50,27 @@ public void setExecPoolActiveCount(int execPoolActiveCount) { this.execPoolActiveCount = execPoolActiveCount; } + public int getExecPoolWorkQueueSize() { + return execPoolWorkQueueSize; + } + + public void setExecPoolWorkQueueSize(int execPoolWorkQueueSize) { + this.execPoolWorkQueueSize = execPoolWorkQueueSize; + } + @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; ExecPoolStatistic that = (ExecPoolStatistic) o; return getExecPoolSize() == that.getExecPoolSize() - && getExecPoolActiveCount() == that.getExecPoolActiveCount(); + && getExecPoolActiveCount() == that.getExecPoolActiveCount() + && getExecPoolWorkQueueSize() == that.getExecPoolWorkQueueSize(); } @Override public int hashCode() { - return Objects.hash(getExecPoolSize(), getExecPoolActiveCount()); + return Objects.hash(getExecPoolSize(), getExecPoolActiveCount(), getExecPoolWorkQueueSize()); } @Override diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/SessionsResource.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/SessionsResource.scala index dd4a8c3a7..84b19eb00 100644 --- a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/SessionsResource.scala +++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/SessionsResource.scala @@ -131,7 +131,8 @@ private[v1] class SessionsResource extends ApiRequestContext with Logging { def execPoolStatistic(): ExecPoolStatistic = { new ExecPoolStatistic( sessionManager.getExecPoolSize, - sessionManager.getActiveCount) + sessionManager.getActiveCount, + sessionManager.getWorkQueueSize) } @ApiResponse( diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/session/KyuubiSessionManager.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/session/KyuubiSessionManager.scala index 54d5b8b24..207ae4c4d 100644 --- a/kyuubi-server/src/main/scala/org/apache/kyuubi/session/KyuubiSessionManager.scala +++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/session/KyuubiSessionManager.scala @@ -239,6 +239,7 @@ class KyuubiSessionManager private (name: String) extends SessionManager(name) { ms.registerGauge(CONN_OPEN, getOpenSessionCount, 0) ms.registerGauge(EXEC_POOL_ALIVE, getExecPoolSize, 0) ms.registerGauge(EXEC_POOL_ACTIVE, getActiveCount, 0) + ms.registerGauge(EXEC_POOL_WORK_QUEUE_SIZE, getWorkQueueSize, 0) } super.start() } From b77c8847f51f7d8aa456f45670c8def4d5e4d151 Mon Sep 17 00:00:00 2001 From: liangbowen Date: Fri, 17 Feb 2023 10:17:46 +0800 Subject: [PATCH 072/760] [KYUUBI #4347] Bump maven download plugin from 1.6.6 to 1.6.8 ### _Why are the changes needed?_ - 1.6.8 release note: https://github.com/maven-download-plugin/maven-download-plugin/releases/tag/1.6.8 ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [ ] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4347 from bowenliang123/maven-download-plugin-1.6.8. Closes #4347 947767d1 [liangbowen] bump maven download plugin version from 1.6.6 to 1.6.8 Authored-by: liangbowen Signed-off-by: liangbowen --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 644119ece..fab9fd2fb 100644 --- a/pom.xml +++ b/pom.xml @@ -212,7 +212,7 @@ ${project.build.directory}/scala-${scala.binary.version}/jars 3.3.0 - 1.6.6 + 1.6.8 1.6.1 4.8.0 3.0.0-M8 From 0be3cbff6e35c8e86635bfe6d856d0dfa148247d Mon Sep 17 00:00:00 2001 From: Cheng Pan Date: Fri, 17 Feb 2023 11:39:32 +0800 Subject: [PATCH 073/760] [KYUUBI #4282] Fix ConcurrentModificationException when log4j2 async enabled ### _Why are the changes needed?_ This PR proposes to fix #4282, since log4j2 supports async mode, we need to make sure the `Log4j2DivertAppender#append` is thread-safe. This PR also changes `OperationLog.getCurrentOperationLog` from `OperationLog` to `Option[OperationLog]` ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [x] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4300 from pan3793/log. Closes #4282 010e34b0 [Cheng Pan] fix 068405b2 [Cheng Pan] fix compile c79dedd5 [Cheng Pan] Use write lock instead 3daf8a4d [Cheng Pan] nit 94176a04 [Cheng Pan] [KYUUBI #4282] Fix ConcurrentModificationException when log4j2 async enabled Authored-by: Cheng Pan Signed-off-by: Cheng Pan --- .../operation/log/Log4j12DivertAppender.scala | 5 +-- .../operation/log/Log4j2DivertAppender.scala | 38 ++++++++++--------- .../kyuubi/operation/log/OperationLog.scala | 2 +- .../operation/log/OperationLogSuite.scala | 4 +- .../server/api/v1/BatchesResource.scala | 19 ++++++---- 5 files changed, 38 insertions(+), 30 deletions(-) diff --git a/kyuubi-common/src/main/scala/org/apache/kyuubi/operation/log/Log4j12DivertAppender.scala b/kyuubi-common/src/main/scala/org/apache/kyuubi/operation/log/Log4j12DivertAppender.scala index 1191e94ae..df2ef93d8 100644 --- a/kyuubi-common/src/main/scala/org/apache/kyuubi/operation/log/Log4j12DivertAppender.scala +++ b/kyuubi-common/src/main/scala/org/apache/kyuubi/operation/log/Log4j12DivertAppender.scala @@ -39,7 +39,7 @@ class Log4j12DivertAppender extends WriterAppender { setLayout(lo) addFilter { _: LoggingEvent => - if (OperationLog.getCurrentOperationLog == null) Filter.DENY else Filter.NEUTRAL + if (OperationLog.getCurrentOperationLog.isDefined) Filter.NEUTRAL else Filter.DENY } /** @@ -51,8 +51,7 @@ class Log4j12DivertAppender extends WriterAppender { // That should've gone into our writer. Notify the LogContext. val logOutput = writer.toString writer.reset() - val log = OperationLog.getCurrentOperationLog - if (log != null) log.write(logOutput) + OperationLog.getCurrentOperationLog.foreach(_.write(logOutput)) } } diff --git a/kyuubi-common/src/main/scala/org/apache/kyuubi/operation/log/Log4j2DivertAppender.scala b/kyuubi-common/src/main/scala/org/apache/kyuubi/operation/log/Log4j2DivertAppender.scala index 68753cf98..dc4b24a8c 100644 --- a/kyuubi-common/src/main/scala/org/apache/kyuubi/operation/log/Log4j2DivertAppender.scala +++ b/kyuubi-common/src/main/scala/org/apache/kyuubi/operation/log/Log4j2DivertAppender.scala @@ -18,6 +18,7 @@ package org.apache.kyuubi.operation.log import java.io.CharArrayWriter +import java.util.concurrent.locks.ReadWriteLock import scala.collection.JavaConverters._ @@ -27,6 +28,8 @@ import org.apache.logging.log4j.core.appender.{AbstractWriterAppender, ConsoleAp import org.apache.logging.log4j.core.filter.AbstractFilter import org.apache.logging.log4j.core.layout.PatternLayout +import org.apache.kyuubi.reflection.DynFields + class Log4j2DivertAppender( name: String, layout: StringLayout, @@ -52,22 +55,19 @@ class Log4j2DivertAppender( addFilter(new AbstractFilter() { override def filter(event: LogEvent): Filter.Result = { - if (OperationLog.getCurrentOperationLog == null) { - Filter.Result.DENY - } else { + if (OperationLog.getCurrentOperationLog.isDefined) { Filter.Result.NEUTRAL + } else { + Filter.Result.DENY } } }) - def initLayout(): StringLayout = { - LogManager.getRootLogger.asInstanceOf[org.apache.logging.log4j.core.Logger] - .getAppenders.values().asScala - .find(ap => ap.isInstanceOf[ConsoleAppender] && ap.getLayout.isInstanceOf[StringLayout]) - .map(_.getLayout.asInstanceOf[StringLayout]) - .getOrElse(PatternLayout.newBuilder().withPattern( - "%d{yy/MM/dd HH:mm:ss} %p %c{2}: %m%n").build()) - } + private val writeLock = DynFields.builder() + .hiddenImpl(classOf[AbstractWriterAppender[_]], "readWriteLock") + .build[ReadWriteLock](this) + .get() + .writeLock /** * Overrides AbstractWriterAppender.append(), which does the real logging. No need @@ -75,11 +75,15 @@ class Log4j2DivertAppender( */ override def append(event: LogEvent): Unit = { super.append(event) - // That should've gone into our writer. Notify the LogContext. - val logOutput = writer.toString - writer.reset() - val log = OperationLog.getCurrentOperationLog - if (log != null) log.write(logOutput) + writeLock.lock() + try { + // That should've gone into our writer. Notify the LogContext. + val logOutput = writer.toString + writer.reset() + OperationLog.getCurrentOperationLog.foreach(_.write(logOutput)) + } finally { + writeLock.unlock() + } } } @@ -95,7 +99,7 @@ object Log4j2DivertAppender { def initialize(): Unit = { val ap = new Log4j2DivertAppender() - org.apache.logging.log4j.LogManager.getRootLogger() + org.apache.logging.log4j.LogManager.getRootLogger .asInstanceOf[org.apache.logging.log4j.core.Logger].addAppender(ap) ap.start() } diff --git a/kyuubi-common/src/main/scala/org/apache/kyuubi/operation/log/OperationLog.scala b/kyuubi-common/src/main/scala/org/apache/kyuubi/operation/log/OperationLog.scala index 84c4ed55c..e6312d0fb 100644 --- a/kyuubi-common/src/main/scala/org/apache/kyuubi/operation/log/OperationLog.scala +++ b/kyuubi-common/src/main/scala/org/apache/kyuubi/operation/log/OperationLog.scala @@ -44,7 +44,7 @@ object OperationLog extends Logging { OPERATION_LOG.set(operationLog) } - def getCurrentOperationLog: OperationLog = OPERATION_LOG.get() + def getCurrentOperationLog: Option[OperationLog] = Option(OPERATION_LOG.get) def removeCurrentOperationLog(): Unit = OPERATION_LOG.remove() diff --git a/kyuubi-common/src/test/scala/org/apache/kyuubi/operation/log/OperationLogSuite.scala b/kyuubi-common/src/test/scala/org/apache/kyuubi/operation/log/OperationLogSuite.scala index 758eeeeaf..fe3cbc7fc 100644 --- a/kyuubi-common/src/test/scala/org/apache/kyuubi/operation/log/OperationLogSuite.scala +++ b/kyuubi-common/src/test/scala/org/apache/kyuubi/operation/log/OperationLogSuite.scala @@ -61,10 +61,10 @@ class OperationLogSuite extends KyuubiFunSuite { assert(!Files.exists(logFile)) OperationLog.setCurrentOperationLog(operationLog) - assert(OperationLog.getCurrentOperationLog === operationLog) + assert(OperationLog.getCurrentOperationLog === Some(operationLog)) OperationLog.removeCurrentOperationLog() - assert(OperationLog.getCurrentOperationLog === null) + assert(OperationLog.getCurrentOperationLog.isEmpty) operationLog.write(msg1 + "\n") assert(Files.exists(logFile)) diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/BatchesResource.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/BatchesResource.scala index 969362f7d..d308c26d5 100644 --- a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/BatchesResource.scala +++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/BatchesResource.scala @@ -18,7 +18,8 @@ package org.apache.kyuubi.server.api.v1 import java.io.InputStream -import java.util.Locale +import java.util +import java.util.{Collections, Locale} import java.util.concurrent.ConcurrentHashMap import javax.ws.rs._ import javax.ws.rs.core.MediaType @@ -318,12 +319,16 @@ private[v1] class BatchesResource extends ApiRequestContext with Logging { Option(sessionManager.getBatchSessionImpl(sessionHandle)).map { batchSession => try { val submissionOp = batchSession.batchJobSubmissionOp - val rowSet = submissionOp.getOperationLogRowSet( - FetchOrientation.FETCH_NEXT, - from, - size) - val logRowSet = rowSet.getColumns.get(0).getStringVal.getValues.asScala - new OperationLog(logRowSet.asJava, logRowSet.size) + val rowSet = submissionOp.getOperationLogRowSet(FetchOrientation.FETCH_NEXT, from, size) + val columns = rowSet.getColumns + val logRowSet: util.List[String] = + if (columns == null || columns.size == 0) { + Collections.emptyList() + } else { + assert(columns.size == 1) + columns.get(0).getStringVal.getValues + } + new OperationLog(logRowSet, logRowSet.size) } catch { case NonFatal(e) => val errorMsg = s"Error getting operation log for batchId: $batchId" From 7538b99e2c60997fc978abbc264220b7f2a43bcf Mon Sep 17 00:00:00 2001 From: liangbowen Date: Fri, 17 Feb 2023 23:27:36 +0800 Subject: [PATCH 074/760] [KYUUBI #4346] [DOCS] Add config type `userDefaultsConf` for config refreshing of admin tool ### _Why are the changes needed?_ - add config type `userDefaultsConf` (which is introduced in #3982 ) for config refreshing of admin tool image ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [x] Add screenshots for manual tests if appropriate - [ ] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4346 from bowenliang123/doc-refresh-userconfigs. Closes #4346 3ada9db4 [liangbowen] add config type `userDefaultsConf` for `kyuubi-admin refresh config` cc3df715 [liangbowen] add config type `userDefaultsConf` for `kyuubi-admin refresh config` Authored-by: liangbowen Signed-off-by: liangbowen --- docs/tools/kyuubi-admin.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/tools/kyuubi-admin.rst b/docs/tools/kyuubi-admin.rst index cf60f67b1..c881277fe 100644 --- a/docs/tools/kyuubi-admin.rst +++ b/docs/tools/kyuubi-admin.rst @@ -69,6 +69,8 @@ Usage: ``bin/kyuubi-admin refresh config [options] []`` - Description * - hadoopConf - The hadoop conf used for proxy user verification. + * - userDefaultsConf + - Refresh the user defaults configs with key in format in the form of `___{username}___.{config key}` from default property file. .. _list_engine: From 1cc14f1469939bc1fe8c816e46cca7758ea4f42e Mon Sep 17 00:00:00 2001 From: Yikf Date: Sat, 18 Feb 2023 00:44:21 +0800 Subject: [PATCH 075/760] [KYUUBI #4361] Fix broken link in `kerberos.rst` ### _Why are the changes needed?_ Fix broken link in `kerberos.rst` ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [x] Add screenshots for manual tests if appropriate - [ ] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4361 from Yikf/kerberos-doc. Closes #4361 26145902 [Yikf] fix break link Authored-by: Yikf Signed-off-by: Yikf --- docs/security/kerberos.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/security/kerberos.rst b/docs/security/kerberos.rst index c4bca8e82..8c871ce67 100644 --- a/docs/security/kerberos.rst +++ b/docs/security/kerberos.rst @@ -115,4 +115,4 @@ Refresh all the kyuubi server instances Restart all the kyuubi server instances or `Refresh Configurations`_ to activate the settings. .. _Hadoop Impersonation: https://hadoop.apache.org/docs/stable/hadoop-project-dist/hadoop-common/Superusers.html -.. _Refresh Configurations: ..tools/kyuubi-admin.html#refresh-config +.. _Refresh Configurations: ../tools/kyuubi-admin.html#refresh-config From ba1a8682eac6e328d6161329ea2948d31624249a Mon Sep 17 00:00:00 2001 From: fwang12 Date: Sat, 18 Feb 2023 00:54:11 +0800 Subject: [PATCH 076/760] [KYUUBI #4360] Support to refresh the unlimited users for session limiter ### _Why are the changes needed?_ Support to refresh the unlimited users for session limiter, so that we can unblock some customers without restart the kyuubi server. ### _How was this patch tested?_ - [x] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [x] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4360 from turboFei/limier_whitelist. Closes #4360 c846148bd [fwang12] typo 839e71365 [fwang12] nit 904bc9fbc [fwang12] save Authored-by: fwang12 Signed-off-by: fwang12 --- docs/deployment/settings.md | 2 +- docs/tools/kyuubi-admin.rst | 4 +++- .../org/apache/kyuubi/config/KyuubiConf.scala | 2 +- .../cmd/refresh/RefreshConfigCommand.scala | 4 +++- .../kyuubi/ctl/opt/AdminCommandLine.scala | 2 +- .../ctl/AdminControlCliArgumentsSuite.scala | 15 ++++++++++++--- .../apache/kyuubi/client/AdminRestApi.java | 5 +++++ .../apache/kyuubi/server/KyuubiServer.scala | 11 +++++++++++ .../kyuubi/server/api/v1/AdminResource.scala | 19 +++++++++++++++++++ .../kyuubi/session/KyuubiSessionManager.scala | 5 +++++ .../kyuubi/session/SessionLimiter.scala | 16 +++++++++++++--- .../server/api/v1/AdminResourceSuite.scala | 18 ++++++++++++++++++ 12 files changed, 92 insertions(+), 11 deletions(-) diff --git a/docs/deployment/settings.md b/docs/deployment/settings.md index 7391f4241..8d0e32436 100644 --- a/docs/deployment/settings.md +++ b/docs/deployment/settings.md @@ -457,7 +457,7 @@ You can configure the Kyuubi properties in `$KYUUBI_HOME/conf/kyuubi-defaults.co | kyuubi.server.limit.connections.per.ipaddress | <undefined> | Maximum kyuubi server connections per ipaddress. Any user exceeding this limit will not be allowed to connect. | int | 1.6.0 | | kyuubi.server.limit.connections.per.user | <undefined> | Maximum kyuubi server connections per user. Any user exceeding this limit will not be allowed to connect. | int | 1.6.0 | | kyuubi.server.limit.connections.per.user.ipaddress | <undefined> | Maximum kyuubi server connections per user:ipaddress combination. Any user-ipaddress exceeding this limit will not be allowed to connect. | int | 1.6.0 | -| kyuubi.server.limit.connections.user.unlimited.list || The maximin connections of the user in the white list will not be limited. | seq | 1.7.0 | +| kyuubi.server.limit.connections.user.unlimited.list || The maximum connections of the user in the white list will not be limited. | seq | 1.7.0 | | kyuubi.server.name | <undefined> | The name of Kyuubi Server. | string | 1.5.0 | | kyuubi.server.redaction.regex | <undefined> | Regex to decide which Kyuubi contain sensitive information. When this regex matches a property key or value, the value is redacted from the various logs. || 1.6.0 | diff --git a/docs/tools/kyuubi-admin.rst b/docs/tools/kyuubi-admin.rst index c881277fe..606396593 100644 --- a/docs/tools/kyuubi-admin.rst +++ b/docs/tools/kyuubi-admin.rst @@ -70,7 +70,9 @@ Usage: ``bin/kyuubi-admin refresh config [options] []`` * - hadoopConf - The hadoop conf used for proxy user verification. * - userDefaultsConf - - Refresh the user defaults configs with key in format in the form of `___{username}___.{config key}` from default property file. + - The user defaults configs with key in format in the form of `___{username}___.{config key}` from default property file. + * - unlimitedUsers + - The users without maximum connections limitation. .. _list_engine: diff --git a/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala b/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala index 01bd46bd3..14a05e749 100644 --- a/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala +++ b/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala @@ -2333,7 +2333,7 @@ object KyuubiConf { val SERVER_LIMIT_CONNECTIONS_USER_UNLIMITED_LIST: ConfigEntry[Seq[String]] = buildConf("kyuubi.server.limit.connections.user.unlimited.list") - .doc("The maximin connections of the user in the white list will not be limited.") + .doc("The maximum connections of the user in the white list will not be limited.") .version("1.7.0") .serverOnly .stringConf diff --git a/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/refresh/RefreshConfigCommand.scala b/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/refresh/RefreshConfigCommand.scala index b658c0e45..69aa0c3d0 100644 --- a/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/refresh/RefreshConfigCommand.scala +++ b/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/cmd/refresh/RefreshConfigCommand.scala @@ -21,7 +21,7 @@ import org.apache.kyuubi.KyuubiException import org.apache.kyuubi.client.AdminRestApi import org.apache.kyuubi.ctl.RestClientFactory.withKyuubiRestClient import org.apache.kyuubi.ctl.cmd.AdminCtlCommand -import org.apache.kyuubi.ctl.cmd.refresh.RefreshConfigCommandConfigType.{HADOOP_CONF, USER_DEFAULTS_CONF} +import org.apache.kyuubi.ctl.cmd.refresh.RefreshConfigCommandConfigType.{HADOOP_CONF, UNLIMITED_USERS, USER_DEFAULTS_CONF} import org.apache.kyuubi.ctl.opt.CliConfig import org.apache.kyuubi.ctl.util.{Tabulator, Validator} @@ -36,6 +36,7 @@ class RefreshConfigCommand(cliConfig: CliConfig) extends AdminCtlCommand[String] normalizedCliConfig.adminConfigOpts.configType match { case HADOOP_CONF => adminRestApi.refreshHadoopConf() case USER_DEFAULTS_CONF => adminRestApi.refreshUserDefaultsConf() + case UNLIMITED_USERS => adminRestApi.refreshUnlimitedUsers() case configType => throw new KyuubiException(s"Invalid config type:$configType") } } @@ -48,4 +49,5 @@ class RefreshConfigCommand(cliConfig: CliConfig) extends AdminCtlCommand[String] object RefreshConfigCommandConfigType { final val HADOOP_CONF = "hadoopConf" final val USER_DEFAULTS_CONF = "userDefaultsConf" + final val UNLIMITED_USERS = "unlimitedUsers" } diff --git a/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/opt/AdminCommandLine.scala b/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/opt/AdminCommandLine.scala index 59ad7f5fc..b1a70935b 100644 --- a/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/opt/AdminCommandLine.scala +++ b/kyuubi-ctl/src/main/scala/org/apache/kyuubi/ctl/opt/AdminCommandLine.scala @@ -102,6 +102,6 @@ object AdminCommandLine extends CommonCommandLine { .optional() .action((v, c) => c.copy(adminConfigOpts = c.adminConfigOpts.copy(configType = v))) .text("The valid config type can be one of the following: " + - s"$HADOOP_CONF, $USER_DEFAULTS_CONF.")) + s"$HADOOP_CONF, $USER_DEFAULTS_CONF, $UNLIMITED_USERS.")) } } diff --git a/kyuubi-ctl/src/test/scala/org/apache/kyuubi/ctl/AdminControlCliArgumentsSuite.scala b/kyuubi-ctl/src/test/scala/org/apache/kyuubi/ctl/AdminControlCliArgumentsSuite.scala index afb946e92..dab796127 100644 --- a/kyuubi-ctl/src/test/scala/org/apache/kyuubi/ctl/AdminControlCliArgumentsSuite.scala +++ b/kyuubi-ctl/src/test/scala/org/apache/kyuubi/ctl/AdminControlCliArgumentsSuite.scala @@ -63,7 +63,7 @@ class AdminControlCliArgumentsSuite extends KyuubiFunSuite with TestPrematureExi val opArgs = new AdminControlCliArguments(args) assert(opArgs.cliConfig.action === ControlAction.REFRESH) assert(opArgs.cliConfig.resource === ControlObject.CONFIG) - assert(opArgs.cliConfig.adminConfigOpts.configType === "hadoopConf") + assert(opArgs.cliConfig.adminConfigOpts.configType === HADOOP_CONF) args = Array( "refresh", @@ -72,7 +72,16 @@ class AdminControlCliArgumentsSuite extends KyuubiFunSuite with TestPrematureExi val opArgs2 = new AdminControlCliArguments(args) assert(opArgs2.cliConfig.action === ControlAction.REFRESH) assert(opArgs2.cliConfig.resource === ControlObject.CONFIG) - assert(opArgs2.cliConfig.adminConfigOpts.configType === "userDefaultsConf") + assert(opArgs2.cliConfig.adminConfigOpts.configType === USER_DEFAULTS_CONF) + + args = Array( + "refresh", + "config", + "unlimitedUsers") + val opArgs3 = new AdminControlCliArguments(args) + assert(opArgs3.cliConfig.action === ControlAction.REFRESH) + assert(opArgs3.cliConfig.resource === ControlObject.CONFIG) + assert(opArgs3.cliConfig.adminConfigOpts.configType === UNLIMITED_USERS) args = Array( "refresh", @@ -147,7 +156,7 @@ class AdminControlCliArgumentsSuite extends KyuubiFunSuite with TestPrematureExi | Refresh the resource. |Command: refresh config [] | Refresh the config with specified type. - | The valid config type can be one of the following: $HADOOP_CONF, $USER_DEFAULTS_CONF. + | The valid config type can be one of the following: $HADOOP_CONF, $USER_DEFAULTS_CONF, $UNLIMITED_USERS. | | -h, --help Show help message and exit.""".stripMargin // scalastyle:on diff --git a/kyuubi-rest-client/src/main/java/org/apache/kyuubi/client/AdminRestApi.java b/kyuubi-rest-client/src/main/java/org/apache/kyuubi/client/AdminRestApi.java index da9782df5..b8bfe7ee1 100644 --- a/kyuubi-rest-client/src/main/java/org/apache/kyuubi/client/AdminRestApi.java +++ b/kyuubi-rest-client/src/main/java/org/apache/kyuubi/client/AdminRestApi.java @@ -44,6 +44,11 @@ public String refreshUserDefaultsConf() { return this.getClient().post(path, null, client.getAuthHeader()); } + public String refreshUnlimitedUsers() { + String path = String.format("%s/%s", API_BASE_PATH, "refresh/unlimited_users"); + return this.getClient().post(path, null, client.getAuthHeader()); + } + public String deleteEngine( String engineType, String shareLevel, String subdomain, String hs2ProxyUser) { Map params = new HashMap<>(); diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/KyuubiServer.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/KyuubiServer.scala index fbdaf85cc..df163bd1e 100644 --- a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/KyuubiServer.scala +++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/KyuubiServer.scala @@ -33,6 +33,7 @@ import org.apache.kyuubi.ha.client.{AuthTypes, ServiceDiscovery} import org.apache.kyuubi.metrics.{MetricsConf, MetricsSystem} import org.apache.kyuubi.server.metadata.jdbc.JDBCMetadataStoreConf import org.apache.kyuubi.service.{AbstractBackendService, AbstractFrontendService, Serverable, ServiceState} +import org.apache.kyuubi.session.KyuubiSessionManager import org.apache.kyuubi.util.{KyuubiHadoopUtils, SignalRegister} import org.apache.kyuubi.zookeeper.EmbeddedZookeeper @@ -128,6 +129,16 @@ object KyuubiServer extends Logging { info(s"Refreshed user defaults configs with changes of " + s"unset: $unsetCount, updated: $updatedCount, added: $addedCount") } + + private[kyuubi] def refreshUnlimitedUsers(): Unit = synchronized { + val existingUnlimitedUsers = + kyuubiServer.conf.get(KyuubiConf.SERVER_LIMIT_CONNECTIONS_USER_UNLIMITED_LIST).toSet + val refreshedUnlimitedUsers = KyuubiConf().loadFileDefaults().get( + KyuubiConf.SERVER_LIMIT_CONNECTIONS_USER_UNLIMITED_LIST).toSet + kyuubiServer.backendService.sessionManager.asInstanceOf[KyuubiSessionManager] + .refreshUnlimitedUsers(refreshedUnlimitedUsers) + info(s"Refreshed unlimited users from $existingUnlimitedUsers to $refreshedUnlimitedUsers") + } } class KyuubiServer(name: String) extends Serverable(name) { diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/AdminResource.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/AdminResource.scala index e3fcc7527..6e05ee27c 100644 --- a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/AdminResource.scala +++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/AdminResource.scala @@ -82,6 +82,25 @@ private[v1] class AdminResource extends ApiRequestContext with Logging { Response.ok(s"Refresh the user defaults conf successfully.").build() } + @ApiResponse( + responseCode = "200", + content = Array(new Content(mediaType = MediaType.APPLICATION_JSON)), + description = "refresh the unlimited users") + @POST + @Path("refresh/unlimited_users") + def refreshUnlimitedUser(): Response = { + val userName = fe.getSessionUser(Map.empty[String, String]) + val ipAddress = fe.getIpAddress + info(s"Receive refresh unlimited users request from $userName/$ipAddress") + if (!userName.equals(administrator)) { + throw new NotAllowedException( + s"$userName is not allowed to refresh the unlimited users") + } + info(s"Reloading unlimited users") + KyuubiServer.refreshUnlimitedUsers() + Response.ok(s"Refresh the unlimited users successfully.").build() + } + @ApiResponse( responseCode = "200", content = Array(new Content(mediaType = MediaType.APPLICATION_JSON)), diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/session/KyuubiSessionManager.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/session/KyuubiSessionManager.scala index 207ae4c4d..038c02564 100644 --- a/kyuubi-server/src/main/scala/org/apache/kyuubi/session/KyuubiSessionManager.scala +++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/session/KyuubiSessionManager.scala @@ -300,6 +300,11 @@ class KyuubiSessionManager private (name: String) extends SessionManager(name) { userUnlimitedList) } + private[kyuubi] def refreshUnlimitedUsers(userUnlimitedList: Set[String]): Unit = { + limiter.foreach(SessionLimiter.resetUnlimitedUsers(_, userUnlimitedList)) + batchLimiter.foreach(SessionLimiter.resetUnlimitedUsers(_, userUnlimitedList)) + } + private def applySessionLimiter( userLimit: Int, ipAddressLimit: Int, diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/session/SessionLimiter.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/session/SessionLimiter.scala index b7acbac3d..6cf739c39 100644 --- a/kyuubi-server/src/main/scala/org/apache/kyuubi/session/SessionLimiter.scala +++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/session/SessionLimiter.scala @@ -105,7 +105,7 @@ class SessionLimiterWithUnlimitedUsersImpl( userLimit: Int, ipAddressLimit: Int, userIpAddressLimit: Int, - unlimitedUsers: Set[String]) + var unlimitedUsers: Set[String]) extends SessionLimiterImpl(userLimit, ipAddressLimit, userIpAddressLimit) { override def increment(userIpAddress: UserIpAddress): Unit = { if (!unlimitedUsers.contains(userIpAddress.user)) { @@ -118,6 +118,10 @@ class SessionLimiterWithUnlimitedUsersImpl( super.decrement(userIpAddress) } } + + private[kyuubi] def setUnlimitedUsers(unlimitedUsers: Set[String]): Unit = { + this.unlimitedUsers = unlimitedUsers + } } object SessionLimiter { @@ -126,12 +130,18 @@ object SessionLimiter { userLimit: Int, ipAddressLimit: Int, userIpAddressLimit: Int, - userWhiteList: Set[String] = Set.empty): SessionLimiter = { + unlimitedUsers: Set[String] = Set.empty): SessionLimiter = { new SessionLimiterWithUnlimitedUsersImpl( userLimit, ipAddressLimit, userIpAddressLimit, - userWhiteList) + unlimitedUsers) } + def resetUnlimitedUsers(limiter: SessionLimiter, unlimitedUsers: Set[String]): Unit = { + limiter match { + case l: SessionLimiterWithUnlimitedUsersImpl => l.setUnlimitedUsers(unlimitedUsers) + case _ => + } + } } diff --git a/kyuubi-server/src/test/scala/org/apache/kyuubi/server/api/v1/AdminResourceSuite.scala b/kyuubi-server/src/test/scala/org/apache/kyuubi/server/api/v1/AdminResourceSuite.scala index bcbdad2ce..d7cd4840e 100644 --- a/kyuubi-server/src/test/scala/org/apache/kyuubi/server/api/v1/AdminResourceSuite.scala +++ b/kyuubi-server/src/test/scala/org/apache/kyuubi/server/api/v1/AdminResourceSuite.scala @@ -84,6 +84,24 @@ class AdminResourceSuite extends KyuubiFunSuite with RestFrontendTestHelper { assert(200 == response.getStatus) } + test("refresh unlimited users of the kyuubi server") { + var response = webTarget.path("api/v1/admin/refresh/unlimited_users") + .request() + .post(null) + assert(405 == response.getStatus) + + val adminUser = Utils.currentUser + val encodeAuthorization = new String( + Base64.getEncoder.encode( + s"$adminUser:".getBytes()), + "UTF-8") + response = webTarget.path("api/v1/admin/refresh/unlimited_users") + .request() + .header(AUTHORIZATION_HEADER, s"BASIC $encodeAuthorization") + .post(null) + assert(200 == response.getStatus) + } + test("delete engine - user share level") { val id = UUID.randomUUID().toString conf.set(KyuubiConf.ENGINE_SHARE_LEVEL, USER.toString) From e60855fbcb20979155e0399519a22223a7d2e600 Mon Sep 17 00:00:00 2001 From: fwang12 Date: Sat, 18 Feb 2023 14:52:00 +0800 Subject: [PATCH 077/760] [KYUUBI #4364] Add metrics for user opened connections with session type ### _Why are the changes needed?_ Add metrics for user opened connections with session type ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [x] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4364 from turboFei/user_session_type. Closes #4364 d9505f256 [fwang12] get Authored-by: fwang12 Signed-off-by: fwang12 --- docs/monitor/metrics.md | 1 + .../main/scala/org/apache/kyuubi/session/KyuubiSession.scala | 2 ++ 2 files changed, 3 insertions(+) diff --git a/docs/monitor/metrics.md b/docs/monitor/metrics.md index f128fd1a4..561014c37 100644 --- a/docs/monitor/metrics.md +++ b/docs/monitor/metrics.md @@ -49,6 +49,7 @@ These metrics include: | `kyuubi.connection.total` | `${sessionType}` | counter | 1.7.0 |
cumulative connection count with session type `${sessionType}`
| | `kyuubi.connection.opened` | | gauge | 1.2.0 |
current active connection count
| | `kyuubi.connection.opened` | `${user}` | counter | 1.2.0 |
current active connections count requested by a `${user}`
| +| `kyuubi.connection.opened` | `${user}`
`${sessionType}` | counter | 1.7.0 |
current active connections count requested by a `${user}` with session type `${sessionType}`
| | `kyuubi.connection.opened` | `${sessionType}` | counter | 1.7.0 |
current active connections count with session type `${sessionType}`
| | `kyuubi.connection.failed` | | counter | 1.2.0 |
cumulative failed connection count
| | `kyuubi.connection.failed` | `${user}` | counter | 1.2.0 |
cumulative failed connections for a `${user}`
| diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/session/KyuubiSession.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/session/KyuubiSession.scala index 5b731e273..18597dac9 100644 --- a/kyuubi-server/src/main/scala/org/apache/kyuubi/session/KyuubiSession.scala +++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/session/KyuubiSession.scala @@ -58,11 +58,13 @@ abstract class KyuubiSession( ms.incCount(CONN_TOTAL) ms.incCount(MetricRegistry.name(CONN_TOTAL, sessionType.toString)) ms.incCount(MetricRegistry.name(CONN_OPEN, user)) + ms.incCount(MetricRegistry.name(CONN_OPEN, user, sessionType.toString)) ms.incCount(MetricRegistry.name(CONN_OPEN, sessionType.toString)) } protected def traceMetricsOnClose(): Unit = MetricsSystem.tracing { ms => ms.decCount(MetricRegistry.name(CONN_OPEN, user)) + ms.decCount(MetricRegistry.name(CONN_OPEN, user, sessionType.toString)) ms.decCount(MetricRegistry.name(CONN_OPEN, sessionType.toString)) } } From 4feb83d0f38f0eb3fd39ecf669772ba8d3780e99 Mon Sep 17 00:00:00 2001 From: Yikf Date: Sat, 18 Feb 2023 22:12:53 +0800 Subject: [PATCH 078/760] [KYUUBI #4359] Workaround for SPARK-41448 to keep FileWriterFactory serializable ### _Why are the changes needed?_ [SPARK-41448](https://issues.apache.org/jira/browse/SPARK-41448) make consistent MR job IDs in FileBatchWriter and FileFormatWriter in Apache Spark 3.3.2, but it breaks a serializable issue, JobId is non-serializable. And this pr aims to rewrite `FileWriterFactory` to circumvent the problem ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [x] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4359 from Yikf/FileWriterFactory. Closes #4359 dd8c90fe [Cheng Pan] Update extensions/spark/kyuubi-spark-connector-hive/src/main/scala/org/apache/kyuubi/spark/connector/hive/write/FileWriterFactory.scala 1e5164ec [Yikf] Make a serializable jobTrackerId instead of a non-serializable JobID in FileWriterFactory Lead-authored-by: Yikf Co-authored-by: Cheng Pan Signed-off-by: Cheng Pan --- .../hive/write/FileWriterFactory.scala | 78 +++++++++++++++++++ .../connector/hive/write/HiveBatchWrite.scala | 9 ++- .../connector/hive/write/HiveWrite.scala | 4 +- .../kyuubi/connector/HiveBridgeHelper.scala | 2 + 4 files changed, 89 insertions(+), 4 deletions(-) create mode 100644 extensions/spark/kyuubi-spark-connector-hive/src/main/scala/org/apache/kyuubi/spark/connector/hive/write/FileWriterFactory.scala diff --git a/extensions/spark/kyuubi-spark-connector-hive/src/main/scala/org/apache/kyuubi/spark/connector/hive/write/FileWriterFactory.scala b/extensions/spark/kyuubi-spark-connector-hive/src/main/scala/org/apache/kyuubi/spark/connector/hive/write/FileWriterFactory.scala new file mode 100644 index 000000000..c8e8f9b69 --- /dev/null +++ b/extensions/spark/kyuubi-spark-connector-hive/src/main/scala/org/apache/kyuubi/spark/connector/hive/write/FileWriterFactory.scala @@ -0,0 +1,78 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.kyuubi.spark.connector.hive.write + +import java.util.Date + +import org.apache.hadoop.mapred.JobID +import org.apache.hadoop.mapreduce.{TaskAttemptID, TaskID, TaskType} +import org.apache.hadoop.mapreduce.task.TaskAttemptContextImpl +import org.apache.spark.internal.io.FileCommitProtocol +import org.apache.spark.sql.catalyst.InternalRow +import org.apache.spark.sql.connector.write.{DataWriter, DataWriterFactory} +import org.apache.spark.sql.execution.datasources.{DynamicPartitionDataSingleWriter, SingleDirectoryDataWriter, WriteJobDescription} +import org.apache.spark.sql.hive.kyuubi.connector.HiveBridgeHelper.sparkHadoopWriterUtils + +/** + * This class is rewritten because of SPARK-42478, which affects Spark 3.3.2 + */ +case class FileWriterFactory( + description: WriteJobDescription, + committer: FileCommitProtocol) extends DataWriterFactory { + + private val jobTrackerId = sparkHadoopWriterUtils.createJobTrackerID(new Date) + + override def createWriter(partitionId: Int, realTaskId: Long): DataWriter[InternalRow] = { + val taskAttemptContext = createTaskAttemptContext(partitionId) + committer.setupTask(taskAttemptContext) + if (description.partitionColumns.isEmpty) { + new SingleDirectoryDataWriter(description, taskAttemptContext, committer) + } else { + new DynamicPartitionDataSingleWriter(description, taskAttemptContext, committer) + } + } + + private def createTaskAttemptContext(partitionId: Int): TaskAttemptContextImpl = { + val jobId = createJobID(jobTrackerId, 0) + val taskId = new TaskID(jobId, TaskType.MAP, partitionId) + val taskAttemptId = new TaskAttemptID(taskId, 0) + // Set up the configuration object + val hadoopConf = description.serializableHadoopConf.value + hadoopConf.set("mapreduce.job.id", jobId.toString) + hadoopConf.set("mapreduce.task.id", taskId.toString) + hadoopConf.set("mapreduce.task.attempt.id", taskAttemptId.toString) + hadoopConf.setBoolean("mapreduce.task.ismap", true) + hadoopConf.setInt("mapreduce.task.partition", 0) + + new TaskAttemptContextImpl(hadoopConf, taskAttemptId) + } + + /** + * Create a job ID. + * + * @param jobTrackerID unique job track id + * @param id job number + * @return a job ID + */ + def createJobID(jobTrackerID: String, id: Int): JobID = { + if (id < 0) { + throw new IllegalArgumentException("Job number is negative") + } + new JobID(jobTrackerID, id) + } +} diff --git a/extensions/spark/kyuubi-spark-connector-hive/src/main/scala/org/apache/kyuubi/spark/connector/hive/write/HiveBatchWrite.scala b/extensions/spark/kyuubi-spark-connector-hive/src/main/scala/org/apache/kyuubi/spark/connector/hive/write/HiveBatchWrite.scala index c4e473ff2..625d79d0c 100644 --- a/extensions/spark/kyuubi-spark-connector-hive/src/main/scala/org/apache/kyuubi/spark/connector/hive/write/HiveBatchWrite.scala +++ b/extensions/spark/kyuubi-spark-connector-hive/src/main/scala/org/apache/kyuubi/spark/connector/hive/write/HiveBatchWrite.scala @@ -23,12 +23,13 @@ import org.apache.hadoop.conf.Configuration import org.apache.hadoop.fs.Path import org.apache.hadoop.hive.conf.HiveConf import org.apache.spark.internal.Logging +import org.apache.spark.internal.io.FileCommitProtocol import org.apache.spark.sql.SparkSession import org.apache.spark.sql.catalyst.catalog._ import org.apache.spark.sql.catalyst.util.CaseInsensitiveMap import org.apache.spark.sql.connector.write.{BatchWrite, DataWriterFactory, PhysicalWriteInfo, WriterCommitMessage} import org.apache.spark.sql.execution.command.CommandUtils -import org.apache.spark.sql.execution.datasources.WriteTaskResult +import org.apache.spark.sql.execution.datasources.{WriteJobDescription, WriteTaskResult} import org.apache.spark.sql.execution.datasources.v2.FileBatchWrite import org.apache.spark.sql.hive.kyuubi.connector.HiveBridgeHelper.{hive, toSQLValue, HiveExternalCatalog} import org.apache.spark.sql.types.StringType @@ -47,10 +48,12 @@ class HiveBatchWrite( ifPartitionNotExists: Boolean, hadoopConf: Configuration, fileBatchWrite: FileBatchWrite, - externalCatalog: ExternalCatalog) extends BatchWrite with Logging { + externalCatalog: ExternalCatalog, + description: WriteJobDescription, + committer: FileCommitProtocol) extends BatchWrite with Logging { override def createBatchWriterFactory(info: PhysicalWriteInfo): DataWriterFactory = { - fileBatchWrite.createBatchWriterFactory(info) + FileWriterFactory(description, committer) } override def commit(messages: Array[WriterCommitMessage]): Unit = { diff --git a/extensions/spark/kyuubi-spark-connector-hive/src/main/scala/org/apache/kyuubi/spark/connector/hive/write/HiveWrite.scala b/extensions/spark/kyuubi-spark-connector-hive/src/main/scala/org/apache/kyuubi/spark/connector/hive/write/HiveWrite.scala index 486a7aa22..2d72327b4 100644 --- a/extensions/spark/kyuubi-spark-connector-hive/src/main/scala/org/apache/kyuubi/spark/connector/hive/write/HiveWrite.scala +++ b/extensions/spark/kyuubi-spark-connector-hive/src/main/scala/org/apache/kyuubi/spark/connector/hive/write/HiveWrite.scala @@ -112,7 +112,9 @@ case class HiveWrite( ifPartitionNotExists, hadoopConf, new FileBatchWrite(job, description, committer), - externalCatalog) + externalCatalog, + description, + committer) } private def createWriteJobDescription( diff --git a/extensions/spark/kyuubi-spark-connector-hive/src/main/scala/org/apache/spark/sql/hive/kyuubi/connector/HiveBridgeHelper.scala b/extensions/spark/kyuubi-spark-connector-hive/src/main/scala/org/apache/spark/sql/hive/kyuubi/connector/HiveBridgeHelper.scala index 88b0305dd..1a11790d8 100644 --- a/extensions/spark/kyuubi-spark-connector-hive/src/main/scala/org/apache/spark/sql/hive/kyuubi/connector/HiveBridgeHelper.scala +++ b/extensions/spark/kyuubi-spark-connector-hive/src/main/scala/org/apache/spark/sql/hive/kyuubi/connector/HiveBridgeHelper.scala @@ -20,6 +20,7 @@ package org.apache.spark.sql.hive.kyuubi.connector import scala.collection.mutable import org.apache.spark.SparkContext +import org.apache.spark.internal.io.SparkHadoopWriterUtils import org.apache.spark.rdd.InputFileBlockHolder import org.apache.spark.sql.catalyst.catalog.{BucketSpec, ExternalCatalogEvent} import org.apache.spark.sql.catalyst.expressions.{AttributeReference, Literal} @@ -43,6 +44,7 @@ object HiveBridgeHelper { val hive = org.apache.spark.sql.hive.client.hive val logicalExpressions: LogicalExpressions.type = LogicalExpressions val hiveClientImpl: HiveClientImpl.type = HiveClientImpl + val sparkHadoopWriterUtils: SparkHadoopWriterUtils.type = SparkHadoopWriterUtils val catalogV2Util: CatalogV2Util.type = CatalogV2Util val hiveTableUtil: HiveTableUtil.type = HiveTableUtil val hiveShim: HiveShim.type = HiveShim From 8cc8052f7653c95f78b1bfc08eee3744698813f8 Mon Sep 17 00:00:00 2001 From: liangbowen Date: Sat, 18 Feb 2023 22:34:02 +0800 Subject: [PATCH 079/760] [KYUUBI #4348] [INFRA] Cache engine archives in CI jobs for maven download plugin ### _Why are the changes needed?_ - to avoid violation in Apache archives website's daily total download size quote, prevent repeatedly downloading engine archives by manual cache in actions ### _How was this patch tested?_ - [ ] Pass CI jobs and check cached engine archives Closes #4348 from bowenliang123/cache-downloaded-archives. Closes #4348 a9deeea4 [liangbowen] apply engine caching to all jobs in master d253a497 [liangbowen] Merge commit '38eb74c26ebbdbb57aba51ad18c7e4a6bb9e1144' into cache-downloaded-archives 0cf2a759 [liangbowen] remove conditions for action 38eb74c2 [Bowen Liang] Merge branch 'master' into cache-downloaded-archives 105d507e [liangbowen] remove c6542797 [liangbowen] extract `cache-engine-archives` action and cache engine archives Lead-authored-by: liangbowen Co-authored-by: Bowen Liang Signed-off-by: Cheng Pan --- .../actions/cache-engine-archives/action.yaml | 27 +++++++++++++++++++ .github/workflows/master.yml | 22 ++++++++++++++- externals/kyuubi-download/pom.xml | 1 + pom.xml | 1 + 4 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 .github/actions/cache-engine-archives/action.yaml diff --git a/.github/actions/cache-engine-archives/action.yaml b/.github/actions/cache-engine-archives/action.yaml new file mode 100644 index 000000000..86a9ccafb --- /dev/null +++ b/.github/actions/cache-engine-archives/action.yaml @@ -0,0 +1,27 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +name: cache-engine-archives +description: 'Cache download engine archives from Apache Archives website used by Maven download plugin' +runs: + using: composite + steps: + - name: Cache Engine Archives + uses: actions/cache@v3 + with: + path: /tmp/engine-archives + key: engine-archives diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index 26d231297..b0ab493a4 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -32,7 +32,7 @@ concurrency: cancel-in-progress: true env: - MVN_OPT: -Dmaven.javadoc.skip=true -Drat.skip=true -Dscalastyle.skip=true -Dspotless.check.skip -Dorg.slf4j.simpleLogger.defaultLogLevel=warn -Pjdbc-shaded + MVN_OPT: -Dmaven.javadoc.skip=true -Drat.skip=true -Dscalastyle.skip=true -Dspotless.check.skip -Dorg.slf4j.simpleLogger.defaultLogLevel=warn -Pjdbc-shaded -Dmaven.plugin.download.cache.path=/tmp/engine-archives KUBERNETES_VERSION: v1.26.1 MINIKUBE_VERSION: v1.29.0 @@ -77,6 +77,8 @@ jobs: java-version: ${{ matrix.java }} cache: 'maven' check-latest: false + - name: Cache Engine Archives + uses: ./.github/actions/cache-engine-archives - name: Setup Python uses: actions/setup-python@v4 with: @@ -129,6 +131,8 @@ jobs: java-version: ${{ matrix.java }} cache: 'maven' check-latest: false + - name: Cache Engine Archives + uses: ./.github/actions/cache-engine-archives - name: Build and test Kyuubi AuthZ with supported Spark versions run: | TEST_MODULES="extensions/spark/kyuubi-spark-authz" @@ -178,6 +182,8 @@ jobs: java-version: ${{ matrix.java }} cache: 'maven' check-latest: false + - name: Cache Engine Archives + uses: ./.github/actions/cache-engine-archives - name: Build Flink with maven w/o linters run: | TEST_MODULES="externals/kyuubi-flink-sql-engine,integration-tests/kyuubi-flink-it" @@ -222,6 +228,8 @@ jobs: java-version: ${{ matrix.java }} cache: 'maven' check-latest: false + - name: Cache Engine Archives + uses: ./.github/actions/cache-engine-archives - name: Build and test Hive with maven w/o linters run: | TEST_MODULES="externals/kyuubi-hive-sql-engine,integration-tests/kyuubi-hive-it" @@ -257,6 +265,8 @@ jobs: java-version: ${{ matrix.java }} cache: 'maven' check-latest: false + - name: Cache Engine Archives + uses: ./.github/actions/cache-engine-archives - name: Build and test JDBC with maven w/o linters run: | TEST_MODULES="externals/kyuubi-jdbc-engine,integration-tests/kyuubi-jdbc-it" @@ -292,6 +302,8 @@ jobs: java-version: ${{ matrix.java }} cache: 'maven' check-latest: false + - name: Cache Engine Archives + uses: ./.github/actions/cache-engine-archives - name: Build and test Trino with maven w/o linters run: | TEST_MODULES="kyuubi-server,externals/kyuubi-trino-engine,externals/kyuubi-spark-sql-engine,externals/kyuubi-download,integration-tests/kyuubi-trino-it" @@ -322,6 +334,8 @@ jobs: java-version: 8 cache: 'maven' check-latest: false + - name: Cache Engine Archives + uses: ./.github/actions/cache-engine-archives - name: Run TPC-DS Tests run: | TEST_MODULES="kyuubi-server,extensions/spark/kyuubi-spark-connector-tpcds,extensions/spark/kyuubi-spark-connector-tpch" @@ -350,6 +364,8 @@ jobs: file: build/Dockerfile load: true tags: apache/kyuubi:latest + - name: Cache Engine Archives + uses: ./.github/actions/cache-engine-archives - name: Setup Minikube run: | # https://minikube.sigs.k8s.io/docs/start/ @@ -399,6 +415,8 @@ jobs: steps: - name: Checkout uses: actions/checkout@v3 + - name: Cache Engine Archives + uses: ./.github/actions/cache-engine-archives - name: Setup Minikube run: | # https://minikube.sigs.k8s.io/docs/start/ @@ -454,6 +472,8 @@ jobs: java-version: ${{ matrix.java }} cache: 'maven' check-latest: false + - name: Cache Engine Archives + uses: ./.github/actions/cache-engine-archives - name: zookeeper integration tests run: | export KYUUBI_IT_ZOOKEEPER_VERSION=${{ matrix.zookeeper }} diff --git a/externals/kyuubi-download/pom.xml b/externals/kyuubi-download/pom.xml index ce9ba2014..d7f0c6013 100644 --- a/externals/kyuubi-download/pom.xml +++ b/externals/kyuubi-download/pom.xml @@ -36,6 +36,7 @@ com.googlecode.maven-download-plugin download-maven-plugin + ${maven.plugin.download.cache.path} ${project.build.directory} 60000 3 diff --git a/pom.xml b/pom.xml index fab9fd2fb..2df40a658 100644 --- a/pom.xml +++ b/pom.xml @@ -213,6 +213,7 @@ 3.3.0 1.6.8 + 1.6.1 4.8.0 3.0.0-M8 From e647cc910ceb612d1097ec9734dcc49f7a4ac8ec Mon Sep 17 00:00:00 2001 From: fwang12 Date: Sat, 18 Feb 2023 22:38:33 +0800 Subject: [PATCH 080/760] [KYUUBI #4119][FOLLOWUP] Add app start time for batch api docs ### _Why are the changes needed?_ #4119 follow up ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [ ] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4365 from turboFei/app_start. Closes #4119 3cf7e7a7 [fwang12] add app start time Authored-by: fwang12 Signed-off-by: Cheng Pan --- docs/client/rest/rest_api.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/client/rest/rest_api.md b/docs/client/rest/rest_api.md index f863404a6..02a914277 100644 --- a/docs/client/rest/rest_api.md +++ b/docs/client/rest/rest_api.md @@ -493,6 +493,7 @@ The [Engine](#engine) List. | user | The user created the batch | String | | batchType | The batch type | String | | name | The batch name | String | +| appStartTime | The batch application start time | Long | | appId | The batch application Id | String | | appUrl | The batch application tracking url | String | | appState | The batch application state | String | From 9aebeb8e777f88bf5358a87c9e941547d4db1cc6 Mon Sep 17 00:00:00 2001 From: Luning Wang Date: Sun, 19 Feb 2023 00:06:57 +0800 Subject: [PATCH 081/760] [KYUUBI #4338] Bump Spark from 3.3.1 to 3.3.2 ### _Why are the changes needed?_ close #4338 . ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [x] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4339 from a49a/bump-spark332. Closes #4338 6c741d82 [Luning Wang] [KYUUBI #4338] Bump Spark from 3.3.1 to 3.3.2 Authored-by: Luning Wang Signed-off-by: Cheng Pan --- docker/playground/.env | 2 +- docs/quick_start/quick_start.rst | 2 +- .../test/deployment/KyuubiOnKubernetesTestsSuite.scala | 2 +- pom.xml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docker/playground/.env b/docker/playground/.env index d50e964cf..abd897192 100644 --- a/docker/playground/.env +++ b/docker/playground/.env @@ -24,7 +24,7 @@ KYUUBI_HADOOP_VERSION=3.3.4 POSTGRES_VERSION=12 POSTGRES_JDBC_VERSION=42.3.4 SCALA_BINARY_VERSION=2.12 -SPARK_VERSION=3.3.1 +SPARK_VERSION=3.3.2 SPARK_BINARY_VERSION=3.3 SPARK_HADOOP_VERSION=3.3.2 ZOOKEEPER_VERSION=3.6.3 diff --git a/docs/quick_start/quick_start.rst b/docs/quick_start/quick_start.rst index ca73fba35..db564edb9 100644 --- a/docs/quick_start/quick_start.rst +++ b/docs/quick_start/quick_start.rst @@ -143,7 +143,7 @@ To install Spark, you need to unpack the tarball. For example, .. code-block:: - $ tar zxf spark-3.3.1-bin-hadoop3.tgz + $ tar zxf spark-3.3.2-bin-hadoop3.tgz Configuration ~~~~~~~~~~~~~ diff --git a/integration-tests/kyuubi-kubernetes-it/src/test/scala/org/apache/kyuubi/kubernetes/test/deployment/KyuubiOnKubernetesTestsSuite.scala b/integration-tests/kyuubi-kubernetes-it/src/test/scala/org/apache/kyuubi/kubernetes/test/deployment/KyuubiOnKubernetesTestsSuite.scala index c8894679d..6cf8dfcc8 100644 --- a/integration-tests/kyuubi-kubernetes-it/src/test/scala/org/apache/kyuubi/kubernetes/test/deployment/KyuubiOnKubernetesTestsSuite.scala +++ b/integration-tests/kyuubi-kubernetes-it/src/test/scala/org/apache/kyuubi/kubernetes/test/deployment/KyuubiOnKubernetesTestsSuite.scala @@ -54,7 +54,7 @@ class KyuubiOnKubernetesWithSparkTestsBase extends WithKyuubiServerOnKubernetes super.connectionConf ++ Map( "spark.master" -> s"k8s://$miniKubeApiMaster", - "spark.kubernetes.container.image" -> "apache/spark:3.3.1", + "spark.kubernetes.container.image" -> "apache/spark:3.3.2", "spark.executor.memory" -> "512M", "spark.driver.memory" -> "1024M", "spark.kubernetes.driver.request.cores" -> "250m", diff --git a/pom.xml b/pom.xml index 2df40a658..a774acbb2 100644 --- a/pom.xml +++ b/pom.xml @@ -188,7 +188,7 @@ DO NOT forget to change the following properties when change the minor version of Spark: `delta.version`, `maven.plugin.scalatest.exclude.tags` --> - 3.3.1 + 3.3.2 3.3 spark-${spark.version}-bin-hadoop3.tgz ${apache.archive.dist}/spark/spark-${spark.version} From cb23a7d9aa861e3fcb69af8f8e31db7cd8a1488a Mon Sep 17 00:00:00 2001 From: Yikf Date: Sun, 19 Feb 2023 00:34:22 +0800 Subject: [PATCH 082/760] [KYUUBI #4362] Add `_configurations` in kerberos.rst ### _Why are the changes needed?_ Add `_configurations` in kerberos.rst ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [x] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4362 from Yikf/kerberos-doc-config. Closes #4362 d8f197642 [Yikf] Add _configurations in kerberos.rst Authored-by: Yikf Signed-off-by: liangbowen --- docs/security/kerberos.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/security/kerberos.rst b/docs/security/kerberos.rst index 8c871ce67..2505fa30d 100644 --- a/docs/security/kerberos.rst +++ b/docs/security/kerberos.rst @@ -115,4 +115,5 @@ Refresh all the kyuubi server instances Restart all the kyuubi server instances or `Refresh Configurations`_ to activate the settings. .. _Hadoop Impersonation: https://hadoop.apache.org/docs/stable/hadoop-project-dist/hadoop-common/Superusers.html +.. _configurations: ../client/advanced/kerberos.html .. _Refresh Configurations: ../tools/kyuubi-admin.html#refresh-config From 8fc7adee970bb81c4f449c5bf8a1be886a511ab3 Mon Sep 17 00:00:00 2001 From: fwang12 Date: Sun, 19 Feb 2023 01:02:00 +0800 Subject: [PATCH 083/760] [KYUUBI #4360][FOLLOWUP] Get valid unlimited users from existing limiters instead of conf ### _Why are the changes needed?_ It is more accurate to get the unlimited users from existing limiters. ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [x] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4363 from turboFei/refresh_unlimit. Closes #4360 a880b413 [fwang12] refactor 6da9f9bd [fwang12] get Authored-by: fwang12 Signed-off-by: Cheng Pan --- .../scala/org/apache/kyuubi/server/KyuubiServer.scala | 10 ++++------ .../apache/kyuubi/session/KyuubiSessionManager.scala | 11 ++++++++--- .../org/apache/kyuubi/session/SessionLimiter.scala | 6 +++++- 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/KyuubiServer.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/KyuubiServer.scala index df163bd1e..a27e18bbf 100644 --- a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/KyuubiServer.scala +++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/KyuubiServer.scala @@ -131,12 +131,10 @@ object KyuubiServer extends Logging { } private[kyuubi] def refreshUnlimitedUsers(): Unit = synchronized { - val existingUnlimitedUsers = - kyuubiServer.conf.get(KyuubiConf.SERVER_LIMIT_CONNECTIONS_USER_UNLIMITED_LIST).toSet - val refreshedUnlimitedUsers = KyuubiConf().loadFileDefaults().get( - KyuubiConf.SERVER_LIMIT_CONNECTIONS_USER_UNLIMITED_LIST).toSet - kyuubiServer.backendService.sessionManager.asInstanceOf[KyuubiSessionManager] - .refreshUnlimitedUsers(refreshedUnlimitedUsers) + val sessionMgr = kyuubiServer.backendService.sessionManager.asInstanceOf[KyuubiSessionManager] + val existingUnlimitedUsers = sessionMgr.getUnlimitedUsers() + sessionMgr.refreshUnlimitedUsers(KyuubiConf().loadFileDefaults()) + val refreshedUnlimitedUsers = sessionMgr.getUnlimitedUsers() info(s"Refreshed unlimited users from $existingUnlimitedUsers to $refreshedUnlimitedUsers") } } diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/session/KyuubiSessionManager.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/session/KyuubiSessionManager.scala index 038c02564..73248cd56 100644 --- a/kyuubi-server/src/main/scala/org/apache/kyuubi/session/KyuubiSessionManager.scala +++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/session/KyuubiSessionManager.scala @@ -300,9 +300,14 @@ class KyuubiSessionManager private (name: String) extends SessionManager(name) { userUnlimitedList) } - private[kyuubi] def refreshUnlimitedUsers(userUnlimitedList: Set[String]): Unit = { - limiter.foreach(SessionLimiter.resetUnlimitedUsers(_, userUnlimitedList)) - batchLimiter.foreach(SessionLimiter.resetUnlimitedUsers(_, userUnlimitedList)) + private[kyuubi] def getUnlimitedUsers(): Set[String] = { + limiter.orElse(batchLimiter).map(SessionLimiter.getUnlimitedUsers).getOrElse(Set.empty) + } + + private[kyuubi] def refreshUnlimitedUsers(conf: KyuubiConf): Unit = { + val unlimitedUsers = conf.get(SERVER_LIMIT_CONNECTIONS_USER_UNLIMITED_LIST).toSet + limiter.foreach(SessionLimiter.resetUnlimitedUsers(_, unlimitedUsers)) + batchLimiter.foreach(SessionLimiter.resetUnlimitedUsers(_, unlimitedUsers)) } private def applySessionLimiter( diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/session/SessionLimiter.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/session/SessionLimiter.scala index 6cf739c39..96ca36df1 100644 --- a/kyuubi-server/src/main/scala/org/apache/kyuubi/session/SessionLimiter.scala +++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/session/SessionLimiter.scala @@ -138,10 +138,14 @@ object SessionLimiter { unlimitedUsers) } - def resetUnlimitedUsers(limiter: SessionLimiter, unlimitedUsers: Set[String]): Unit = { + def resetUnlimitedUsers(limiter: SessionLimiter, unlimitedUsers: Set[String]): Unit = limiter match { case l: SessionLimiterWithUnlimitedUsersImpl => l.setUnlimitedUsers(unlimitedUsers) case _ => } + + def getUnlimitedUsers(limiter: SessionLimiter): Set[String] = limiter match { + case l: SessionLimiterWithUnlimitedUsersImpl => l.unlimitedUsers + case _ => Set.empty } } From 6bd0016fe271d0e5abfcd558e9204cfdc8641978 Mon Sep 17 00:00:00 2001 From: Fu Chen Date: Sun, 19 Feb 2023 01:10:04 +0800 Subject: [PATCH 084/760] [KYUUBI #4326] [ARROW] Fix Spark session timezone format in arrow-based result format ### _Why are the changes needed?_ 1. this PR introduces a new configuration called `kyuubi.operation.result.arrow.timestampAsString`, when true, arrow-based rowsets will convert timestamp-type columns to strings for transmission. 2. `kyuubi.operation.result.arrow.timestampAsString` default setting to false for better transmission performance 3. the PR fixes timezone issue in arrow based result format described in #3958 ### _How was this patch tested?_ - [x] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [ ] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4326 from cfmcgrady/arrow-string-ts. Closes #4326 38c7fc9b [Fu Chen] fix style d864db00 [Fu Chen] address comment b714b3ee [Fu Chen] revert externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/schema/RowSet.scala 6c4eb507 [Fu Chen] minor 289b6007 [Fu Chen] timstampAsString = false by default 78b7caba [Fu Chen] fix f5601356 [Fu Chen] debug info b8e4b288 [Fu Chen] fix ut 87c6f9ef [Fu Chen] update docs 86f6cb73 [Fu Chen] arrow based rowset timestamp as string Authored-by: Fu Chen Signed-off-by: Cheng Pan --- docs/deployment/settings.md | 33 +++++------ .../spark/operation/ExecuteStatement.scala | 13 ++--- .../spark/operation/SparkOperation.scala | 8 ++- .../spark/sql/kyuubi/SparkDatasetHelper.scala | 13 +++-- .../SparkArrowbasedOperationSuite.scala | 56 ++++++++++++++++++- .../org/apache/kyuubi/config/KyuubiConf.scala | 8 +++ .../jdbc/hive/JdbcColumnAttributes.java | 2 +- .../jdbc/hive/KyuubiArrowBasedResultSet.java | 14 ++++- .../jdbc/hive/KyuubiArrowQueryResultSet.java | 54 ++++++++++-------- .../kyuubi/jdbc/hive/KyuubiStatement.java | 16 +++++- .../hive/arrow/ArrowColumnarBatchRow.java | 20 ++++--- 11 files changed, 167 insertions(+), 70 deletions(-) diff --git a/docs/deployment/settings.md b/docs/deployment/settings.md index 8d0e32436..1e0567860 100644 --- a/docs/deployment/settings.md +++ b/docs/deployment/settings.md @@ -429,22 +429,23 @@ You can configure the Kyuubi properties in `$KYUUBI_HOME/conf/kyuubi-defaults.co ### Operation -| Key | Default | Meaning | Type | Since | -|-----------------------------------------|---------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------|-------| -| kyuubi.operation.idle.timeout | PT3H | Operation will be closed when it's not accessed for this duration of time | duration | 1.0.0 | -| kyuubi.operation.interrupt.on.cancel | true | When true, all running tasks will be interrupted if one cancels a query. When false, all running tasks will remain until finished. | boolean | 1.2.0 | -| kyuubi.operation.language | SQL | Choose a programing language for the following inputs
  • SQL: (Default) Run all following statements as SQL queries.
  • SCALA: Run all following input as scala codes
  • PYTHON: (Experimental) Run all following input as Python codes with Spark engine
| string | 1.5.0 | -| kyuubi.operation.log.dir.root | server_operation_logs | Root directory for query operation log at server-side. | string | 1.4.0 | -| kyuubi.operation.plan.only.excludes | ResetCommand,SetCommand,SetNamespaceCommand,UseStatement,SetCatalogAndNamespace | Comma-separated list of query plan names, in the form of simple class names, i.e, for `SET abc=xyz`, the value will be `SetCommand`. For those auxiliary plans, such as `switch databases`, `set properties`, or `create temporary view` etc., which are used for setup evaluating environments for analyzing actual queries, we can use this config to exclude them and let them take effect. See also kyuubi.operation.plan.only.mode. | seq | 1.5.0 | -| kyuubi.operation.plan.only.mode | none | Configures the statement performed mode, The value can be 'parse', 'analyze', 'optimize', 'optimize_with_stats', 'physical', 'execution', or 'none', when it is 'none', indicate to the statement will be fully executed, otherwise only way without executing the query. different engines currently support different modes, the Spark engine supports all modes, and the Flink engine supports 'parse', 'physical', and 'execution', other engines do not support planOnly currently. | string | 1.4.0 | -| kyuubi.operation.plan.only.output.style | plain | Configures the planOnly output style. The value can be 'plain' or 'json', and the default value is 'plain'. This configuration supports only the output styles of the Spark engine | string | 1.7.0 | -| kyuubi.operation.progress.enabled | false | Whether to enable the operation progress. When true, the operation progress will be returned in `GetOperationStatus`. | boolean | 1.6.0 | -| kyuubi.operation.query.timeout | <undefined> | Timeout for query executions at server-side, take effect with client-side timeout(`java.sql.Statement.setQueryTimeout`) together, a running query will be cancelled automatically if timeout. It's off by default, which means only client-side take full control of whether the query should timeout or not. If set, client-side timeout is capped at this point. To cancel the queries right away without waiting for task to finish, consider enabling kyuubi.operation.interrupt.on.cancel together. | duration | 1.2.0 | -| kyuubi.operation.result.format | thrift | Specify the result format, available configs are:
  • THRIFT: the result will convert to TRow at the engine driver side.
  • ARROW: the result will be encoded as Arrow at the executor side before collecting by the driver, and deserialized at the client side. note that it only takes effect for kyuubi-hive-jdbc clients now.
| string | 1.7.0 | -| kyuubi.operation.result.max.rows | 0 | Max rows of Spark query results. Rows exceeding the limit would be ignored. By setting this value to 0 to disable the max rows limit. | int | 1.6.0 | -| kyuubi.operation.scheduler.pool | <undefined> | The scheduler pool of job. Note that, this config should be used after changing Spark config spark.scheduler.mode=FAIR. | string | 1.1.1 | -| kyuubi.operation.spark.listener.enabled | true | When set to true, Spark engine registers an SQLOperationListener before executing the statement, logging a few summary statistics when each stage completes. | boolean | 1.6.0 | -| kyuubi.operation.status.polling.timeout | PT5S | Timeout(ms) for long polling asynchronous running sql query's status | duration | 1.0.0 | +| Key | Default | Meaning | Type | Since | +|-------------------------------------------------|---------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------|-------| +| kyuubi.operation.idle.timeout | PT3H | Operation will be closed when it's not accessed for this duration of time | duration | 1.0.0 | +| kyuubi.operation.interrupt.on.cancel | true | When true, all running tasks will be interrupted if one cancels a query. When false, all running tasks will remain until finished. | boolean | 1.2.0 | +| kyuubi.operation.language | SQL | Choose a programing language for the following inputs
  • SQL: (Default) Run all following statements as SQL queries.
  • SCALA: Run all following input as scala codes
  • PYTHON: (Experimental) Run all following input as Python codes with Spark engine
| string | 1.5.0 | +| kyuubi.operation.log.dir.root | server_operation_logs | Root directory for query operation log at server-side. | string | 1.4.0 | +| kyuubi.operation.plan.only.excludes | ResetCommand,SetCommand,SetNamespaceCommand,UseStatement,SetCatalogAndNamespace | Comma-separated list of query plan names, in the form of simple class names, i.e, for `SET abc=xyz`, the value will be `SetCommand`. For those auxiliary plans, such as `switch databases`, `set properties`, or `create temporary view` etc., which are used for setup evaluating environments for analyzing actual queries, we can use this config to exclude them and let them take effect. See also kyuubi.operation.plan.only.mode. | seq | 1.5.0 | +| kyuubi.operation.plan.only.mode | none | Configures the statement performed mode, The value can be 'parse', 'analyze', 'optimize', 'optimize_with_stats', 'physical', 'execution', or 'none', when it is 'none', indicate to the statement will be fully executed, otherwise only way without executing the query. different engines currently support different modes, the Spark engine supports all modes, and the Flink engine supports 'parse', 'physical', and 'execution', other engines do not support planOnly currently. | string | 1.4.0 | +| kyuubi.operation.plan.only.output.style | plain | Configures the planOnly output style. The value can be 'plain' or 'json', and the default value is 'plain'. This configuration supports only the output styles of the Spark engine | string | 1.7.0 | +| kyuubi.operation.progress.enabled | false | Whether to enable the operation progress. When true, the operation progress will be returned in `GetOperationStatus`. | boolean | 1.6.0 | +| kyuubi.operation.query.timeout | <undefined> | Timeout for query executions at server-side, take effect with client-side timeout(`java.sql.Statement.setQueryTimeout`) together, a running query will be cancelled automatically if timeout. It's off by default, which means only client-side take full control of whether the query should timeout or not. If set, client-side timeout is capped at this point. To cancel the queries right away without waiting for task to finish, consider enabling kyuubi.operation.interrupt.on.cancel together. | duration | 1.2.0 | +| kyuubi.operation.result.arrow.timestampAsString | false | When true, arrow-based rowsets will convert columns of type timestamp to strings for transmission. | boolean | 1.7.0 | +| kyuubi.operation.result.format | thrift | Specify the result format, available configs are:
  • THRIFT: the result will convert to TRow at the engine driver side.
  • ARROW: the result will be encoded as Arrow at the executor side before collecting by the driver, and deserialized at the client side. note that it only takes effect for kyuubi-hive-jdbc clients now.
| string | 1.7.0 | +| kyuubi.operation.result.max.rows | 0 | Max rows of Spark query results. Rows exceeding the limit would be ignored. By setting this value to 0 to disable the max rows limit. | int | 1.6.0 | +| kyuubi.operation.scheduler.pool | <undefined> | The scheduler pool of job. Note that, this config should be used after changing Spark config spark.scheduler.mode=FAIR. | string | 1.1.1 | +| kyuubi.operation.spark.listener.enabled | true | When set to true, Spark engine registers an SQLOperationListener before executing the statement, logging a few summary statistics when each stage completes. | boolean | 1.6.0 | +| kyuubi.operation.status.polling.timeout | PT5S | Timeout(ms) for long polling asynchronous running sql query's status | duration | 1.0.0 | ### Server diff --git a/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/operation/ExecuteStatement.scala b/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/operation/ExecuteStatement.scala index fac90f7ea..2b90525c1 100644 --- a/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/operation/ExecuteStatement.scala +++ b/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/operation/ExecuteStatement.scala @@ -162,17 +162,12 @@ class ExecuteStatement( } } - // TODO:(fchen) make this configurable - val kyuubiBeelineConvertToString = true - def convertComplexType(df: DataFrame): DataFrame = { - if (kyuubiBeelineConvertToString) { - SparkDatasetHelper.convertTopLevelComplexTypeToHiveString(df) - } else { - df - } + SparkDatasetHelper.convertTopLevelComplexTypeToHiveString(df, timestampAsString) } override def getResultSetMetadataHints(): Seq[String] = - Seq(s"__kyuubi_operation_result_format__=$resultFormat") + Seq( + s"__kyuubi_operation_result_format__=$resultFormat", + s"__kyuubi_operation_result_arrow_timestampAsString__=$timestampAsString") } diff --git a/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/operation/SparkOperation.scala b/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/operation/SparkOperation.scala index 06884534d..a6a7fc896 100644 --- a/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/operation/SparkOperation.scala +++ b/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/operation/SparkOperation.scala @@ -24,7 +24,7 @@ import org.apache.hive.service.rpc.thrift.{TGetResultSetMetadataResp, TProgressU import org.apache.spark.kyuubi.{SparkProgressMonitor, SQLOperationListener} import org.apache.spark.kyuubi.SparkUtilsHelper.redact import org.apache.spark.sql.{DataFrame, Row, SparkSession} -import org.apache.spark.sql.internal.SQLConf +import org.apache.spark.sql.execution.SQLExecution import org.apache.spark.sql.types.StructType import org.apache.kyuubi.{KyuubiSQLException, Utils} @@ -136,7 +136,7 @@ abstract class SparkOperation(session: Session) spark.sparkContext.setLocalProperty protected def withLocalProperties[T](f: => T): T = { - SQLConf.withExistingConf(spark.sessionState.conf) { + SQLExecution.withSQLConfPropagated(spark) { val originalSession = SparkSession.getActiveSession try { SparkSession.setActiveSession(spark) @@ -279,6 +279,10 @@ abstract class SparkOperation(session: Session) spark.conf.get("kyuubi.operation.result.format", "thrift") } + protected def timestampAsString: Boolean = { + spark.conf.get("kyuubi.operation.result.arrow.timestampAsString", "false").toBoolean + } + protected def setSessionUserSign(): Unit = { ( session.conf.get(KYUUBI_SESSION_SIGN_PUBLICKEY), diff --git a/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/spark/sql/kyuubi/SparkDatasetHelper.scala b/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/spark/sql/kyuubi/SparkDatasetHelper.scala index 46c3bce4d..1a5429373 100644 --- a/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/spark/sql/kyuubi/SparkDatasetHelper.scala +++ b/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/spark/sql/kyuubi/SparkDatasetHelper.scala @@ -17,8 +17,6 @@ package org.apache.spark.sql.kyuubi -import java.time.ZoneId - import org.apache.spark.rdd.RDD import org.apache.spark.sql.{DataFrame, Dataset, Row} import org.apache.spark.sql.functions._ @@ -31,12 +29,13 @@ object SparkDatasetHelper { ds.toArrowBatchRdd } - def convertTopLevelComplexTypeToHiveString(df: DataFrame): DataFrame = { - val timeZone = ZoneId.of(df.sparkSession.sessionState.conf.sessionLocalTimeZone) + def convertTopLevelComplexTypeToHiveString( + df: DataFrame, + timestampAsString: Boolean): DataFrame = { val quotedCol = (name: String) => col(quoteIfNeeded(name)) - // an udf to call `RowSet.toHiveString` on complex types(struct/array/map). + // an udf to call `RowSet.toHiveString` on complex types(struct/array/map) and timestamp type. val toHiveStringUDF = udf[String, Row, String]((row, schemaDDL) => { val dt = DataType.fromDDL(schemaDDL) dt match { @@ -46,6 +45,8 @@ object SparkDatasetHelper { RowSet.toHiveString((row.toSeq.head, at), nested = true) case StructType(Array(StructField(_, mt: MapType, _, _))) => RowSet.toHiveString((row.toSeq.head, mt), nested = true) + case StructType(Array(StructField(_, tt: TimestampType, _, _))) => + RowSet.toHiveString((row.toSeq.head, tt), nested = true) case _ => throw new UnsupportedOperationException } @@ -56,6 +57,8 @@ object SparkDatasetHelper { toHiveStringUDF(quotedCol(name), lit(sf.toDDL)).as(name) case sf @ StructField(name, _: MapType | _: ArrayType, _, _) => toHiveStringUDF(struct(quotedCol(name)), lit(sf.toDDL)).as(name) + case sf @ StructField(name, _: TimestampType, _, _) if timestampAsString => + toHiveStringUDF(struct(quotedCol(name)), lit(sf.toDDL)).as(name) case StructField(name, _, _, _) => quotedCol(name) } df.select(cols: _*) diff --git a/externals/kyuubi-spark-sql-engine/src/test/scala/org/apache/kyuubi/engine/spark/operation/SparkArrowbasedOperationSuite.scala b/externals/kyuubi-spark-sql-engine/src/test/scala/org/apache/kyuubi/engine/spark/operation/SparkArrowbasedOperationSuite.scala index e46456914..60cc52891 100644 --- a/externals/kyuubi-spark-sql-engine/src/test/scala/org/apache/kyuubi/engine/spark/operation/SparkArrowbasedOperationSuite.scala +++ b/externals/kyuubi-spark-sql-engine/src/test/scala/org/apache/kyuubi/engine/spark/operation/SparkArrowbasedOperationSuite.scala @@ -35,6 +35,13 @@ class SparkArrowbasedOperationSuite extends WithSparkSQLEngine with SparkDataTyp override def resultFormat: String = "arrow" + override def beforeEach(): Unit = { + super.beforeEach() + withJdbcStatement() { statement => + checkResultSetFormat(statement, "arrow") + } + } + test("detect resultSet format") { withJdbcStatement() { statement => checkResultSetFormat(statement, "arrow") @@ -43,7 +50,42 @@ class SparkArrowbasedOperationSuite extends WithSparkSQLEngine with SparkDataTyp } } - def checkResultSetFormat(statement: Statement, expectFormat: String): Unit = { + test("Spark session timezone format") { + withJdbcStatement() { statement => + def check(expect: String): Unit = { + val query = + """ + |SELECT + | from_utc_timestamp( + | from_unixtime( + | 1670404535000 / 1000, 'yyyy-MM-dd HH:mm:ss' + | ), + | 'GMT+08:00' + | ) + |""".stripMargin + val resultSet = statement.executeQuery(query) + assert(resultSet.next()) + assert(resultSet.getString(1) == expect) + } + + def setTimeZone(timeZone: String): Unit = { + val rs = statement.executeQuery(s"set spark.sql.session.timeZone=$timeZone") + assert(rs.next()) + } + + Seq("true", "false").foreach { timestampAsString => + statement.executeQuery( + s"set ${KyuubiConf.ARROW_BASED_ROWSET_TIMESTAMP_AS_STRING.key}=$timestampAsString") + checkArrowBasedRowSetTimestampAsString(statement, timestampAsString) + setTimeZone("UTC") + check("2022-12-07 17:15:35.0") + setTimeZone("GMT+8") + check("2022-12-08 01:15:35.0") + } + } + } + + private def checkResultSetFormat(statement: Statement, expectFormat: String): Unit = { val query = s""" |SELECT '$${hivevar:${KyuubiConf.OPERATION_RESULT_FORMAT.key}}' AS col @@ -52,4 +94,16 @@ class SparkArrowbasedOperationSuite extends WithSparkSQLEngine with SparkDataTyp assert(resultSet.next()) assert(resultSet.getString("col") === expectFormat) } + + private def checkArrowBasedRowSetTimestampAsString( + statement: Statement, + expect: String): Unit = { + val query = + s""" + |SELECT '$${hivevar:${KyuubiConf.ARROW_BASED_ROWSET_TIMESTAMP_AS_STRING.key}}' AS col + |""".stripMargin + val resultSet = statement.executeQuery(query) + assert(resultSet.next()) + assert(resultSet.getString("col") === expect) + } } diff --git a/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala b/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala index 14a05e749..05b6a056f 100644 --- a/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala +++ b/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala @@ -1676,6 +1676,14 @@ object KyuubiConf { .transform(_.toLowerCase(Locale.ROOT)) .createWithDefault("thrift") + val ARROW_BASED_ROWSET_TIMESTAMP_AS_STRING: ConfigEntry[Boolean] = + buildConf("kyuubi.operation.result.arrow.timestampAsString") + .doc("When true, arrow-based rowsets will convert columns of type timestamp to strings for" + + " transmission.") + .version("1.7.0") + .booleanConf + .createWithDefault(false) + val SERVER_OPERATION_LOG_DIR_ROOT: ConfigEntry[String] = buildConf("kyuubi.operation.log.dir.root") .doc("Root directory for query operation log at server-side.") diff --git a/kyuubi-hive-jdbc/src/main/java/org/apache/kyuubi/jdbc/hive/JdbcColumnAttributes.java b/kyuubi-hive-jdbc/src/main/java/org/apache/kyuubi/jdbc/hive/JdbcColumnAttributes.java index 06fb39899..b0257cfff 100644 --- a/kyuubi-hive-jdbc/src/main/java/org/apache/kyuubi/jdbc/hive/JdbcColumnAttributes.java +++ b/kyuubi-hive-jdbc/src/main/java/org/apache/kyuubi/jdbc/hive/JdbcColumnAttributes.java @@ -20,7 +20,7 @@ public class JdbcColumnAttributes { public int precision = 0; public int scale = 0; - public String timeZone = ""; + public String timeZone = null; public JdbcColumnAttributes() {} diff --git a/kyuubi-hive-jdbc/src/main/java/org/apache/kyuubi/jdbc/hive/KyuubiArrowBasedResultSet.java b/kyuubi-hive-jdbc/src/main/java/org/apache/kyuubi/jdbc/hive/KyuubiArrowBasedResultSet.java index c3e75c0ea..ef5008503 100644 --- a/kyuubi-hive-jdbc/src/main/java/org/apache/kyuubi/jdbc/hive/KyuubiArrowBasedResultSet.java +++ b/kyuubi-hive-jdbc/src/main/java/org/apache/kyuubi/jdbc/hive/KyuubiArrowBasedResultSet.java @@ -50,6 +50,7 @@ public abstract class KyuubiArrowBasedResultSet implements SQLResultSet { protected Schema arrowSchema; protected VectorSchemaRoot root; protected ArrowColumnarBatchRow row; + protected boolean timestampAsString = true; protected BufferAllocator allocator; @@ -312,11 +313,18 @@ private Object getColumnValue(int columnIndex) throws SQLException { if (wasNull) { return null; } else { - return row.get(columnIndex - 1, columnType); + JdbcColumnAttributes attributes = columnAttributes.get(columnIndex - 1); + return row.get( + columnIndex - 1, + columnType, + attributes == null ? null : attributes.timeZone, + timestampAsString); } } catch (Exception e) { - e.printStackTrace(); - throw new KyuubiSQLException("Unrecognized column type:", e); + throw new KyuubiSQLException( + String.format( + "Error getting row of type %s at column index %d", columnType, columnIndex - 1), + e); } } diff --git a/kyuubi-hive-jdbc/src/main/java/org/apache/kyuubi/jdbc/hive/KyuubiArrowQueryResultSet.java b/kyuubi-hive-jdbc/src/main/java/org/apache/kyuubi/jdbc/hive/KyuubiArrowQueryResultSet.java index 1f2af29dc..fda70f463 100644 --- a/kyuubi-hive-jdbc/src/main/java/org/apache/kyuubi/jdbc/hive/KyuubiArrowQueryResultSet.java +++ b/kyuubi-hive-jdbc/src/main/java/org/apache/kyuubi/jdbc/hive/KyuubiArrowQueryResultSet.java @@ -58,9 +58,6 @@ public class KyuubiArrowQueryResultSet extends KyuubiArrowBasedResultSet { private boolean isScrollable = false; private boolean fetchFirst = false; - // TODO:(fchen) make this configurable - protected boolean convertComplexTypeToString = true; - private final TProtocolVersion protocol; public static class Builder { @@ -87,6 +84,8 @@ public static class Builder { private boolean isScrollable = false; private ReentrantLock transportLock = null; + private boolean timestampAsString = true; + public Builder(Statement statement) throws SQLException { this.statement = statement; this.connection = statement.getConnection(); @@ -153,6 +152,11 @@ public Builder setScrollable(boolean setScrollable) { return this; } + public Builder setTimestampAsString(boolean timestampAsString) { + this.timestampAsString = timestampAsString; + return this; + } + public Builder setTransportLock(ReentrantLock transportLock) { this.transportLock = transportLock; return this; @@ -189,10 +193,10 @@ protected KyuubiArrowQueryResultSet(Builder builder) throws SQLException { this.maxRows = builder.maxRows; } this.isScrollable = builder.isScrollable; + this.timestampAsString = builder.timestampAsString; this.protocol = builder.getProtocolVersion(); arrowSchema = - ArrowUtils.toArrowSchema( - columnNames, convertComplexTypeToStringType(columnTypes), columnAttributes); + ArrowUtils.toArrowSchema(columnNames, convertToStringType(columnTypes), columnAttributes); if (allocator == null) { initArrowSchemaAndAllocator(); } @@ -271,8 +275,7 @@ private void retrieveSchema() throws SQLException { columnAttributes.add(getColumnAttributes(primitiveTypeEntry)); } arrowSchema = - ArrowUtils.toArrowSchema( - columnNames, convertComplexTypeToStringType(columnTypes), columnAttributes); + ArrowUtils.toArrowSchema(columnNames, convertToStringType(columnTypes), columnAttributes); } catch (SQLException eS) { throw eS; // rethrow the SQLException as is } catch (Exception ex) { @@ -480,22 +483,25 @@ public boolean isClosed() { return isClosed; } - private List convertComplexTypeToStringType(List colTypes) { - if (convertComplexTypeToString) { - return colTypes.stream() - .map( - type -> { - if (type == TTypeId.ARRAY_TYPE - || type == TTypeId.MAP_TYPE - || type == TTypeId.STRUCT_TYPE) { - return TTypeId.STRING_TYPE; - } else { - return type; - } - }) - .collect(Collectors.toList()); - } else { - return colTypes; - } + /** + * 1. the complex types (map/array/struct) are always converted to string type to transport 2. if + * the user set `timestampAsString = true`, then the timestamp type will be converted to string + * type too. + */ + private List convertToStringType(List colTypes) { + return colTypes.stream() + .map( + type -> { + if ((type == TTypeId.ARRAY_TYPE + || type == TTypeId.MAP_TYPE + || type == TTypeId.STRUCT_TYPE) // complex type (map/array/struct) + // timestamp type + || (type == TTypeId.TIMESTAMP_TYPE && timestampAsString)) { + return TTypeId.STRING_TYPE; + } else { + return type; + } + }) + .collect(Collectors.toList()); } } diff --git a/kyuubi-hive-jdbc/src/main/java/org/apache/kyuubi/jdbc/hive/KyuubiStatement.java b/kyuubi-hive-jdbc/src/main/java/org/apache/kyuubi/jdbc/hive/KyuubiStatement.java index ab7c06a55..b452ca6aa 100644 --- a/kyuubi-hive-jdbc/src/main/java/org/apache/kyuubi/jdbc/hive/KyuubiStatement.java +++ b/kyuubi-hive-jdbc/src/main/java/org/apache/kyuubi/jdbc/hive/KyuubiStatement.java @@ -37,6 +37,7 @@ public class KyuubiStatement implements SQLStatement, KyuubiLoggable { public static final Logger LOG = LoggerFactory.getLogger(KyuubiStatement.class.getName()); public static final int DEFAULT_FETCH_SIZE = 1000; public static final String DEFAULT_RESULT_FORMAT = "thrift"; + public static final String DEFAULT_ARROW_TIMESTAMP_AS_STRING = "false"; private final KyuubiConnection connection; private TCLIService.Iface client; private TOperationHandle stmtHandle = null; @@ -45,7 +46,8 @@ public class KyuubiStatement implements SQLStatement, KyuubiLoggable { private int fetchSize = DEFAULT_FETCH_SIZE; private boolean isScrollableResultset = false; private boolean isOperationComplete = false; - private Map properties = new HashMap<>(); + + private Map properties = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); /** * We need to keep a reference to the result set to support the following: * statement.execute(String sql); @@ -213,6 +215,11 @@ private boolean executeWithConfOverlay(String sql, Map confOverl LOG.info("kyuubi.operation.result.format: " + resultFormat); switch (resultFormat) { case "arrow": + boolean timestampAsString = + Boolean.parseBoolean( + properties.getOrDefault( + "__kyuubi_operation_result_arrow_timestampAsString__", + DEFAULT_ARROW_TIMESTAMP_AS_STRING)); resultSet = new KyuubiArrowQueryResultSet.Builder(this) .setClient(client) @@ -222,6 +229,7 @@ private boolean executeWithConfOverlay(String sql, Map confOverl .setFetchSize(fetchSize) .setScrollable(isScrollableResultset) .setSchema(columnNames, columnTypes, columnAttributes) + .setTimestampAsString(timestampAsString) .build(); break; default: @@ -270,6 +278,11 @@ public boolean executeAsync(String sql) throws SQLException { LOG.info("kyuubi.operation.result.format: " + resultFormat); switch (resultFormat) { case "arrow": + boolean timestampAsString = + Boolean.parseBoolean( + properties.getOrDefault( + "__kyuubi_operation_result_arrow_timestampAsString__", + DEFAULT_ARROW_TIMESTAMP_AS_STRING)); resultSet = new KyuubiArrowQueryResultSet.Builder(this) .setClient(client) @@ -279,6 +292,7 @@ public boolean executeAsync(String sql) throws SQLException { .setFetchSize(fetchSize) .setScrollable(isScrollableResultset) .setSchema(columnNames, columnTypes, columnAttributes) + .setTimestampAsString(timestampAsString) .build(); break; default: diff --git a/kyuubi-hive-jdbc/src/main/java/org/apache/kyuubi/jdbc/hive/arrow/ArrowColumnarBatchRow.java b/kyuubi-hive-jdbc/src/main/java/org/apache/kyuubi/jdbc/hive/arrow/ArrowColumnarBatchRow.java index fa914ce5d..373867069 100644 --- a/kyuubi-hive-jdbc/src/main/java/org/apache/kyuubi/jdbc/hive/arrow/ArrowColumnarBatchRow.java +++ b/kyuubi-hive-jdbc/src/main/java/org/apache/kyuubi/jdbc/hive/arrow/ArrowColumnarBatchRow.java @@ -19,6 +19,8 @@ import java.math.BigDecimal; import java.sql.Timestamp; +import java.time.LocalDateTime; +import org.apache.arrow.vector.util.DateUtility; import org.apache.hive.service.rpc.thrift.TTypeId; import org.apache.kyuubi.jdbc.hive.common.DateUtils; import org.apache.kyuubi.jdbc.hive.common.HiveIntervalDayTime; @@ -104,7 +106,7 @@ public Object getMap(int ordinal) { throw new UnsupportedOperationException(); } - public Object get(int ordinal, TTypeId dataType) { + public Object get(int ordinal, TTypeId dataType, String timeZone, boolean timestampAsString) { long seconds; long milliseconds; long microseconds; @@ -131,17 +133,19 @@ public Object get(int ordinal, TTypeId dataType) { case STRING_TYPE: return getString(ordinal); case TIMESTAMP_TYPE: - microseconds = getLong(ordinal); - nanos = (int) (microseconds % 1000000) * 1000; - Timestamp timestamp = new Timestamp(microseconds / 1000); - timestamp.setNanos(nanos); - return timestamp; + if (timestampAsString) { + return Timestamp.valueOf(getString(ordinal)); + } else { + LocalDateTime localDateTime = + DateUtility.getLocalDateTimeFromEpochMicro(getLong(ordinal), timeZone); + return Timestamp.valueOf(localDateTime); + } case DATE_TYPE: return DateUtils.internalToDate(getInt(ordinal)); case INTERVAL_DAY_TIME_TYPE: microseconds = getLong(ordinal); - seconds = microseconds / 1000000; - nanos = (int) (microseconds % 1000000) * 1000; + seconds = microseconds / 1_000_000; + nanos = (int) (microseconds % 1_000_000) * 1_000; return new HiveIntervalDayTime(seconds, nanos); case INTERVAL_YEAR_MONTH_TYPE: return new HiveIntervalYearMonth(getInt(ordinal)); From a896e95bd4645244a6ee03ee4d928bdc14633b1e Mon Sep 17 00:00:00 2001 From: Cheng Pan Date: Sun, 19 Feb 2023 16:31:26 +0800 Subject: [PATCH 085/760] [KYUUBI #4338][FOLLOWUP] Fix K8s integration tests ### _Why are the changes needed?_ Correct the Spark image tag to recover "Kyuubi Server On Kubernetes Integration Test" ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [ ] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4369 from pan3793/spark-3.3.2. Closes #4338 3232bf9f [Cheng Pan] [KYUUBI #4338][FOLLOWUP] Fix K8s integration tests Authored-by: Cheng Pan Signed-off-by: Cheng Pan --- .github/workflows/master.yml | 1 - .../test/deployment/KyuubiOnKubernetesTestsSuite.scala | 2 +- .../kubernetes/test/spark/SparkOnKubernetesTestsSuite.scala | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index b0ab493a4..a70117826 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -434,7 +434,6 @@ jobs: run: >- ./build/mvn ${MVN_OPT} clean install -Pflink-provided,hive-provided - -Pspark-3.2 -Pkubernetes-it -Dtest=none -DwildcardSuites=org.apache.kyuubi.kubernetes.test.spark - name: Print Driver Pod logs diff --git a/integration-tests/kyuubi-kubernetes-it/src/test/scala/org/apache/kyuubi/kubernetes/test/deployment/KyuubiOnKubernetesTestsSuite.scala b/integration-tests/kyuubi-kubernetes-it/src/test/scala/org/apache/kyuubi/kubernetes/test/deployment/KyuubiOnKubernetesTestsSuite.scala index 6cf8dfcc8..bc7c98a80 100644 --- a/integration-tests/kyuubi-kubernetes-it/src/test/scala/org/apache/kyuubi/kubernetes/test/deployment/KyuubiOnKubernetesTestsSuite.scala +++ b/integration-tests/kyuubi-kubernetes-it/src/test/scala/org/apache/kyuubi/kubernetes/test/deployment/KyuubiOnKubernetesTestsSuite.scala @@ -54,7 +54,7 @@ class KyuubiOnKubernetesWithSparkTestsBase extends WithKyuubiServerOnKubernetes super.connectionConf ++ Map( "spark.master" -> s"k8s://$miniKubeApiMaster", - "spark.kubernetes.container.image" -> "apache/spark:3.3.2", + "spark.kubernetes.container.image" -> "apache/spark:v3.3.2", "spark.executor.memory" -> "512M", "spark.driver.memory" -> "1024M", "spark.kubernetes.driver.request.cores" -> "250m", diff --git a/integration-tests/kyuubi-kubernetes-it/src/test/scala/org/apache/kyuubi/kubernetes/test/spark/SparkOnKubernetesTestsSuite.scala b/integration-tests/kyuubi-kubernetes-it/src/test/scala/org/apache/kyuubi/kubernetes/test/spark/SparkOnKubernetesTestsSuite.scala index 798618e4c..019de840d 100644 --- a/integration-tests/kyuubi-kubernetes-it/src/test/scala/org/apache/kyuubi/kubernetes/test/spark/SparkOnKubernetesTestsSuite.scala +++ b/integration-tests/kyuubi-kubernetes-it/src/test/scala/org/apache/kyuubi/kubernetes/test/spark/SparkOnKubernetesTestsSuite.scala @@ -45,7 +45,7 @@ abstract class SparkOnKubernetesSuiteBase // TODO Support more Spark version // Spark official docker image: https://hub.docker.com/r/apache/spark/tags KyuubiConf().set("spark.master", s"k8s://$apiServerAddress") - .set("spark.kubernetes.container.image", "apache/spark:v3.2.1") + .set("spark.kubernetes.container.image", "apache/spark:v3.3.2") .set("spark.kubernetes.container.image.pullPolicy", "IfNotPresent") .set("spark.executor.instances", "1") .set("spark.executor.memory", "512M") From 9d33b849fb5cf63ff05eeb2f80ccb74b04751774 Mon Sep 17 00:00:00 2001 From: liangbowen Date: Sun, 19 Feb 2023 16:33:26 +0800 Subject: [PATCH 086/760] [KYUUBI #4357] Bump Jersey from 2.38 to 2.39 ### _Why are the changes needed?_ to close #4357. ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [x] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4366 from bowenliang123/jersey-2.39. Closes #4357 bd214e8d [liangbowen] bump jersey from 2.38 to 2.39 Authored-by: liangbowen Signed-off-by: Cheng Pan --- dev/dependencyList | 16 ++++++++-------- pom.xml | 9 +-------- 2 files changed, 9 insertions(+), 16 deletions(-) diff --git a/dev/dependencyList b/dev/dependencyList index 9813bb562..7932c5cdf 100644 --- a/dev/dependencyList +++ b/dev/dependencyList @@ -83,14 +83,14 @@ jakarta.ws.rs-api/2.1.6//jakarta.ws.rs-api-2.1.6.jar jakarta.xml.bind-api/2.3.2//jakarta.xml.bind-api-2.3.2.jar javassist/3.25.0-GA//javassist-3.25.0-GA.jar jcl-over-slf4j/1.7.36//jcl-over-slf4j-1.7.36.jar -jersey-client/2.38//jersey-client-2.38.jar -jersey-common/2.38//jersey-common-2.38.jar -jersey-container-servlet-core/2.38//jersey-container-servlet-core-2.38.jar -jersey-entity-filtering/2.38//jersey-entity-filtering-2.38.jar -jersey-hk2/2.38//jersey-hk2-2.38.jar -jersey-media-json-jackson/2.38//jersey-media-json-jackson-2.38.jar -jersey-media-multipart/2.38//jersey-media-multipart-2.38.jar -jersey-server/2.38//jersey-server-2.38.jar +jersey-client/2.39//jersey-client-2.39.jar +jersey-common/2.39//jersey-common-2.39.jar +jersey-container-servlet-core/2.39//jersey-container-servlet-core-2.39.jar +jersey-entity-filtering/2.39//jersey-entity-filtering-2.39.jar +jersey-hk2/2.39//jersey-hk2-2.39.jar +jersey-media-json-jackson/2.39//jersey-media-json-jackson-2.39.jar +jersey-media-multipart/2.39//jersey-media-multipart-2.39.jar +jersey-server/2.39//jersey-server-2.39.jar jetcd-api/0.7.3//jetcd-api-0.7.3.jar jetcd-common/0.7.3//jetcd-common-0.7.3.jar jetcd-core/0.7.3//jetcd-core-0.7.3.jar diff --git a/pom.xml b/pom.xml index a774acbb2..1a583eb78 100644 --- a/pom.xml +++ b/pom.xml @@ -163,7 +163,7 @@ 4.0.4 2.3.2 1.2.2 - 2.38 + 2.39 9.4.50.v20221201 0.9.94 4.13.2 @@ -1495,13 +1495,6 @@ org.glassfish.jersey.media jersey-media-multipart ${jersey.version} - - - - org.junit.jupiter - junit-jupiter - -
From 5b2c40659f25d6222c96bff9e144f7bd5719b18c Mon Sep 17 00:00:00 2001 From: Luning Wang Date: Sun, 19 Feb 2023 18:55:31 +0800 Subject: [PATCH 087/760] [KYUUBI #3081][DOCS] Add Hudi connector doc in Trino ### _Why are the changes needed?_ Add a Hudi connector doc in Trino ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [x] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4356 from a49a/hudi-trino-doc. Closes #3081 346cf11ca [Luning Wang] [KYUUBI #3081][DOCS] Add Hudi connector doc in Trino Authored-by: Luning Wang Signed-off-by: Cheng Pan --- docs/connector/trino/hudi.rst | 80 ++++++++++++++++++++++++++++++++++ docs/connector/trino/index.rst | 1 + 2 files changed, 81 insertions(+) create mode 100644 docs/connector/trino/hudi.rst diff --git a/docs/connector/trino/hudi.rst b/docs/connector/trino/hudi.rst new file mode 100644 index 000000000..5c965a0b6 --- /dev/null +++ b/docs/connector/trino/hudi.rst @@ -0,0 +1,80 @@ +.. Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + +.. http://www.apache.org/licenses/LICENSE-2.0 + +.. Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +`Hudi`_ +======== + +Apache Hudi (pronounced “hoodie”) is the next generation streaming data lake platform. +Apache Hudi brings core warehouse and database functionality directly to a data lake. + +.. tip:: + This article assumes that you have mastered the basic knowledge and operation of `Hudi`_. + For the knowledge about Hudi not mentioned in this article, + you can obtain it from its `Official Documentation`_. + +By using Kyuubi, we can run SQL queries towards Hudi which is more convenient, easy to understand, +and easy to expand than directly using Trino to manipulate Hudi. + +Hudi Integration +---------------- + +To enable the integration of Kyuubi Trino SQL engine and Hudi, you need to: + +- Setting the Trino extension and catalog :ref:`configurations` + +.. _trino-hudi-conf: + +Configurations +************** + +Catalogs are registered by creating a file of catalog properties in the `$TRINO_SERVER_HOME/etc/catalog` directory. +For example, we can create a `$TRINO_SERVER_HOME/etc/catalog/hudi.properties` with the following contents to mount the Hudi connector as a Hudi catalog: + +.. code-block:: properties + + connector.name=hudi + hive.metastore.uri=thrift://example.net:9083 + +Note: You need to replace $TRINO_SERVER_HOME above to your Trino server home path like `/opt/trino-server-406`. + +More configuration properties can be found in the `Hudi connector in Trino document`_. + +.. tip:: + Trino version 398 or higher, it is recommended to use the Hudi connector. + You don't need to install any dependencies in version 398 or higher. + +Hudi Operations +--------------- +The globally available and read operation statements are supported in Trino. +These statements can be found in `Trino SQL Support`_. +Currently, Trino cannot write data to a Hudi table. +A common scenario is to write data with Spark/Flink and read data with Trino. +You can use the Kyuubi Trino SQL engine to query the table with the following SQL ``SELECT`` statement. + +Taking ``Query Data`` as a example, + +.. code-block:: sql + + USE example.example_schema; + + SELECT symbol, max(ts) + FROM stock_ticks_cow + GROUP BY symbol + HAVING symbol = 'GOOG'; + +.. _Hudi: https://hudi.apache.org/ +.. _Official Documentation: https://hudi.apache.org/docs/overview +.. _Hudi connector in Trino document: https://trino.io/docs/current/connector/hudi.html +.. _Trino SQL Support: https://trino.io/docs/current/language/sql-support.html# diff --git a/docs/connector/trino/index.rst b/docs/connector/trino/index.rst index a5c5675ce..f5d651d45 100644 --- a/docs/connector/trino/index.rst +++ b/docs/connector/trino/index.rst @@ -20,4 +20,5 @@ Connectors For Trino SQL Engine :maxdepth: 2 flink_table_store + hudi iceberg \ No newline at end of file From dec432713107cd52eb5fef9be3e98a02e11e5276 Mon Sep 17 00:00:00 2001 From: odone Date: Mon, 20 Feb 2023 10:10:58 +0800 Subject: [PATCH 088/760] [KYUUBI #4345] Add the doc of kyuubi trino server https://github.com/apache/kyuubi/issues/3901 ### _Why are the changes needed?_ ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [ ] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4345 from iodone/trino-server-dock. Closes #4345 6f8e3149 [odone] add the doc of kyuubi trino server Authored-by: odone Signed-off-by: ulyssesyou --- docs/client/cli/index.rst | 1 + docs/client/cli/trino_cli.md | 88 ++++++++++++++++++++++++++++++++ docs/client/jdbc/index.rst | 1 + docs/client/jdbc/trino_jdbc.md | 92 ++++++++++++++++++++++++++++++++++ 4 files changed, 182 insertions(+) create mode 100644 docs/client/cli/trino_cli.md create mode 100644 docs/client/jdbc/trino_jdbc.md diff --git a/docs/client/cli/index.rst b/docs/client/cli/index.rst index 61be9ad8c..19122ced4 100644 --- a/docs/client/cli/index.rst +++ b/docs/client/cli/index.rst @@ -21,3 +21,4 @@ Command Line Interface(CLI)s kyuubi_beeline hive_beeline + trino_cli diff --git a/docs/client/cli/trino_cli.md b/docs/client/cli/trino_cli.md new file mode 100644 index 000000000..68ebd8300 --- /dev/null +++ b/docs/client/cli/trino_cli.md @@ -0,0 +1,88 @@ + + +# Trino command line interface + +The Trino CLI provides a terminal-based, interactive shell for running queries. We can use it to connect Kyuubi server now. + +## Start Kyuubi Trino Server + +First we should configure the trino protocol and the service port in the `kyuubi.conf` + +``` +kyuubi.frontend.protocols TRINO +kyuubi.frontend.trino.bind.port 10999 #default port +``` + +## Install + +Download [trino-cli-363-executable.jar](https://repo1.maven.org/maven2/io/trino/trino-jdbc/363/trino-jdbc-363.jar), rename it to `trino`, make it executable with `chmod +x`, and run it to show the version of the CLI: + +``` +wget https://repo1.maven.org/maven2/io/trino/trino-jdbc/363/trino-jdbc-363.jar +mv trino-jdbc-363.jar trino +chmod +x trino +./trino --version +``` + +## Running the CLI + +The minimal command to start the CLI in interactive mode specifies the URL of the kyuubi server with the Trino protocol: + +``` +./trino --server http://localhost:10999 +``` + +If successful, you will get a prompt to execute commands. Use the help command to see a list of supported commands. Use the clear command to clear the terminal. To stop and exit the CLI, run exit or quit.: + +``` +trino> help + +Supported commands: +QUIT +EXIT +CLEAR +EXPLAIN [ ( option [, ...] ) ] + options: FORMAT { TEXT | GRAPHVIZ | JSON } + TYPE { LOGICAL | DISTRIBUTED | VALIDATE | IO } +DESCRIBE +SHOW COLUMNS FROM
+SHOW FUNCTIONS +SHOW CATALOGS [LIKE ] +SHOW SCHEMAS [FROM ] [LIKE ] +SHOW TABLES [FROM ] [LIKE ] +USE [.] +``` + +You can now run SQL statements. After processing, the CLI will show results and statistics. + +``` +trino> select 1; + _col0 +------- + 1 +(1 row) + +Query 20230216_125233_00806_examine_6hxus, FINISHED, 1 node +Splits: 1 total, 1 done (100.00%) +0.29 [0 rows, 0B] [0 rows/s, 0B/s] + +trino> +``` + +Many other options are available to further configure the CLI in interactive mode to +refer https://trino.io/docs/current/client/cli.html#running-the-cli diff --git a/docs/client/jdbc/index.rst b/docs/client/jdbc/index.rst index 31871f138..abcd6a452 100644 --- a/docs/client/jdbc/index.rst +++ b/docs/client/jdbc/index.rst @@ -22,4 +22,5 @@ JDBC Drivers kyuubi_jdbc hive_jdbc mysql_jdbc + trino_jdbc diff --git a/docs/client/jdbc/trino_jdbc.md b/docs/client/jdbc/trino_jdbc.md new file mode 100644 index 000000000..0f91c4337 --- /dev/null +++ b/docs/client/jdbc/trino_jdbc.md @@ -0,0 +1,92 @@ + + +# Trino JDBC Driver + +## Instructions + +Kyuubi currently supports the Trino connection protocol, so we can use Trino-JDBC to connect to the kyuubi server +and submit SQL to Spark, Trino and other engines for execution. + +## Start Kyuubi Trino Server + +First we should configure the trino protocol and the service port in the `kyuubi.conf` + +``` +kyuubi.frontend.protocols TRINO +kyuubi.frontend.trino.bind.port 10999 #default port +``` + +## Install Trino JDBC + +Download [trino-jdbc-363.jar](https://repo1.maven.org/maven2/io/trino/trino-jdbc/363/trino-jdbc-363.jar) and add it to the classpath of your Java application. + +The driver is also available from Maven Central: + +```xml + + io.trino + trino-jdbc + 363 + +``` + +## JDBC URL + +When your driver is loaded, registered and configured, you are ready to connect to Trino from your application. The following JDBC URL formats are supported: + +``` +jdbc:trino://host:port +``` + +Trino JDBC example + +```java +String trinoHost = "localhost"; +String trinoPort = "10999"; +String trinoUser = "default"; +String trinoPassword = null; +Connection connection = null; +ResultSet rs = null; + +try { + // Create the connection using the JDBC URL + connection = DriverManager.getConnection("jdbc:trino://" + trinoHost + ":" + trinoPort, trinoUser, trinoPassword); + + // Do whatever you need to do with the connection + Statement stmt = connection.createStatement(); + rs = stmt.executeQuery("SELECT 1"); + + while (rs.next()) { + // retrieve data from the ResultSet + } + +} catch (Exception e) { + e.printStackTrace(); +} finally { + try { + // Close the connection when you're done with it + if (rs != null) rs.close(); + if (connection != null) connection.close(); + } catch (Exception e) { + e.printStackTrace(); + } +} +``` + +The configuration of the connection parameters can be found in the official trino documentation at: https://trino.io/docs/current/client/jdbc.html#connection-parameters + From 3f6ba776fbda59e0c424283ababc4b6b18912b21 Mon Sep 17 00:00:00 2001 From: fwang12 Date: Mon, 20 Feb 2023 11:18:21 +0800 Subject: [PATCH 089/760] [KYUUBI #4216] Support to transfer client version for kyuubi hive jdbc and rest client sdk ### _Why are the changes needed?_ To close #4216 ### _How was this patch tested?_ - [x] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [x] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4370 from turboFei/client_version. Closes #4216 efd934c6b [fwang12] save cf8acd54a [fwang12] version properties Authored-by: fwang12 Signed-off-by: fwang12 --- .../kyuubi/config/KyuubiReservedKeys.scala | 1 + kyuubi-hive-jdbc/pom.xml | 8 ++++ .../kyuubi/jdbc/hive/KyuubiConnection.java | 1 + .../org/apache/kyuubi/jdbc/hive/Utils.java | 17 ++++++++ .../src/main/resources/version.properties | 18 ++++++++ .../apache/kyuubi/jdbc/hive/UtilsTest.java | 7 +++ kyuubi-rest-client/pom.xml | 7 +++ .../apache/kyuubi/client/BatchRestApi.java | 12 ++++++ .../kyuubi/client/util/VersionUtils.java | 43 +++++++++++++++++++ .../src/main/resources/version.properties | 18 ++++++++ .../kyuubi/client/util/VersionUtilsTest.java | 30 +++++++++++++ .../KyuubiOperationPerConnectionSuite.scala | 12 +++++- .../rest/client/BatchRestApiSuite.scala | 21 ++++++++- 13 files changed, 192 insertions(+), 3 deletions(-) create mode 100644 kyuubi-hive-jdbc/src/main/resources/version.properties create mode 100644 kyuubi-rest-client/src/main/java/org/apache/kyuubi/client/util/VersionUtils.java create mode 100644 kyuubi-rest-client/src/main/resources/version.properties create mode 100644 kyuubi-rest-client/src/test/java/org/apache/kyuubi/client/util/VersionUtilsTest.java diff --git a/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiReservedKeys.scala b/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiReservedKeys.scala index 6036af855..335253925 100644 --- a/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiReservedKeys.scala +++ b/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiReservedKeys.scala @@ -19,6 +19,7 @@ package org.apache.kyuubi.config object KyuubiReservedKeys { final val KYUUBI_CLIENT_IP_KEY = "kyuubi.client.ipAddress" + final val KYUUBI_CLIENT_VERSION_KEY = "kyuubi.client.version" final val KYUUBI_SERVER_IP_KEY = "kyuubi.server.ipAddress" final val KYUUBI_SESSION_USER_KEY = "kyuubi.session.user" final val KYUUBI_SESSION_SIGN_PUBLICKEY = "kyuubi.session.sign.publickey" diff --git a/kyuubi-hive-jdbc/pom.xml b/kyuubi-hive-jdbc/pom.xml index 7b45c27bb..36ea7acc2 100644 --- a/kyuubi-hive-jdbc/pom.xml +++ b/kyuubi-hive-jdbc/pom.xml @@ -171,6 +171,14 @@ + + + + true + src/main/resources + + + org.apache.maven.plugins diff --git a/kyuubi-hive-jdbc/src/main/java/org/apache/kyuubi/jdbc/hive/KyuubiConnection.java b/kyuubi-hive-jdbc/src/main/java/org/apache/kyuubi/jdbc/hive/KyuubiConnection.java index 9931dcec2..0932ea565 100644 --- a/kyuubi-hive-jdbc/src/main/java/org/apache/kyuubi/jdbc/hive/KyuubiConnection.java +++ b/kyuubi-hive-jdbc/src/main/java/org/apache/kyuubi/jdbc/hive/KyuubiConnection.java @@ -738,6 +738,7 @@ private void openSession() throws SQLException { } catch (UnknownHostException e) { LOG.debug("Error getting Kyuubi session local client ip address", e); } + openConf.put(Utils.KYUUBI_CLIENT_VERSION_KEY, Utils.getVersion()); openReq.setConfiguration(openConf); // Store the user name in the open request in case no non-sasl authentication diff --git a/kyuubi-hive-jdbc/src/main/java/org/apache/kyuubi/jdbc/hive/Utils.java b/kyuubi-hive-jdbc/src/main/java/org/apache/kyuubi/jdbc/hive/Utils.java index 59cc67f9d..1daa322ec 100644 --- a/kyuubi-hive-jdbc/src/main/java/org/apache/kyuubi/jdbc/hive/Utils.java +++ b/kyuubi-hive-jdbc/src/main/java/org/apache/kyuubi/jdbc/hive/Utils.java @@ -486,4 +486,21 @@ public static String getCanonicalHostName(String hostName) { public static boolean isKyuubiOperationHint(String hint) { return KYUUBI_OPERATION_HINT_PATTERN.matcher(hint).matches(); } + + public static final String KYUUBI_CLIENT_VERSION_KEY = "kyuubi.client.version"; + private static String KYUUBI_CLIENT_VERSION; + + public static synchronized String getVersion() { + if (KYUUBI_CLIENT_VERSION == null) { + try { + Properties prop = new Properties(); + prop.load(Utils.class.getClassLoader().getResourceAsStream("version.properties")); + KYUUBI_CLIENT_VERSION = prop.getProperty(KYUUBI_CLIENT_VERSION_KEY, "unknown"); + } catch (Exception e) { + LOG.error("Error getting kyuubi client version", e); + KYUUBI_CLIENT_VERSION = "unknown"; + } + } + return KYUUBI_CLIENT_VERSION; + } } diff --git a/kyuubi-hive-jdbc/src/main/resources/version.properties b/kyuubi-hive-jdbc/src/main/resources/version.properties new file mode 100644 index 000000000..82ae50cfb --- /dev/null +++ b/kyuubi-hive-jdbc/src/main/resources/version.properties @@ -0,0 +1,18 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +kyuubi.client.version = ${project.version} diff --git a/kyuubi-hive-jdbc/src/test/java/org/apache/kyuubi/jdbc/hive/UtilsTest.java b/kyuubi-hive-jdbc/src/test/java/org/apache/kyuubi/jdbc/hive/UtilsTest.java index e0594dde0..b01957b3e 100644 --- a/kyuubi-hive-jdbc/src/test/java/org/apache/kyuubi/jdbc/hive/UtilsTest.java +++ b/kyuubi-hive-jdbc/src/test/java/org/apache/kyuubi/jdbc/hive/UtilsTest.java @@ -29,6 +29,7 @@ import java.util.Collection; import java.util.Map; import java.util.Properties; +import java.util.regex.Pattern; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -138,4 +139,10 @@ public void testExtractURLComponents() throws JdbcUriParseException { assertEquals(expectedDb, jdbcConnectionParams1.getDbName()); assertEquals(expectedHiveConf, jdbcConnectionParams1.getHiveConfs()); } + + @Test + public void testGetVersion() { + Pattern pattern = Pattern.compile("^\\d+\\.\\d+\\.\\d+.*"); + assert pattern.matcher(Utils.getVersion()).matches(); + } } diff --git a/kyuubi-rest-client/pom.xml b/kyuubi-rest-client/pom.xml index 0af949125..6ba88e8dc 100644 --- a/kyuubi-rest-client/pom.xml +++ b/kyuubi-rest-client/pom.xml @@ -115,6 +115,13 @@ + + + true + src/main/resources + + + net.alchim31.maven diff --git a/kyuubi-rest-client/src/main/java/org/apache/kyuubi/client/BatchRestApi.java b/kyuubi-rest-client/src/main/java/org/apache/kyuubi/client/BatchRestApi.java index a09ab562b..f5099568b 100644 --- a/kyuubi-rest-client/src/main/java/org/apache/kyuubi/client/BatchRestApi.java +++ b/kyuubi-rest-client/src/main/java/org/apache/kyuubi/client/BatchRestApi.java @@ -22,6 +22,7 @@ import java.util.Map; import org.apache.kyuubi.client.api.v1.dto.*; import org.apache.kyuubi.client.util.JsonUtils; +import org.apache.kyuubi.client.util.VersionUtils; public class BatchRestApi { @@ -36,11 +37,13 @@ public BatchRestApi(KyuubiRestClient client) { } public Batch createBatch(BatchRequest request) { + setClientVersion(request); String requestBody = JsonUtils.toJson(request); return this.getClient().post(API_BASE_PATH, requestBody, Batch.class, client.getAuthHeader()); } public Batch createBatch(BatchRequest request, File resourceFile) { + setClientVersion(request); Map multiPartMap = new HashMap<>(); multiPartMap.put("batchRequest", new MultiPart(MultiPart.MultiPartType.JSON, request)); multiPartMap.put("resourceFile", new MultiPart(MultiPart.MultiPartType.FILE, resourceFile)); @@ -96,4 +99,13 @@ public CloseBatchResponse deleteBatch(String batchId, String hs2ProxyUser) { private IRestClient getClient() { return this.client.getHttpClient(); } + + private void setClientVersion(BatchRequest request) { + if (request != null) { + Map newConf = new HashMap<>(); + newConf.putAll(request.getConf()); + newConf.put(VersionUtils.KYUUBI_CLIENT_VERSION_KEY, VersionUtils.getVersion()); + request.setConf(newConf); + } + } } diff --git a/kyuubi-rest-client/src/main/java/org/apache/kyuubi/client/util/VersionUtils.java b/kyuubi-rest-client/src/main/java/org/apache/kyuubi/client/util/VersionUtils.java new file mode 100644 index 000000000..bcabca5b9 --- /dev/null +++ b/kyuubi-rest-client/src/main/java/org/apache/kyuubi/client/util/VersionUtils.java @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.kyuubi.client.util; + +import java.util.Properties; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class VersionUtils { + static final Logger LOG = LoggerFactory.getLogger(VersionUtils.class); + + public static final String KYUUBI_CLIENT_VERSION_KEY = "kyuubi.client.version"; + private static String KYUUBI_CLIENT_VERSION; + + public static synchronized String getVersion() { + if (KYUUBI_CLIENT_VERSION == null) { + try { + Properties prop = new Properties(); + prop.load(VersionUtils.class.getClassLoader().getResourceAsStream("version.properties")); + KYUUBI_CLIENT_VERSION = prop.getProperty(KYUUBI_CLIENT_VERSION_KEY, "unknown"); + } catch (Exception e) { + LOG.error("Error getting kyuubi client version", e); + KYUUBI_CLIENT_VERSION = "unknown"; + } + } + return KYUUBI_CLIENT_VERSION; + } +} diff --git a/kyuubi-rest-client/src/main/resources/version.properties b/kyuubi-rest-client/src/main/resources/version.properties new file mode 100644 index 000000000..82ae50cfb --- /dev/null +++ b/kyuubi-rest-client/src/main/resources/version.properties @@ -0,0 +1,18 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +kyuubi.client.version = ${project.version} diff --git a/kyuubi-rest-client/src/test/java/org/apache/kyuubi/client/util/VersionUtilsTest.java b/kyuubi-rest-client/src/test/java/org/apache/kyuubi/client/util/VersionUtilsTest.java new file mode 100644 index 000000000..d4675f340 --- /dev/null +++ b/kyuubi-rest-client/src/test/java/org/apache/kyuubi/client/util/VersionUtilsTest.java @@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.kyuubi.client.util; + +import java.util.regex.Pattern; +import org.junit.Test; + +public class VersionUtilsTest { + + @Test + public void testGetClientVersion() { + Pattern pattern = Pattern.compile("^\\d+\\.\\d+\\.\\d+.*"); + assert pattern.matcher(VersionUtils.getVersion()).matches(); + } +} diff --git a/kyuubi-server/src/test/scala/org/apache/kyuubi/operation/KyuubiOperationPerConnectionSuite.scala b/kyuubi-server/src/test/scala/org/apache/kyuubi/operation/KyuubiOperationPerConnectionSuite.scala index 341f00639..669475b6c 100644 --- a/kyuubi-server/src/test/scala/org/apache/kyuubi/operation/KyuubiOperationPerConnectionSuite.scala +++ b/kyuubi-server/src/test/scala/org/apache/kyuubi/operation/KyuubiOperationPerConnectionSuite.scala @@ -26,8 +26,8 @@ import scala.collection.JavaConverters._ import org.apache.hive.service.rpc.thrift._ import org.scalatest.time.SpanSugar.convertIntToGrainOfTime -import org.apache.kyuubi.WithKyuubiServer -import org.apache.kyuubi.config.KyuubiConf +import org.apache.kyuubi.{KYUUBI_VERSION, WithKyuubiServer} +import org.apache.kyuubi.config.{KyuubiConf, KyuubiReservedKeys} import org.apache.kyuubi.config.KyuubiConf.SESSION_CONF_ADVISOR import org.apache.kyuubi.engine.ApplicationState import org.apache.kyuubi.jdbc.KyuubiHiveDriver @@ -272,6 +272,14 @@ class KyuubiOperationPerConnectionSuite extends WithKyuubiServer with HiveJDBCTe assert(MetricsSystem.counterValue(connFailedMetric).getOrElse(0L) > connFailedCount) } } + + test("support to transfer client version when opening jdbc connection") { + withJdbcStatement() { stmt => + val rs = stmt.executeQuery(s"set spark.${KyuubiReservedKeys.KYUUBI_CLIENT_VERSION_KEY}") + assert(rs.next()) + assert(rs.getString(2) === KYUUBI_VERSION) + } + } } class TestSessionConfAdvisor extends SessionConfAdvisor { diff --git a/kyuubi-server/src/test/scala/org/apache/kyuubi/server/rest/client/BatchRestApiSuite.scala b/kyuubi-server/src/test/scala/org/apache/kyuubi/server/rest/client/BatchRestApiSuite.scala index 6110ccb1e..cb7905286 100644 --- a/kyuubi-server/src/test/scala/org/apache/kyuubi/server/rest/client/BatchRestApiSuite.scala +++ b/kyuubi-server/src/test/scala/org/apache/kyuubi/server/rest/client/BatchRestApiSuite.scala @@ -22,10 +22,11 @@ import java.util.Base64 import org.scalatest.time.SpanSugar.convertIntToGrainOfTime -import org.apache.kyuubi.{BatchTestHelper, RestClientTestHelper} +import org.apache.kyuubi.{BatchTestHelper, KYUUBI_VERSION, RestClientTestHelper} import org.apache.kyuubi.client.{BatchRestApi, KyuubiRestClient} import org.apache.kyuubi.client.api.v1.dto.Batch import org.apache.kyuubi.client.exception.KyuubiRestException +import org.apache.kyuubi.config.KyuubiReservedKeys import org.apache.kyuubi.metrics.{MetricsConstants, MetricsSystem} import org.apache.kyuubi.session.{KyuubiSession, SessionHandle} @@ -215,4 +216,22 @@ class BatchRestApiSuite extends RestClientTestHelper with BatchTestHelper { batchRestApi.listBatches(null, null, null, 0, 0, 0, 1) batchRestApi.listBatches(null, null, null, 0, 0, 0, 1) } + + test("support to transfer client version when creating batch") { + val spnegoKyuubiRestClient: KyuubiRestClient = + KyuubiRestClient.builder(baseUri.toString) + .authHeaderMethod(KyuubiRestClient.AuthHeaderMethod.SPNEGO) + .spnegoHost("localhost") + .build() + val batchRestApi: BatchRestApi = new BatchRestApi(spnegoKyuubiRestClient) + // create batch + val requestObj = + newSparkBatchRequest(Map("spark.master" -> "local")) + + val batch = batchRestApi.createBatch(requestObj) + val batchSession = + server.backendService.sessionManager.getSession(SessionHandle.fromUUID(batch.getId)) + assert( + batchSession.conf.get(KyuubiReservedKeys.KYUUBI_CLIENT_VERSION_KEY) == Some(KYUUBI_VERSION)) + } } From b99ab507c782d33d1cbe6a1b58d77eff14df5cca Mon Sep 17 00:00:00 2001 From: Yikf Date: Mon, 20 Feb 2023 13:00:45 +0800 Subject: [PATCH 090/760] [KYUUBI #4371] Fix typo in `kyuubi_ecosystem.drawio` ### _Why are the changes needed?_ Fix typo in `kyuubi_ecosystem.drawio` ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [x] Add screenshots for manual tests if appropriate - [ ] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4371 from Yikf/readme-img. Closes #4371 eecea309 [Yikf] fix typo in kyuubi_ecosystem.drawio Authored-by: Yikf Signed-off-by: Yikf --- docs/imgs/kyuubi_ecosystem.drawio | 2 +- docs/imgs/kyuubi_ecosystem.drawio.png | Bin 658886 -> 352492 bytes 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/imgs/kyuubi_ecosystem.drawio b/docs/imgs/kyuubi_ecosystem.drawio index 723b306e8..7171491ef 100644 --- a/docs/imgs/kyuubi_ecosystem.drawio +++ b/docs/imgs/kyuubi_ecosystem.drawio @@ -1 +1 @@ -7L3XtqRYki36NfXYNdDiEQ2OdDS83IEGRzvCga8/rB2RVZkZWd3VfSrr9h03Isd2scARy8ymTZu23PMvKNcf0juZan3Mi+4vCJQff0H5vyAIAuHI/QRGzm8jMIIR30aqd5N/H/v7gNNcxfdB6Pvo1uTF8psd13Hs1mb67WA2DkORrb8ZS97v8fPb3cqx++1Zp6QqfhhwsqT7cTRo8rX+Nkoh5N/H5aKp6l/ODBP0ty198svO3+9kqZN8/PxqCBX+gnLvcVy/veoPrujA7P0yL98+J/6DrX+7sHcxrP/MB/Ra4nvI2YIw+kC0cw1vy/kPFP12mD3ptu93/P1q1/OXKXiP25AX4CjQX1D2Uzdr4UxJBrZ+bqvfY/Xad/c7+H7ZJWnRsUnWVl8f48ZufN+bhnG492eX9T22f5vIewrYsum6X3b6C4KWZUFkGRgfh1VM+qYDjsONfZPdF+Ukw3I/6c73Hb67C4ze75OuqYb7TXZPR3Efjc2Tpf66avhvJ/7ViXKSTiFwPz9O4/eZ3Yv3Why/Gvo+rVIx9sX6Pu9dftmK0X/F8W+f+u7nCPrd6p+/Ow0JfR+rf+Uw2C+DyXdHrf52+L/b8n7x3Zx/bNopiAdiL9v/5x2jTb/RSpgQ/4HTf2Baolu/zx0Ihm9GvEfnDXgh++M8/23Tb1zil0FwnP9YvozA3DvAyHT8+hNEBZ755p78Jt3W2xj3oYv3PbHLL1dy39m3i/m273/ie/B/7Xs/elf1TvLmNuxvPCynCeJ3DkTd74G9mzvwme+OtI7TL/5sjUuzNuNv3OuX3bXf7dA3eQ6u/keHrMd3c90nTX653q8r+H6vX9d/31kzVPe7/6B+Fxrf4+df4awE8htPxVH4r/gPvkpQf+Cr1J/lquQPnson9zQlbQG8sFgT8PZ+yVjK/x06/YE/kEVCFNCPSJQnBVVmP3gV+s9g02/R5l9pO+yvCEH//R/1G1Oi1I+g87dE+mtDEn+WIf8ryPmOCLc1tW/GdZ7aL3b97wLCP2Huf6FJ/zPP+bPMjUH4X/H/2sJ/lFb+NAv/4k6/MvE3SAfz9WWI/ysI/4NppskcIsk/sCZeUDn2gzV/zPbfDfJrvMf+GZP/cUL4LWj/K2Ia/SuM/tbK8I9WJkjyryT67zQ0/IOh/yiWnfVd3JN4Z60/O5j/59j9qy1ERhVp+cfs8L+FB/8CyxMY9Nd/AsD/veGN/NtJI3aTxi+2+Dve6A1N2XxxRmZb63ua71j84lh/PnXE/qgwyXKE+gMY+WdQ4h8Q0QSH/sz08Xumh+F/ACt/xPP+NO9Cfqw2f0kevzPeXSxP4GXTf9Xn7Nczs0zfSnwwa8kvb8rmAFb+h4Q8Hdd17P+hpb6fgc8BxUSZb28RcQKIxjU+a9ofSJWqkbn/GY5XC151v/I+9wPbcYwOxu2YGwfwIvFZ3Rd8ORPOuP9v/3e7tsg8pd+8k/+lj9JvzvLtldi0ekCDq1dC23E7naEe7FMwmM9HYZnIZKqM5T6jzj91i2MwiWVGjn22GvvR+onFHQEFn+VCVglCMBuLcz9oQiU7aEhS9+tHDnXC07exwYQzAp/nrVaE2+QsI9peKENxRx/7HefiaenTSjiE2fAN1LVv2jdGh3vnnYcNklx/HiyFpjxEv04BH3JDq0s0XJECUj94KWLyTT7Z0kDzLekcTc0nS5VoUy+n2XPNR0CS5hwGFPBbq3xF2Z0JYnhAyKRoNpyZGB+/p4hdWxep8AeyuN2R0YIWiwsysPiRyy4SsVZqPwaTy3c0zsORhEt8kbcHGsnkZp3Zu2tTevPvKGB15TXkTtY8efuuiMVZ5op0ghBuv9jFeiz6+SRwXlGCIn4M7UcC0fRIoscJ9+AmrJQu3Du2WfXppfamXIaaD5jX9pJRqKiwabdrsqiJRvfT86My4Jl4zyeGpMhTOuMidO116IkE2oeXk5f6ez91fE9JOv341cUxF2LO+C6C+dIRVur9N0rIIZojwCOCj9SgBq9LF2Fx0w3p7IfphIekMw+aghD5thRLTsJTe4iexbdPa4c4x2T5pXXQx/JCWilzn/Hj4dbqVPDJJT0N6QKfMRea2xSSe6xtBxuzvxtroGBgfhisQx/JwnAmrUqXADmyFSVO8llwmmQalXlQRHlDB9v20dMrpfsTvBf05TBTspuFohrc21JOzUIMzEgmeMj7BhHWfTpJIBgyu38Q7zLTpuXVaEDcsnDvrVsJlZLEfi75vWL2fUxB5scTV8GmLuReMs983CXA1CUiN+fjtHuXApWHDdq6FRD0YjeeGmFwyvAkcO+YUzqSK8mZk8YwKavTzwnZtNaEoIvOa01e3+Q7C6iqQqOxf87OboGr1XvdfMfYQMRT8R4W5xXnCJ5EzD3tqvC+J05s+ceLpWeqTEjfsAUVPTKtzz73h4elux2aFQa/Hp7+LLqiGX7NwLBUpuPJuf6CTbJdlk6L+50JXojvnMLeB+iRhMaD2pG6o87AFCvcW8ZzxqTwEiMs8LZagzFS2qm1fQ9N++rV8dgkHne5dc6vIdg3aDPY2d4M+aHig/CmapICXumZmFxlbg15Anym+HXAejWxCt4/JhAWE1ZXs5S6gSas+07Kh4EuIQFcQKIrQT+zMKHELeujN8El7MQOVMwuzFNbzkZsobS2alh/rM1HEesKWDj6PO5HGe6Xk9+36YCNCT4dO3Pb7Gw/yqkHW+4w6+s+gSw7KrdNkqas7oMHQRm81NfZKGphORQrSVcTq/HUZ6+S5dSkVBYP6s0tYXARlxZTar0ouurt4Iyzz8W9K9sPxvT0jLU6pHLXeoe9CB9KPB+nVMRzs+l5sFC8DTzGwmXNd9n8EzNv9pi43ISOG6AAheFji/Dy6WnUNIIeR/T2YSVehRRJ3eOQBvZAJwXhs0FADwNLbdpRIyk6T38MLirhEBmVVvmMjzlUSQ3jbO59xbsY8908CRumWeprj6refc8AV0wMF+ElQ0nsOdKHl/WZ4xDhGvW8q7zGTJc343y6UlHb+hAs0pg6RcHBdXfo3jII9E5StZI+OQszsoV5dJhdOZdqVy5V9VGqqJD5mMgRW3R141ReV/U4WMZFh7U2pR6pC7fCCSW2dbpOr0jeGKBIujW6KzxEvfdJTqoKMpj0hxxDnxSBXv0Tu5PR5Uwa3MIVHNRvxe+DY3ptevaKXKTWXkdD41n7HE5PcgBQaZ+g3doPlCrxYxpqs54kRDeSe5rJoE4FvUfoyPOTrXYA4srC5XJ2Z7fIhhT4YUOt/jEf7fP8nPVeKSd08oh3H3VMrBZacPhZbf6dnbi08IQLEUoI+C3pqLHmXYK1JnDT3VQg0Vvv2MJj7aiDLNOzVNN9LaXewHiOak+m6EL95bl60EurfeeJoyUOwjsu+vOOHb1R+Vh4PfT65dcgIy6EgprLcea2XltZjDtSHafiFT6m4GlMw9NUYOZtO7yEPZeg8BeQCq7ObwMvVBdtUh9kmpjDJot1aGRfnBkmaX9nJRt5zZzMYcUipnHjP46a7MOZXaJYJlaz7bsA9vzPppdi7rA9yGk9fAnpA5cFkwmVVzpLERZ9ghng38TOjHz6H5Gd+GkO9TG7kikFWek+YZjkmXT6bhNajvnY2HOnvDfB6LblToPjusiM9vY6PfG2OjVp2AC8k8UstmfnNigaQLM90AWdU12f1cwL16bQIs9lvpTgIs436TK6u8DlNoTao1MHYzf6z9QQXY04atqvPKxMG9yhQhWfCz4qcUtdjaB9euEQ9aXu8xEx8DFSoruSYN9t38NBLMakVPKoFtuvx41u5oAJnbRvp+NKSCpBsLSN945eevjppJervF5WceN2oB58F1XLMw1DUZmmhzrT5KCzYhGP0ULujB/45is2sEf49rkuMY50peuVzrKIxKZnjfYTuT1O/mHoU7DMAWAFkxi8m5x9i1uBvNeBzo+wS5BIYOKpXV+MjQFKHlikQB6uBxUxq/lbA5z7pTzlh47lyIn1ltDQIK+pQdq9JiS4PfYQ5myNXFOFTRsA6eMOOC15iPT9mRjJumZJEjXGlhfrA+COn/wcODHsJFbaqNLbeCkbjDnni3/JRZ3r4+ZsDRcf2hCZi2LO0yFnLhc39mTt2SrW8ln11jW+7GFI9eSERTSyxOU86GnSa21ImkelRWPOz58E7aXG2eL1QJQAXJpNvfjbl0SLajpMvRKiljhHejCET4+AJAWxebYjRXQB9+nJoDx0wQumgKv69c5IYgbyhJj5ua/a7Ig7xNXeA9BOvl+7MhjHPrZz37TvJ1Te4zUaZryabL6XJR9LtrR7DDuuaFj7edRZ8kn3Jme7FTxGE7uoOcRlH3U16juXVHceqXu4SZbkyF5fyckJYvkRPEfXlD+R8Xzcpq/vowUD4yDzXPLMizXw1GSPYsVut2d5wL/8142+ak2RlKV3raxUGIi+tpoeFTozoZsxiqprQ1kL9zVpMyKE7/HSAd1xuSSheqRdHTv0IPvaGZ5ctOVRGF3S05eRnR3tEOk1nLYw6/dxaR1vjqB4fQghPjYEl14jHDg3y+LCq7BnwMxALcSbt4V3cYVHoTGTQDXZzBQf6kuGn9xBSjcsxVtLDjwrxPAzyl8RH2wp1UGgWLzptikKkI1DaiIaKPsoPUq+PMXgdhN7tbcPjXfQr64Ku6GR1hyKP+JS8/0PORMfdQMOwCwefWUh26ZSDKGvZ9YnBCDAbCIlySFgbiK97Wuo/Vchzqr36NmRJuiUwriMRpKyGl+zMYor7bpmXTHTSziRKrCuGQb31yt7BDwEcDFAMeOTAYxqn+GSluGiPQR1LckYhOL9Z3zSVyJX7mhIHyrnSPtjZKzD4a/d/nwGv8LcXO8DSmTxwsK/yJvMnoOFkje4iJAJTKxebddby12p3Bjnc4AZhzRkRZL1yPJRsqW8H7TxgMf45cIcTpt32pQSiO5syrkSnsu7d9BQzvt0zUipNsBkIhhC21iKNREDpRjjeL5pqzgXKQqopv9IIf2N/JU377uU/lYtL+MGdmLf45p8H/oPGvq9IuYX7zwZkj9BDsVI0EvF/kGHA4eov+LQ37fSPwga2B/oGTAK/xWj/yRNA/ux5/Er5eo/bVj9j3Sp/4kM+nvN6h/q4/8DPfxPEKr+ZsT/14Qq/Mcux6+aVvrt+lXRgxv+84172zb91q/+nSBJZUX2e+NS/4wd/yy9EaXI37Wr4D9QHBGE/nca8scuhuBqP832n69e+WO7/Tu7T7+sG/uV3Zgh6c4bgJaf1vt1EwlF/gr9E9aD/xab/x77ET+V/p9K/0+l/6fS/1Pp/6n0/1T6fyr9P5X+n0r/T6X/p9L/U+n/qfT/VPp/Kv1gmfNfIfTvWj5J/m+X+vEfv6fyU9T4KWr8FDV+iho/RY2fosZPUeOnqPFT1PgpavwUNX6KGj9FjZ+ixk9R4/9/ogYJEb8VNf4XaRp/+ANAf/R93/+PiRngjxEYjlGAqKFSXfu5n5uw/Va2I9uRc2HlxuXN4cX4TGJD6HdDQhNoNINn3x+QoODnKVSKmr2nz3tRZmwcN/UkdvxGXmg/JKPrbI8S/Lz2HVy0zWeAP54cHkyT6kD0KMBpW+NTO3Utvt8n0WkIdUIOUUb1oPYzt27IMvBp30/qIovbJ0TUV5qGY54Ce/8ZBz0u1bfXNcMoHFOBP/Zr4P5jmm8DCmN//wSjVN/3Eu5b/Rp07ldfgw+OYf4XHqtVfn9wsNfz+7G4/+Tgz9u6X4PKk+H+Fx7rb1PzrH5/8D+emh8Pzvwwzf+rjvUP/CHtCSW+J5CTSct7MN2m9YJphrRGvA0SfgCi8rwLEYZgMJlKOssecb654+w+gvSUM80ABCctS7og/LJohpDhR9RZyEPiP0oU8aBAUU08RpN9MMIz7Z3x43XVZD7So3QMcpdTT5dA+iPLBwG/uK7jmVErF1QZm8V9gg3bzf/Erc9BJZTRBiBJOJuBdHZlb5DijYKN0L587wdF2Nub0d8fk5y9mifzGwfZDj6XUaUIcnc3BV0+mRmQ14hHAr6HkKWlIyJW32dWwpYR1PPQssR3WSHih7x3Fx5EjZrf5xUH1KODC/BRDBEPTGL4Rie1wXqx2H04D6YwNY2ZeLMsuKo95tO/4CAxCOxIXY6gcp0NQp5caWR83wfb2W7QxauIoe9GNDn08WFVtJKwy329AQzB26dDro+oPBiBWnu+XMkX3eZaxJBMuVl8lQZvUqtVNXSfzKI/hLe/htageSxjxCvKA/Zyca8bpp7CfmZPQCH4XQrI6Rs14lzqLHT5CQU327vfF6s/fkkVdVW38eDAGYXnlZUJeCIMo07IZ46fMMMejC4DPSXKInq1fdED95PDvcUhhmztGvd0ZIpj3gqvaC/bC1roaL140T6FEQwq/g7ej7vG/nYNhIPfjx7hFc9VkZoEMGo4MReaJyT9at3VmTm/dVRn3TXLVSAgGoJL5fI9NvpLnGlsQM79nsOYyZnM1JurDj86MFfBXskelqhXDd3bQndXKxE2rYimX/gY4wG3XoRXAywOJqRNoyYEFaMC3RVzeDg3zrOGzyYXiY9W+XxGMCJ0ovrdWhK6oqx+UktoWQ+WtIZruLoj7ZvdJ6AGtx0VWFCDtEqwogyrpBVvYlwbJ3qbWkbY6rbUmNMEuURMPlRWopbsXi+kboJWfm0BzAsu0v4SqfXhSSO6l+/HjN0npNnxpFQQGhdrrXTqGS7488NuOvu+7R628ZKjuIxYMM1+6e9HDu53fAIy37DQtvNwiJLIK9+GWKqqkMwXGk/pBo/jjwqoCAKtbVkxZZN/jiLzBg4pSkAjF5RHU8rYY4Z8Iydzre1St5MSdPmnf+hNxVZqQSaj5VTlkgUlzU7mMinPk8kwUOKenfk+jg8T8WKIEK9W6gwISvv57W7CAwipeQOMp6BvINE3Hx6PNs168x37Ddn4SkQ0eqVpQ+KXhBFBTwfoCvKBHSN51BuGD49AifiFyML+3tAkTCRgegfjT/HJLY6Ra3cdV9nDzsU1QBPy3NPD06YP7M48ugPdA5w6uSZiQ0KANL0ALkV68gsSliNZAvhxTeRJ68WU45Iefcdo+ChvC2nzGG4Dkz2jV7A0hrFEmFN9xvjku+rFIA4jFzfdQs/kKVo4Zt/IDHUxwJ3VMsZ2tSfKTfwxP8RdAAAWj9P73dHneqnCtVp+Z4cHOni9W380lS7p88Pc9byowduR7Yk+F09Zab7y3cdKgIfY6nxEcu+lLyOiMZNAAbbJ13oX41449C1V7gfXJuareASvjLJeiqPLGiFQaEXcsV7GBE648IBaD+4Z8Gz7lB4VO7TlMqWH6T9jp9NLe4Tjht39uOOPAymsPEjNaVpls37GULW8QHzhCMMU5rXHZHFDAXFX79+zlszAiWZfYB/tApVrkJuDfB5KagTnaspV+YRFMhEoYouIuomMvbQ2oE4EdBoyryrYr4JwkekZvT+Xts5cHlI7FNGLNYb++ZJf1JDTlvvZ8IFnsLDWlJBoeFXLQHp8FNlrp192VBJUBb+MlLQvj4gNlDyQvCQeCB4ibW2I+NvRya3Lbxx9/JI/y6Y1ZX0n48VEUcDjUyJML6aumflja3jUuxuupTKdOkUZcHTeijE2zkbow6QJ8zwPir6ZocLkVF4SfIF02XBp6Z9FWEtjC7T0BURcmQ0yQV/7DIL5Ldvy1T52/sSVBi6S5ZV05+708zUML9qJ+7QI7sLw8w3jWWbKVAM98z42jCt83z4rklvDHbguhNtMUpieST2NXGp9h1cm7nyngDpxoCUUqBtwaXXuN6QeFRgV7vDDeXYlgx0p9qqECsDvL7+ZTDwhv+2nz+bFJgfJCEp+vBvLAKUt6NoA9AB9wL0ZSorEInSSSzdwACicKt1JJESuWfU+TaE2G6W3yBi476crSQVvdbHZuMNQog2c8CDIyZIZXWGcNjedZaepwhGWxJBfAg0k70X2EAGvvQOct7Qm209ejaQXMpqn7r4rfGANeD9Ir3pHsyYqpDqnvVWWfSdMhJAZFH7jUdUWgjugpc9WFJ9nEk1HROkbulmUgzQ0/37eYAcsYuwaOk1U+pVD9xCgCpRw0eVH7odndjyJvU65CInVoHToiUzP21hANAzcSneMuxOedrOeGOo+ttnwXYLIPEbHPdlCJj7oPiZKgvkzr3fYF29yPyq6mJOzHz/zs7Du+oZ+Bo/MNawizaNuwKM1rLmgPf1djqZ32oI5Lw5liSteu3AgM1xFRH1kjyw3As03ipZUdjY+AovctE7QGnWTX1DvoiRfobP1UatwKhzKlJ0SwiLouVXPch5Cm1/vOQtLQGfGKfMtLX6hlAy9hjiFedkrRqV1MbJctP2A2v2B+R5S+jxyAj9gis1HvMnatNzjJ7S8iQf7yNM+ljZqlbGeLgzrA0JqmbczgEvJgi47OXU9KJGLPgDzI3DPzQS32CjxmvpJfzPSneNEc1of3I5N5MH4MqXfDso6mM4+90cNu4kDDAa/csA6fGZbnui4NgLrQh31qtGPw68FDj2ZMaoDaBQDrqv81+mgE+FmPgJAp8jwxnRqYUxseGztKHpc7tnwC76jk6FJL55ypi4OCrvHTkbQgUByvsk3NzdcNRblAC4RenneDdEupT4ZQxlo2xpZdUNIrffK7+kMsMyL0TH3teWP5+FzB0vND7CJsLBa6cLA8GM/uSTicBZxrF7eFmlJxz9Bp0UJcRZru7GrLc52ENLK8hvqkZWBKkcuxodsMPAp5/0KmxGfe59JogQyAwwb+Io3nDHUl3Lo0Akx7aCvZpSy3XAOJYZwkmST0tBAndvtSH9xV/hk3AIeSqJjSfSKY6EfkdD2x+erPkWFutG65OP41Af3jU0au20yeTM29tQfkVJLKbVCHvMaLhyyASDKeBVS4SYlcLqF+9GXrYcu1ucuYaw7s2v4MGe4OqLytIHYNjfE1CdCekEZZkgu1uegI4mErS6V7xKRfVfgI8B5tiiyBv7o+uFFdgDDGpwkvuaavRkbDlxNtx1H6FoFxPRu8jTiDFwrz3spTdKWdzulOuKcn0TZ1mnFamENpWcXzsPKim5LadsdW2TP6EfV8JmvfoNfcp5IP1e649mrlPjJlN0kPcWt3c9NqYhcdcYhvSMJ0G5z55cn8MpDdkdiMZtwXypsCnODD2WgAMaO9Kq0wZe3k822UdUnanyeFbmQab3iXvelIEugPgnYaMNBxURlRoM/zqQS0bfvC01POS8PvnlQYEFI8TSxTzUv/PwMoYToMEf9oMi7Qy24FyeQfV5DuGNnORxQLmghvCfRVjMkWk59bJIxvS+bq1tk2VgPC13HxiWW0hn6k4IhGLrY/k46NZlQoLlthy11dNHx8SSyZibOFutogzeG43HJZqqgIU3KJz7T2xVyC/k8sGIVeAlti4JIbHYxW8m3vlkrj+7DVXfyNowi8F/N7BI5lO69pI91sp9Dh4MdhEl04Re5c2o7vyH4LnWvvkOgq61O7niI44sxmk/81AVnGJkJohWRrwaPwQbQ1xZkjyGLJPXPdVHc4yNVDpJYPeDPqhkfpMgP/m5X1WOHAnvmbFrOiMTHCZ299pWPA+Bv5Y4+F8ZKTbK9Nr0yP0zaVz4gluyypP1ExN+yZdGXOMihlOR0GiVcxrkNZUBr2wroQJsMxU2il9dHt70hw+lEB9nZF7+I8hHTTuJ49sdfxrN3DnDnbLozE7Oh6AAqNNCKkWYSd+Ao2MVjzW3dJaJTz2+ML9ZUAVgkciFSB8PZv1mJoyaNkhvAOhgFNWA0zWMSf1EqwywASOkyJfDRQIhRjG+CqoYi519h9+jQz1ujjGK22TKwrqgnaJx0Xu5ywQi90UPmbYeUCcy7oln2LQvPVr2mzlTE18B9XptGjXqlRQ1Gacad+jLNjFeQVcp1Ms2mtlv78yr6xZvMPmC8j+Qx0FPEYJxz3m4ibbJj59c20Ef9pUsIxgumiS/14WFeIQriuWb8AAF1aDGgKwF4VPjOlQ/OSkQRrlWIRR+QvU2XRi8yGcTlSDeJk04w0W2nCVVyJxqv666nySGMVeLEQqMkEIBZXHcf6VcN2/qPGZQc3apJIfQ2w9jwow7aPewCpkZodAaBzpZGMt0ZVypslU2Ju1ZmHLOLOvVd6mS9J7mEu8DbRcsi05TWZDR8g29NihF/THKGvAHEvd9BNGO72j86lzhQQ2BsWJriPX1mN6kV6/kOhkcLsSpxp/EPxbweuui0T1WXZ8VQYc8vgvLToFN6aq6xKo9MwmQt1yK/igJthq30vGc0dSsKgzXWA/9XAJYunqTYCxV2k3adMxr10gMy3VHFf9El9knicXuUTvrVs5pBYRu+3Gu31KAGQ2XnhGB9lb1pRQXFm8yz/OPsVVRgPKpLUWwHPHOpFPZcdlMqTeTFkuRy6gE9azvjZeITwyBRuxNEzyiNnlo6EAOsOT+wrs691OcmYwOw66HbaK0TyPAP34EeDh2uWP8F9DBafEkIDU9hZ4hoAs7LkYlADFCqHtZhHa5coyIVKMzhihf5MnHV3hBAGhq0Gvz01cc6k1nbR3GxoF0p9JqglQ5R7dOU4MdJFSbAHSPZqMx8KpVkORc43folxlnsG8NTiDY2ideTadc6cu1RAtukucHCTkQUJrU2dKCksg/5UGN4LN0gslNmJwd8gJCCnb49q5XlJz35CF19CkY8UipI3ZDBeDB/nRkllhLVCqKL4vjGz3agH+OWjD7mbFIKj/vLJKVlxZv3gWEfIrKu4GVNDz0KqVdsY3LwCh2VWMe3QS4xVq64S7PLrnggmwzyI88qhenwVy7QG/zJrUYpBVZkr1NH0JI1ghYsbcpyJufv/KperPlJnte5+pq38E1Q1ejma1bTfFWoMsuPhFwMDx/nPkx2XovIaKiTCuIN2EHvvcb5pg6F7CLXBnpZ16dVP75FkiNcwmZjS/JNRZLa4MXAelyd1vfrrBRANdyG3RLuPGdn9oP3uFgylZdgRZtYdihf3BSzLUCaHCXNXPVIZaxNnVtP/uq83ScS68mGl1yG4etxB2ozk6K1WRpllyelJWh5Cevp2jybVtlTZAUiSaymtGXHqULPj0fNQfFw6hG0yfZrKkqSMP0QQQtGW7/kl0pqw/jNt0Yrl/HjJJ9G++jPRXtlYVfJDGbhdDl7k2B9imd8Mfvx4D6U4kIPa3RYiPEf9RhZflmSPv3ZmppBDQLDv5YUKecCig8qVfNJnCxwLwdTDeBHLNgPPi8UY71klIpVAixJyi8RVCAx217iu2UyfchkVtq44IVndnkRtrsyBhMMSHfSDNt5Jn/lvMFKB56DUs4eujLEWlReI7jr6vgradQ6MytCkDjZi9HcN1yXUHbXdJdVKb05UxbDyHIcnh1JrdsDgKInmJ71+rSx6XyiGJs2c/uWGG9oea+QzB8kMhs2dOS8VLmp8iiP6ZNj2TDNGyR4D3BvomCNSPzRh4/Tpusu2zbyuqZoomkiD9+Gtm5yPg30tgz2WbwwzV4RMiqvF5wUKglYkziZvPXihTtkNw1TvFSovWfESDzd+1bKRDbA+cEoSLCWx9lICNuCL0gHaxNeE0LiJWfzCKodpG1g3PgQQX1ypNMLfjB9zlnWWqHZprWzWj0hB1NRNgnUzIZRriGk7EmUaVUs+h4KFNasZF8KTVMl78W66RHqr3J1Gg8eBUplJsRgYUtgeiWqlUB4da+qRvY36UC7qyOP6FK9lzg+LTlrmCk+rixSK/nNjv5wnXFewLMYrATMnfheq8AvdB8TVQ7PlqWVxahyqzdtWdvO4THEhBugsO4LeWrD7DlPgZbrdLuKTBxzA+6qPfNKkZg/kurKKzCEVLEcU7ydoz0OaFQtHmKJ02Nx8kV42FJsQao/rlx4BFW08AUOFq3xXVpSMml6GH70+Si53UHdrvFGT4Ko1lCPlQ/EpNqAhya8pxrPDBtmuw43MnnOvqtEYz6EDLNFU97VjUdROH3gTwWDZJeDe5QCokR1jJ+KveNpztj+/OZeKAIW+iCAhrHAWDtTMfJ7QpGlfpT+L6oi2OdmEPfjDYWS8859CI5WHAcJzeVeM86FgGZJuQWj7vvVPHvUu3hxzWrbFsaRU6fzS1smgkYMfIgUY+S9f3mReSnx6oQ06+NhqcYPb4/z11hgpExuC+rm0NuG+veV+yJRGINLwTH8+ERk3rYvR41EuGLwB4v7BGwc4a5A8VvPrM+OOE38RRzQNzFTcJW1WFTg2V32NfwoQFJfawwXRmT/GGvl/d5pa4W5Jyj6gCqgcd7CdLGZfapFSwVBZyFuVtUXjDu9QsY2M04OFAXcUVUl710v+eDysjUIZFb5LMRHvS5KK7mSCvEcJX+kOdxijZF87EuzEb9dZxghA6JZ2f2B8coTqj41wzIC755JiBJAar2LoweeFJzzUWxLer3Jx3Z4NrPXcBXg9WDFvgfjQbCTXJ0aInqjrLvfLHPOgq/2+VRGpBsMd9oF6ZIHaXu+MOmM6BDgI0+TsZdaWBahRPWI57m405QD7p7kgKfDRW5SMehHDdurqPyZfiekiTadp5RzSJPdvHI3/y3Md3tP0AVJwg17hyxv9HvQw47R+v16P3GiRX3DBglmOGX/UYRph5Kos+xQJyRPuxXEmdx7Wcy/t2HEcoNmeXtcC6pSfRf0zaau2bmZGgDwsMoA32MVi42zzPlIxCDG76M5ClS6juB6xjH5JGCnl2AT5Yrs5LjcYNX3tdrY4zDr3sw0bapYnTaEGfaCVwfI6krQAvvU82lrNawYSfR11CnQXj+HWy9PrvI7hO/pt4oUl3ig2V6mcHV8sM/D0Bu0UDNdTNoXDlY9Znq0+N6IGvbuHa2cnTQp8RggugAReGRM7edSSBqffVckPx8PYt3oXcDOVh1g5b3SO14IugN7GpRQnynXV/8N1OS7iiSf8MLhjM4l9oITRin7uiAy42tRyzBtcOJUr8qnKaFhuSaaijWQB5DLpVYLp2Z8EWkZlKHf7eFX4F8Hf4JOmFeAcjKamYeU5fGLxPNBeIXfrhF5vAAlLJByC/cTXCPrtXISFPqk0YqdkgSW+HF7ecwV33C95xLilMkDfu8pN53cAs+vCnbZl5+ltLDXH95e5CkVKHj0WEgRHNaW84UNXCYKD1OeWQYDvhCe51F/SMAYj3pvtm8rmNhGz5x9KPPBcR6pWiE5WG11No2uGu8KkgNxbzkmq1T5eQCtBphX+DDmW6RetVq1gg93Tr0Qud5wN2jzO/8Mt48+PsvxUwcBDelj35QF7jZuDqoOBvr4OCVzuP9sP17P3mgOQbEFCP3+5uSitFko2kg41T0MO3KaQIsY2mYoHLIl8MA1g+uzoPdGs1IU3dDMj00E2IFrt8eZzfWSuHFhn3NXSEv3LQZwSYirdimBhAHxhFbQ1jlLL69ymEMiFXd5XfCaLkFN8qsrCXCRvUW1fpkUqHCRPiuZ5bl+idF8DgPHwYvIc7inSmjZwepIoYdh3EzerrkQQVIircKF/5Tr9mjQVc3ebf5IwFLSd3wNW5eLSjV5Thbe/G2Ek8lX4SgTCe+GDbY6PKBVTqH5seYPQFvjQTD0Z+c7/l0BSal57IbvoDS45auzyb6ahSqUmmROkdySUScZjNQTVvSVrXB5Jl+SDQMKFBSpoUk5U3gAXyUQA75iYu8itDwHrHZfjJnh7GVcGxoYA3yqjgto5Yw16Pj6g4NYYnqgUr5f6uF65ToTFG+0pysjA/q1mH9K71q8blgchabJROFdP7lIRbOoCDHQfgNVPislBiY1vW2PYE5G0fIba7rQTjZK9BwgWrBw6mVpKHpa3c5dvh9J4+2WBLazd61JYE+XuZO2ZAG2KxLWfqhFX2FEVHRDSBMQNFbPj0S3nKBkiogt4hKoedNNAhXptkDA5cMNVGgNwf9GQ/Q04xEI/NNrHvpwKFQ0XI8L3qPr4dyVo8glN9/Lz8K5k+ELpUkX2w6KxcNYSy0c7nZScs5zdzpgmJ3pIW015RrwJnUzAyIc1mAjrHQI87o8P64QbZyb0JZcCR2zeU9TypVc0BLIwqsV05HcbLg6Ot0uAUpdBboZBAl4Gb8rEfyAMDYzEYIuLtA70VdQO6rTG+39sxUVAVrL/FgV68G5BSoAzycmZ+vN1B/OeIrP0FgOMlGj3oBcwnkZwtBd17Zic1RDKFiGKTKdznOcUjM3Dy4Oq+IrkPYy6/SeNmUyxlgt0H7EMBLOxkk76NjciXNygeXJnKUU/wI/w8ZCTzoXduBAmzmkiK2NHbXQInvMRgLL1JTAH6c6TmoHTIWeRtLlOw80Rl/KdJFmBMenKOApKQvk43pogv/kXq8ce74Lq6YO745N1rPNilUkeragbaXX3H5T+gY3LLNgG9JIBrYJ3gmDSEpN7xiJsaKIhZXKLNXClK9aQvS8vEw/VgVQmjToMjcUDu0L4BMzqmk9l9JKGq0yLOUXH05fh2r5R9O+L04ZYdNueXfHtfo9sBAwFzs4i2TGMdJ4HyCU1VeCdHAGPz07x1Q7tofd9Wmhska+7JhYQBn1o74+UGg+d+8dueEGchp6XzWqnDBzV43+sS+i/Y0pDi/jgheCzQ9XL9U6D5Y3ik3eFKUNaICRA+GIkIrj6Z6GFsN/MgII7mJIewtKp2eHvKyN+kz1rrsJYWPW5VJM3fHJTbo/G8tBSqsMPRK0CL5Kcdd+lT2KX0Rcfsp91erOs8bOJzo85+SRHGMV8U6ISKcu9VhDO7lx3ITDwTmZN5Tm6tCYnhJ1FLGHKb8gwQnujC9FouSe1b4VVwA3/WFkUM5jZVPiR6HWs7MrqI/bhZlCVGp2mNMxKn+N8hHxzXwzkxefSE+YyskxPORJ7qGefxLSBgVtjBcEJlxxN6z8NPmj9hHM0g762YBiNV8ExiKyRkuz486u3zRe7/0R9w+K3nFHslyFnnpY7UzHO0jwSDqXtFZ5+lhSOL0PtmIwwjzdDHb2LNFshVO0uiYFcyM7aMUWIb8RKyD8IMEi62Wi0cN8yxc2jVb6kTdND9SxNQBiGstIzbo+tnaCaj1JL5j9TF1JnWro5iBrDqyoEnMddhfgkkW3+UnFF8oIQG5yE3897GIs8NK30RXWwVf92CB1b1hcZC/UsMhACnLdLE8ocQIVTPhC+CRPxNAW7Zo0CVthw92Ai8PWYwjXXqpvZ1SiJijnEqrmpQ82F5mQJRtET5jYqOU8cYHEGck4mUB4mONbD4k8jJSKxoey2X1lqSYUxg1NtI9X2yoTWa21o37K8sghj1pOyw8HlDgEC1L2XSXE8oKeOBvz+arMzTVRVNO3KdAUdqxnkPYV4QwEvf0uy7ctvzx5i/JdXZjKT+Y3A2AVe/aEMnTjgMDPRVB53E/c6rMGIY/psxvKGzY3nhFMo3kTn2d7V2UXSDsfWWUNTpjRhgOwVKNP0DUIQn2NR0Rx2ZVMenYmpeulaiWcgtRKV4ZPxbYIz6cxn3vvHjPe63MxnzhqIr4V7Cthem7Zo9YhEbgCs2f+zjcTT4kvNVCLQDzzO0yREXzJJ/2G5/D5GBc3vWO/Htkb2j702VAiLzN289Dg+54wHA8/kPLShm5DmUQ/ZXSQDvsobhRhuPR0n3TXRvZWlE/lAekxEBe2TR8sGqnJfWftmdrzyGM4eDX1SR2LKUPe7AGtTna1jCQl2XseNLp7XiGe4VMEf6OAhzQPpf6AE9Xjpohln0NidoUxTZ5WlVHu6v2kBSVh+2DW6radVuUEGDdme2QiBvhSC0s1wovuas3qStLsw7RMQ+PqjVnCRGafYNIJ3yJtb7Lh+l6Ix96dz2nk2+nBUoCbLzcNbiMpZsygxcjDue0L3cwmC7t4IN+FgoMHDsaD/kvhwb79rlo1IRP4rnN9VgnhQT5AmUnKaAG6GE9LHhR/SDPQFflUypPjwkOv1TqGliKSdJLMka+v8SBGlybQUIByCFVUkMF6ZwaNVGfAX/yTyz2YcaiNsq4PM+pVJnyDkY/O1DNnLvYkvANSgu04oKPw5oelETyoc0eu9qWQFCouw7pkuK4LRjPjY0I67p5hXoFH22zfcH9nfVEOjIu/IQaRGAFiKXMeZ4EfVKlPandBqwRlQ9nbWn7MuHMORUrcUBKmPO+uz2MuRFsFkTK5pDv4zEDKHg2VdSAPl0ZOs2/Ci2Bg1YJ8gYSrlw5HTsTE899uY7Q/810gwMFuyp9TIi+oa1hhu+uyb1855q/PGAq8Y3zaIEmf9dtmldNdYYeOPFqTKRglCo7RQPXnS0e+oWmBFrXfC4Aux2Wu1sOYuT1QeIOuB7lRhPjkGg+5PEgSqr2YhDF+Zo7F396M2980XxtufisebBScFrG1MqYYoO5gySdOretBjcVpPpJtlcG9rgPiBm06iMlQVsGbPQXHAaa0LSKA83iRdLQWjV7/drfJEG1OnslbckxYD7cLWvILXXWJ0csCLDC1N7m8L1sAlCSBQSg086Ls5pOAvdMtKAo/s9WATjabARIBJ8kObeikuVPIBrxyGowAW5MY9AcODjJAH5jE8IfZA1GUJtmZMZ/8OrvSI3NZM6rEJOkE6T6tCJHlHGGFBYDuDR6CY2KS1mBPBI9TegPaUv1kIBRmPAH8KC67J7UTQ8TBsEynkklohAuse8pVzBrvXZNTBAWIU9B7D7tIGBXnjm5u7Kpnsx0hI05ygN6e3RKSzzW7DILxipicp2hLB0AJPOAJ3B3UPJMAbDivz6cM4ka8oqjWSLRL6K2zWXayZ0bja6Ix7IkVGFsdBS8Z9Ip55BJYCSU21AWarFPwJE61teS8TLpx3xzq1U+Om/esJhrTp+I20i2fc0plJ1NlaYhWQa7KR3Z7Vbg7DUq7PSRgEufxah5rjjp0mTKrA1W4kA9wQgS1bqS/NrzFQ358YAjBZvXwqXfrPUEohzoHYvcobQ4uhFKbdMX77rNr/vb7J6k4NFjSASZi1iyPhKiL3xqVqmVbhOSHhPP9C0vc101cumj2WRvGbsRj3QVhRtvoX7IoqCGKPskAVuF4wYbgLr04RVn7J8yzrwbmZ2yLKOk5KBfHYVwoqFnBpj7N29Kk5Ix6ipLo7pW1kx7XynGuQ5OvHxGRmdwTM43jejMmbp+4BzqB8sRRYzWK3pPfWzJHA6qM3z5pIzWrHYGhL1WEj3wlgHJuuK1fmQz6IUEYoHlO7bC3kaZRQpnIf3H1nkx9X1PYExg8ldSw1PpaQphetjXwTZ7aA13TeCxJ8Mysg0vnLfhoA8of0c+dbqTF9c6SkR0+nEIAC6x0pPXDIP6gIwDlna0aiTchhLYPczaNpEFKsX5r2lens+sXi6vioh8Vw3avO/6Fd0ia2nXBXc9YSu3lriPUrfu82bEIh7YrFHj8Bs55HaJpWUP9zXYLW1uDsOTiU3cq+er6VWlJv9AY9ZnX/hWvVDnqxmM8EOg9MMnzaaucLdTV8915qFp0dwz17ROauIrRWy4mYFFUOI8B7b/XvEXqcNXbk6mYZelkriaek+4392VFmaBb0l054uTusVWakRfvha+aYhbtogkPxGEXZAlpH173vLlEmnAcKx7EWpnaIC9DwSYlczHtmpA7YHixohQXrT0aEhTszgp9whzBVj6vzccFuhrilWJKDZu7UbOO1afxp1L320UUBK3lZ/PWyPmkGITvLIt9fjribV8zitucP7YkWsBRJIfSTa4KRxd91keGkohorBDlDjH0zcNtTIIUdSKyzQdf3ut0yoAt/wVDJPIhzpVrMvy1RoFUW7OJacfaug9zI6YF4eO7BmcQFmbwhyf6SXJEjZFAAbXYjRzdbM+33hZNZbmR0/H72+r762NpOJvvHMHtOIS8IaCW7WIDEQT9cO55PXFyCmiKhfBJg+RzhNxCWVdt5QTD4t7tDUJAcTPktI/34Cm5wScqhocHFUdOJjJaa4qbnjJPsYwunnHNv1gGJAr6EzxMMnmD3qJ9uBeFfLXRsyRrcSKuCDe01VefRjtLSIeV0AUSytLz9O3G8Lrsfd9r5xA6xrONHD6KrNzHflH1N/aSgMwPdRTRqd5d3/jKF8UARN9MZZgmGr9p5xMbGYN7ddIjKIwO1qULMOsZuMrzpFKqSVRaLAu96enZkFzk6amfOh2jZyF95aB0nkclD2hVevW1OKu9XjyLw7e8PZOhxUfSTJy6Dss1IjZEtXOAEg00aYESBYz9+p0Fw9qMqkyvEeIxxiaomjnDWmUSpX3BIOfc7ofPrzRXco78GM/xEVy+fZGCc3t6gGj7HK2mU+CsAtIxQM8D026s4ojypFDICLWXEdyV9CfaKMifHgLufjG/R99ACqIsadfmcQ9w5AqUHc4rHMOKTMktDEK89RFoHEEC78uTM3w2lJU6La2a+eYKtwNcFCcIlLG+sFSxGO1JrihGx/4n8jQPJGuUCTyPrz8sjhYK1RIPWOyBXrEaQKSxMj0rXUAQzw0QhOUMIxr6HI3icnlen1NR+uXVoO8GKgynpE8PnfoYx44hEcT2wxBB3562ZrdTOhrWfJ7D3G/SkKU0+yGyglMGIHhRcPqi9id2TcRdirdqZ+zhaOayf3aC8Kq8z2S/uZokaHobvlawkieK05sd4Wr5IenO3Ml9CYM5zMytXvjYikN8w+SKXyV+zwh+DdXRO7KJxvB3ggyTZs8V0g6P3ImgMTJl1x0c31dXPEHULhNDlZSNTySxDtU4mNJtHtD7wPznCAf1o8pMxusZ5ymzoacXeduz11GudROzz7Q0bv2PB3UY5e0MJepFj35VrphjGeZ88s9ZgCgDvT4O0zxXhjNkLcfJYNz9NuYqpVe6tDZ8hR26Z2NnU/1WW7L/pq6otuTUR5sHsOiJol88mqIE0ngTiQ9296xMm/yi7zK/d/Q3gw5qacWnOr6Zw43nimRXxse28SPkW/ZyB5J+NQSZdMF8pLnoYDeHUNHQXRgdKoqjjJ/jqxkSG4XEKSbfx6pyAlN1VK7V4gcTm2Lier7cShZ57bLUNXdher7h/Gj1mke+dOQSFrySnsDyH9AFvKgiDHMWsgU7CancOGb1FcMyxYKGczhlJZObD/tju5qWxXgg3lCtvV33+BQd1dmdsHcI+raWAn3gpLkN/oNSPGgnXsJRC5+glmGnA/J7uE0HNPuetJQ5VURrDaU1alrMVk+vD0sxxZt6PArtFR/BV4OE5Y+FOoHwLtnjHY989FLRDvh6A20z6cri+bLPtVX5DRI1Z4lmKDVLInQ7a8tu+3EbXZmPbPOe+l7Fn/Qw6zDGTPyLCAsMqguY4q6y7YuB6Whu839Yumo16Y0t+DQ3F0MoZubJxDhiGj39Ve/vwIH9eXel1oGqOtCpmTqhA1mf+V6Mb29RGQHiKrKS/p7TDZOyolizzAt8sW4cw0SKhI9z9AqluU+gWWM3idFQ2MY8J0VrfOKohcJrcTXmzeJbZPDVnGkUqkeMogqm6rwImC53lYjjGjc5FpYwnyLVnO96oNzR2Es5uI9sPIrT5J9R1eNouJsZ93Hpq6TTupYY3QEXLWvmN7d9OPoqQ+m8Pn0gZw3d5IetTFKnf217gPJB33az1F43gi/U4yLCV90q5MMWq5petQ0+fX5bfye561Xuy3aIj53UrMBfC/aeTurZjwxxiuWd+gXEFZDi6Qwd5/vL1VEweIvGvg6cWubKw6L/QJO/Nh2kJXBut1sgmiSVY66T2hJaJLCTSpAH/0GyMvpTNZZVwNfwGSbp0WP0xSgpZVYkqDtHnR0TVTj2B/WJ4uNWDxLBkqFeN+zFLPeCMDJvU1RpCc8h0ApxgnIZAtFVMClw97otH8BzgSYvmLmYeuLPwybO+PcRet1pOK89T4W432+moMM86fxlTi4FWP15nJDldUFjJx26Zs91NBHBT4ykyFLgHRrHQr+slCgw68dyC+hssq/A0cHPktRHssg3eoQFFGo6ihBXs2E09bQQkEQbRc6NVPubDQhsGbuWzm5yfw39OuHcj/z3zeJ4s39fTCrgOnDRHdTt5A4fVZmV/ctwdVxvONWUQ5I0v/SG6E940xTDmNLnWxKbDbIwnC7XodPtbUTohEicyM5fplLzokw9nvkbcReuxwnc8cM5JhLKWRjA2pt6U4Fh2gNoQeTZnxjonxrZipzxvXvIqKguEPU2kAm/IICU0SPiBsPGsJIy1LTiWPpXSfiQveTp8hsZz83RzgmPZb6UclW6+XX1PfUih9CcdR7QHp/Jnb2ysIrP0i8hdEWjSx2I7pdD51BsFzgBx+v3I4mXOzzz77OS8nmpygCQbbxaPzIyEGQdc7s4c7H0bppsCq+ZNLJizJ9siwn98rPDSMnPr0fiIdUd746zfPOiNy0A4VKQoEh19P2oPvZZ9Zz30zoCQ4OFnSqeYmj6fARq98oOVM85ryOIF6MB+XyYOFrCgZM967H9V5r8beaZFfnuEZrWl3lvMVq+fulSJUtTrZLPC8EvrPH4pK8QoQM/pWSDUcHUltdgzstOZqOrdJa9W2vUxba53igq7BUBl0RUdWE3Z9+LAwNpbAplcW3YarR+0dvrKdO3HObihk34xgyowwUfqFnND32DFP1jBPpFZ+7BwO25PJYrhGmexAiNt280Fz6RK8bFE2JGzdr8iw1ki3cBXFPHyt6nvPLQdcU/DDGhcfA1vBdATiMPXSERAN3pZ5BRN1mDrR2bj+A2U7iEKfuU5FX8QrgZbRzwDX0sb1+oedQHHNm/D94S5b9iAetUKDnLFX1+rXixGYGqeLR83ybN0+2vHZg+nkCe4MP6gbKkmF0Fc+MYri9oNPxx/nn72iPf4nBg4k0+VETXIMexPYNSUCINawQNKqPnXf+NtG5/BRCm4AfLDEQrL86924bseIkfM4W91HxmhcLQJ722DlBF+wdLbc2mvxfiWY0gjKfqO6kaTajEcqo1IpQQkFJfVXtx8dynCu9MrXgmUzjEdtTW0c8RFrnI/+s12MQCS3taLRbEQt/oqz9+O+/ovkuxxO/nKb94VW8hoYKzJDQGc/nMvPOJTQ05x4wzead6v9znJTTjzhU9zkTz+1KVom+FKXe9x5zfI7sJJxkBuq0rWdYruIqKZ0QBT74HCRja/fkYR2b7K8j21hsius3w0UpuIhr1fCai/Dzuq1hXzBN17BpEV+qatIKRubv2RwKoYkHHEx+YZHUYC164QPHMaGKmeR4/0Tuecoc+kv/0RFm8iJQiDnT90051iC2iKHUOEEMGJufXhovZnyu5cKE0ulyk4LmT/riZkEZj42h2XYeGC4qhRXQsf2R/OMQNytJ6AMocrNL4hTEzarQD/s4OXRmy6RL4MDv7v/B9PMGmYRty82+fEYZMKvjXTrFq7n8XxoAB2/BGKoT6k1aZ2XnOXVMdAYqRSwLh1/l8f/ZEfJvvNEBhXYE8OJ3QEBZiUgi/vtdTaZocaDICzrG+hWAxcJfCW3lbQqKyhF6nSqKkKZz/oBa8swP/uLm9ZwDoheNBViUL80S50syvA8ZcsHBYysSnHIzJUH71v0P94fHrmXfCFGD7NVuRUk/T0rpoTBzLpZtCYPxVOvhmRG0umcqDwsxAKO22mkWqGrTnCrhGFNgUDg4THA0WuHHCsDrVdkdQ/mjkyXIfG7TqMF9geD7EZkXlZMU7gywKqv89tnqx8p5m8PHksgyI65ePhmlN6/HFWKdrz0+ldgyBtIX5Wg8rl5qffGepC6jP61rItVriUsA+M7hbZo6+jmjNOGYAL2McpDC1sukcTThOTwcZWnGlU5VtRBtCrfCHZQ1OtqHW7nyVXzntFAOeyDCZr4vabHfGM4akBDOqgkxaxmUexS4x+48osy+BU1Z/C6ugHY9l6LIetT7UAc0czLggYL2M6fRYytxOBrLn2Op1feXQltPHxpIz1wdj11guEdTND8bMQmXVP64LHMAnPuLnmwh/FU08ytkJt2R6rYorH6GewdM00MrbgZqxVZG7OaMtgaWLPNg34QtQLvv8yzlipWiRQjYgDOI/x+pcdnnl7drwOy+yz8umC18Yb2/ihnTJlTJ9MRI4/nD2ZdvkQPFhRAsCTICydJQJEJZrgDN3GaSDGgJxtYPbRnPni5CHAd+KtGYzeiRF/uz7ZE3b9qUpoP2XVU9kQgEZg9LGCZQBd3mnBWFeJkJqYyheCWGmua06Sl95TPn+dbtaWmMhPj94GooSnKmGj7W79qoYrWaj3HV76n89OJk8Zb0TSbTM00mlqQp3u6qaI2HP8mesch0v/KiycV1mstpJkqmFHD4tBKF54HlLr0F6oCJ44bcJQb0pbGGoIPx+z0UcqRfqVDfAT5Umo5GF8qLqePAyu4e92A0nEMfWeE6cneHC/g3OkF3h9fkn4MX+d1R9+klvEb+CF2GT/khtCcMMj68rVaswmkSrPlqM1Aho9x9SF0KtrusInM9hkCPJhibIgmWahw3+LO95sMgtDSHUt8Mq/vZJeU9GSkvU19N9sxtJuzSzP6SPvHG1V1fe5YBMdFTcZQtyNY1bTTF475sWIpkGtfbP52ulA7IzBciYiaA9bqTkrK1LpV97yqEkgsMY0skXjh/Z411BqB5jGxMunnMCnJWfZ2YlPhrx1otgWCtEQVoT4GmiEhSuZiN8cMr4GWfjNG7jrE9RJkN1QgFUpvoE2gdBn4FoVdlGfBY2Qx1eUyizcNSpfynP7OnMQ2hsvb6IS18nOOiia3lBbnINIXam3aIOo+7wEUp2BqnTYjDOgcGS96xxTXlu31gfzIP2lAUOMMFo5MJCM60k0f3HPjTBfTnvQmARlpr1E+9zf9/TI04KQAZDg79xV4CpvbFC0sqyQdPMeeQYtvmSxX4RT7XVuzC3KPrGTm6u4vNvqcHQ8tj4nlFLDttqUg65BpFM9xImTVr7VzwxBSvN0n13MhSfl3S/jQBWy1XqP0Uutid7e/nYATTxnUSFuryWfmy5vEWD6cnKzCUHVDJGeASVLHcJigs+hsuwflefcBZrMdEZT1BZf3bZk0XBVeW1v1XxUBCvG53dfUMFQh4vHqjVffxrHVS1ddUd8Y0ZDCMVFuf2WI7s6+syDHPFodAqM8BCJ+iM0eX7wPJyvb4grQUffeZqiNF+666CGixaGFoAFSD5I58Jz8a63E3cECTKKponBp2PWIq3tqmGkUfWAlQBwXvIT53ZJ+DkFsMF4S86keFxiqCR4MsRJ+FO4mAVjo/aUiYKxb/x99wekaEbfudD25MHE6TamgqSnl54Vp0UVAbq2uCa3/N9FDHYxn9gAkdrsuT67TvA+KbxC2RY/J7dlznm8YumoC5kp0BrsRmXNeVG4xpQEi7AzaJxx3z+OqxruvClm9eWlQ/kmc+vVm7POaqI7K4jKqi1Vmewgc+O7rt3wWQLbWHOwnhf7WIkmvmChsgjek+j4q9eZzOlupqzmwgDtEF2fGLtRl0rGM8woltcPQR69qSNgoRlgNC/vtFT3onS/DJnjXlBZEfsVF5zxM5HyinuIMDE+jM7tpD5phv2KNZ6L5+wKh0FsU+WIlrHTiYAOEHqVvxRNJ5r3efzV6gfNKpLIkZPLXHycfu77uWGLCZav6R+Ze1hHOKt5/QfjZn1XCtZRLfEH7MD9YzMOIE02YAmWjEH5sLPVfdFfxBcjFrP9Ob3Mz6f9WLhP2A4Z70buGX7q8W8KYAcDH7LDZHjR/CQ7M79N8VPkIDJDJ9F1UFs2PkTN6S0kI2IvxvAmoTpp1zGgwOf1kWznn+oP24+r1mh5S6k2i9hznxFPCJFsqI4VvLaXT5XkYh4eAv6mX/FPUb8wRGHMTiCwJdS/YbtMPMu/WQK5OLB++aMFp1ocVZCikRLoCySqUlHu2PgQjlxdzXjSbGOmvOdL5bjh/An1QQ7cM/CYpyt39MIDbmxcDf8P13YWHNcOFbbQawu0MwMh5fG+uWXKjnC9HszzGCoFX1bw+D2JmdF9jJe9BuJSEPdC6ZamcTOZhaoJmP3qXGcSCGmh3L/KiRofR+8vSZEX8aTNsqe/DjQ+AKDtD0lbhGHXyf4f23ox7CAGM5+XD3vmhkdGkLYmNr3ecabVgQ7DMbtGE6aSHTEp9rHIHHTnJd+0dAVqNtyaMWLFQr8SJAmjbaFj0gZaIfHZquW3fLKB5aeAuwPIc7yYPWnKuDOPiz9E2rz+fs81FVZf9NAsbabP+ISnW4lIVik1aQVcragGzSMCju9UoXbFCQfJOkwpliKoYwGwqP82RacN851cHnme4SKox04QgeApbIfueS6YBXv80J3h5QcSSwOD4WNb8M0t/ZywTOZrpVNnpc5le0Fv6nyWkj7bCWd02llv4RnHw7KrHHQkqvdhb5znDrXGpLzR2mEpfnBLnmLgT0PWPGy4jM3oe+DjWVVY/tlP8ZYdDN2Ayi4tpwBpx+Akmy/mUhG/gJT/eaQ4nXJQaRCYd/dwy/KEeIF69vTjpVfuk9RFcvgaFDKUGa0IaL2mtXXGPvRKIixxvEo6u7cfZ5s3XZRa0jL15skRK3JzSTM2M6lV2fKG+ZjGyq6lKHXC35llLoM3186896f0UXAGNiWUD02b2aHevSUohK+II16vb3r+9TGwL6kE8vuBEr29yv/wOYV3x0K2mo6I66K762E5darhk8fGvOZAuvs6tTgrJq79qRskwtQtHTlP5v7haaCoCg7Fj0gyGAid5tAhqXcGKVoJag3yhqprt6p5PjaFvqh91yiCPLwY4QgnqispcVQ13ai+BeOQ6EwZzO1+1/8h0cons6rQm0764s6I41MmQ16768MvJdHRPhK2z4kqndJu9Pq6Qi82LCRZ/AiFLILBQt/aovo06bWpFtxhDvXasQRpjgQRj7PfmnZeQ0mOa3XTuKrlO+coVrfTdYopYMj4UaUs6mKxg4+wuiW7hQM2+3jR+/vOds3xyVE15A5jT0u07mCYd0lZJoqvxGp8AxsOeXc0z1gvsWdXwVW/G0xgJjH15ZBIncLlKHYgdFNo9bPnIUWRmYTvku/h68JLuh0rDz1SGTQcBGrAlIdmJ7zyxsxGnMmMcwlx1lbxRxBAuh9ovXHyLiOydfKSXRJ7anKBMAYKss08Xs/raOytUt2iuT7HC2oTnaniAKCmXHbZw/9Ij4YvXbE7IRQIH3E54tjkrs4QANCNuElJTe/xdryFeBCq2N4p7lrkR3CiqdKP3rkg6iQzxCXjaORn/HzWpO99y+qOd0H8/ATcVQ/QDS0kzjx7g/o8N8vfzjfaiMh4AGQjbsRoim1N366FdhR+PvthNzX3lFzxk+5DTIqIQvVzoJ6lhjtAQZjMXlUI6G30TbeKX5VnKHlxqGlQKHedlcMqxLBW93KvtKY+9q1YLpbkKr5OJKlj+8iqwpBdsc5wXSY8DTfGm54hW1nlGu6c7cUZ4J4yK37vK3xv2Jgd1+ooWhI6Tc8Try4lQhkQPXwcj5JQ7aTAQZd5V2RKVyi0rJ0vO4mu5wewu7Kvt9NsVNyhARC6unyc61hIJWuMIUoLi6aNtUTUpU5MtfBgrZ8u4WgXOkhm31VBh6NQ/7iX2tOo9f/n+xN8YzdFhzD+LGMe/HEtglEHs0y6p92DutwfK5nx2x0H3QXi18jdgqoVe9DK889M+5dm4jvkA8rPddUfoHkDSxvKRw+N0E4hA49PIWSLAjNJg86/OdWclgO1UNcjaIA3vbGeomXQisO7qyurHAkLj7A1ovB4OFFsa4xwRKZwQv+0jo6COY3iP1ykLfSCjlUnXTQ3xH6/YYWR9AoQcD3x6n96Id+ZNQc+snmHz757iVoP2Rvgk3tJinh+KNJyhEcp2JlCpMsZgI2bHiEeadFztsqTtpEF9msBGnkbiLZvN90GIVwFs6BfE903EDWF4drN8+HIfX8aNXNkeDTlXbJfNAsx7AICj+2CpbNlf+rlZAbKVDPS4LhMiIHlOImuXqDQLzpIKoglgvm3vA4prHZr1/I2yMfFKQ3EDKxkIFBRaC0gwOrJ6V2LwbnWI7vKq9xDrwpizgDH43tRn6JprKh7W9S6UYxQBSEUNi3UOliRy3PAqzheAmWzQQi/IH6RJ2UdVm0dUnFeV5Ga4rRNaN2BBVJPB9eg6yoT4DHZH3mcei8vki4EEaYcvU7j3Jddp5yWzbHCwevZieDIcwzS9yDeFxv4OVGCaZ/YQ6wje6cyduS0GMbyUkDr3WaTfWN/1ZRIYa9A90DOvotm/Oojfap7Bz723duiK5HnyYl5OHTfAHwRPPfEMdY53ID2P9q/q5sNbZ48WCytW9BmiOQaZzvVS3Vq4igupDGNe/SSsShP+KQdjZkRgmaf3+lAJI3mK4bbFsOHhMTGlbm+dxhkE9awD4HegPia3VK+3QMm6IYDsTeOE2z+vix1/NkHoQUNkvU+Rmio1V+69LmtWXppOb+mO30CbH9Ib6aBhT+8wWu6PmractXt4EooGH4wHZIrDrJou8/iNnFsKZ1oJx4ZG+etUsPzOxnJkGtRAiLBKnKs7R1B1qr5OcNgVk+uwqjB4tB8WeA7CA6N9dsk+VV9MXQbn0h4dzyYliQZTP92xGal4YhMTIje0x6xoMtOvQPz1ZNO4Y7/Iz7Mgh14+IjKD48M1Akf8L4rGGE0Xrbo8pLcLIHD3RTSynRBssM3+yjz/XE9Up4A+2MqPc5PcmhEKdiXSN5hW4EqsleW/OqrFBQPwCd0JEj170+aUIL41i4eBjWiNnoQh30ZkTVRv+GFMEbOSLiiiQK2axASYajVoZ5MapKcDidcRpv/tKPw0itDjZfiZ6kLHzxbc2iQjPc4eH2LmlS/hpJbh4MjG7W36a3N6vcAkK+AYeEF18UlSesxPtz/6hsf+bvs9eG0KQFEp0j2/32jGnP398gAAK6AWMoCm9j/VETtx/0mxYMfnJfqvxXxcpLqdDtiVzdM5lVLP+FP2kv6EN2Dsk2Qvm0Uf5k/ngKe3J//WhoOLu/kFLq2GqvLSYVL1EGEdSKQSY+3mynPKDSAsgovJG7eGlDLQ3MJHAjUnXc9hcsCGDRCyHg0YGZkVZbEi9/cc8xh1Af6GfBJfM34dDoFzOucatk94J91BhjVesLRzyrR6oEL0Qr5n+vYRLHao5b085L+ifSFDYJI0ikArLe9PUFj+vn5ena2vSDtj7pNI3BkJCNNQT59YLmT46mLAg4/O10DH5TBPABUFvjGDkv2R/zkm2XKLnEGvfJfb+fJotZEbXm4IshEzRoprosiQwXP0g38dLo1NATKDbLbvjBKrcruiEsoJifrdisVTp5w8U2ZOqPacHuUrGeV8/SnHBskcMosuCL3plAfGB/RQieNMjs6pRsjxyuncXjxLhKfx2BcziKGsVc/H4DTdms+oiCMxzSkcjSvSaGFvfh6PMhK1q/9LEqtbIIGa+GE2eBvik3Acm01/EvFrYgwVnmE/+eKnDIqZZwdbG44q/MvY+H7+3y8/l1sOLMNWQdZBq6m3zoKIOz82u51bjOhyR1+IbrPwNzTUOOP7PtoqIXVsiK/je/8ccnrN7MfU+mS73bxbP6gloFkUvFjv3IHx5YlQPX1mgRxvh3MDkd1QLOu3cvIHVfu5oL31QyhZx33CW6zUx0UPsdAvuDAZdf3rzy0DLqfbZC9NsXQVUgqVm2CR2aMnxF6dKn7tjwVO6O8YScdQDdXumpFAna0BFvnlrsgS1HeHWwMGpMEB1kw1DFoliQadRPf4Wwb+cOFL2a6+85jEOwxMharjF6EUPixXNA0anYuSi9PQ2ZAHxcRRl9LDj6+9tpCDYJ2uSKCuzZpwRfE9OliYZ/JlYtfSljQVAOmphNhKi4KC/KeixwA5O4g9kscY2noQAyJDmKtPc5nI/NIOGoBRNX70DxZlCjpSpviTR2Fsyp6cHZQ8rrbkMEjVK+tc1tmJpafm6mVSlVlI467wk3WnqNd5ZaKQVeqy93MA5aKxTLG/hrDxKqsfPJm70CF8jpPcSxwXAbDAIa66joZItjJxQIgue1a6h1rpgvyt5Fs/4pVVqvftyeb7uJFyR88LYUEFofTdon7UoTEjJp47YbQjObLYsl/tvxmZPhle5Mcb6Q8b67GXkokh3v+fPCYp2rDhJsKcDNn23qk61UT+2/NsoVH1+ecBz7juKKgkOyI4yqe1YQb92o1qA+yuhcBQXoKE+iRmtAqd0RJ5pnLKozuBgWlERiHBD05Rt3tcFdD2LXIPIQcIFF4mmsbEUIRvuC0FfXHwR2/YjSbwF52X4sAlgH88Glyvxeov0r2Plgtzxexm4XbrL4m6VEdiCXTi106tHIKpE/9G52ZL372Dr+AmiQTRC0PMVoDdfQbkdLhRXm592pG/bEtHZrRY0fTGUcUf3xb0pfGeXFjCSLBdm1Ug/5pXY2rY2Ecdpk0DlNfSFGQzPnw3rB0QVcawQLuAJINHVavyeMCV8Yp5L6RDfCIk0CZd7fJuAMu8O9FWkxXIQyuMin7pfo8WyHdx/J2ksxpOeNyFFWefG25/upyN1fLR80zNsyXyyR+rCUhY0LmL1lMQrRenKMKW+xvVi3B/Tj1YuBr/FZWXi60S1ZKAjD+5tEdM/fePvlTjUleqUfdmlpASlqP3Irs8+BDAblhFSmADPhLMZqfyuKHZ/8ruFCVxTVEzz9bVwbvaRVMu2etYvTL+xqs/hyrdgf22P0awDBQE2FBCLHdv7+Ns2GTOe3qy5VuUPniJdnZHhPPTc12qeXnm+iZ6IfaKLTZnnZfl0V9Hq8eLKS0DupGYV3jgq0v4kGnZ6xu6VwcrnqYe/UQg8N2FD4wkE1Kv963jdr1IlQaKaWdyFASC3eQMpoCCNQ04XmjBWG1uwmKm3wmiKF3eqzvZ837rdRntoDBf9Di/rzs54v9jibzb28BMBz4k1C09fsKG1otUUR0ur1yzrtz+doVHeIcxs6oLZzjY8HODs8KY+xvu5FE5gGhNGSUmjISGFgzglNz9X0ZlJK6jvDc+nHoVAv5UhvC1ZdGzMQlQqp2FDQ0zQKuVZ1az9L4PfslY4d59Ubvnhj5GcfG8No9Xqq/zgWe8ZSD4HbDFis1F8C+KVFAix8ApdeiNqcuXlZsQMp2gSdy93d6JJPpHUycLMyFSS9p/bXxjeC9OXqPA3uzVMhe+d0XnLP/DG/qH1i8U1+gVwMx1Wbz2a6CMUq7PlKmW7yomcw3INpz52dyPsHbmrz8P5Q0uk3apN98ge/jw/epc3o/jdeAKMClVrD/BLZhY3MsdreSD5uE0ytF2mfOntRwXZKltJqgfTGUH69ajtjghs0/p8PnnCP4FGnhV2bPSIrOfMtBUpxXbkXodvNP4+u1vfpY9BaIIazyZATXOym1jdMhGSlQGu0++YHdBl9BP6GbrPGHz6BQWMs+wB2qD9mHEbxLAWS0oupNHx+FdHdzxOzFt1BQiq41WHMXWPmRHUchyu0CuouexwddnpHRzCEO6qkksqHYfa5pL8r8eblOBLjjvPJRU0bhy+mY9U2nFDk40VDVqsSAHxu32g+k/ieMR7n2qvUtAPSNMXZngW30s6Osuckhf/K8gbUQYRN4L5agDHDsj14/dN4vVPUz0ugl9e+ZiWjLBA78CkSXwxN8mGuDwRshJ/AXu2BhVNJ/jya4DLM1FaVf1zph7uoE6h+2jl7YDn0++OgjlEABfP6ZS/Fymzp0UmEyADEcMcNsE7sKfe16CE1obt5dsThE1XB18AVTFykeXoO/ooLy+nxN1FNSKulbwAJsYETcC3pvZDBxBgm09xOL3sJde1F72M68G6XDeJvg7khVN1UgoZI/uCa07vsPg0IN1fVsX/NXWqsuusWmXNYIj40fWBfi1/l7Ogv1DXSNycrlTkfXW5UkmxnZzHiF5IVa1n5tMMMe4qPD/bmN7D78veomvt60EiLJ17zfOkauj+M1lqf2DnExDVrYvB9U8Lxg1b9eUHXC/hZfvsbG8tPMqXxRCBy2x4o/Ue/GGcUJ9tXmD/2e6qT/D1HAbO6hp2BS8TicqxS0zDhyxjT3UKrSoPU9nfMC4+TFr/W0TRw/lwaznzUZyYol2B+JD+c26HcNsFh5qlAZMHYC8g9ozvrDDHYJXwSA5WuCjXBsuvN3+yCDUuapVdgSphjvrRrgPdF+yqdkyCkSrx4eRAYVoOIBJXxSl+aj+fxh41OGwC34N0y5pp13/rnxrKv1HUQ2ntv550YhOezpJSNlT+m8fLz05iHRIO4hOdHBKWFHa0wlsnuznPpsvn2JgPUOFLu3x1QU+CmM+uDyrtXFiT6nfYR9gQLdYbBCY4RnbMzrj6DZcibclYPnYjMeadO2Qsgy92U8BRv2ruF342DhwNUJPqUjqgsObS/cQR5EIhjwhDAlu5afjI9xV3VAd9BC2qq03n9TT/DxqPX5ymEO6o4Ky6o2n7TbLnYaRau5pHDiNXO/depxoX48lMWQ4eG1+PKNP1SGY2VoQZXZSDm0NYkabQD8JM6dohw2A2kEWACJ1TCbfYjZMviAD2l7aOJmIFAYtbMU5WVN/f7GYf7ileCxer8r40iGftjEeORfh/wByPlTaprhLoHi5Q7+QLA4ztnchTCweinS7x8L7TShxWSvorVmub1PL9CUnglZcVkK3EPbv803elnBYX9Hkv5dxFDBs5OzDaLdBc4Xd+MVa9/M1OZiaLX2GItrXZTL+RGJBjZDieRHPNoIYtXhQfMKnTP2ARntbeEGLA6F91wWNjYQVhX0pIeSFlsh4lynqCjBXOoPAMiqzUJ+ISx3jUg8Xa/YO+L/uMn1fmhDdzwkMtQwpqQ+Gvj54ZHKxsE9jBGhS2tRvUTR3nV6iRIeFSemgXSpequ2Z/kz7kAlIorENr5buSfD6/xEkCcUc045rlzWzqk5C8VJfnbXWOx7ZMeU5zoxQODfgK1ZYRcNW4mjuDyhGG0hgyJJ35Xmda1Y9Z8QwSuzw0cxykGuWmMIEC5Kd2kj6d097cwm/RB6XL7W576G4gRiCa/DoCtC++ofcihJm1SY8jiUx+QCO7E4aooWnCRtihUqPiENJLtBe2KZeeaaUrk20lRgb/Wz6H/JAHpLUfqtx4xN1BWZTH1mQSwPIGeclKb4THxQnLtW+mR+Jwmy4FIEErSSWFbCvNN59yYGm1rHir8O0gQ5hGdbeDXel5mE8AJYpEdTNkpju1eL478VjXLAQoBKXN+wEt90QYhd1cwZONv05Bbo0UaJEJ9VQEzsT8KwmbdzftiSL5z3TDM5SDKuI/Fmt0FwyeEu8o0Swl7ekJiHjsku3Fc8iB83VztE/ZAyKjO8SA49ha16tfbvy0aIbz4d1+uEvQ6o718rnkjbnUyW/aAbTss7hI/XfeI74hjsULG4ET4RYf9lU80QWrC9QMckiisMdTX3EpZ728yHnymY3+NiGcckO9AC2ZcREIN07fJza03NcZROEhFX8TCM/svIVXlmZDPLrVuifhXO7Csm01wFzBJxOW6z8o31a0vqgAob0rYtIsXATYDGZejOP9OsQuATWc4glKjjMFopgfF6hjuCsNQDaMomaA3i8BZzHcKixKLCA/u2aySWuZRfibXVq2QpSbNWFkcATFAocf6MginfsM4oLOfNMC1ULPqsTLfoIDZIncD7pLFk8DeHYT6lWn8DYHJhJiYkanJqV8c4eKk77OpZcxeXO5/GtyH2r+RQ+OxAhaqebJF7Pq+PrfpyMHNupfAlrziS2O1rxvH2Bhik1/id9L8M12Gn/DDHITXvOBZRunuugsNstbThcHIEj3JyQaG7gDhIFUlqTU9hR7g6wYiSxoGkz0EKrEJM0nFEhbKRWfs48kuTsAawul+HrYWnqy23RwrfRXvpb1gNYMzy2lwsB77ucVhDg1A1F+H2D9s81GwCYovIVq2w3L+9sB/hW1EvAtuPqA3oVTnK/vSGgZJQ/nityhATFK7qvomZt9iBCIm6IhnT/Y7dYG60NHFMLgW6ItymzDpMGCbpHDvq/etnDzAHbbtoAcorQPcbj22atzFJLoxs2cVE5uVRheob/V2YfNr0/FAf8SMxs3ckAlWHiJmUagCJ8wZg1gXOg8GMxlUR0eY99Nrq7EeRNx+OFJ4A/oibvtPuolCtlmdzwKT4dPzaaF/d570nDGOuo2cZbTIkcsYi666DszZ1zBwjilWu9R9QZezDhfOBjHqckwLBZv2zuQT1VjpC6Q3LVn+ukpccHKvrXlfbj9tdAbEvGp0boED0PfHIiiPuhx0NRF2E0vCJka0g/9u7E/xK6qHIw5ixdvgPWC7l76/H/Vv02lQWmBmv/mEO0HsPxmxmCXz+7F0kFwPeIZtrB7mTvk1dTkjWslg1UxV2tsnJLHOpx23uPRWcjoyO5AC0GHDcn0zcujAG7xDd/OAgnpS8l2wW/A/GCYyDx1Y6PtQ6MOrgvxlBOd9IEuiNt6Xv1cmRvgBYXwxGUGoRggVhgGO/xiB4Vk+uZv6yvcqMt2+xX5Wn96fuOpQ3+A0Mgm42k1P4sAr102suLtTP4GLsWDSKx1qNo81R+JYltdfQBm9uKNfEqHVxQXS+Alcj23d7eD3fShvQApLhpkAKXPRr0u9HEazM/GgT8+Q5wMJ4IFkZIt3lBCjp9/WKJzWBS3pYmT9GFWmWLuFRW8UuiCGGT5/Y5Dz6M4ewfCW4KdtomBRxjpyYXMr5XEuIxtfVqb+UMjaNyrPJqHIsMmBVIykprxKIJpqi8aakRRr56zLngjRfUAuINfKs4gSCMrY9scU+aoolu8bSmBssETjdYnnYcxHIbmFYcFFhusC/H0R8F/OhvWabxcN0NaXQD7yP9yJ7q+Pykn+YBsmE09Bh/CM2G1/H6nXYnk0FgF/0kSfqPzCOCPZkUmhyxMvwBsWfZwaLdUsR6qqq74jqfQs1HJInTvZWJZBWVzqBOpA7uEuIssT4UDaWoht27mTExa6hQDxc7GskHeZWlNV8DD7f6j/C8iYJxwtyRBDoScQI/PFUafw5wVkIcVKN9Xgt1/WqP/bk2tW8XV1HfUBvdOJMnYlor14/FAextOnzW3EOmApLwnBMcYHkH+npmP8+HP0+2cj0ir7UMKgMVgMrzi65RWYZgDTeQDBdR3BMnv/Onf6guYIt8B8CozGvMaRqDDqEHjYHDGbCX8JjDfyxbiL3UWvF2OVxSQLjSKWgxU7zhpa+74mN7Ti/HDUzpG3hUMJPMVI9MHITJAzwTP22LR4LTLES9eXFq2w2Xf39r9GGUE+uP62P3fgJJ4JDX83Db1vUxvzLVqjPwc8uobazPUypeacI2mbFypSj8+jv6Dya5vaxP92U3U49PyNczbdrCyrrPtg3WPCJkEXa/ERze7c/u25EF11zDXIoktrwh66oHVfAhpMn5o3s3NGrDOTzfkCGv++R7tcLN3QzXCgcGnUHrU8uS6JXokm6byxQV7P1nf1NNFXmny/ze+LdLHQxnpQ0GYv3mVS0xX4G9ksw2xPGtUSkXsDR8gDGUGTCnDwxGpK/y7K46mYuVJD55p1W3bN+du5dbFM3s8yY+1rRsWNLYJJfqZCeXL3RKCT7q7oospjPT9MsB6q2nX0BBcasNE4E+0qd+zI1n44hef9bciFMNyZ+aVTxjyaQygsFt4RDHGceGTynRyBuKfHWsLzkiMs6yv7Gy/Uv+QnytPwE5WmK74Xr2eeyTHf9P1uK0B5kbl/MkP63KDx/iwIo5vXCOoiCqq0DwweWW2up02hPRAbt3TYYtC46Zxb2EESBrr/vjuNXcfXh69IRsBSHDh64B9zeRR2gjLhVlxX0bel9ZCyh4DeRxs9Uaa546LAwJfVdxTF1QH5pNsQhfieq73ya6QRbKGQApwVZk5+T5paWeWHMAmzyIxhesXiQ7NzT6vemItkMN7ouHyLBJMWLTrvGMnGuXrWP/zvuXsk0MgU7Nb3PY9exiJ/dvI5yDA9q3II3OLmwDAbirSJW5BkZLzckYZJeafGI/vsFmXFlS/Zxz9LVO18DC/Q8q/iFZlpp8ZZbREdX/OLOnEEmjEB9w3VJLv9y3/eYNvWq1+4hi1HeBGLvhZjfzsXCbXt0iz/2h2+mDt/S7q0RMymdKJvSTdUIiYYgmFd/bURN01qaM9Au4h+wnLDKypO8hkjOXS1Fz8UKBwI+OIx5BTMh7wfw62oJ2Z87v4rfqQWwxgventTNnq6hRTLNEbKLkn+7YVvK6xCX7SJenpI6PvQ7L+f09nFoJA3XKmiHQD0zfj/tVaJoGYnCpzhkI0DdE623DS67BsHKMYqnkOyg/6IM37z6fuhaWwY6+BfoD9gtIf0PDYMyBJnGQc2wj3Cm/Vy8d8Bo2gOhEkprsr0GPe/y5lpNNHBa3wjcrrrecP7sOiIeytkbX5U/V7P8RHzQhObuD/iwW2A0pxBDnp9yZyBZ/0SurQ78/dUGSn8WIyjMJUJabqVLcSxBow0NipOCQY613O3U8k2WENyi6f8AMiE8f6U1CU9ADGvFPxiP6TPy3CiTmkPBIwRmT/bj8u+ZUqu8uhPK9wU9Yf//70T8hFJeO7+dnduG5KnNkLHC5UwIV5Fv5trkUT+i97oODBfbbI6MV8EtPi762JHQL/MWB22/aWLyJ5nHLRbel7wfqHgQtkin/Ikq76a70Hrc5vDUrwhlneu5DiIF5TWHPHSruK8yLJzlnYUvi7GsC/CqYmX0r6/aj4phiSgdaJw1CQWPevAcN98yD+RIPwXLaB7la9cymTWhqmnAjWaCpbZYuZwnNCe/80dzCvvJ6VL06JJrEADMNSLaaFqfYSXP3wAoizWyAXHonltwYF+om3UuHmtun6WCBu1i5NCzrsz3ePLF9K0l1MrAYo3OOQQfY19+FXu7YeVwLgUkRBoaLWWFP5YtmYYBwiafquso+EZ9K5hP2ZCXVwD/O9AgWbFHXImpavMTtm6HOuBkE9YTtu9pSMA8BzQGki9EumZWCAy+wyfFFlGf/Qqbz3iI3DFvaw+Oz2FK/7S5c+IZj+rS/WsrBbvdUHatGaiQCt+0gsR3CPFarpO5zjpkVZlU65z/x3132VMUv745Dmo64sTh+TwE0NIwWWjjDjxCteWUX3Dx3jXn9jbPjjtv78XAKZj/K4ZHvuJ8gOOw1qsi1an39IPSvuBqFYvnXKLnf7+bVO6jqcQO+VvMgWU5r5tyftGyQ5NgGGd3uKp53r67x6qxmZE4H7+TMZX+Rgr6IIwl3U9VpTf+8CKtERyqvD9klt4EqDJjf2KIrbD+YWKPo3/kGuEaV4BDkKpvmePAgVdgsEob9B5KZGYRoPSNIyJgYtSRdrurvBLFOv7nd9/VQhzAignj6Yz85NLbOwMrrpxSkM1uw0LqIm+mIPJhZL7JuCeEZGOJIseyubkkit4LQgqqi3CkzL2K/TaNk+fgyPGjSf4NKK5/XT5VnxLW8q+2PDpw4vaGKGI/ncX+7PsZwj+vlp6pXzjqTLfX3l6c0S37lxC2dTxZCJMifMMGvJg2ttJ6ZdwChZ/GjiVfdvLie7q8F8sn77A8faffC98VI9v9fE7TLgYlD+0AWh7/sZjC0U94kNWT1mnuNMPnhwUqYYP210+Q8Zx/II3NuTPKeg9AiDl+zfbH7NgfY7KvjHarXoe1UVhklYBI/iQguBKvM9UgrcA6+XErvHNIzuYW7aNfcq7x8lN4BVP8PyVYv/3d2+jqE02iOXd9ZftK2SUetsj9ZlGuotEwnQbjc5AbQskIgxCYJwIx9XPCmghYhPf9Vl9Iw3WUSsVoL1013rnoOTdFrfsaG+oZUDws1qA1cRhqQWfT1u6yE87uj6yu8frkw4TGZtFbiHJovdf4By9GURofJ9N5aV7o8H283eFvDYg90sBMlQ3zYLxC6pwicgoJwj/wKcEe1jdhgNRbiYRpZzvP+xX9U/po3gy+hUXu2tSLHmSh3VSiSSPuznTaiyN1vwLgDg05l9+hYcuEMqfYV91dYNZojyVUMyZyG2lEdW7LaDENGJTwB+wJAq4zDDnDbiKmzkjjvawmZJ38lMmUJRGC35FRiyCtkq2+wGTMEHTRSzefwiSl4n7TKPT/OokaErT8KrdAYifti+hFysOfi71gp/Mb22ID8JTaGk2+rckwq5cd3Y4tL6BxqNOd26Ykb+GJJpvTC6NFGovEo0TYti+d8XpH/uWwJ+nMZlrn+9z4MdJUd08j/8igxuJb2zIpS0yW1LyycZWWXzK93/Zfh5Aiyu2D6nxhyHkq5NrHdOFCxelKMXzM3RPjqd/1c4GH+mzVjEgshvtIxEFtHEzAPHicM0+sppU3+gWxa0wyz6rp26ol6jmXiX0/LqO45qBmBVzM1u+7lUyC5Ho3/27X6jj3lxD3077c2HYxBuSAfceilUHQZQDlfl4G2X8J3ervROmNm4+9t/SKWSsfwc69m3Dfib2+zGcfzHp0tI1JcLfggwok4eFi9FSFmWfS21p5e+aZWK3/pYB/1KA18Bl9SyKuAiCZ7MMMrNN0G2vAbCiBkHA/NhW6AwpuaUxgUaCth+oCmaJOitZAwLYDSHC11iDUipS0/lrKGThNm+l+w3FR/qdmgmydlV1F22OSjY8qS+20ufek4l3G2fYjc+Izr/ObIftb0sNrNoa5eFf/QlSiTVwGhVEcNm1rnYzMUwByLB7uO2VRh2/sD3q7FcSONls+evRa5ylw+0qT1wRI7Vk6QrYTY5KGwx71lw4f42SzJzvr/fs7eN43j8sI79uy360ZJd6HnE2jmiXZ5/wbg2q1Dug0Ga+QpCV0d/SCzklY0mVUk8rl0qzFzskj8H6ZEurn6U1e4b2pP4cTbBwYDzYQDaaSZelY562Ev2tonn8br7gbhWUN+XyAUIJafUmaod+hGFu/JFmvdpGGprrEhIw+qPNXpFBM6sTBlHx9L6PDda83H4fHTovCjzFXURp1cB+7YYG5nF8x7b5uV9ZLqTelHQ8ScH9K5sq39ocUtIHhXQQ9ZPPRLdb5ORVSWMxCyN3M9dftghYxXpWDe1SbXE7NwjZLopQWaVYs61Dkr5gApdJ79REZXL4AscgcjGgiV7ALZXi8cAXKdNWSWddNwXxeOjz14KHe8GJOL3gC5UjrMr9ebO52Q8BSHRlUu9fcrz78/kCp7gC2GZBEdgZtrz/fMkztUS4luxmUyWbASNpy03pM/W8vlGmfP0h/B+OhA5pI8zSdxcoo2DWxr0xDHzfzJ4kMM/JkiX+s34DF6gfO77J8PTOXtmEnqqRdIujdbWsDGln1LGRH+qhmjzNZpyc1HplKPnNIMjAShu9q5Ry+eErAPPajfr15YTi0sYQ9bOut+Dj390iTSqKNlTbt+E32VLG420Ubw7tUzMOS7yxLEviivNuXGUlzVheDKKTyR6uhU6vSePGhfWmc9CQSaXZSy9f21FdfwGRXc/8Rvg4YMH1AAzYZliIyQVO4Gym9fdh/In/Zpe0A8ZIygr/tqirMKsC4P93Ceuf+vlv9EUVBpic3Sf+bLvZbPg4fIGw+cR0LbKZClJCmOM13avIrOpbckGojhh/zdKuguiiI8U4dpQVUURr0TK391FIcm2fx4JMLVaDpM/KDacwJROkyDJwMBdbPIskuU04DKDUQJWyL8rV3MXfCRCvvLsMEMWV3bXZy2Eioa2qCggJgfM3dpif7krrzw2ubxYVoDkpTapuVY5IO5u6Vk/D5hPAyCf5W8IMQv29zSdf9kPsd1exMOeh3Oj34dAdq2I6CgXqjaf91zqWk3fz8BsUVg7LaZkYBRPr0j3DN1x7Bib+Gj9arShLjcpPz++3aoYQF5pq43g5KHwCGO6kbFpLdN36Oj/DcEQGe+xuE8HVlhlc+h55XqLmNr+CZO2m+sK4NsR4IK+dPe2E+kHP4xfj5RQWSPkg1fhcsDwRb7b+Oqtw5FxtaavVwlN1s0mAvWeFtRNenzfoXl6ej3sKxvfYqP3XuufjJ1/lbtqPW9SKW3wJM0nba+G/MWbbzbQoAoxX24s8n+RzIVqnGPGAMwF9zv8n7r2WXUdytNGnmcvpoBd5Se8leoq8OUHvPSmapz9MrV3V1V01JqJn/tEOrS26ZBok8AGJBM4FpsvdhP3r1iZHYH01+VvEnA01YD7pK+275iRoQoz6eFnPlMeucmf6oeQJ4JydBR5l2Ys99fw3PnotPNiRkT+GoQ5R7YnVs0rfHuxl2abNbNDiJg5rM98vrJk2cOHbII61og0N9G7TaH7z0lMfWUXmQjvTqpSMSIXFoGe1AOR3VVr9mcLdAsSk1xoWmVR+k/dqcJCHs5NO80GzQMY4FxLA28IXugFpl1EAtZSUNHShFMcB+rkh+nBuVyhgdUfFo14dhDPTHe2Cxu8y4CMXuhnjFu6dVLdpct9L1JMTLdID7BLeknHE+JYLsZ2Wrhla+7qkGmCEozZyaoj+cncfgT4515KLT/Yp8ZgfaJGdM1Iy0fyJlfCWnZbEa4f/Bka191O/Oc/gcxBKuemS5rOuuCaDtp7QwhSlAZg8nZl6oq/O/yYMabpBIPA8adnqu/EpKJZ8JCu4YB3/GxS3OUmydGKn44Y42gwCvAZpibbczO+KZPBSmpB/X6qIrOZ7GxWOv4iC4zzsGZoS5/cvLHdvkZnNF0S0H686oYgNdymbzDdQkpfN91o9vPEaEoKymW9K9RxMbcbnrRsavqHMDCN/9kA+CYoEyN3r4b59s/uxn2M/egPxkt+hjcCfz8Sb0XceS7PwTXvBjwnMKxKOAWkD1na4MEvdjXNy+HNVZDKfgjkCRsE9jPy71NxgBVHPZuz/5AcGSmv8eUEI6krvFnjnGUizeE0klst6uelcGo+DBUrELPerv1ei+flsr2xDgdaGbW9Y6NJnuX2x2a5qAkGpb179ZDn/GtEDWTQtGQSJJ9QZ+KXfUkimDRr2Z0VO1Viryov6PFfoit3Qtwy+Kuj7pc6osu7X+ddCjDS+5czwkPTPN2GOEM6xmMMuZX2y8t2FMz7pk+wo79YKdjYGy7K5uqfZj0FKKPden9xSkOeVTOcnSbXRoA8nLuoMK1S5mtalUjmi6ick+W3Sjbp+IX81dHNzPnM+gB6SFdkjdGO3CQ0zvY8IYA//oM+LJIGeFXu4Fc6pxMXBB3ncAiKP53uOWv3K0XjwYr75N6+v2qjp4Tc8qisAZHmJ0WvTkOwB7LWLo1tsmRPnY4uJEs33J+P6I/HECOhrbroFMrRjXxdWMk7rcWEbBP+ED58Cgqwkh9eHNlwaE69o1STk4PMPggc+jmLxN/U0sFRZvQNYQ95/iMe1GimgN3R+v9IE/xhaOT8pDhk6Mm2zLAIoYdhfQ9/rAj09QSxzRgpuXv2xV83+3POf/KzpliUj9Xkw6EEaooFwsBIvD7GdvffNSYkcpsCS/UfUNwZVHkZEJtkLIS4vL5G7vmbBYxk6U/kqLMjNDj4WdWCOS64QdXEY/BBHBSVjYx3egmEiMvfsnsCPqIAX1Iuc1/WZ0PcDzu8S8Bn/RnlCtIjY5vmmSLRKM5yLJaoJyah3GQsIpFN/JMcB1mlviHUlUS2h5zecUUJ3rE7SCRN+iKxYJwPgBlmmY9t/VdW2bdpFB2v4AHwkGx8LZaQ0TBix8TFTMaAoGX1J9fIB87BH77mXWtmqsb2S33DHIj0cRUOvj1fi81n64fOaPw/jgIZVGYZWpp5suaJ6Yo6nggN66l4Kl22mWLB74b9ohpRC3YKT7yLsrdo553vm9YHkrnl2QxA/nKlAyHKB+tw6FdBWOWzAPBj8AriP/mqw/8e/fxDAz4n/09+AAxXObwe/Kvl/+vtPXabT+fwhn/c4/nAxvOjTYKadsxm/d/Kt4DT2ZnYs+2/ojeqRf0OgMZqzfv039H4EgX9OfbJ5zY4/nEL5f0PZ7hCzocvW+dZ7oN+u/v7M+XMCQ9Gf471K1/LXTRjyc67MqqL89arHr+ei5ee4+L3sb76E7xsBRR9s1ra/VeD7G4Gq9OcZvRS5DrI3/31Dasq++tmw/x399bJP1G7Zz33/hhDt/VomrW6Sh5b1bH8uENM2gPP50K//vlTXffbWFiAYAbvjfrt4/yp+/f8tZBmj/n+7lP1XR4Fy+mHuItDq79PQf+stajbH2TzcnJuIuvE+0cfL+Ie3A6HxrcD/ZNP+HdTxX3nBv9pqjaON/9ct/j9s7j83lR2WFQTi/E+r9B+f/k6O384i/1BtZAUO6Cizl9Wa2WOUgNP7HIG3lmt3T08O/tUq+1vpW/P4dSxEXdUC3sAOXXVLdMiO+pssId3+7YFfb0Hv46itiv7+ndwMKZvBDVXbskM7zN9qoPn3c58HDKpKopb+9UBXpSko5X+CpSGPx98AbPojUwMJv/6JqWHY42/I4898DUH+t/ga9hd87Z/GqZgH4D/y3+0DBEpukJYlaxT/VgL0n/YNhSP/0DE4+ueOISjybw/0zx2D4tC/3jOjH/bEJ2/+vzlEq26j5HdE/Dv1557hojVqo/Mmon/uoruH+jQDxUH/NUWn0VJ+7wUHyzoPTeb/ainyZ/LMvp//NuH/minYXxH+PxP4Ooz/NF/+XqM/1ACCCOI/EO3/OU3917Pjn0cYof7++X852NifxTuXtWukRU22/CejDf/Xo/2PAwzGpZijtLq78A9dTD1S6PH48+CneEam2F+M7V8P5W8j3mb5+t+hmH8c6n7o/5rb/auj/OsqilF/IykYf1AwCmEo9iD+YdrDfzHtUZS6iQLD8V9PkH8mChj63yIKAv4TUfwm2UDPAon6M+h/l6J/7uQ/CNj/BiDA/kokA7ZjD9ucAFL8XbL+VOE/EK1LGY3g54+ETYct/n16/6us6h8YA/j8NRP7I+l52ZxGffRPZEz+BcH+I/WTEPj3H1L7/xqhwvDfYJSCH+hvhPr4J0JF/kSoCIH+DSEpnMCR73N/KcT/1yj1N/XoD5Sqb+1asUBgDcX/Cw6W5ylFEH+JrpAk+Y94zb8gsv7bGBD+36MT6G8w8tuIfynlH+jkr2DMg/zbzff+Tid/phKS/Bv0P6DF/rWYg/5rtPcb66i6qABD9P2fXsYb0P3iGdFvB3l1ABr6fZC0KM5aY1iqtRrAYMXDug7dfziK/wkO+mfgT/5WkfsgvYn6Zpc/h4hQj1nxbwjoDwp44WM0bdpNqFgFzQAfJ5tm5B/LBWaygfUux7CgaceFaLX4MXkwJnCHcsEBA/7YBR//8dgt+AEYQX6OgUWEP363jNwff0jH+6W/3x/+sqjQ3zfTP5f470nGgtqny9KnVheb7oBzL3BV+l7k6FN32OIPx4fO3ccmzX3L+eWb/VvpP4Zh/ib/EkolmtBOaktOvAxFqgptvI4BKYvCrl38prMkbXAUlohCHSEeJItKGyLtZtj5Z3uQvSy2jWGn76cL7c6b4UK/HG1pPEPvSTitVWfdWr98szcuTDWkgshEeI99DwpsBov9Y0uuEbuf/3m3M4LjNXxbpXYCvxK2Ps5QDAi3sURZKtdYxK9XrzRhDfWRZEEJN3w0NEXTE0f1E/8kXfLRnQZ/2eSuV+SpVzB4fk3QdktFAdN8/JJ/Si6GwpBASSMfvp910rV7KrafuPq9Xm0mtt1dtyGVrP1VkZ+417fAtz5B524BQq0a4mGRH3x0G9t/1beiC0P8niV8QWkThFrStzXGyNev5pLJX8/8xdV/eNb7T5/909U/Put23hUjB/xtTett0RvYem0fryNu/4SAjuvRvtv8iSUPinxq83x8TDlMlVma/HXdiZEQShHhDB2c+aELRol9oQ/vd999gBss9e2fV/v8hL2FBm+lNe4+lbnj53wDahP64Vu5wDuM73FQeJ13JgjoZ0DL/O9t+s6vNmyT/vkXbbK6o0yQ0oze5R9KDJfg/RxcscVk4fcnCzAav9fuD/f8/ub6H1rK3X31SdrnHvjP1rj7K0CEJfKVM3hbbfj3HvvH+wD1sNSXNi3RuwJUGRPp99EoBlJDv7RlW3zwpeP9k3EjFr+ZL3Xfz5MGWoKy5vBtFqGPg1l0KhdYFQPpcoWY5m8mUfDMz/eu8c+XY0ydo3edY4qbSe2uxBSJxOzB/S0Udm/umwaZ2ROVMweV3Xdws8SamMGZ++t7bPwq/afUb0ngu99PFT9F/ytfUDpLM7xM8wkjmjrDmzTLmzzHm8Av5GaA6ddGXOhfLvvHD1O4Er2bMm3KYDMoad7s7L7DdP90581FOYZ2RdpMwBoINdyMTgc81qQPcFVa9/uvQb+kj0kzUrApihsq4YoficrG7Rov7AvDaxsPyKkazg8S9+rQp8iMZD+WaZrE4ff0SN/DvMlz/DKDMHggTUK9wcrcmN93CEbpie/m+rTep0IXkCVDJRzCkzhoi0sxaewIeTr11pXnQ0GsyVfGmwI/0ylNsd+HypXNRkNsN5xJvPcM9kS/ydgB7kgM1E5LyEjAtWnZYf3KSwWVJzEaC0yYYmps54kKyRttksVLeO/enAk92D2KzxFxM6npBXFEDSKaXe75IqbZCFrEO6xr6dZKsFQfGR/24507df6aeVhvZRuTJuIjIlITWcoYwND5tCZPPYKHZntkzYWN7PgUZx7eoqblnmjA88zOJcKnLrLAGazdL3e8odnbuSbUmSP99IBLvBDJWYCa2ae2P1COHQIe54iN4Oy6OfNlAbd1ZrL8PFaDtCYfz/3VUtI3s2RY56P6tsBia9d7iBItGpxS/bOS0dUUx4PP0ygKAvtB71vXTPFsjGCvc/SeOrqNu48NpeWHeeBeM66cek4zNAtzWFnANbKuF4hBiwupjdQEqhFjj880e66t7dZuZKRp9ONxmHwm9EpTc7bZm3Y8ZEbh9ZrrZ+Yh1WKflzpLi6jNkSYsj2pdrjfp54IZ13kLZ6WHrnum5aawRQ/2KRYjNzCel+FjpqC14RoqNQFQ9o2sfqGE45vAP33e2ofXk+fDb4humRTz8rHwWePTZ2jsquNK3Rb1MyNHnCOsdJ53hNBelTO9IbFTNYgb3lHGnBMSqfsH6h3Foq742t/GtfIZcDJ3eIdQsBAK2tyjtOm7CgP3KxARgV8r5gkcoHYmZdb+QkDMaYZigGc2bFjTBJZ6e+kbO/QB18BRIyDAhgfCh9Fu29c1i9o497GWg6tLXZkSqXtlbqJnuMyNTcetbQP+CH2aCJYRTz9Y6cF2sMowK3Ja78X3KJ5QKmf/VutZum/hLX+Sm97E7lnIu2zqdS9bUhpgjJ9s8ULib76DEbef3dqqHbKOm/587UP1ekd4lXzDlwjDy57rVyq+Ds0Y1vgjxaVb6+X+TxyFZo0HauwIV71wPfOx4Ocszwr2ZOJlMHm/4Fn+w5s0gNm+PJlnGdO8OekvLmYGN2+SbyTGy3wZOALjBjJDW8LJ1zrrHibgkDduLG6uRjOMqZE58Jr/UO/oMILSdcRwYae7QWEjtksB5jVNxRS8juc9hS/rpnQFrfhXrikR2k7aPTYnmP2MM+U90OcY1MNhEMeJ8Z+Tt2127PQP3EeeT1SyctWbNUmbDYmUXOjFzsSgNwit7GCVfMa8/J3Da8B10Uw/cWsT2DljFtR8nL1b8ygL+6EhhnP4URSQVJrBQcBqYFRwIBiJ6vhCOAfXOBgevmvYYDHX0bSZE0+sUn1v3EV3h7XES8YgACzPD0clbRfGURWsc5mRppL7tWSNcxIZvT9c5+Y8RjU62J7MnbhmlExxPAfK46VbPO8c3C6UMzCTOxDbW8E01F5g6C07jt7P4lErk3GcjfvEsQxtymV+hLXIysAFT8As2lqTeYZf95T2i3l6JYq3pCj3dPMAOZYjg0veQRiFepUIyASspZtbKz5RE53ejde6Hdmt/qUJj4cWMnwIXEJP49PkjJCrkaNK2Bt0jRe9QhYmc6lD6ObzfMMKEreOtnb3rMlGhaXmu7lqFXBPv6DHyqJLay6BA0B7KeWDMcuPKSiws3xnZaOieaO8CULM3D0BicUZjOV69BaDwD3SXDbRKXHlWtJDQji+4XwyOB9OTg3tqyBo7nMj9Hz63Ij+neMv0gW7nOWHgLw5DHhR9fAWGB6/9VYlCfVCJWUUfnwjtyTY51GlLX+GeufM1lvaG0zAiSEAlxDaq4l1RiEK1Cg9qGqBrOOA1LWtphfXcmG9JBXKCqOk+XvxfI3rCBy2OimM1d3U5AZd1Wc4TbPjJSbuBMPKIyF1DHVnNIH8OKvVsr3U0hrz+VwfH2k/lRcSxYifEW5IVteaKrqIpEStulGBelJgWW+ilKKxPGtRsH3yGKLTWDE+2iaG9CyzRRXzaJ10YdGiXzttQiEJSN5cSZjpaUrfbdvA7fZEVJRRQEw7oSovlpt0mUjp2X8tsh5saxk6flp7T67hnBTZGOGanUyf3+sNFL5uU+/uu9nhICawKc1+5qDdbmSxKkGqA4HaaRvcWJ4Jr0mJvWj3+KKTt58UKc9gpgb1k8Nc8LjHTBHTOh577hvWqysl6POOuJ4k9NVfqkcBYWHXM581mCMnuAgbOnkle6fP7Bl84IZ2JfiTfbCtyquvYUNYLmP9hFRnI+IwfjdjhE5FQQ2Zv3ButtfrpAH9opT5UxUIFpLdKNEnzkh6WsRdpkERUo3Ka7C8kDJmrwu8Cc+Z5tnpXnDPFk8dIeCKxKmfI/cJ9WPDN5qpD6uGNy3WslcNo9KTStSK3Qhs9U7WyKF7wr3NbSwKukCWpzscQgFp/PMBZZ8wGSprYnnI1zoZOGrRepe/ZXz4VF//H0XnhjrDQwimqQ9vQAkVl1TxTbVmNiQTuZ5wsTiGj9/QQNSqOtcjcvt6Hp5R+wQMq95W9CEehqQKr53NnulzZJxMXRGtz2vRvKc4tr+XDfSImk5O+z6kOfxutBWe0Wx94ykSXl2Dsg8IerUaW2IY+3rEputHwEl/+YBXFo+DcCwQEEhQvpvbD/donqx94wnueURu4vHfDF0hPA1o8Q1NEwzwSxWnogKWYQECRMXTHGQBIHpj6fu7C7c28DuUvs/xjMXbssOL/KkwrqOWzEG7ghUUMqyUSSgewkVf5iDQUCY4xagx5N8l084zxi6RXaOvWGtLENbIeA5ygWOnr7DNk+rdT6Mko+6xIrS2iI2zQTkieuciPZs+JAh4He4v/sClTTfWCvuQuekiIoLZOOivIF5zDIXkLBVnzAmdgvhG9ZbJtLRDZrjk6iSdRCkm0mFGLAO+wwj2+jr/A1uoYGpeBSI5CrJc6w9ehuneoEo6QLQGRH51vUBYTMSfixtU9A0e2FSLO36c84LyVBCPuDRRJnf3uSFBY87hQMZ14lVyWZKXYpEW8tgwx8ceJKM1gEkuS/smpEFZThFxonenh5h9HaJZb88LhSxoRGM7uQBwBagbE9wgb/s2U8Dehsb0BLsYdWF+UTdEsWFG0a+9UaOSEN2ibQ++yb4NrPKB9fNRt78h+QrJe4MNv1FPCFgzPi3EFom3/kZ8gLuKaNRXMGnPcBVgwJWBNDbPVvWkFPYr4ESOd2qIvaDAvp8CxomSg+zRYR0jzd/GoxQvNbzwUxCKCOffBl5p9tC4sy4v+rOcBHUvLsAAgg8kQqlsk9cHEg4BrdB6Zw3hhejPUVF8XfVK/dIVv420kZtfz8xslseHDSYhjj1Zx0HGFPkQuskuKCj7pruTfSIUP41XwIqPtDafb6Q91iMGKnkNhkH+7DO6+Wu18TbqNktlrFGp2bWK8zwcme7nBJsdxDc1M310viJShYHGiFU1ZHen35zwLU443gJpkUZZiw5Zb0JRTR1xKAP/vPAe5hKPfXsG/+aHUOjMTpGhgMOLR0TvAc66ZMlqE3sDettmQjZW7P0cj50sa/nZJlbwbRTIE8nnMiA4X8XcW1Fi/WZ0gqJtmmkzp7Av1P0GaGTbLdsQNQckKy+5xKPjcTCniRSLfUn9DTt3EgPYPJd1Rz7rhxk0O2JEdnK85iWy8VwI5R5+iZYYM7gvSDHawM+LN+JbUovNIp1MdgttoTGO51gSnRDnLtZBm/zCa9JV0ySvasec92aXOLlh0L3YeekbP4m3+zIv+YLtHk6WtBh/y//C/4ZyuAFsd6zqEukXu6yWmw4BGZfWoYK9YaNhtxlkaCNWvfZSePA1R2RybV2MGNlYYZ8+ZjUaasiC5iBeVJ/tx70Er2u36w3xL3UCPGyzmfh5GhmPqLiYoFYnEslDLZ9tRdRQmOCaNEC3irPwdrMJsBA12+vzWC6IjwMiIU4HlT22flndimGDC+9I10snHuWnmtKNg67I1UlrNT7eIt6Y/d7L8ybsO6WlFm2WCoEKr+Eixxepma+HcvpffGXuK4v0C0Peylu72TpDzqyx5EYWKvDHvF9nWD6FDY/mWabYlHn1CaR6tKm7B1Sjpzk989a+rteRGreQr7zAqhf/QTDH0oYqowvT8qi1RllPr3JOZuOzXH8LebablzY92y6e3cc9yXzJSbpJsPtN1oc5sxRWfpp+zTcFzt7yD2sjtmszu+dlNaU0Rd3CErEvcRRxO785q/dJS/r9eLEdCnDDkHH+iwD+2mc5vuRj9ZROtOTGl2uC3V4YPlU4eyuXwhholsLtDkSNHRvXbbleT3vKNqSyOOczpi3DqDd0lS796akaN+DW6R0IJMIa1Yyx1j070bGhMX/DTR88GpksUCk5nu8y5u1HNav6y1AireLmOlx6mMxg9jLoohKvJoEs0ltNYqYLmGX3mFwWK6oG/aNkRiZ0PFxl5Yv/Jjyks2mhYJEJcYlk7GBCRfTGx2xKnh++d4s0nSxCfZRyfjeoGR/64Q11HceRral75dmhoLG79BUR3MPeMJegKS3bsl73wjEKo1ovrvf4sHUuWkp5Im/9RgjxXDaHuXbsMTq/0qeXdeP1NOJjdrOcKLOjR5kj6YLHEXZqLLwMyb7xyjy6UXKyt46mnjXnjMMLZ44d7iuPGENghJeEOFKU7dW21KHYcKh2p/BUWTJjDqFlKFdodwQVTbAJwao1pM5R9p32WZRvXIbEFPoozzewXvTL0gXJ9JS05RvS8Glmk6Y0kX8zL8BXTo0H/56jFFu31hdryw28q0qohDL1NSyHwk6rsMZeyfKlrQD4Cq1q2qvZMgporvKzfHDjjeXGGzx3YwuRLhKB/VnGAEoyDfAGb7q2bAaOXN86r3VjDZuu9U5kSI61hkQCPO8yW+VYUt43eSDckIIG1j6GdkiGAnNwct4GDSGKq5349nlay4WmqdUAzcPsCHQEW+QF3NNv0O+T5/rd7PhRrp4EyiT3wiAkJvA5IfzNNRY2jlpPfLrNq0bVEdnL8HEjaARx53pfl3pF5auXUle8WwgeFzC7s5+Zfjk5vLyRsi6YmI9bO/aD7g1WTEj/6hBH7W+oDNDiB3QU/czkm9NpHdkKXaiaj2nA0I7qUxcTn2N+qbpTC9uszGdSo7mxt5qEe3haffUoaaaiOamPa33M2XPNCMEF/Y24bfZyXGLuFUiddA/lUujy0NRLw2+ORj4ZjapHlvHtR8thJq8Jed9wk9yLLOPvgsA2aOa5AvWO+eqg23RcESv6ebIZC/IN+UDlGq6uXayTW30L53bsPd9vxy4JvbAk15tKutg6d1QMCI7zW2CXFnAtjCXnfdpLEiO4ypZ5Kz7Eqqng5TF0K31rnmaWFyL9frcB5NOI787JU0tvjXyYfCQ6NfSJmf2bGLqzaRNcQNoPOzaatY+Y8Xk5ez+iT2fJcMQqnEHvlv7Jl0HsnJ7+8K3ay7xkInxvWYJwgMiLSuHVWvmzq9GYm9yLf5uBh7ePuXNudQRXfeeZTFUEYamBfrfRAHyGTNHYFM21vKZuDsNxpeR485wKaqCwvAsEewOImeqdHK1sNCb3KdxsXncbfu7f53wuH0PtjterYJ/SE3m3SowsH9Wgrxc77tekTZn6zcv8GAZCfb+00ptX1GnmswGwTRms/RZe7ZSWyZcC7I9i34pOWYD+DbMSGNzOSWend3SrzduYP6Z0aT3tK5Fjd+Z1mmyu7BV2Xqhp8pMFeQOZau9De5yuhNQyYu8ZNSvCSI/AzhPmYWK+inya5MH3awr5rMcpquiroxjOz2+A9EcN3/jbgrvSghJYMK+1/wb/2Vf4QYJEjIyKOlu8Wc7aTqctrJ7DgXgsQk6d9a5+muZkddQ/xevTyMVPAnp8ysJFz0KC0kwUYouOvywZQIe5riaJ0KDlatv3mPbWlLZaPBVlfaMwsImaUQ+iUJuDaeZlbb01oC55Iya1evVheor6ngrOpfdxqhcBMSSPZeo+dQmpca0b6hmZWFhMTIGmsFavhkgeDTG1M4b3eKxFvYo06vhsieqbtQ1lYVe8PMGRjHbD6eylwA9VvaeunRTrN0omgzXxe7nFagtGbztYsGtu68ZQJ1XqaS8f/KVbPfb+UPgnxn1n7Ld7MjpjEAbPwyXwX1sVV6D60YCFsKP+yllGfKPBy1yK4xMGHv923r52+jWXs2XYjrtgQzOL5i9nqloGVmMXGg3W85q1VNg+3TDW7MlZo7EjaZ/YBBA1Ezy09ArObg3z0pdu6ZWksO1rnIb6L0e0h5fnchJPqxGZ3BNWLgvIISI/29B1FYolC1o0DCDisaMrAZF8AoVVK+zeqpT6S4SUOorxeiRfXx4AgCMneV70Tcm+4nP0SYOUN7SDqwiqLUUGFvLnTX4ap7JzfdhPxEEc2jRmrH+TBq0xHXK91CPYcu1+7gCR5KfKSc33NvneeIul2nTHJ3TeGsHV0V76gsdIStqHanXeSUnuJyY9/lTlhB1wZEuwuhsJXwWK181nzlaS1XHyLQvZpudDski12Ykue7xIVS1HDA2RaZjEVyaLlEvVitjWe1l6GsRGiNhL7HQm9hmWBEkZkn4psla6Pg+toh2lnU/M0a2DV7qObewkluUolxyr3ZBEedeNa9/sCXfFt/o+1aS63rqnBwrTJjdaIpQauDl/ShnykNlNmuQgJkAURNsa7sDPupfMLFhNotraXU+IeauX4Mdg8gGl3PlqkgzZ5T3VwIekCIdhPH7W024tH6ynmTJH7wnP7tBvcpn5kcm0wJd6qZuBK9cKc7emTgKx5BkXvmXxWYxidat3hX6ZiIuodqv4zRJrjMK6e0IqtAlZdRi8HCywa/KPlmvR2LVHh6KC1qbCizKAJXfgpzQSn1kLFuWu+PPaD07T6dWTMnF0M6o1iTcVHF1DInF48cFyTLLnq5M6TbMcc/xmX7C75iysXVvfduvq0rF96nmemGZu1MjxDc0FXbCx2jz1+MZLz0m6/UhVPUSS9QjCBreVXoBLz0pxDkWkEG0CpEoGHRd+ksjC0a8g0l7sxPtxxv6kJ4Jj+JHaRum1oGSrEOv+/ATZgMPu7HazniF8iRSSkRoStj2k7TrBpHs7lC8Pa9Jsp6vNTew+/If9jYVE7/HTt15oKuFg47VoyNBC9gfC5u96cBPgciIma/FUbAts8d3fIB3GR+zeb+ebEe/zxkmmML04Y19gHQrvYx1Y/bhbvHhsBzUB3acftkYv/7MtuZ5oIGDlTTNJS8D3eDAvoQ+N0tOnZp3zzK6aTFE+xZFBKW3mub5NDqVTAAWP+9LEcBaRfb21VdJFE1Cst5CDR1AnyklL5I0FN3pXh2qa/ORt2895Qpew7Pw3RHEGA3Q59B4z60XmHnreTCZG429MZ1rJ1IrT5cnvxUy7Ktm0WwhZAjqvBrRNwYZ1p352QwKPhC0h8LQVlLQ6eSStcC2kXbK6SIDjbybyRXPgxaGDIvkTaN+YtTVS430zFPzr5EiygX+2pGbbax6zXFIehrqwinw8TQ8HZEvT0BxZCbXDIOfNkTwKPenfRZfZ7aTvAWsrSK2skvq82AOrKz0vRQCl1xeIMYQWBxqhxtPwAeT7bn2tSeWqqRGWwBoeCI3ECWMCncTzFn0lnWOYGNjeQg8oqigZBvboaqRWIkHb1CW+tqkGhPOsNghKVXKBLgf1TpFAxUPb2o8vAK/HV24+dyqjx0sctmMRKs9VyFelJ1c9hctxyY+bsWXeGGBg+Yf+7hmNv/SX7h8P/sro4iDAQDo71Xog/DMteuPIZV3//JBrjWDEjVpexLAEsG7HjIViQurD7LG/zCbrx6d+YGohUlXOcXilstXJaTDxMW4I8HaibRCKWqaTBoEAo3oxnZc/BY9cIuTZlMHE3a2I4kZ+iXMBwo0L4+ulFkRTTxGRbRPiP07bf78j+BREjMsREH9FnXXyVqvGZBmDY3sw6JJBDUqzNdw8BuKhjceTUTI02FEErZejp6SWfyot4bnilhxXHD2KMl3FEeLi+PHMdlTN2nh0n4GaRX5xqQo2bV3ZaapaCHazItjiwUy9wf4nSHSTUzfbT56mlliNrcaPK8+cD4oemqHdcMX8NG06vCM7lR31iRFGgZA3aGiLt6ngfX/fmyLPM3GX5WyRAUUWoNEk4mJl6Ggs3uALdhe8QgWKR0W2NSkFjcB4v5qjVmK7aGlBambGmlbiJaov0qQHCd6ZrJKvUSUZTOITHbb4BkLRDUzeVjH0D49V+Mvr3jDscJ5yfRxOiZ3MXeEZJbDXc7W+ceq+4xUR9XP0PG8FqwuEkxOL2UbEMAbiexjKZZcOCpCIfcyTEkaAzEt7exvz3sFcDF7osHFloRzd9kFKR/IAowrdB1O9iHKQzQIDe9JeH8A4BlTGgjYCbrJn67FbSJ4005huNC2Q6bPMt2/WFu/BmkeamYhUYvenPbexmRQiuwlzEZosj16wPdkvYoZ6xNWsyMKaOgMrqd2VGJp7yNH9t8N2uTebbt35pi7ABH0uGnL4jqfAZtJR/PPJTag4NOYLOqSiVkGU18wRP8khHFSm1x9Y7fqJyPF3EzLEgBzLiMEJXbaKyx/LiwPsOtKTfVSzVzOmyttin4ZUE7lUl1EliqrmSOoK+8gnslX4oxe1Z2Hxdgl0ua7ce/JI5AJigdLDN22izRS+E/r9RIg4W3pmJK7mOpfmYscpjKYpk7TH8NjQMCmot1ODLKGMnz2+vBqwohKLHCVzKecpalvjybU+YAmj3Ddu/vthvB+Zcfh4mC9UvmIV/7JCtG1VX3qpxGcQVfekRmgAS/gX9vLIbiDPSYZRcaxK8uNmMk0MFFD2n5XvZfZbnZYrG3wSIlsNBqoGN2228CHpd3krFedHD/jp7oYNVk/FAcvtJntDYqT1d9h8CBaZDzl8cJejJvg0uK4yVyNLdMhjOR4FORiwsGSYoXKUFQvfaHDQZkLZdUuDYxff8lcpcnZ58FGM2jPXICpEuvl1exHiM328KN6c6qtnmoGcrm2uXcKGnspMG+n2Mo4Md/KJ2Z85IMbWFh62vm03Z7aLqLx8dmsE6H0juRhYz3Xg23DjfU28otPs0SUqv7kckuT90Jk5ZiUsrmu8RDBon6Ea7uP5+RD81XIUd2V4cgFyWzXGZ8U+saOh31OtqREgxuotuhceuiQ0J2jzentGQsp15kQ+FtuGDaSgYKz7fsMJgaP7KD1VPVT1pRNioV0a2DIMng3ukQqKIZ8WO3zt/dTzXQnn4XPIjs9gJLnEIy4+vl2K6VrdVPgMq9BJRO3kCT/iR8Jtx9pfs1oKQCdk0m8I2Tj/HIBt+xBxHq7wfKmR4W9tF3Z0er5kLOHQtH0E7Rw2bfbSp8EW4u0Ii4GWxl0hj1NrNXMH9qfCMCxG0JL4rWXM3qbWw7X0w4cKaBlFncqirm/b3mee7nM7tPWgpAsoEfQCojaE+bzfULZ7fvQexvwMw7cztnHxYUVfDZkTdDKR6x7kJWQgW1rBaVfqWITK88oJIDHyUkN08LuO8XNO7K8liHNbzYaTWBFDP3KcgOa5D2nVij527sp5nubxw0O0FTZVSNZ3g6qdLGUMS467uRuaUOdcv58tkllaBqMjDKx1/XEhNbJXXKdXXqqp+5ITtRjiFSTryHvfraKJ1+Ek1rxnCYtIkrO8kc3lL3wGZ2oxrhtkU+XdhUt0aQk1KtmrijoPpAgO1oodqluXK4XGJLVp3wyvVGSZaqW3x6/2lDNMBICZ5SUOa2jLYwowKeQ0zmGGa8QaySsnu7GXuru1ezykaYtgk6THBIHYnOddvPlqBpSYc2sXW9dNwkf4AqaOmkoXzh47kKhYeCuf9abwPHFFK4zH6pJaWgmg6W2YowzdJx+Jn91wApiXDA4o89Z2rVrjPBgWJ9z2lbUsStwaFxkH+MefU0aehz0YSLcwrFQUKZ/B+IAL1068rzs7i1Xz2wkqNDqnqbxIIJVoehD7EgvOWwRGdvlUvZHo5sIV4GhQfFo38kHhCbtf2seNJbZtJvx2CCP4urapnv0OHihqWtTIGuGijZesmhMR4H0eCbR286HZlMN3Z3eWT9ObkyHSDa2IpHpEa0v68TfzF0AyNshsKLTkgWNotb62BcTJyNObfmbAj5VblgF9M3QNkjZqNagDAVu/5PxK3ySr2zjlQmABuiMvmH14qX+YSyrTRgmY3EuRlWnWvLZ6tceSejUhHIKze+L7vJxNhkwXJyyDTt9iEMvvtq/xVXyRJ8isIRCLdx2SuQq2+NL2V2/381rYq+25iVJ5lgxpjPbByBeEXfYWZorNcOQDTInh8F9cmixDgLEvHPeBSWmnp7lWCfUzoaPqFwOwCNYVUgvdq9Fi6H1YLuI9u+Q+kYXTDJJSMPEIMIp3HO1JpHrrZOlHclMUSu/h+DAwpxNElBpVtIJIp6P+DQSTRs9p7vZ1lyuxZ6HGp1bblA3+i1UXbEaqbYIoNRjitFwHB26CoACaHjFpABMmrpOSuKiox+SngTCmjr1sTeZFO0begF/UoRk4bGwDXEhnDQxrZ/YRPQslXjaePwEKU5vXuZIAgz4mWXNvMZFaZ5zVE0Y9XKjPY/TsC++FFRjih3M7tOdDrUJ8Ejq5R5oBiTZRstYhLJ7U3N+Mk2ufIlThdQc7EuMsdcSJ5t6ED9IaPTuu96NDPLjLQesvTsOCUVY9Ykdv1T9GA2pi5jlC0/xAIDyzbcdbArmFHadhcRa4mrwL3T7cNuW6XtV0FelnsSjA6Kvn/OQtcT1gz6LBBMZj6rOEsPx8bZnU7cwjHUxgLK9uxUrF6llvWAkgvcR7Wm8P5mwrdNppv5Wx7wKgeq7ztl4knX2YcsCHqFN1E/IMhUfG9+cDfCfEdAnrG2DZ/eh8g5DFSSRQoah/orq1bfXV1zX6TMbQ1G6hTdb4TcYJUeOZC1QRXTwEz93e1uHxCgw0EaSJ7Vz317qhJM9YsDE5/BB5CHyeY+/QIXSDU+WpayZv2T/pW+pFIxkNuIsfGXePJr0+6evh2FK4NTFAZrbyKOuWGVqLGPAC7PMEVBrxjGd+K5sorkexEHRqA1eiJkIxO/fwNhj4LUYOQdr3/CqnZzuMsoA+M7hdPiCLtzDW6mUty2S5p/UZKQxzoyIXKdKG+c1f1xDRPmPbuPytOH9KdepOBLaEj9XdU0H6LA7Qx/X5ZEVPmapotqPRchJFEqBzzhSINLgr6AaiFduiF0aBkT+fz3G3FHG/c+IbLe/D+E85WYfgmsP+1qgPBevFlBzD4F2WVztgBP/S9EA+AgPEKGcMoKo+/DWBU6FTrcc9uvUkFp2XPec6schAEpROePdihAOV4A2qGI2uqLM3Lmq7V2lNT+BnJ2g+1PrJekXlJkeNsgohBw+oc08RfI/5YFrOeRrCvhfekDg5wnII/It3I/TV5s7nBTzYL36LobuQRqR4/NLEwshDn/5gGHXk6Jk/MoVxQsnsIxFHiG64uSgFoEbfnF466ke6Ph292DKMmbmLkzbtZ1DBilF8PMDayQfE50XIMAeatYN7S7Qlj2zs8EPHxuElUI8EBMlNQlx649HVNTM6YvnDUscbc81qeuVMEkTzrfrMySZSHfxcZaOOKYy0bq3oi7lhT1ObHR11Rb5ENBqXAwtQMww10p7A2yc9w9sXsHO0PwYlMsNhue+nmsperdRGWfb1NJqllpj6muTrJidpuAaJuJjUQYmdY8wbQkCS8ImC/sBxg/r8Q1tpkQz7Tj7daFLbpnPFpV9sQaGBQ1BeLRP1GiO7hwrqeuUTF5xvJUK02HUFw3jDrj828Ew9nEyT5t/7Xejr8yX0TnGDHQXU20orPSOhDPicBHhfv89vAMLNu+DeqIknooSCq0IHc9f99ZyBVxsAj9LiffRZadcUj4O0NYAo2gOfRHr8Q7K5NuFiM6rd3nhvlUduVp/FDOJx7kFsr27tvgFbg3Yit3F0pzCgqut0Pmr6fCC35Iu/qx3G17pJD/x3dVFmaeBj+9018OPhxNs8rQN/Jos5ilAsF5tj9Ke0ky73+8ri/aRDfhVZ4BwuWgj/AHKFJ78LigJNFZIPzFCWVZHsyp8Zvw9EXBWsKKvACSWOi6bgbz0QEQPdlwws9ixoOVOrrYzSGoXItZWDcUNnel9cvtcofiSyKAqYwjQnv8Mav3lbzUMuakAeIAaatHHTTccEBDuiRY/WPOy4RQJs3OgfT7iQJIAmsayu1NwMwSQOPd8cRpa+GdzWXBbpyHYbEO9MqOYFDdkxYemm3jX8g6kEIReVYzRo3k3UZXx80IY+zGOVrKEh0GQczB0aT8rPYt3DUodE5D8d3HqU9g4dngAIX5uV/dSWEyx7ZQRQT7z6eTGGm7piZi/CW1446zivwwzEUO8Y1H6QUNhVtOJhJyTOF3bQ7bJAA+ZghbOjLy9yzv76iG9IH4TACzj5AotEU4UADBJyOlvRqz+8ev6xP8QpluiFr7SUV4hFgrwaf7NC0GKaWXjddkuqPQlLkrFb5cNya2eraGHmxYEt+Qpqj74YnqGzAu6Y+lRvoKTgffXlqWdSfIAMDfyMBcqeaJQfk0Tb3RTL9Ba8/FCMph7TI2RuNLxzU7W0yc2FzEbQWoHmlH3wWFROTULDodNvLo9O3nT+YrdzFHu5xeW2rl2BmM6arlKlaz6T14kD8H9qPmi63Jj/ahnL7tVwKO6/Jq4kezu9i5lHegb4PsWQKyvwMyueWrrPM8bgCQbJjh0ean0aYWMnfWPqJs810ZUPmfb4RpKbJOXis3Y79fEW8o34Ju1NlesqTxtjSMSqmNx38l1sQK49unmEwtYrj7lTpvRyVtiBvUk0t1a+Tw7ZE3NCVGXzgqa+wYPpdq/37sBquaALw4aCBWWHMF1Fu2Ff38jPap70vuVX3N2T3xx+BLMDDqGTPXFqotWLCSXsaBGGS26sfOwNLR0fbOq42zVszlRn/lgIKHgSMY6qa6UZxzWV8kY2ULgq06tKDhqGbbZcW1Adf7KG3j6xh0pUshykkfTWwagqp+GaqmLSceJN2KK73kgvyEtu5WV+lylRdY19spDRwyKnsTovIcd4AYgqMct0FmezZ1kQUZcKCZ8EMuSb5pqIJT2zMbOXK6TnRkNTNSmtEcRkUPF6QrdJXXMSxrrmNZmlRremoFHjrgdnyZRtSM4wiRVM880OOEChyu9tlqCA0Ur4XiaV/KkAV8qMxfU/14dBQjhb/Bc81WBLP+No+K6Hkprpmu9k+zfuHU1ihElNFIUZzHvwCL0TZrkdnWAcT/rjYuPNpT+G7JdLufPbc7SHXZNllyfnnVkP7VjdoDvNw+2ucLU6LpF4Psj4LNkgfHtMm9+ubhVsxsAB5+e2Lz5cQ0Ztw5eDUDiWTKusDDGy9moIidXFTOZ5RVek8owGxPOCYoQgACBqmZOL+lnLq/tpP8bIGQ2E1EDcMMvzXS0WRryY6xTCZ/g85W70zuMh+1Noi36H6MCVgyF65gWYV2II6Folotww4uE5dMEt7A229Y5aUXeDX2YL9FCaF4XnUwaQDte5qaUcgTQMqeHfIOY80x2cfYFNLKMy/EjXNhvvtlGh8aMFjaxJtMJLqZKhemvyMkjd+KLh5+GzC0ITZhDD5FHbcWPZ7xZnNm645x/0+tQ5o1mDteYgICraC62WzxO7JrRZcGURy7KMhq+mdujM00v8OWZYo2g8KrbSR3zk0NtZLMRES5/eZSxNg4Nyu/qzqhXWlPKrpnl+J4t550/cda+JbofjYN13q3+GvJZfAYZY78O2KsGgCgMpz8Ki2LA2Fg07xkEOPw4dGaocIdBziOZQFTJJycfKGZYyIjGatf0hqclPrRAsOhzqx1Bfh9Id5/sb0JHoRabhPVwAscUZDz/Nii99M4rOyOgZ47QX+Vlf2m6UYkB/QMhrwaGdqksLIG49CpdLtnVEDu967ilykmtRSEpGjV2WgTXnvEQxFBb5sQd1Io9zkUyGTPU0vk73Zd0nV/P1Yf36STV+WGCqbLtMHLIdP3sMu/MtDa4fL6WEUc4CUvNGRj72KlD16v052Ztm1U0UlYNNiJl48LexaRMFnfind+qtNAAzBkuG15dpiSZgIu4AcEHS1aISO8KrrUe06OshvuixaNOAoG5UCGyg+66du0aGlXyzZh2mbwTDSgsfYicBV2H0nbwMe1kxyXXP8seoIED9KVMyrjSiUrs7o8jwmYRsQ2ku8jKj1ckuDisreioOLnp8pEu/uIMiz92uIJiBvIgu/OLBj4pFv7T39WpO55LNyGMVbfcqA8ngRiU5vhfFD3Y8O865Z0qnNG5zPuVnYe/nZmbJlB8q+45Wt2DhgStOTwfZzhlu5Sxy5gG4s3VtnNYJ2ej4seO3MumGb8P03DWoYZnjcZzGEdb0ZasY3nPTr5W3LxahZDMuc8LQOCKNm4zbTpLdP1gdVR6Q7UssQXMFJQ+LZk5wcckvv96GOTA3BWzXneKeW6dvVnG+xAK8KVWCbtijDtQpS1ul+Fpm5RwK5zyevwtnJO0QvlkZ1zMHSCjnTuBiwbgGoQcgiGh6314l6qicTT5LXQBDeaugQpG/ZHg3sV2fZqmf6GUwx5EM8Vvxivb4MeTNtWe+/8wf33fUMhE35qCkNH33/uFbTUKz5M4hN2hUuGKXRbU6AKu+idcGqjHPEaZt2a59XLe607wk1h/rM4EVoW0lU+beoId/csmBdfSdu7Es+ysYwI1qK/oW5pb5BDvPmMAcaDa6wK379ViErK5nQiSZh5u3z4CJSPUad6Ws9aIrlGkcohJFlZ79Bg8ehHWeHgkaIuws4r2yvOgUOXHAWFtI8hDWpx7yNw7AuNM51KAg3w1Da+Z7+G7NZSVhw0elc/dZNX/bUsCj3w1u5e8RBAA6H3nzi84ZunB5sGvg9/0HDF9+0fn75iOJZPGqeJAsvWO5AJ6m7sYBT0IT2IB4g0iccnXWXeKuOA2GCkLIjxeCZEUDOakq4jmv4O3vYdr6smGVXIZHCuNn6PtUvHm+4E2fzOfoSjbaPzrsLbaoa9waNVgWbbhu9jtnRo/5/BC3/qwBi2tf90ftDQ5FhbkLYAAHVgHM6AVjsq/BcJL3BtKBBQ31c7DLSVWjd2nbja0euzOWlIRX++SAqd0Mr0Yr6tTgR6DYtTVy0I9dhEbi9dG3WF2G5+YjeyI5L6V7GQjn0Xu+YvM+rO4YHwgH6RI+4oLnU1694x/dIxDpwyNzCrmSV7jCW0hQaOJv3vNU+lfkwl8vJEFov7mxUa3IB3Nv38W5+2hfUXjKnaWvv9vt/2fsO7YkN5Itv2b20GIJrWVA7wIyoANafP3Ao0hO97wzPc1DFrMyMyDczc3uNdnIQe/MWjghDcMiSOVtvASaCAAHZvTRKY7NZ2oGXpLkDSvENOtEqs+cCZ80AtPJ9QEL5TNJfKti0MmiV1uytaNwtw0QR7txeJifrpqzc/V6kNlkGj+nqCGf9S7HvBtWddQQRZ3FZOYaqSNX76bwGVl6E/xZ2hEhomPfxg4R5pZNMcl4fSOpMAMENoFLbO5psk4Fq7BDtkHFMqaU41HRwOejUfJ4hD0uq02/iuQhF2z16EbF1+L3c4B1+iVTn3cXDm8ahTBGU2dN3D/T9kGMcF5j7TFGraKch8HCH/I0GVRv2RDByJufgMsLRYpl/WRfnnnspYQvWMuPnTgwTXT6JmVg7kHunQDjHTTL6WI6fcBIdzXPBi3P8qPFndwgZXjQ6ctPNIrpSyteKRGSJ5zCxPnRpg1eoqfw5jkEftNIKokgJBkmNfGS7i92Lfy8lYbwWN3hFgj1AbCrRqL0jX8+640Nv2lA3vN/hoWxsaA2ovhSVwVHU2moPAIxuxq+sdJkYnN+TzgaoC5ivLa6F7S8wqiILqAVYeeqdB/yZc1dKlq8cR59Jj8oYjHaTfPV+KUIENv1b9CrVwxq8LrgpMbhLxf3GwFa86vB4skTu3+ZBaz8mm0Z62ORN13RoXJ7OqxQP/uI67dkCZRC56nImP3fb8vYYH3hHave+KXcMTVfZhGgxAvp8mBfs8jIcX2hv7m7BvsuzytT5EDnFHiLJDlIrFNR5epVrVJLpOszqQm3bX9hlQXvvx+jlPZOYUckHlMcyEDpPjjYjMR3Qd6fXgrxdOZ41AHW5K0c2a8m7mC2kOUsWfwQcYP5z6MVKYXHqzeb7pvjizj5VA8H35FGIhF70D7SLDf7Y6PlHq7ffL5TDE8tYSM7Yxas+8wEN6MR83SypmYnNRPLKIji5MlisAGMAaVreG849sIZe3hP8Otdz3CyiW7OV35J7ouibUvtRLhM0UGAYwoYN1Ar9Oo2JYJOM19r7MT693K6u03UmoXStPoOZIR4FEEyRqQzFS36gV7krdGPRXc/dSNjdXFvh/2WDVgzgr1Elg+fmVAdqIRdh9mnCcpayVz6mga0wyCbYxOZzr/O517VyeTXgtIMtgG2zCXwiMC69OBCHCl+AH8PiLTO0fI3IDu/4ff0rt0B0M/+m3hsfthjWQLHmPgc2cpUnxdJJ8XC34TEEGAUjpi/5nZJTVr9XkbyLlF6U5mFAInMw88rphtQQ+d6N+hEKfnpXn6oBGAEJrCvVrrnTca2HwNNAtxSRtQn/RFrBD5t1h+SqOh2tEukgUYGaz7Vb2bZYwsK5csUipKaFyFX0Q8eyoCsMeKbzLQRyIf3j90b/7Z7D/f4Y/ec/2H33MrlPksOppEz/+aV+uW7g7zQ5gVcedVlCfLZVSIcl8broB8TAIlhvqFFGhD2MJPDRy4lxPo9Ejox+Xsrx+V7w7TqqS5ejSBkUD1gpvZaejmt4ZfvIRkF9nGb25d5sHQv0WVxG5skTH93dwLCW5+HWEcjTMAGRuuMrTeULQVCNkBs5VYpOBhF7MJ9AJt34KyHkfNphdFlD/I7WLy08cH9Js9jjeKSTFgkcNpNxkmgnNBMluOaNtCwqUjssh6DlzCvybfWrXCoDnn5zp/VfhtMn3GruBjuhlpZh6VDoSK/ioaKQ743UIroploV8y2r5XbwDNFZL/uqSA2DnxEzGXexhQ7Jjs3aBEevnnCgICmJmtqVx1Cv66CGkcVCa2WkTJyj4aZxxxWSTbGAhd8E2Ji4L+76hQydB7ppDzX5pv7r9YbZ+jh5kdrRbLo06qwv2z3lHOvdzcLVbEF4QV96LY0/BnMzlkM5YBCXnBiCo3y1+X7OIR49eyRpOq9D6TxGnWwsf+QJkAGDw3lv4Sq63uvOv1VZ33gtTMU312Cr4PtE0Oxb9Oi+eQh5yCV12+3mOymmCe9fULk91qlViO0WblWAxVZ87QZ+63QgFAj56NDH+grtGBl4xsqmcY8dr/WZ05gYpfcCocxvsMglLIPZDjbRNeDkAgA9EvBn3cDimiRh+D2Lcasrv0kireLtV7qshPLP9z4TtruACEapgpbtj3xdWqdrqXC4h1K71Wf9kFoL4d3P0cpBPDd3AdM38imMCDGP/t02NRUmBirCKuJfWACtlgaBviJR5AWrlolu69WnlK25614Ra6i9+rC9KjW7z1vimJMRYJAKUD9ww2c3f/IG+WV+8FqUeJGJQd7l+B6RHa8MLcYeNRb1TL/W8mWIfEyWMOv8Rnb5jGaW/lvkXaCitl0ufXdkmwVjMYn6HB9UnxNXmE45buIGctVGDCG1ECP9DUNIEPjBUvTslr5cJ9OUQTvbpWgyoM5y2c4vWmEP21dFeMuzKOOQ6IaS4AWHtvahHgUyI4NBRmhnbeoHXS+4yQMjjaR9yLCxO83Uu6HYB63kHjNhvkbC3MlmyZxuxNps5kjK41prmFCq8bmvDObLF+flHmHhw+xR0MgRudCBLchalay3obI9sQScnTuY4JXDd91WpWAFhwhq1cX8W+0ry30kGO2kbtXd0R712POzQBORXMrPVcEqn518JJBvvTTMeiFPK/2jkckzi7ybQabF70e5UPHVCHnq0JMIrzlQS6CohEWNMX3132YXqA0pG+M8pe1dhwd9fmOF+GN/ERfCnPLmhkdLpfQMZjWIB+c0APGSARC8VZ+nHbhtQnvSSgeW9LqHe20exEyJ3qYIp2amG/gWs6V5i821kWY7XIcneXg8hQNyymmEJ2i7uMnEHYaPHLrH/7JVxWwfph820YZiyHo4/YutMcbbek51BbcNkwMxedSYB07xwQNzQDmMxoKecYA6Pbr9METmb9bkCowCOoCE4ikokquY8mE8TOj4Vxb0cKY/LAhiXpwOPFRLnddZj+MDIVSsE4z3x5+Ft3HWMO9w34uJBhofyeBnzfi4Yh7CKBxbgpihFccfYebG602Eypqx9YAdnzWqxffhqHxe1zVq7AQtmOP2Ro+ImWff7V+LO1LV3F+x0eqFY6qHJ8ANzCusohGfd5vo/BvYmIewpdbosGtAxbBMGIR1ylQozhSw9Rif2+2DeimF/oWJH9wOZrOJgIO594mCvbPaz5/xuSy/GhTmMaq7jPxLPfqBVlKuCGF1Oq9NzNaSNFJRlZij5+o6A5MTkS+K0gh5kfpGjt3nUWsQR+2fVnAsoa0wgRf599T1dYwuTAUG97BZ1PPmyqyPWGnrCNMYZP1S6CJmO75p9RbnTfnopCj0H4zP5Hi9kOaQOffdbUxdNXXBKOBdCUNe3bCeXIYxDzb21yplNTPRe6qg6RUhTw4CClMrX+E9YnIXNRJC/7qv/AkL8mKbtOpXT0V34fiao18wo4nCh3WwiBx55+4yHnvn/NhfbwRZM4ebfHP75YG/qjPl1pfuPahJle/vL3QoKx/zfdJ5rOnnSEhAO7CnNAoMxoOsofdG9HngQA0eghr8+YRFxzm1lhUCPj0wdTG4aXDG6YQYQZFdHIFe7mRORyCtbYgprEu+KGG+pJ4ntzgOmBCuiI+2OPWhx+yH5hn6gyWbz2luT5X1uXxpt5qyINau3QXgp0uN+EgDqNCzsa2ndWFCWsLIFetazO1rFc7DOzwmgoUkrpdJRZHMXoIOgG0+Wyojqy4trOxrE7XobStMe9S6sxcqbMw5zxnq3TlspK/fCml+xRrykZiRkq1GIAqA+iv+0zPlPfpqSH38MaUMgQPyJoDp9q2Xb4InjpL+q8fRz+fDMYssnKTENScoRA5fOHt9DpPk6tYHNGuaitdxPxQhzBfidJJ9ULVYGZWaM2KHYUcbe7D82oQxWYtQyXCLeuQbO8o4THU4K5r3QGYAm7Y2yVZrPPo0bLFT8QH+Ukgm45gHX5lGpHj+8uhKamwOiUk7TZj8I8hS9aEznvNRGRX3W3bvleE1E4WWaBysgiKLD0njt/8YB6tHzYA+xdz+mqzATaKop7LLwUXN+KDRAGMb/qAGthtQ/S+yqlX+l8EkGEPwRd/yNIXS5phcZ6cqxraEl+wcDcNzfSvr1Q5pIgQC09JtmrPvVwKqJBf2HSF9xNFlGR7L2bxFNl00T8I014jYUJti4TO3l09l/HBlAwNhh19ZUya500amVSOQmLJzsRMFDP6Y3K/wwXsuh5D8qA2X6tmjwaBBlFUmfY/pSRXO3bLO+F14JPllOhhz7R9hqQDcwtkD7z+otqt9iksE7r17XZY+L1CixVJwVWWd+MbbHd9cZqko6+taY85SVeJB7BD7jUHUYpT6Lyc5Cud3uS8/QDKXTu+aomvC7E7jAy732paJWg14/ExTLNZleCh6hrvDGZthdLiMGPOsyn2EVFH0owqVgPNljW+m/gR3+I0rZGJ+YscgYzY2ZJqzkKoqyYK6IXTKjmX30Cr9Xu0iVpLq3WR8KznfqvnsjqfkjbOftCRGoOzlsybh0g21hzEM6fT71FG91jCXH78h4nO61mJjcgbQFF1dBl9BZv6RcwZqPDBZloV0J2XSj/K56wWKTVk/6pdFK52sbbJwACWJPLr3Q7VVb/iCyHk9j13iGWixfGoPRmBYNjwe5cHD3B0fil6HxClgPeI6dhUI6dS539NZOhdufIQhokqqHWHJYB8n6JHy5a/RL0jEsNHG+possaoyFNsXj5nq3DoqFiWaeQelYq8HXEnt5EJqgpXXUcjbKb/4fHFcPCaULx6ApQgyrJ67B/fF0i7WBabUfa21L92vTLHBEEcZn6MgPJuMmkeB/TrdtAYfsbsq8Ac78i6wTKoqFhWHOb5DXzpdDDWqKFXfm1HGPyCSjg00r1VvI3gaxRNWEBpLEDy/+Tq9xFM4YQEYxP1Mm2iJyfXyPZ0cMb4wwsheIk0RMZWrMs0PN/G1Bf7bFXDhKrTujQsu8yjZSaKy9dG2PMIJCF8sbormF0p9Zjn1kJ7tXnXBh6pi6y1P9SCUJTnqLg2fwZW3QjnQ1mvoE2prnvAhkKkvogMc1n4VbsAIdGsGkiBrDBkJNCER/p7CDGOr8rBbk2HRddfwsxwuBWTTOMYVVOpSGrImMngetyNYIRZ2nZXa974j/HApZEuCqzZQkHSEYmIUSR7JrBVefEE13YTQMpsYriFSsXO3v5LstJmZyJzHtJIuxuTH5aovzSlOf0OFFkBjyLSHecoDx514wmCCcgpvUVcdT1oYaeGKF1pxVCXbNn0nPAmDN0xFAbAp7c4c/+ag+pf8lCq2xk4utqlEb9dj+WgAbxJ545xxs9N8N7peM65wxCJYDCflDFUZz7EN/eYhdJ0xqiawFRBU2pI2h8JB2CNj1Iyi1ADDChamB5AuiiZj8Lm+27lIfoU15ExymT9VPbrOB+kqpmuZI4CshCAvO6ZDetRq3ue7hq3mVkUujteYgKt+kSTGD8642iozAd6UNYA4JpseNVdhp+Kd11hn6ci6rUOBJJXNVafaHyHfxXBFabGjH4VCYNWKe6zeR17aUvSJh5eClI2QMpyFDxil44JKA/lsUToofK2y2VcAJoei+rTkOHwyvtbqOQWRzO4b5OeNjVy1ygvXHV+oqm8XKnvDKXHEOR+zRyYRqfH3+R3LPYLZ7FGfmZsVmXr2pSRKCokiVLw85li8mpLzpYqDbogIj27zhkVhRcWSJC+A2tijlZBhP36svdHXObjDutMtfvFWqKtr6jIUzQPlJcln6nQ0gMf4jF3d2bB2JZj0iWa6K7jl+7QPGZIZc/B/oxVFfZU0azJ2Gk/K/TcHMHUnCYJEbdUdizFuZAorFLRlZxOHe8gHK6gKskJVZgI2iAGYC4k2P6+Y0zM8n1S2+mhGFP9gj0LkVKGQZcV0zCDL6MwtoYSQS/N9KP3BLoFoZDKn9OTDUZ8r4Y+5O87NvfV3INN/RX2S/4Wy6XspCOx/IVwdsJZ7QJpUjYArmC//I/igyWAE/uAVDgB7hq8V+zv82ERkvlxIYeYFy4gfw+iOl9jdzxe6cDAMdxosoz4WFjii2PwbiB8oFGDD6s09ff3T3xlLo7/YCX9i1vBZMwnuckmoCgle0sEgCh6q/+pzDNqJEn/6NVO18lf/5lxaHlvxMX2O5VNU7RTe30wOO5Q/nXX/7qsMet/+c82sd3v7pf7TSzlHc1Qfslvv6Su5qNPyWly/mUu/lUuPns/X8F2EOBRH1fp8vvn72v9y/f+7b/OvG28cqnseObRSK//8/t///fN+zzt4PkQr/1X/a3pNn3cH3Yj/7Xr/dKM2Ib8P/u3d/kefaPTzyTjq1Btmz2AXzyR//3VpRoMrRoJXEsaNAnon/9vz/p/+2M9+/ZfP+p+e0/0mffwfr/OsY5/Uf57zX9f7z5o/ciLAptW63bPmSBK6UtbTq/JnL444Uh/89Xu3RzZ+/anhrDe7//s6f671Z82svtsy1P2kz++9/N9Ewmdn9L77Jvx4uYICGb5zm81zWz4+Dd9HHM+BTD8+LD++zds5jabCXO/Yn6uAVSTeIX7nkviseqC6/H+687M7aPCAKBz61zu7/37n+7+/87OOTR7CXTq4/+PONkeDzs+m5wGZVb95H7TuoO6p9/9c4//i6Szv//90f0k64f6np/vnrv/Nblj8f3/X13/Yjd9d+S+f9cEnl+grkOg95f/pNf6AVBpKUXNMUaZyIKMyGuY0X8zohWLz7PLvZ9pf97Ja80pC8fme6qcIvdj/fpaon1w3X/uRW7ACn5++aLDz2RfoHSa91dLXOwxAh/hfP/R/+fzffbz/Xdf8ref+6gAOutPbv57f/9Lf/tnbCKAJGviCErtTXUH0C3NeS6fPZDH84dCjAI6CEkGVyPCEB6s/SE2+Pmm79Vv/bR1RvrSNk8Za6Hq1F1E87ljaAnHZigXBjEA8jxobSb2li0Q0fBFmHCH7LEyqOY2HerYHghsUDZPeHn37beq6jfy+CvLn3R/RljqNvIzWCdATUH+Do3upFc1xQCgqc82dpVA/4nhunQFdZLmcoJiVEgQS6TzvlVnPmU3kNHF6b3hCyeO1YdEMUyBUSAQ/p50RoXNK1MCPFzjAbYdsCvhxltr957NumylbvrfVWRWFMWTnLkaZ+UB3uc6T7A6QLjnLH3ICIzHEvMF8g97PEcTw1tQG7mv8eGvOdJIreAVJkhACM/K98uaXwZQpC5qZsTxjwy9mN3MEIuc+x7XzRoZupNfvhcTocGr37dH33oy+nSdLXYPCXdZk0OrPKqSZSMdDwyqOl5U0DMp8Rv3bvBezBtlnJl8GhvBlbb8CtfAXeuRETRg86qe/skABJYSxvHbPrMgWjXiwcu83Uz1U/4Z3pQZ8kk7IG/5UlYmAv4UsWqdE2TR1T0gFaU73NCxJ/Wugjt4xPJDlOi4Ig7Vy+duHt2KPQyZj9DciydaoyjywwIUwur2Tqzk8BDKFuw5B7HY7IpzD7HtbXjNYLSx7L9W+gGBkQoAPdXaF3F2t37FELqLrcCA1yqQF+DQX28XREtwxMyxTrIhP0r5l8TxFqMx47DmGMf7z/YFF39e1EYgbragA8kksTtO5OgtaIpkySz26QaGXC5q5Dv7rVB/EnLf42ybJjsJ2R2zul16rpr1tvz2FJIvBtw+WknxrlfaaT8dB+9ChUsobRdGXNiLz80yGaSJJx/HRDTbJB/Vc0Pw6SR9ZkpEA4z7EX4N+ZJCmb59I6ng4ImFE8m8VM81OIG8BgTrzU8GQKFzIXY9TwKkVa7+CuX4eLfVucxoA18M0EzMfLHqMcFTZHQSm8h7Rgj5fwzx5kuRDE8otG/uoLqRtxPxKLqVEelY8jlPPPbn7YI8TK8gdk9t3n7rFTyX47RrLJv+j+fgJfYG3RgaRjq9nDH/eER+Tw7MZjzgzSDBcekQqv33fGCjufJacbAPELCQ8VqCL0HLKAE858gunF/17LE6BJSmCnHPaMMKNkcmgrPFleZDyJ1UCeBgcNbj/7HAyMqTSN/2RPW8AYcwGGML85veb+blY5eQ9EeU0IW2IfbTl2eHm404xM2MP7bBR+lZF2whBnjD6fjVv3kNO27Ln7SS+8V7/OQvF29BfgU4fZZfKcc4ado9yojkQBwg4Zc33RlfK+fvkFBfQmCygMADFiwJWNTuayoQP64tBOJ6PGS9Kg3ES7cOPm+NK/OepuonBiQ+ea81sOS9qvBlZwqihfV3dKDtaKiQIyCGcSKIKBm+BPhomZ8wW/qQvPNzdshu386BiZjqnYZ3mCMYaLS/g1HHXVBQDTVG3rKr9k9Dkw0z9tTdt2EnpnZtDLOQW7FsDnU1u2AGkJkZT5rUNtoq9CZT8uiL/0IqZu5AtYa0h5nCj8LSQ0nRjWRoGRXcmjaAeW38RY4Uh2fM0veW69Ips4kdPsU2wVQnPKyA+e1TvBKNBglC0fRkPJWEeDR3rfOyQQ7cpWbxfj9mcO1uc0wSb5jntUCB+hZd8+68hCvjNwHvz3jiDk2EKjsgcg+w7ukHex7Qv8TjXlkqWhonzgIYHglcda6EoQ7OlDDiKpommvBBUPqNd0lbTMyLIL3f/eHBzgegjkYGiJbGnBxLN9Q/rZR68LFrNEI1suk4CNrfZKqf7+lj4LS5YmQo6eFEV77yT8tMcNLrH5a8wPv866RTMO8hsykIM0zdII4oF/HVvS8LaceIYFjmrSXDyljNlHwNHJx7BQqAPFDqsyVGtAUj+Z77rH6KmGwTILFxdml58fK1DyMr4klLwyvloW/kzApBOIV2UJPGlQ/t7XFXzYzYMicpMzJvztIEE1QEDdql7uXmhns5BiluDOz7NCp4H0IHzcby9PEiap1ItzaFXa+EmfBwYEjazzx52QdUCO2qofTQBs7iboiicvZWd72AODu8n4epcPtWkMEeMqcdB9Om/AiJ3ApHX97ERNHKvK4gEiNIZigfwkgwkcJmKfITfrbafNMPv7VipaNmTXThM21erQFJosHilw0zaBI5FnoWP3vKT+TEzHeFYMq982wPlee4zfV0ZHksxivwWxRuGRsprE6UHFUVc8T32KF4Zzmadi6ykL2V3oyINwzh9yYbTAEKwGjJbMNDeJl1EjoV4OR29EkQ6nFDXuDvua2gYbh9p9AWpXTqNhyKLigLYLii6mAB2WLcwdxQ58gdUBtBzHANzpI1UrI9YAFLQoBV6nMGCAh/2ixaOjx2t7TV+kZsNGJTrCPIjCROhbJ8V95AtznvRGSBJjBnhAaL3s/qfMYKInfbIlr+q0xrT/JITwnuAz7czOB3Hah3WTGaYzMFv7Z0otplsyRvgKAmExw5tV3qB3+IqDeLKfNOdmji5fX+wP9Y8ajhO+6XI72OK09yrT4oUXQrO4OVfASvyZae1FhW+0yqRWNuyHKkpR5ziUTKpOkADqGnxsmxlJLKVSN47ZJ/D1G7rPY8Z2tuLaJNtz9IJnxObyUX5Wz9XY3Z0GLWY+jD2CKuHo9Mxawa8YmBqPi1qoEgEq9kkKDYtoDutaEn5WIswwXyLMuyjpO2UDQMp+7xC7rJ57sASqTiTV57jRoI2Dc1/meeMaeskmuXmOYlXRLIFaxm+p6pUrP/Yw5WthgjkBWmZy2joYA6o6Nbmwm4MXOpfUH6x8Bgpk7cWn1vUQJn10BMMi3B13vce+0SbjFKl0v/a/NW2BMqBbsQmFAoV9uh6+ROI2VaxLyQaAEL2vnr6opw3NfnR92YiROLlV4MXAtoAIFeS57CRg/2zUZY3idTnkyIRJLRTmHnqObHagYTCtylGlQne6vTKK+2wwFlzlG5ZjyFSlWftgnvFHvaScoiYvDNGHuB2s5dgVT5UxTqojT2nacLyVdnG6PWWej8ryRXvxt1j4WerO9nuxXZ3UbkhrwFFs/SvvfOew9+UibzhOHSkMc+/xpGRmYVWH5IgVJ9jioEFtfRygGs61iJoo1dUVicPMcSAN7yO0YyV5s560m8D6wA6vwnWHBroQ/LzwSgcAlpdstwnw39d4P2NiVrgUA7fbwiV/1hv5hcxb7XuohU7/RqdzMDyZnGZQUT5ztJZwZYswfiC12bI4j3wEmJiJ+LbULSON2MpzKu793P1P0jvHy8Ft1a6+4if6dZMtSa23/QrgLKIrpkD3fGBs52d4+TbCdI6/tKdQAIDX/rOb/oQkvt6sdrmJ9wA+HfFr8o52LfivhTp+7IEGU1LoEUiHxkewQAzZdUS/zCSlD1SIUescIlwYmxsPBmaNDLw6wWqb0TSbyDBotKe58yMfOicrToiFo5q3Luk9cjsIC/0nBc/XZnqmjqax6M8PrrSfNqQZV6n9yxZnGHvh1dBQWWWjEfeODJB56TxOa4zorfYeUAx0pFd6hvaR8CRHiaSdW/3TKQoglYR8zzi4ehNSR9qInzO5m7yfMM21+eD9h0xQKmWUN/1Rog8FGJYpvGQ+hcorVgQTQ3aIzIeinUCK9PzLrw6+LN3wK5XJojcT74HoSVYUhuxZvwmYPGGnUWzVLy99Q9SX+JF1sFjDOY46rr3iudZlTQTOYUwYV1/5CK7vmG2pLl+XyEc7muu4NL0Gvw3YpkfukJmw3cL6KO+sbSMqC/FvnrVghZKGl5/DHBc1e7XJBnbLuqmgAgTpoVpqqZIPM5b3wE6brB0OJg/HI1wmF9YM+aWL73YacBGw+0EahbesXKgtkWv7+fTiRx1OTXAGTkz+kvVldCDLkykkt49a4WJFYYAUHt2853pi46KhBdoLQCIF10eKAaNl6R94EBF4XgaSTQbHkMEECJvAb4jQyjeDsPnuzoKvUmIk1L294jML4A6J5ET92p6evVG2kDVYkOSn+VcaxjrRRIF+TsOaV+cXqG5zRt4yTZMEaXRDlF5Ergvkn6ztHz4Ek3sDsNE+xoIg8f/QeqPFbe5PDanpZPHowvfRXxa60kv33P3nLMIw1qz4Bzd0doo8+yuN83Mko+/WMrby7MuHxeIO5uEeY4n0JKLny/greXPL011nYwvBTs0RaMDizvBIETD0q1rbYdzaPPUWRCXCf3O0LMTMUrekjSkLApFzCJKqmoaEw+b6FgH197Uk6BDcctGR1/Dqh77axS8mBs7XfpRkw8U9beX6nJ7Oyj9QAzO0sfyKT0Ehl6OsKJ/vewtX4g0J0URM2vS8pba6ChIi/DO7PgmK5BttHe3OwyNC5pWI5gt75sNcVaxSKR+aB4tDEBgHkAH14kiG3OJCoH6gK4d6BPsC11SZ23Ey5n3SlNEhCl8zrfe+cHg/m2odNa/sOTkwiPKlpLSm3GLeTZoXqPifBVi3ySf8EX806qu7joeNExNh9af9ier1psBAWe/5G3IEJojjjUVjh5Y0jwnW7vVtdySUM+/CrOb6YN0UURWyjt4hMbSoqXM0M3Jj7qPEpzjW0HlLdUYZvLT+TS6gTFFLKvDiWpISwcFfIRADL0EXaL4mKyNPMH2xOXlN/2Yrun6eTiYRoK5cp2Y4iyXuq3CtRq9xyhEERolka35SjEuIfPGhPe3aNlUZSrfrDz6xlb684XcWmJyxiQzBVjvTf/ea3Y6X08HeprlG0iZSe5Vi5/hS31cEQQTm/F5uKDYYTItNDi5NTZ3lN1s4BJSNnU79xzI9C0wNNd5mzXb5PV6KLFfvL+cArdXeqD6pDKR5dF+/D0f6vWANTz5hN/vxHwebV5p5FryFvaGvBYEhlw0k/Cwq78bJpQPW5QrupBfdf7BAnvN3uSutSBI9pq2A2Df6PVK3p6Q7K8i6VIpcOhJJN7lWI/BbS2hAexvG38JI+5MV4vdttiFq/JpUQONdOqWH6CbcUdgftE6LAl8UO53IthnJ6keJ57v90jVQawevCoKs/RSN4D8WGqB5ZuPeA8lYrtU/zAu9UJWowDxwWiniTH8Blm6fNh62BlCt3vDPIsMs/4kG4m5kV/kjv6KujZgO1Qmhd+ODNfWs2dM9IZdeSxVkSEQSnD0xelCUPEwv9VgeDnDr6/B4smiUBZUrpGj5X02mqCqNEIoKs5pWSBANfXw60EQLlnj8+KoShpTesBXd3x+2RHPv60ut91Nc9YXYxHbvjKRcLMZHF1xlH6+FvN0QFQTGRncqQ/l9tnQl8WLZTnjnUKVR2pQ0Hg8o5V5C+f9o3inqL+Lhez6Skvi5a3Fb45eCzqsI0t2HLaSieTsK/AOzedRXMB3SJTNWxzGgb1fwMMz/Yo/qVMQfc4YPXaerG/I4FkmeOzOBD9yzdUr81q/jVpsveitJnihaxuwnjKErbvYb3ILl4/u0h6Gs+LAnrNBJzqJUb681uCkO+Yxf8FlTMXxbu6/vZK4X9nzW1tf+vwGo0LFUz+gw5V2ghMwKWYsn8lLO8wnau6o0AxzTw9uLXVJCSBvJ3B9VE9uW0TCRP3WKj27GeMUMEFgmjYW1E9XY+L1ZoDnRrNE59KDB7pw7/E6gxyWfVZ+FVAmb3TsLtKBemPmf2PTxvEE9jGJTl/LO6GbyFAC8StVCSyKKAIqcCto7iY4VrKZFZc4yO3S8I2clGrWth/ofVk5CnyQ2XRwMfFKQQBVgOqru0oCOwz/G1B6+SlcZaqiyvoKm9Mo63TOgZe1dDvJssOEYmvarXW+3kqH4DjSyT3bBmVxEx6ajcPMdZtuHVG6XaMDPJjinAGPzvnFLLLyFiysoqQcQTmoWIsCxmY8SkWMPtfQHY4rcYtRypALv/wmcfa6nL345BzEK0AgjocHvWM8pRsE2zQ3gi+W4DdaZExFyQrElUwkMsXxeClYRiT7nwdB9iV/whjnELgq8yoWx8rxl78qNXpBsJGPUK/p/HVMjLGZ6LZKlDhnMzRZxWiSYx1bv6AkyE9o9of21T8oS5ETVeu3fA/7sXvbjnshlgR78aEHiw9Jq2UUliRd+ATTUgtFlU0Pl33YwJHiqgbTVCg/viq694NVG9Yce1vdSMuhf7bGh1z3DxYybahti4bipon6825tI7IS/EmpvOFI2LkpfegssPsQNOAGeldScv+xV3ylAxG0563CG6k2yUN1grKje/n7Nd5NugxHm6blGfcW0/Dj+4i7M32QKlcooRWaMrSk1dzA30vTPwF3VUjLLCCGqGClgOLKCsxw5w4E8B7WJ4sZzw14BvOvuNggKsZEx2DCPoKmZi5WNqzkKe52xlxkFOhaovw1qq0i4vVoCuCVCRjiM4QPffaVtHmTk/UBJaGszjGNl46Ga29fchZp0QbfbT3/XpgHt9xxRcn5KmMpaWPZITt3RZ3YG53su2Jh5r3kpODzyCuiUTyC6OjAtJAwFsZ3KFJF8HXgO1GsFD7NsrN+HcFqp+6dTQ+oLP2SmLzJ/mk9iylSEBNQgbhgPmMGB2n8Ajp91U1s4UhMtC6znXGS5b2DQEYe5MBeeZK0jmBrOWNHN3huHqScljo9QJYKK/Cie1f6FQyQEgzceD5YE2okM7nkGw+m3kzbmgmGcd5FEsHytjHNCGGIOAOJYCK/fTnydPOrPWoCFDphaXh6DHR1IwHek9C5XRNIYXYz2S9c1UPk5mC/Vse//P4zirC6cSSVSL5a2pfnXQJ7xN60PeSLYZrwDaGVYEknIjXctVuJSL789l3T1SeVTGfFpmxu1rEqBwnRpg0v1ogmT2clvAZye8woMMkHMPxzpjIDVWR3Qe996Bhry/y+nwuinL7b9hyQpuZ17+2plfLFyU4LpT11sz2rIDIBRTBf7j3ABsvPaHimjB6Ba4LI1Ps6GrAHbydC87TSJz9x42sgJqzCk9yGfeB7bvy6Yl5VZWQlE1KgoQzb13js2JNvGUdlQ4kNGIiQmdkMHzRqR5FkPnZieHDV+m5exxxTqEDDugDSWYJANzaZmAqai8V7ugqhIhSKSOlw9Qsc+ulz1SpiCH7TseBic8QF9I4CX9x+25+7lo2VcevKdrH2A994aByP6hsyL5GtzM5g2vYzhN2MmZ52Cz1yaFa3/Ajg/WW5NFTin6T4ZMqV9osLf/DrlfCuG+KUDd34Rup4030Ii+GalC5HUkyaiPnFEZP2c94FzKPlxpZIoX2b+nkz2oBe7jy9Wigr1p2HipDh7INUkgiF7D56q2i9HrbSfvuGfUh6f3hmge0cQtNEKU/tNFqouMEF8tEzhi7x66FF8r6MbagPAy8IfkkV527FoJvk0kwYRzVCdEkf/3yJeug1K80RYyPJsMwEBEoChzyu3BkrxUX5ZUIfFqPyg0oeCdE48AbSHMecF0bePeQZZQ0a0WNbSIF8o1+LZGBIOiDcVvl90+VdhFTnTJA0EEzT7NsGOLKb0Rp+LyFO8sP96yYOfKrA4RvP6LADAw9SAkEwLAU/eVtWiDYZWnWsPdYZ4V0yZAflc/8hiwiCHtd7S4EP9GswKTvEBEzIkLSVzh/nBuvcv8kn35+I2b+2cjMBwlslXlhBQg605x3hlgz7Bkro6pLsbd6OtvLh+r+4biSBZznZQwDuMZXYXXvMSbpGPz/gyCbjMJgpd+Tqewf3+I3AXVfdsCl8qO07TBr7l6StNEAlAGTg0RJw/S+AL3pDyOx12sAbZtLCaNPtI3roQjK5hn3CppjO7WuZr8zmH1D6glOlQzWMGR5FWXiW9V4+ffArwliDcA3wVOIPHqoycCfw2rwN7ownFghbZ6ZnrwWIa7/c3zjZt1rzmHGji4e8mbdcdx39bBh//8oPFltnIQz6XjfYBgqNfrssM69QjdkeJbQfXBWxioPbOBTmo6gK2G7gC9RC/+xrKdLkS4lUpt3WlHEBc/so6iSntAijU7Gj543bjVlaTHWg8GbPX/kWmqr0tpwsNHBxwvP6z4yVR6jEJViya4+A/keBE8P88wCGfNvbb6TuCvM5y1PUVNZl3pRafJFntFcHSQzWBeM6dJY40A6efZuOk+WgjztrVgzGnp8ZkX9TkLlUaaOdb67Pr8UM8F4Q9Tbb30rrcah0XXJLUI5+NO9hAftrDUBIyrxylD4ast5b5sK+gLecrolz2xeLcQ8OPDOkOhLeHBzaFyQMBNBGhbYO6PvHdRax0Mt6mEodiagDxjD9xriOF+KS2GF854LjYYrZsn9KdRQsp3vHjUjkAZCgjYUJrmJyXTx08cIyiyagsIIzrZzLc8uJE7/1R7rbobV55Yt57g36lJAgSPOZZ6SvWLum4hSXZxPIRkLY2Cgl6epgu/GpbGy2P8AENBvwg8U7JdLFe4rrIqAI7OZg2epxL+I/NHhpQImEB8uDycfiprhb8mCkjBddx32bVyTIXk1YrAkd1OiwnFq1S/+IvTHYDvpi0EvrQa6pHIAE/Z6QoNe3c9bpPgtE7TonLVWi/pSNfGwr/tYx1MMJmiK8NztXnMKho+XTDseiuXEo9vxJNO1Lr/AIfSvHMTO+Wh1vJPSo8CuqPbBdAXPXQVinJQAD86I+d3SRkakB84l3+GKnscmANjP3Ew0ToB0UmOoBRX74vTJHwBrxsjLZFhT0njepY3TGSvQBciHsIX5ZqpChFKDHBRbWqfcteqzt+7IsamYhfwiAPoJ4tgdCpt8lA7YEQy3YUkHSMb1DCWon/nPs4HIvC4PVoutKx7XOwqMuxs/XvVBkb+araJaUm6iiF7CioqlkS44AxNmhax8DG+jWtsjry8h6zurrpjDZb8i6uQh2LAUdeCh/BC4HmbFMi/1eddN5Jn1eiblMtkbxX1XnsWXy91RzLPSLMJT1EJiyi6Do2bnIWfw0E/2d1vlY05uVLN8L/ZmHQrvvUj30WwXGISihmmduYm+3G1EhQDJfmyObVV402YUBSBp7KBYMRPKAdISg+M2QSeVNkj4glzN/xri1FdrEt5ayY526KfvERMOvhxXgipWIAWhRUvZQ/8oS1JzVUTugdTtq17L5dbrZj5jU1/403TMxmtqYN4B8De9zAeevhJxj1r9ZjTvgKhwMgBXJ9cWLokC76bTvDdxqTWouy4RLeihtdm2n7VKcbo4DXfTQ4vIL+9EVfM7YljQ1leW6opUfxzyKA30l9m+9o2mpoZMVCyanb9cucIu/kMqYriRbihfEQmpucBZLtK7lA7dBuiqSMT9cjoTLsxI30w8FHweui2/HNhSfk6xu0N7DzI9vFMX05stAI/5mnO3u3hPA9HozAlVMIerwIJjlryu0P3TfhxG+gAWp6jdFU9O2BSyzJh01W2In85LM6acHGvg9v3IkO0B9ZYMe/YA17zxwSS8fz8Eg9rNsQYcJQpVIjMZgQY649iVbwjuVs04GuxRKx9pDyPxVsq7obVgFLVdgUuEoyyW627+h5UEs+XHf9nePEd3mg21MLMi9ALAYmiKRfdKann1hLrtCX/UAhAZPv823p+NYY1StDh8dOIfzvNPhpJHnD/GXTGXwHTVZtr1/3abUY2f7penxhHcPFolNq+ywvjJnYMC1eIlJxPKuMLD0ckgOlMtk7wa+SxFklZO8Sc2ZpWouunafenJ607erd6Rwq8gfVlQ6QJ1fUbSwrQNoczV780zidSirjY7nRYT/Zk58sXCCv7fmVRUp2mYSRoG3x4FDnr/y/2P5+ojE0x64mipKFFMOH7KixLmsHjIrUhp6Vy4c3m9eqBkU4V9mftC5J6W924GIxCr9Bi/Bb7KssKllUm39JTUIgRlbSPpSaRDhgfILgJpj45F4mmUmYvZMkNtPvIR8PTphx5WY3A68VtGGShGJHz9WS5T7C6ApNrgiYnUloOoReIGyZ8c/EGO9eQXf9APRmEVQIcl/WV9HUO+14hhdFb57rUQQV8U9UAp2QebTY7sOqAD+nqnW30Hq2mvZhfh9MXivGhrbStAdfVC/w+zNWEqlQYj8RtmDtUt5LG+vfU0lDHTZrxGSmbPbg+ch+0uINFSXJ7bQzf7BsdeoybnrjoQazOPPBLbwHxN/WnCLqdP7FeVfGEaWrM2VX6PNQrgzTsRvlFFeIL1OPgA+TTVVB6Kuenr5EuBj0yTVNbHgWxUeu/25JOD9K8vl1o0BG335bGYQH40nRqUh4AKliYHMel0erzN0jjdh3rwFn4xCyao1sdH8MGi3GNnhq6DtxaxG+iwNukNg18QvCLRE0C6SGpuOmxIz1FlWIYFaiLckF3CfsPtMym7AvPV0wzvFcRkZYLrURE0HbLx8+hPTHeI4wczMOyzFGKfSKylw2ZGZz6WUyzC3JYWjOb8x1n0sSYtszImkOXhBIGG/YCKP3pQF0MF1uOd3G0za3hEqnCN/fE1fYM+CcSqGU/RGJhQYpvQhsO3dEm5cNmWOPrwIzHxtH0pTH/gHtHa/i36x59eK2O1Bdl5zlq98/AopTl+Bzu4prywKE5EMNIB8Mwun0hmVwPZ7CYWTQOW1fnpIRWa4/uFP19YHVl5vYpN6w0c6lzYtC51dRPAgWByprqqteRbo/kD7CwscAecW1ZdbpBrMuVJ6hBYGmo7+sJZL01twkzTu8x4ZUwy8EUxax6JYVTWAupnoCI4cFXTF9Y7H/FpkOL7PllH0niPpRRNvjKaWWGJ2BIztBbIy03c2SgF0CmB3y8HI5HW/9h2TP0FS/RyDivbN1a/fsDRshcLyYzQuufoKwR2JPZx3xHBn8BBIgPPjL8HD1IfML+Y9V+nRlS+AZU/JFXYti8QRvM2vYOnHeMa2q8LEzj+01S9YmhF0qaNt8wFpETv6/sbZWoZa88gmZ9hzekdXEeX1SnijUP5qsS1YhMF5+qAi8abFO3pE/Pn+FLZTlVFwDx12QNk6dwN9bworq3/tpZo3EC8yEOkGzcUDcptSnvWaKNBJdAWXuxr/W/c6c/pGaRZ405rWa10ZrlAj69hoAKtya0ylg7LRktUVd2n0EizpJ5yZ0cUI5o1xFe0DxTIgL5jtEvSFdQi+vA5dEl0mwcj2dV3wvuyeI8QCkkYyWFkf5KYqNEMReTpLBSEYfsFTsRMTTe/R9J1fELXjZH7ib3zQ9bEydJjvDdNnHNAxRIzJKALwa2Te71y7B0KzMeOLuFCWq7wp4NVof8FOD+VkkEv0EEAE0yquYFRkLxJN724ig7JfryIYJXnE3fAEey0KBGiubZz02UQcejWt486FJ0MRVqIK1A2ovBNmKe56NZN7cb6g9r0X02qXdf4YVJ1YKWtnv3JP1zyOhBazjz4dtivmpQcC3nsubS/N8nwWCGcp6mLN5l+57bdLhxXNqnCS9OhRK9XuQqgR3Kt5HxjxJhFN/LnW9j4BBnk04ym9Ab4TObYM6lPrpkt7Xw6H6REXKIMnGmgkBVW2PfyEsV3nTZs/Kslz1tXM8ZyySR0EYVae2rqdxkFfbDbsBJIszhybV9zyFWECEQs04isE3ClW0UOq2loKG4XOpH3DuoLGrWp/VGiNzQV/xQdSe9F5/OoMI7gHiFEneOcHGBGSw3q5G7awTVQAl+2vW0XvhZX7iAwdjtqN9PX1t7JmoWrqRDyBrQNWGjSE7JXIbKe0yf22UdYNBSTCAXtZD4nhiWohpRPyk1QFDIOHdQUaT3BEx/1HXY1Bgds3A4Rl/bUQvom8R8WSP3x5YtfjEK5cVFcDhVgrdNjozuUpSV+kxX8A0xrpdNNzgh6HGmh3OssnKE/Do1VRT9OzBxBSqsVF1vvLUrM4JqbZ0Zc38BuaRakL5TCi6S/QTgeDVaboErAoXX7Ozhc2SmZPKegx2JQ0+QufvYZ3gA8Hy0JpPxPiXAw0Eby1uc5hVYuwTQ5+SEV72Bp5zc8/qIkw3Sc52ETdILAkS+7XRXrryOUwnQzmvYtEVE3KYj14UScXTgOjC0RgNKhSVaiZw4YednhMB9AcCh3f3P9Q/v2dboyhMMj24eYtolzTtrviJYj3bzjDFjDfOCLc3sjqzG4+QEktVd06Z0uKpyAs8oSDWNfeYM6EcOJbCNdDEVthXjLB8SoNctjx605i1aiM+diKjyFoDSUTjOZvzyF9VSKt+Tb+FWWvVxMENzQFr72uARcFNixeM/nUDdMs93EY3u9wQaiJnsQofBcSadpZtbUPmlAv9lPX5jaihnfmu+XNhT1bI/C0JegXd0rg4XiBIGz1v3m6im3XkSD5NbMXw1KMFpO1E1vM+PWjuq9netPnka+sqsyMiKRJuWogi9W5HX2D1C345PfXt3fi456P+d+Ewkr/cEXUly7/rQX3pNZTFJeGD1SwMmxwogGC/eYog+0+ldebaxlGuDSVkRUIQj4zsQT5mONtPjCFMxu3QZXCYi8dyJ8Q4SYcCdzyByL/VzDwOjX/Zq+lEcJVvrTZ4/e3ZlmK5E0AjqvKvle3ak+XHl83jwiTOuP2CImuYpz4dXua60FjQNx3mFrkbwGqWE7GI2txCIp/4S9mPh2FGFvzGLYWkx7jc+TE1YFRUzP43hfHDo8e2qESqEheA8gDZPcAJuk3XPkPfUVBygCuZem2AkI3UC+wKhHZEHIu4H6biQP/A3jG6RvnEGvqdmsauARRjT5DZFV6RSk2cMSYwGvGqYrVigl0ydam4VcUPczLenr4ya7SMuadwHvQbw8a4DN1pt1+zciYpyx+hnETKOqevRhnlH46tfkUneZjb1pvRcmBJ4mGK+Q2FRZU1v8pdwSo1m6O5dCZfCZ/tVoETvy5gcKYPrdFZykjc8w55NFM+IsdQWemRQ9O/ShQdaL+YQu9OMGl0fOJCO3bqYViZwdLbVeAe+u6S5eo896jkutPZgcktwcEERnlJ/NsrSkCkfZdMZK5YFJddaUsm5F6nDdwnyG0XGD6H9HFCqwP388ZrkUOP5XJdAn8Mgd/wiUcPynVRsI9vJZWyuZGeaKmgj6XnTaADQrrRt/M3Hv6BZ7V0HTcI3Dil3QCOMnbBujOsldZC6uLJdPMju7Ea/BsaYnczIT8c+sZLhtwzuLKmVcJa2Wk6tiT1+IyxVXxl4RW2pizWPnNu8lx3VgN6J/KoY8cnWEY/LeRvBCCSrp4DNntw4n9YZkQoDSiz5MSG7LX1DjqWjAruR8SPLBw6BFQ86DbYcB+sM3CUfDSVsaV2VuNMFYBj61HZndvetMQ9tKf3CVRJZL65N2peC710BTqz48zAIdoorHC26dqPlY+XJQF0z3VX76CTQAdUaRYBdnI6SsNv7StAhYnlc++V5W3MsOtvaChcAr2+ULMa1HgNkvcDP89w17eZuM7W4a9ZKz8K+D1giosdxCjrYB9IZjE2/Rls6yS0Hv8Quxf/Zts7/pbBEWxmo+uPYCJF7GqZq3x+PkNIPIivYtJG76xKVVfIlrPzhebFJbrY7YCUoqiEoa1Qx0P+hvunuzLAbxPfgYw7zm+G57+8NrJ+iG5IYyquQpWsWIAvdd/ZegSZf3sGxsnQ3cl4ucgJ8Jk33uURnXulfeJBZRLHamrvq2QAbeXoP6uJJU5pX3M8JHM/p4sJoQjcl5g9hU5wgN/TfzR29eaFjahgYldvuieQSxmYATXcPMJIeAW9DeFEyCEonPEL6inusLvoMWlUf1eHPONoorUeEeBufmUtn4sSTQ5BgsLIJrLHMJ6Ssna+48AcPUThPPldI75UltZAep5Hqqvf5LZmOAL6JIBDsghnxOfsXNgL3eJ+RZ1P0UmEQx0MmZxhj6Y9ekYRBmP+Jnpoe37+2/Zky1NAd87VoX2xWKEtAteLlz3GEtKhbc9aS6axqflYQJHNFCQaLMu8mFLslX93vlNZOIOdXmupR5/RWauYVyJszGavbWusfABNyl7iQjVDDQGBzVinmPJJO1sUKjf9m60KALIFtnO+uGDsVa5yDrn4CukvqAlGJk/IL2A+KjgwlGcfkR7Ng9h0VEEhQBBK0DEQWPDoQK4FPUPMlWEqW3J2jTzEYrDBTD7T+UPL9MITqRWGdquIbQv4eVxY1Z4hoceAiLz1n33daS8sbNiD4blPQPypZVRfUU6b7ph8oHGP8jBVPsM+X56S+h+GHRjjRwYZENqdVfSxzIRapValgslL8X1OqRsG6eSwrDsfyiSktTXGvKUxwPwpSoKWqs/Q4QUZOvhuxM89mwhVFxt0WWgZHyhGl+fT/U5IBAJYXdzK4RnmPDj400mV0+5ZX/HVUlmb2dq6QVLa+HIdfZ3/lI8oCOiaAvVFXaBpwwjVXVn+QCH0kgVq+HdOCpu3NHpN+3+xhgdAQv/KpWuzm9ENfmIbIJTGD+hgqvuFo1AQ+Vir38KNwltfoq44sKAS9A2QJ4Ks4eFsp2hxptFKOJw7Kh7O/put8udsVOrqGlwjBimgjd0IRbO0Svi850YejI5Hfhu6ZIXQdCPjScx6BsNd3AOGNfyCGfVLMKkhGUOIJx9+JrC3AEQb6WNZMjUidxY6Rvfwm2rg7lpJCaNj7IttigYtE9CxzX2bde+c2imP3OUiRa9UuFT/W8oQGEs4mz314eJCKEes69rF14x7GwRNKfg+sshkRPpx88oPgoWqGG9xbej7SlFXqI9cBojMm94LpMfIf9JhTVaQBJhbfP1VRmvSmgmq1W1SLQQMFKgu9h5RMlrc46+qffCZFNUwWWpF6Ba6H5oK0o3pYECbJC2AmVmCl2Ql7yhQPMT98u7DVNnEjOvuETfgQVVmUXZdUX5GjkFEDpRvxwTlxkvqkD2VDu/axLWPeU7rb0RcgaA+dQ/EMwgZ79/aYQfqPPg2qd08Y+F4eqnLCmCAgkmk4hISX6wfHrQ95pB6RGUeQDUx9f0c+Pxl7Q+cgxHLGhlwBAHtI2sDiXrM77WHMeLtEweAKGGzb2P3mZBcdyewP2MG70GpDA2llaYWJMD3sPoFwDoPq9DoNb16+RjOGxKwzCLgsjnYNbmTwNO3rzjEObDPiGQFpXbZULV9iaead1g35WEjF4PKRVRgb1rc5LMH1Gqv7J2Kq0Y5j/S94fdQAIwXX5/bWGcbImY683fDbhUff6bJv0FMlegwkM0qKVJeR89NNVY5XTJ+1Ff9p+IJdZKuC2IW2ypLkt3Jds0RaVF1q4PZ3N3WdGgzSkNp5/A36Oo+St86axp8/vXA1lhsCZGJH997hPFxEyklD3pRZgvPQ5/TQAIXQ/vPWGgXyGrFIdKUUzZnHVOQAjyOw9+lioJjl1XWzwQ1lkxVDNw7C7i1UnAjb/lbAKhFx0KxBjWmA16+uX++5vwgeAFRG6QC1lfsG9uhB7UQhrCCxB5W8zPQ1bte0mcaKlPmZkA1liEfGviKbDVXxKrOteqV/dMXsIYQu3xZm2af3YVQQXQ28Fl9xITP7BhtkzhsjGq0JJO3Yw/SHD3y2E2IPXfdG0JJwRKxDM9tfjVhe7QCT6DuwnWxiO23fIgJuXnq1HNB8fozsi/QbG+oaq10TJr2xbLSa9mRCii9+J17iB4onPpmvdylTn3Bw8/m/colT6bBJrG0hPjhi311NmTxItaz6+e6kem3VP814sd3GBjpWJy06e/mZsjnXLk92p4YimFGhLCOcTHAV/FHo07rI8yoScG4QdtuKJ5TQl77eCqT8V7C815wg7+0jEiyseXRNFC9uNv25lj8B6o1TzpOHc4DDNygkjLlzR/fxtsXH8OinVxS+InpcEXeg/B7S8+s+AMW/Eryj/0Cd7lb+xrR4qVpt7grdlAWrChul+xEjAW4ssXT2NbfRjdJaZZy2uuJaLVL6zDMFacoHMDeaZjgoNIZ8NTJ9z1VyPok5icNNNruXsMakOoslcWx0ig9+NDyWeEhPwutAE26ZoSLFDEV6qpKcOKr6GeRsWjKONQnzySx5SkcJaaxGarHoBmsTky3tqz5dLcr7uNzD9zFDckjWwUZBw+jziSUXmr39bzxdH+chaPLYY6JkQzlVEf+c1nhujfjgeHmLkOLdrrHxvp/1NDZWZURL36+jd6S23pdFxvK5mvcMMb1f3bjSViOhsrD/e2kOWAB1BBloYGh2xH9/3+DAOoOR+hK6vSbPIqv37EWFk3O+4f3ZwXkF9J7M/0PMYH0is+qAbDz2xUpjqTsSAzed1X4AMdT41I+kOylSLvHI585A//GaBUjp7idt1056knvv6Yc6Y86l65lQEkTTEVZ5TWDq9UFdjbt9y120Xyecm+CCFZJ3KRhywwq3RvpUVMq6dF0vaFt+6ICao2NWuSMujw+20k9GOqODThBOJFda0fMdvyNzKJrpAiimUEGDoAMM9KD9yANA474DJtjd78l6dniN/fvPN74o5d/OYWArEO9HXkr9x6KOr+De9fZOc9mBIq88Vnihc9vKT34OFM+lP5c+DetpgXvO3nCxWN6HEhpnRSF+LgKH5054LofQ4i7W8csyh2V/SHPqtCxaZ5jLd5tUga1NN9GV+Cf5+fqsWQGiZkzo3ICaFI03MGCq9zCDf2OPen7s0nVAvE8enZqPxaiWXr5m9Wf2no98/7/R5NMx/8I+7khMacCJZ+i1ou00PJL5j7nSNTJC3kTP5mXgP/BWS3h/vp7pL8tTSJP55xBMwjuZwSK8pEpOc2OSLhJRxLsGeHIDPTv1SyjrSJF3Yd61j2sY+Em4MCRV+Aj7atFFodEAloD+SbAq+RfX95Xlhic1tX/82NyDfiebDfmB8S6DLRPgT71wvCFbRz9ZI6ferx+qR421ZtALwtK2LLc3bf6Psb2MDgDnNzzUFvbvUSxggpCC/vJNyGWB0zaDueEEXXZea7AXq2dQCbvFB0+jzvBdCeASLLFPaAJySlpjBWeN5LqLLyI3ecckqqyU1Mkzms8ktoKu4uyvNronXJM4GY2NgNj1ZUw7l3tES4r4YtPH4unnw1jV/dxUWLl12tVQLG/bGc/wEzGh9D/Yq+A1tRbxFkon/TuLq1P04CHdFyrq76rTV/BGhCLzA3an9uns3NceL7UqphV/UpKTs0gUkiRsz12Q4JCHlJLi32KB/FwjDUpDhjkyUq5Ktu0iYxX0cuVx1KiOaA8KLZb6+b8fb744OlRZ9I6RQj/uf4S4RStzf9CkMe8UQbADqqhwDa9W7JJKwb+S0mY0d33/8yrgpKgsFEAe2aDSglDaF2I8kNt1HgalwuFgzCN1BzB7b8vvxySroQCXAs+xukWbTpsf3iAztEyx3ZA2og79TzSJzpiFLAtqVVIS3uWzooNRhLPiWqfm8zcnjEy6H7wxQN5rhE/+TLMvpJkrvPc/KBw1/LjS6xnbzN5ZloRC+LJly3femZ5ntjyW+uK42a/rf+j6GXgrfh8zevAQ8Jar92uT4MFSmdXWsvTJGnHvC3X8x3Ezod6SSw4CMFh2kiIfgaGg5DR3J2gUj1gLwOjtAnpDMfJ0O3mN86+1+/QGCSYKPc33SD8jYkSnbbhHcTWCRgeZTtOWC0NqLHnFevh8TkWeWYSp26Ca/buPjeuMuWKRhY/kf+R13jPtVM7gOOtbAVqAz5UR39gb1nBx6Oz3J/rgRxgfDVZ2iqERdXvY0FHlqobe14u3FS0sfhK/MemrJGesIExZOrbA7xMR5g8asIbiV2EVhB/I3TrRN92B3eMVbA7mqXD+Tfr/iyzbx1wjXPdO7F6R6ePFbVJtONOIZALhDoQPqTkQkaeEpo6dH6CJe6Unvdm7w2Wnx6AE5otdnCXqHmFky+x4E6LW3ID2IU83VFEkVD4BSaJhl8oU46CIjxf1UDhO0lSBvGuQlJtkHHaUGex3ii1tGvPVWDKbfiG0QV/b080zUtp0Fhfm1069nBlHV4NtF8hJZDp5UZEfTL/nVhAua9ww0SwAEDqhR12DgwpYWLRVN/TX5Kqr2u8v6Yj6llwlIFm9HjxguRGl2qvWMc04hVycXOzc/VZEpyDoyNBc0UX7mG8kZyHdD96T9D29NRsvsOiq28nZWi/ZJClN0rXk474fFt7NeuR7TfMcWF8KojhkfXr6sMSoehs78yspeRzpZGHsqRuMVrjaiS5Mrn4E4j/Fv7nUmVFn0+DJCC5wSg1oBdNDOg5SeBR/NT4wTJ9SRyD8iFkRubNCuJa6kbm9Ad7NqOE8iFfvkSuW6nu3RYzEhbIW0PsCDnmpCfst/AV/F/y8++zlGW/u+BDuZkbPOlFBfgGMP08rPo9jJo7WsXV6jLhGL78fTFD/OB3ADS6P42J65phxRhAEBFND+TTzGN/iVUbuklb4HhjbNYb1zDI/mQeTKycgnhZtRi3evj3ThbgXWBEYOiiq2j1esp/HSpEDS+DVV9XJWJ18b+HB/PNmVpVpV5imm7QYkOOf68IgZPENULSXdNhR203NCfxy42H0F1a8oq361XnlgGyjxcjo9n9qXAP8BXERx6KB1fiWPgD5tLHftspuSwhi7qZ36Pt4sPf5kXWQ87jKYFlXIXVkmF8CzxgTaJTdrXteHBHiw+67Ql9YI4Q9g1/QJ+hoXQAdse6jFST9DVrD9U5dowuDn0rtRpllD1Dp6+0uonfq5n+fJLdpZXbcoy3WmqzVOjQq7/8OVXyMVYx1Xt1/XH8PWDrpgTH5L3/tKONSf/JhLZL+dyvl7cCRaWyIIVYUnbMWw7sbt1MqUBJHs4b5NuFJCMbRyy6fnfhxV2Iz5sJ3S2yLBhD8TwXPvCZdXw/n79jCOMdljSvrGJa3lRmoAOxfuP4a/X4yBGvD0i8f0eWVS4pJiCusBiAaCKvb+0uIFiizU//2btFDPtYwYxMTFZ0LeySnXbtb/92olKptSQ/B1PwCXR1/FWC23JzG5uHV9g+2eB+jV6YK3ucjZlZZpqzAIqOVmLX+JZSYjJvpM0PqPuPShFmQ7aN0cvr8xvxCejzaJ7rc1uT14+i1ChtR7c/ZNpL2+K+76hEUPmU1B0bJEkjx9KeE3aw+RCZf6by9v3zwXHrmHBA1trL8Qyru36onhETbBwvlR8mHfaT37oen8dclMzvTUcGgr6PjmuaIaCn7b/JWBCmmNgX8cVpaeU4pt2wF+1DjxVW5HF7OpxfG+u4o3oyVm/4XWbBQKGK06apqFNZDI8IzIzmjOq9zgbwzxR48h38wS9ty82+TmpRJwEXrjoLKKyvGyGOtPkDXEllxIHFIm+9eW65qvOu0C0ofORmXnUD30XeriHOSRyJca8cF+cbNwKmT7dKIxdknvpUx4ImnkTPPSCzMX+8/VJxdJN75tzAjFrrsJjlse2E1V3SkseBrAJ6ptlyLtp5qMHhAkMGntN79/AsRfQ8BtXMr6KiApi2Jjg1F2VkvRSpmQMnrfOGBTas6r6eQiyjSAHPjX7pUTAXbkTi7Iaw8Mv0Jbi1sU3FoWj+Oyhn+EDmCgH9pE5Cc01R6CWrSeUcvwjwYvhBeJBeiYbrnBRhK+qg4FPInJzh4KICvC6AtyMGtSyIG/CUlc+IENbhp/jxcUGOwx4xHGqYyuYZozLn6nEz2dtASX6EK7wsubfkKtF13BJ7/obj/4VDm0v9A+ByoK7B6i7kPL6+5Ld4y9N3h7sKETs9yS4Dkg/416IrKkfKsfR/J9CwRjeFqiSZhOndGladYVsiQtpgV0GmKhhiz6yyNC0jjH9N1C/JoVjcmF9cdJZSMm0exRX/FI2a8dcnuNBWK80moi/qfpIo6DAMvzcgw69nBYgwQVuOB8SM87naOtz6FGDytLVDJlFhhzYvsx+Oa0ohO5HS6Wu6V+2lHW2oNzUGnESKn+lUYnpKvvDdmVvcAHBX5jFyCzrWXW0U2nvXxnMgbYBcoZnvuxP0k8SUaoAaYn5PnW4xv9UtJEy+9aPHEqpP1fIEEr52ohe5pnhvUgcQnuUKD55yVY05W848DG8/F5LzaTzNv+p68u6+g+NPoUuuuR6B8YX6p1CdDYgr7nXpzzbs/G7u3SZ4neMP0IQRziHvqy9dp/YGWGkwB8RqS+Q68Dtz+uOTg2GvxJTFis4z7lIekjx8pewNwS4DiKKbdTPESpYWFVwK8Lb87hp/KgqKkydfMxUMSyo2OVEVOgVcWwZx/5wDzU9KkVqrfUiHs1o0UEKTgI31u5Z0nDQFLMpvnpUCk9wg7UjJPTT10XeFaqYYbWczBPv9GtoQ+mz4ky0pdsJMMUDDKeytLtjIqXpKkuUYNZbjHuNnehPRlF/5ZJOTxssOhgkrEF5T5zj3Xif8G+kHqky4ftihntQ7mewBUioacwx0kLB9wmgTPflvEpecG6/+Ent+q4wm0vhPiLsKhieteR+CFFtO7uaXxhSjAzIzEHR9td1l4F4Dwjm0d4mT0Udg+fWLVRWtgfqsFsf46apb1gYTBuf23Q6OCToDer5XxpCLWkRvANPkOCwRdtVRc7sALbvoVDt5mb7/nYAftGuO1aKwTc7e09Nq1UOfa4MeTFf9LcKqfElaVRldDtMc2IOraisu536I5ykjZ1wM0miZjQVdAuzqD/uPovPFaKUmFPwo/zxKaDgcolCPoZOp4bYn6AzyQAPr33yZ/SWxyUDVg3yU4oGf4qKasIq/QLMZYSlPH0GnaxavzHbOjq5vRm52DmqR/7yPfYBFFFLv4HeqEdELPGdgWcumVmNShCHQwIGTCgpITo759sU2Stx+2TLIxWW3OSUT1VuyoIQ8O1k/Wun6aBW8eRCnBItnnbM7hx7cbaWUUCQy/ZRWpVBV2z29PDFDaXtp9HlY6A/SdcMtxVdTlJjo+0Vva/szibGl6Mv04IyxlXWx2gHSZC4oAbF37r/755JLY77cgsNoDnQE7cBIw6ksB3yXKD3T099OU7++3XWT2xjoJTEntfqj8akLQZKuFP7+qrBwWXas6tISFCLjTDolHItkezwC+6pvbSMtZZV8YzmIAGFf+LQ378z1aJJ40t95VNXl1ew2bC0JPf7GTCv11v0jVh/oe1k8eYHfA43dCs0bH6lhvNQ5l4u1A/hYSxK7p1KSgTdknCjU93PdYmKwW2K+br9a/fsg6XIUQlBMgdmuhV/jWYjD78mefebYiMIU/4c/MLiYp0oZofYZD9QxtZn8JaGL5OM4qkggpZiMvExyhc0Sc2WXtQ5eCcZDYAL2aiDHMksISM3XKPxAaaUQoovYwzFLxP79MRfidsvQSvJ2U8dtec0jQO4kGHE/6xmregG3091Ws1Na4uBToyv3/5ZjJUqtRHpOy6VsQhVQfgd7s37u9o+ODKdm13+LH1PTqD4QTukRWoSz6mAqEcqhdQyKTXB+8an6Au/3ds5TxJxDa/135W0suSRzJUoCSxVQ/K+Un4Xs9BCoRT4ACqOcgf+VDwtjDKiEmUstG1J0CgK0a5AyC0fqc+8Ay4CMgXYJgVRVhZXDTQopEqzGGlq/8uztnEKx8LYothAeEsfRlb/jSgpiwHSueB1jIKEXBN6OWOFA50swRQar36cxfMc1/1xVCN+dCP8wra/9M7ZYF5RQn2ECqUV5TQ0i8xwk+0yt3z75CGEVuiaqZj+Xr/PeOQuQ5U8fVgIjPkin0gVXq08XCdYz/CViz/A2RFzxsOpubcjjO9FdZYQ8nHCrEnHeKRitl0STanyJpX5PybEm4jPg+BjpzMKTXR4UkmYkzSp7yFCEksxFec2osxR6NIlQCwBz117QzBA3Y5hkniRktzrdwCRBr/4iTZySD91/MXOunAHj+KITqEd2CsbVZv3qbSPTH5wJAr7w5XJb2il4JZpjxaGWK8qU6Dhlh5xeeLE0qxBc8ouLum5fVUFF749eYwg7PE6MKWvvXQDCQj2r3UPaFdwTYnfK6dbENhc77n1TBHpBwSApMfS1zvl0ahf55goy4Wfa9a6f10RkvN9/270ALyMNI4QmHf9/W5bTKHLS/7f3+0IewhsLW6GQxrg4usSMJKQ1AYxtD5lecodu19D7wUc9kYq/gbv3v3zPHkT8jruCtNc1JOUcId6pt13t+06Vanub1OkMM8zk6EEgUJDTfwcIwMGq0iVa/u1tjee6cowJzYs0zSfrfR69MSPdHCPdq+gWqofTb5/iYKaJdz5xguiv7FnRjdYSSm7VbSBmpCONKL8D4rCplAHpw1Q3F+NVgTwX8/Tix5f5CPqf/09RCnMki64XMUxQEihnLC0yxn3rxDE82NvxGVK6Kuuw/qp52LSEN3s4Bo7S/bGc7ETmUD7flt18bafrWFdq/ltwXuAu+mIEqnT87QgJfQyITbyR5Cg/yFSmzjgXnH9l0CM1+Oqa/z34wiEtbyRo8BZL7EoeY8t4tQJOscIDVQzzivEFEEPtCdQ0DyS+LeeOpcUMIxiYH6iHZyhXd5AnIN17jgudeYrqdu3m36dPUlqWCm6lg1RWztZXJBEd7j0y4rRpTrHH6KLqCp1rIeWERECi2crxv/oFn3+rVTBJNRF4icgk3FwOQKPewNV97sWp8cICbYvvPgsipBCwOPLY9JV24DThVJQKJZZO0jUnV9oEamXmwm2O2RgtxcLtX3rXmOPMOtSD3eAm3Uo6E9KDmGnq76BKIPid3P3iFYUQsGB53t/0LMhqtqj76nR+zxETdNHjYrBgOyDoOpWWcRP2zeJ/ntNTRDL2B19zdG+1U3WS63P1vuc1EFpMzCm+3vj4MJYWYI9CiKryMtK4UulyG2qMU102NBf+AI18jOi0KynJOuLTwH+xRoq1NcDTpkj+CuNr6BSFAsAI0Jz3vIuVdpJ4iX3SRsU/C54iTP5QSo74b7IX9H3Y2D4YeFHb8Egeur2RgV+k8JAYfApdPXrZhLzBypoZGa0dWG/a2dIqR5SxGuxa6pkYSkc93KCOO0qE59q7hLkLO9jn/0YS5czxJRtme59O0fXf9dP+WvvoKcucoqmcf5wkHgbL8rH/iwF3SN0kNJdWZf2X4O3rcb+354x/mbU5YXww90Z2aP77BoAu+qyQENHPzZWyGEyf1FBKtT5fBgHYACk3e9/hQAnh271p/oQBjuWdWqVUr2hwmdfT5ku0iBVulFK7UXv7xZ6blzmzfADF6oYIMQQ3pA6oW+ETcDAY5GEKjaYP+q1i2JQf868Y1B35KyfEdtc4yXguXN3EOeIHPNyeiR9txcMU+/v3uG0mhWtJE1ypK2MFXm/W2uHFEslHG5pMcLIQacdbH+h+dHEOU4qxPzMkaqSu12hzor+7vkK3nhrv3Zor2x3gJ8mauPVWDO9x0VDpJ6U1Z25rhU2IWX2y13CNKgqjlHn8OMnGmUn/wfnNMdT1af4Kc08KTRg6YtDbD+PeIQR6gvtp88VO6zkl8XkfZmjj2wTt99uVIX4y0NZXSIkxYzvF6vE/I0rXVoHyzp5xDJm/iFgs4vvBu9G3zDwyCi21JHo0PXUIcPOMeioK5ToEWlB8/b0LZf5IThZBwq/J7QJm2Nc23HJ0jP70Yf0DAy2wr+4r2laWCe+21Hz4p9z0dC96jjZGxoDK/0rspePpWx94J6yXCsudRcY0VBgbBFKA800cVs/9pO3uuoMxtHc19w3U7TAjnAEMSqZv6tcFi8COhVxfTF/XmbZJjMErz31jv7KWXp290KYm/2rBfLvHrX7KbVGRA4QctlG4UuAI6jkVxs/2nGiGG4hkV2zNN0lvcTUsPv+1J80Xv3eX2LL2La67k4BSqPKQ0oWuT9ZoIImw3Hvv4JQDX2bqScCR71Rkj62ekt7QJhxJbt/Q9Fr0mClj4jl8T4K2sluOqU+aEKk2tSNAvWJmB9HDks4wOD5DYnXJ37P/Pxvs3nGrpEaD3OTiR82i11SZmZu53OR0jnHrg2RlnDyYRfRLsltRYPntWdRCgNXhPhgFgCq6O5am/QbtCf/tdpVv1ET21RJg9UgG9O9DOf+pGIHZveBmF37qjrA9Ja51/KUUDB3SfXGCekmmttknjPh45p7yac+eGoSnzVVYyZqZsz9OQxm8fVTS1S1XcTT4kquA9MNbgeVt7XEzptGJ3VG6rjYKTm7JGogqw77ynJ+EHXS3Xf+owWQiDyhv5kgqwxmDLJIfrvzqZX4LTqDpF66MZhh2VoB3GaBbywkeQr0demfEcnoAHYGFH4fOXtWeb1JYwklDv0SNKRCT2B8ycE5auvb/9SOcsZpy6gvLqxOF5lLcofYWKtF2j9nAGHzlaDSYN9pnsH9eCkd7V2cmDM+dip/6xbJzq4kAX90AA/c89pJz+Ci7K+yoaPdwOrD+CapXVXwXlBnbwHpQ/oBVyXeAoYnZnjWr18s6D0JIqAXqGn9bVAdjhRNxwnNBKyAR4PAOFL4/omUc23hvIyOXPi/sGlxTWFHr7YPiyPhpzjJTRWVj8a5hCCqqxjuH3WvQfuM1vLgaJHtDaQFQpMHtDDj+WW8m4dV3HOdk6PgAUQ8xZRNh7bdl0l1EBOn2YIupYSCeEQIVE45PxfaBCsqu+MTOc37r2tI4xIrY+9YbDfTcjb76nqsDRn5HHcQ7ttxjqPZKgMiZqFmZKhYMbR6+/qtXqogTfJYjkfnvOf5eSDoZkGlXBq9JKoEShV81hCfVrzni5kcc9bSjVNM4nTgQtDlS9q3v5swgHQAgUiWX0sv3OisSab+DexXblSUGvf0+Qmk6BZmAGNzqCY6Q90+wue3tp+laEwM170Yy899qwmjfd9RKwgIBhWTM+ch8gydsTq/8PV1kFlvS1pGGrRW3UMA+9oDGO7GfnCSNHo+6Grm7f8BS3+pDbwPneHcOaQt0uHkWYX1pn0WODf4CpLCBBPRP15YLTVb8Kg9uLmvdH4sLwtJK00AUFDettX+s6QYL7YGLLkpAVRUwQ1sCRGxsRwe+ZIQfI1MbjmJPkZgFihSR7/8Py2A48tHJzxoffAUq915gypt7pDyo28766SRC1nZskJcUDgEI4HMoRsqDvNylYthrIcS/HFeaMAjy5GTBG1Obx2Znx/cLOeHtt4f3YYHcFeONPy6cNTLL4yqnze8fciB29pfrPVgXDy76/el6ym30kRuEKRGafgfuuqZT9UQ1Ll95lvwXZ9yDBGec8lK79/S+CVPTPv3fvmGQP5InoGhGmP4kz8vS4HxY2tkvN5K5/rNUMSXSlJN+nX3Jdwy+/sWS30wk9TYdhkUy7HB9m0QmzOZibAJEuvHzOIm2HHOvx5G7Fg5OYOpT0F2Q/bBsmzd74sw3PBfE/aaCxkoQsf6XsXwuf+aVXNRO+x77y8+5uWof4goT0wLtm/GaZB2cMhk0qKfrcp2tAUWJI8EkvzNBe5cJmkhr+AVNnJ82TIiCLA2owGh4LZ5GEABYSQKA2+xxc0WKNgtNG4g02oAoVtYWZOzox7Ay6Xxh3ZrTP2sxZZW3NRv2zqqs5GEZtfwReGSFf+iqo9GeEtny4RZeaembSyt/mtdbpP5890cOK60ScWBFGBlwt+mbBVO1kiPlliglwOfj1PUe9DeBDw1a4jMzfwp1sLGWAnNVLzSxQfQoRi+K11crz6jiSkWdb1R6SVD+OivHAWiM+vP3+Tww/Kmypr8PUAeK5h6M8wH5du54iksi4XMIB2WrECa2eH6rmuDQEHBx8MmmTZey1CtI1rr56v3wZIE/F0MsJNEJ1T3GQTW+rKFSs3sa8EycZ37Io1D07EyKJzmdI/k83jkUZJQWoK2AjMNfbK6A+ceRKzLjVwqPMxEQlv8qMQYsKtPfSo2yw8JOb8AcV6UDA/FFrj53XfVl94+bcKCxNz6DH+d5JUdk1wL9aIgAm1ilxHVPwiesLMbL7AhzF3rJRVddNoAZITgiuotzKxbiG1dJaCsXLU1cRyit6BF57JV4H7gl4L7W+UGc0qZr0noyJCYYz6us8Hkli2rtDBykWPKDGZbpKs6uRINRnFl7SxigfWnFLA5dsiFt7nh0BwFGiLm31gd2YiPnePnkvXeoIcZhKurT8y/rzpsfj0nV0kxjfGfsrrKeokqzpN1xci7/YJwGe5b++JyZUrG+DVSiRE4opGdO+EJIIREogMbtN3aHSII8zOoCyt0rvc350Qn0RB3fuklOwaNnZLGBwLzNyUfJPLFs2Vaq1CJ7gs+COS/5cWBmQcG8z5YHRxzWjduWkeTNgK6H3Q3g98cG0ck3mAZG23YUuwnx6p97bqaI4u9S5bXBKZQCf1S3XBImSUV3R27rcaZKh7+Nz2mxdEG2IeancxuyL0NRu6wyqybizHDzwcYyMSPXF5M20qqgvdSS3TEQMGb9xVmn5FRZJ1mJm96uA9k4ncAy//MCVbKTPkYr0lFyAV6GMHnyvkqeeop45u8guhSsUm7pEJ4QdMxRIX0t3WVoaVGJmr/3Kptrfou+h1X8DvMwewuPILekHyz74vNgS95KFzGZxvSyAqcL9IzvJjnjxt6L938HGXlVLuEHov3/K1Kqmu43myMBA2cVQpSG6whK6f7tZOWOkSmns2/Nmh+QL8YA4QAvnU4WQzCZfz+zBw1FAbkD6fQkAtTfl9AgWTszCf6pbsaPlaVRRsjrnXExKlfLqKKuwH+dFa8fKNcEqeyOIBIa9ULZfmKUY3/zQlJFtBvBaki7Hr5YO90fmfu6RLagBj8qZip0VFFiG+FTWxmu6h3jNeiuHOxTJ6dl7TYuJxIIygB+/UlsbIuDsBMH0+Qb2h3yuxqeYxxpUdo7HCvjHeydK6ya8MMJ8paVVwszxsDWP4DkP3wea9Nr+osHAqDAEqaHLDAZKS407eR0xHDO590Z8kYj5UwN5J/On5BWUXwJUdG/Eta0aR3P2DoCovvlHlg6fW3/cnYbd8ykpzhX58MHAEDOvZf7j5jyBZfidaw2o86SM7x8VXFDwFtpfkj0auPTdKMOaZb8ZwCSspyxcMu5du3pPYUQ8pwPdfa/wRCGNAXuZvDfnZc9zoo/Y2TH4rExzLAtmTal5OuwLJNUSbSOQ8e3FOp9LI5rHVlGaFP+QFMoWQVv/qQrw/LDgxFBHOYNl+V5Y9wR0BWzdVkmaD3tTo5JbRqMd7zNoB/SBdKO3srOtkiln0aBLW6K0mtb3x44aHgkNPDrsJ/e6uR75fXtDQUsqJoxZNZl2dy1yABieJQEw3MDjKpTBUCuS49KKNZjJpQuL/kvBWMYurPk+QwmOWOsC49Nfa6SgYd5+DFK+oDWxt0jhPIZu3ofOZf00h+LP++AD8supE8UsdhWel9d+u6psvfIkttq6ZK2Dkhx7idsXwT+qqXNfbg9BpOLqqh6LdIfng9QNuchVwoCBDr8oA5bbQufNoQ0byDSWXv8cd6Lk4vmkq7IwauSv/aci1yhxitxTfgET4wH0xaPB/s1ipR4HvZrETfz1dFKwIUNd52nis8RX6gw/d5r+HqmfmSPx5oVn6eY3gUeNsybb+zqnFLx3zzUhZeAbXG2XIu7chanwXoSXjM4E0Z4I0vnLai4n75KShqbxZcfRnai3xVF84NawQnKIrz6OpQfB/kQ1Je6mFH4/JsLuMeOQ+4YJefXQ8J+iUOfOPbROtQH3nAyaVTQmPv0Z94wUF7SSdyauDk9EHB2sOxnU+/JVpmQuBUHwV4o7soLN6j7FaoTFXNX3vOqcl35vWC2USmyX4pSlW8qapVAurbq9TBsLlt2m7NeTqXJ0FWNPnQnho+mQLMizr7ftqmW7Cx0ALobfwbXpFXAXKrqolkCssrrUG6QwIiUhvYfHrKxaEVcDFLn3uR4mEVW1VN3HkanJQrqBDlGnbhF/4sjQ92k3Kzeus2OJoPCa//+6G49TVJYp1IKkJZuiRocmZSgruJ2kMlzmvHql1JEMKMmUnuLmLvdPO0OJCQrJq3D5fCGO/WOUk2lxrKg+maC12UEpk8YRL3EFnKbW74c8vTNOWxv7OTXpa6edWqitV3uewUW8+0fI1AhYBf7o9sp39//aooNFvgXjrP8+160Ml2S3uLFtq4wiIVBTulc7Bmy2TmeFs0+pbdxajep9jh/LbYuyekp6g32gWZor7PQfvHwUiY9sIVllg5G0RkSvuwAXy8wIFU/Q+s87OH/s2OF4rvXoc/xqpRoAZ1OQKIwN88zyR1ttnmj8XieOvaAbkWG1B/LvbloWeTG4ueK2LJwL/QHrgp9bjnn5eyimrks4fvK2PgvkqV5RZ431Lx7iiWnfzz+1sClMU+74tEMkvnx3ydWFDxEKW1PT8mv8/oF66ky5lyjjCV/ayx9nQG3GPvUh1Cqvxu9W60d/T6xrnsjWiIB/o8Nf203fxaNUERUEtipGPrpv7rmvLnxY0DG3VV8SjV+xDGEN/Bx+9X9Fn+Uo1W9IG2C/5VzIhpugFiHVtMqMiE0lDFQTGINbl9AN9ToM3HrPDSV5Mx2MvOJX9s929UWbg6Y4wFgujfpC6+9MPUjwJdwDeP0EVRBhUSg/lGNK+Jbv9XqaBKhxVN5B6lvJO7rH+xseNHeQBXvoz0kUvoledoVrCtWycv7uGRZeYixnh1ctBRVoRC04nf6DH9TWvp/Yo9o7PnBjekNsjRPNj7tNci4lfV5i6ywTqMmLoywx/y9Tf1OE37cffIpTsfLsQ8Lqz9urITegM31LmCv/m5Axrrw8J95+Nr/WRhjQXbp2Lsb7A5jiZq8ZdSpsPmOyrBi3wCo3UO3xJ4RLGtuSyQC1o7/OsyNwLrST4aEf7bbAZdp/t3cM4BaFpIWi2DfcZ68Zak/KF7IdjqFwTKTPY8LgDdmIPNPVz9JyamuBlvTMDCwciQs3ZRET71+GQpzs0SAb6MRzhuWv9IebBzv237WeXHrKMGN+I1LzJo8V+m/n7QJTMDl5C1ydFEknpNTpNYhxMzfh5Vu0w/JdiZ6agqQwOVo/e9FMStUfNsfwCbh6UscJucC7ID6V8Oq4qO7pSxYPzhOxBDBLZ6wasZzNc89SC391fjy/ps8o3NkPYB8Lin8HD5162hza6zFIXIWTXAGbsUdfr9fm2GciB1BrFhDhgkdm6JVZv8AEkmg0Bu5vxu1N3DAhu7zI7Uxc78+BOpo44dZ3f0cIfURlIDrpzi/5bZHxKq+n/Ny08DhfdrJkLuMHZQB6x7Zo4PPHHFrVF0mrn+aFZWRFXe0RX4AiysZjn4rPCKQoeuWpVqstehUh73Ih9yoANG9QeunwZpMti5zJQeR7fEhjlmYNBfJwi5bPPoJXiItJPXjXz+euuxUYzaXeHNHEG0YWKTCcVDQ0V/5TN1l/jygHQT72N/gvHFyVs0SHB1U7Kn+onN0FQVZrJfeDuUl908J7uhWaUViCLIyVnhLLdVqBCl6QSl/DAFqTfNPY/vazj4+JY8gWdY2C+5lWru6ez/Uow2/RCVz/ALbdfAvp08utHRJuPjKMrSLMq6yxd2/GSyY+InCImi43x/K72kcpD2dVsRLoB3NfsVlM3mbZM0QniWwl3CRUq5t79BScKDoIRbueGl4VDslQ1I+TtngKyL+dV3CYgONam5rpt/eW2HkNkQrGP1Pnpmqrg5pcUJSuo8o4ai1AtzgqIvrdJ1UcnVABxjC/Mf4N+8JsZ0rQ0SrLu8NHWFks+gzL+uw8ku97ggiUlfX6Bm8iXh4etVzOMfRhXO87OR50cFIHhCpeGmTIV56FaKutc8gILKZh28cpFI5jq2xx5uFR/W88X890I6u6d6bbnU/W+zq4UVDh1W9vmyytqM9MlUPWPtPWgeJ9wb3JMRTMW9Tw7ImpWkGCzRRcqoa6HBfcy/cUSu8TOd87chjTL8mPTMEYfasJkgGn/1nwCCGS75yXjbjdttBy1xze5v2TxLH7Z2n4PfbuStjjwdbZ1cJ5P2d3NN5wdc2X4wFuRHuzerH4xQ01IYeezIZSr/cbgee32JfxG6ZbXPpKWknZilzXyOZw6GBLhjtUEE0lVgTZMirDMy9eu6QQJUzu/dUEYdoIl+MnmY/tjJ4RChhIV1gt8rsZZAmlWP0pHrML+Y4pfu7EdjlhidRU4UGP8L5ddHU2Q8177T0SG+VVXuGJ/juOVi/xIUP1nJ7jzxyBerPFwgbzyIXXJZnxrnfrR+IG39LU+BIDOu1aCaQSh/IsI8Crg5pHBzDEXv99SrC0BIMesqD5xXCZIlP00Z6nl22OCwIsc7++sU92/K+a16e6aKSfLZhaD/J/jNkOelzmJdvYpk96zxqHS8yCB3cfLcOx8+9YGeRL4exhI94TKWKI2OoMhYndilQXWLwXMJbRkYiJKtebb+zjFfJ8X6jUjx+IbpgN+ogHDPJtzjOSkDCruYHQIretijHRNK1F3PeRGMhsd01yiw4gecgwoI2t8VetIx8XJk/gt9Fpy1YvZTJBx6Gkq3ckXjTtdy65sv0BTjyU2tPAIpeCuz59eWZx6ztzIrFkPn9dphTFzvjlGtdusSJeV7D6jqspuR3YEyZidpqUD5w9EbHJ0/RTbDvfpmKBSGUjjkaYntbd2yt3kgSqCIzoKhmmqz2qrRmAr6wQb63rVadAtV2NWJlywNLnfx9wbtdvIvOfgUsqQXN6x3N04r4zjuX5qVmvOlAgGu30Wfo/vs1DmdWQbwMiI/3zo7nmHDFOOCFsOfDv24CNX+GCgDDqTJ3D3VPjdTo/nwuk6H5NKhSPOp5Fij5LqOXTAHyMof1xaWqGMQaIVJNkP5X8QHNpFNCJuk+E/VqZlmzUdpe5obK5Q84up1gfY0EanrPuwHK7kbSkKcEjO/HAkd86xluitLW3LCvnUq2B3rxRf876uTH+LS4N/pM3qpA7GFf/6N4RYe7G+6XERiv29TTEteOXeBa0Hi8VysGO0YUEL81YLCcRX9D3JVSY+wxEDk/WxfjuEWsg1maJd+reIlCb6YbVxUZSyTrHVImIVQZV+3Vms5akBpvh/IN8tkqcih8GAV4SPgSoFy7P8yd11ZjipBdjXzjzefeCO8hz+sEN4Iu/ohVf1mDdOnT7dUhVCSGRlxb2QYIS/cvXMuhenbB7nUrzrK8pxQiNco4Du09+u3nHS3zgcyuDeFhZD2agro/LW2/Ttd9qoEkjo2h1+KAHuMao/SfK6xCUB4kDPLgbZfQqlLsVm3z9D/GgpDNZP0Cor3+rZQD/I8c55T/25XAj8F27xCHlT4Fm8bfYmz/jniB9Gsr0LR2l3/xtwz8atCy2/rV5FOido9EYfcaX2xS7wd3nOvYZJGxLyAYDqWybWh7Tgnh0VRgbFfniQHdAqiP6wJCl7XOzocx+94UjXmPoE4PYiKcqmUT3A7GUUj7viOaXKFkBQguj7kgNdzH9uYzuDOdRlt40/FyMMzpqYGqT4v8ZINH6/Etj0z9b0BL66yJT/whLdyxIWXiWevWXuYrD8zdpG3YnLFHuWbYFz15rd18l5AtXXJS2KPJjNTGXTDMsSE86LDBWdUIEwLR+Lx5bO+1WVvoEZ+UfDA3ZOk7HDFE5NyAvmrJ2N78zy+UJrgKGkLyzyi/gzkMpqI9RmY+UsNOJjzF7LTtfv+Fc2huf35EBMegeLhHcxgvZT2d9753gitSyjR3RsPnTQFIVqiMWhFaZFE25bneZxCS1vSqSbaQXmSU1bYAj84YNJxEImMnBvF8uLsvbJwrUwWjhujfvhtb0cEcOqmguzZzRZ1XIwmczPHnzfXHt6wLrLsX3ekD6NUEh/itsUjEPD0V5Z/Zgeb4WEoKZzgQc8Nn+HajVYF1otI7GDihrYWb9yRpnxvp+nhJUF0idQFKamIfZh0DddZpvIZNx3NDuKqNsToLyCfifVrfj5FWVeipi0YQmP/KjpU8OUAx2bRR6m+FZW0rHiJRVyC9L0o1Eq97yor91q4vrjr8irozQdqvBnhq9E5GHhlFSDvpXgFIF0QbHVLm2+/74wBexPkrwPI+XkN4uy/6eOoT1iSqjkvIMhr1/1bwO7slOQ959zQO1fL1WPJoIFJiQyiJ1Kxfn/ZrcnoLYJzjf67FQmV0T4k84EO+yroWRpqU1ZT1708ejhiLX0n5iebA+WhPly4Jm4n9VU3rLzocxuTi1Hd2VcegRK9rEuvFAEXc8bAOp4uerfmlmRN3whNzi4qlviFCvu4BMt+fiZNffWUhcRNfXeEyFhqqpPZ+wVvr1PVE2/6xDwtDNxypXOJN42Cl8dD3TlceDUu0XLzgqqlKOwH0cqzxs3Oa7VF/5tm2qMDJ01YFVs0sW87NhRrXjuPk3zfdqkyvj9nZ1eYkgFH/z0rbzNYMIO77kfsbmjslQvmzoG0M1TMd0CKRLngunmvjMtFfTllmbaqIzV7TYTjwA91N70m219f+uW0i2UfzNYAP6L7sRgx4gW41mBrC8kW80gti4AaZNy5xUH1bTR394Cx5dKeCWYatjaDZCsSF7fUykZtjrV6YZ2zKUfv8nGo3DNwiudfvQsAw9zt4Yr8cHxn9Runj3UrYBDZiDlvGFd1BkTr7YZVT9d1sQ8Zf6s1HOgE3B4LLSpNocOxdxUlhawbDmJnhwUkn4uCHLAGj/VH9Iv0/0Iu0AvsVhEfD5MJU2PYMfm6SNoEkCemfUSlqf2Q3J8LyNeSlXJ/UlrUmc7kH3hlfiXkQ58xqrn/8DBKQO+HJk/rHIxWv3+sJFIhNbMrSiTeJxj1Vn7F9ODuQll9uFzfafG16EJzVcH7brhqWIze7My7bJOAjw5ATTFfYIeS1bySvStlFZFO1d+v+IGBwC1s3Bq2xCJcZAbHvXy/2L13m+1ExQIjcyT0VqfSh1+3hb8hr/nYHaGl/VuVClH0rL22iQEVYIZPUJN11mM4sNiTWIItAiI6slzvWDo0SpVXtnuSgtfovi3iJbITJkDIxEovUpXaB0fwwDcqnKM2fEKhgdhLt8iPwt8hJtL2nYhZ2ecW2b+AEu65VryP422npSiekat/NSMyTevzcvZH9hTlSykZq2Z7saEURW5m++zbCWqJAFDZFU6TPGY5kFw/R5j10kSOfWwtEQAXwdIYxNIC/Zgwtj/sQCS5DuxfrgptIroi+sKUdz0TInTufLkC7xY2rhZZ+PKd7LVHbPKaqWLt80EQMh9TY6Pxz3jYsEI8KtXUP2VL8bUVfkc44qaNWzI9sg4+KyeBmOPyo6urU57XeCQqRt6oV8nv3g+euZV/pnFsZRFd7omLvEEI09dZeAJgFWWZ3LYRFdV2f6Svvv5aw2ZCOB9LBkTHzGv1FD3blYQUitf9Dt4fM6anj1wJZIfB6yljYqbGxqqbo6adG1qcQecal0CJPqVFWieg8wo5ptBC0Y7ne7qPe0QZmaKpMfxuDtxK+s2OyVo+P11vYC+DkXsEokaRj0Tu7SNTffLYdLlgtJJe2aVfbms8QrNHWCC4/9ergWUtBqSSIznO2e9o9SzP0IdD2TR6e6F6/Ko6r1N/zZm6dMJm31K3PqEDA48qr4VOntPajmJpHSm3U/UqDkQZf2XnHZb8Mk7bcKgAB73beAq8gcucdZ+51KkV2rRnKmKqOoO6VA06Uz2U97f2kSMsbvCkDbEbG9uFDwDrkAe5Dzj1LdKSUqcNiIEIE/4Lt6mVVU7TAZTalVHrLRMA/VX7hq2RNvRqt1hEEppvhzO0ah+bywp2cUCqkInI/tH6oZYBUBKIWdU9WLAddSLui4Js0PezAp2ULdK9fB7mpU4PEtvdQPfmgVtRFatXhKY8aJqbdkwYOFFv/+uE5xD3oBefyG/PrfzGZzuRRH0/nD8QxK0SEtXxVEV0Z8h5Fz2WciPZN/mp361B3NTGw78yd6iLUQI74h8GKpgG6/toXeMXddP5l0Qg/Zeb8RUyWO5y5c7Y0AmHIPyVagHYlgux+0Q/Fa/2teGZtmxCqj+9JgrzdQekKrOi9DAvn41/WakuLvwAw6q4EG/xJcFcv9YahswFWckl8JKPRnvGZN4s0qCOTJTO0JfhSYophl+LQWqhrNOeusqzvQ57jIL6sp0ccFy1nuOCZsjk4y+H//pMDzQm436TpJDP1LnXySrhh9opWEt2Qn6LaGFsZ8ZhRHpQPM+f3VAC/PXCyjmdUawqk2c/IX5xPFvCu/xQYj5vDSnIgCFtWnaYjw3Htt35EYuRXeMY6TNDwgbjLUlXnVz5kPio1wTFeb/hfKGQqiStaV+GuAI4Kg45ME+e5Xe7MlN05mUXfvgmUeVVisJ7SslD96j0YRfpfk77MKFeiGC524HsQgkmgjANbatXfKzLmJxeCw51r5S+vGgBzmiKJHzegcZoFgsJTelxVcx2DmqtO2SgIC89jEhT8Yn8e5UPLAwl1X0UO66eqvmF4KxkjOCXudIrKTCFS+loSZ7ZhZ2604x+LrO6nBZY5u9PB7KLyJ+U1QJV2x0k5OtB3qhrdqmIOLf6Gtfp4SV4DF0ZJnB9PSrHXrDjqmsbYx15ObQfrnX80aHiyxXGG/eOAHz9MZXD/ULAvhlWKyO9zLXI0Npob+xRbOvdUqxKWgXcD/L7ZUV7Kc5FC47DXnk/9H1if2YeaPudaQqFLhyJPvvNn/taCrS3cMTnd9qV4qUyFESXQK5HqeqRr4SuBsVHSkUhmasoKSCtNWlmDhxQoNTuVuDAMwi4QvPRKNqNL/LOiIuEgOcR/1kkJ1UTaZl34HXb1X5ZcDmWE6yrNdPp3pIocLV8i23DsME2Er+yU8JDk8PLAQpTxmKAxOgOmPM9n3lptGzrl4+mtsKcqvMgLeE7zM9BdB+Wvf4wC6y+fg4MQ7jum34lY1PH9baCCPHs2WCd03XvuxQ/6lKe7Ow5ChcZw1bWcCVQrAybZMwZGVW22PeGWuhapcYZP4VASfikvJXAKUOLsXipE97hG4FgFdogPxBiQt+cV9AyGKX3PeLYgv9xr3CTcUhnEdVLsK+8nb4tTZosLquccmb50PfkQYMFoo3EiH+XQ/We/fth7hGrnJBdsi3aMPh7n+8F5X2Obsved4Hp896xv375UmiUxQihrrnNfiH7r032S7pcG5+G7YPa9lV6K1fJLs+X7XlMvsavnHxJ5y/UC6V1WPlFhMCReGyzU/Rl8CfL37VmpgU55TmhF5ZLBxkfNJrFvyYpOT7LP7RBu8NLeSkzoQUURxL0Fa/MzPgja8LI2f6yHNHBdyRpsddRYbTTwfxBtY03cGQILKSDNUVaXnvY+/uxVEguDTQLV439soipUd9W/GgwC5reze7nVtom3s+h/coIPv6l4J9X/wqS+gOcKG1oDRQHP1tdBiS3TKDV/UYZV36asV6p7dfvVUBpWjJjs3Dm/OSX7FbRhMgFHnRLYyXaC2euFmClFAMcjZCByXRgW8RxHT/vHAeEvBpSBP76bfh5K4c5yVChejnZs/glTazI8t/zYMRGqzCd6fzA/ro0IbAwu4DmPyK0R2SNbBpRKbSiis1wTmME+yus2c+3Z3zxa0QTN2a5+ejIGed7LZgRlqrt1xSB6a2hvIHBTbFilo22Jm5Oau4CxDINmoLPbTQH9N3uhUQH9IuLZzITBqdnHihgJg9q7jzyQD/Fh+iLROk7X8yoaAAWSr84cRw+8D09V9w7g4Re3j7zzNImQFe3BbG4L47kqljeK2cCq0Gj1Nvqek2WVOE868VcURIzeEZV1zcyCigSsL7eGGb/NRMYOXgqb1Hd+WLjTvxaDkDC+hv0qX69mVLdBColXhBVs7hy1jc2m2gG3dv53TZTliJGxWi9E9xIWXuE695+4YaGPmV9VuS6bhj0KGCiHI/LIhnHfYwLYr4k9TwO0AebbcSx2sjo06avNuva93UYjwE1HuLTP1/zzRbQUXnjn68RMYiSKDhWYW1UpIHoIF/a2uLtfH99KCVSJzvBc62r7OOHbKUlu7c5tvMb/984DMwQjZrcJaC3PtLsRUeAeCtu/vrJY5ifpkoRihDwAPGV8jbuUAnMSN7PmZJx1GZILM6ypXHYN7fLldmm6KHz3mESJ7pV9JvdWTAWUZIOhTJ+ShjUi2TT+uO60fx3Hs22cAwx5nRI6K8TONdUNl9urOgaOCJI+9tlqgv4GxLH/NfY+Tc6yE6blT5Q4bG/nmC6CPPoOe8EYSpNpzslZWIGfIBZRd7RG3OKTeqXFntLVFM19IawcGNZKzmkNOjk+8zqzW58J91KYv5y5Ejj4Z/52IbIWtDyhrSZhqpI+8ae0fG/7tE67Sx9peltPoUiPB7lp+QdZVqjOvi5A3Hmvystg2GB65NCPjNQ9q7ZFK3HKmXgymN0MW1xaDsYw/bVcwXp01q5F/2cP+KvviZRfGOy3NvlDwC+XR8oLCjMoZ/M8HxTwOsa8fx/3+bMr/5ob3XTld6gz+P4dx3vFT3QDOcv7MRS2zonahpjwB/XD0znhXOxovwPyv8PymbpWhLPVHCfgDWdA3pJ7xFcaLh+Lfjv59UF3vIpx8TP/2zZuukL/ESKHDGUHS9DEqh4AFpis2wi0Z/EfchgKA5JoHZx6OB53nXWc/3ri6uOIPql8QACH74DsSwBApC8Pl985vgISnXHok/mYhmmnyz+ih3pZ8Izl7TsSZS9ll/fcV8UE8qBkNlu1oMs949TMqFyw45+xvgryYe9Xlo+9ukA6ULIjSmOyRxNE7YavwFJ6N3EUuz2k+D4Sd4LWnmQE7471KzR7z4MP9t+HxNrM/+P/rDHrDM1eGVzwLlB50kegEAqNf6VwHJ+rTGrtaL2FL1RNEzhmO5/JUG6hzMAa6TtSYXW9Kb3jvpbYOYdQVIju+os21KD70IDW2HzYD30VilVfdu/GUi4+ggElDGlq5wPYIdvcbg37lU7TOmyfWkyjPL8ZbFnfWLOOZu3nZo4LjD6x2QdpXSkHBuZ3834uo5xIJSR43+jEXcaDYKTsW/E49Ow7G9QrUSOrcaekRYQZptclBplcelS/Me8I0YQBHCR9n7IggKde9TUynSXqv/lKwhTMElwy+PvGuO5plYQtUWxg9Vda06CGS7boxTeQKiZF4dionAfqSVB0XvjXHXgOOXvsyxD/d9rxo6t10IQ6Tl77iHb8fMk4AZcxdpXGoG+oKLsxoRUeCqIIwie4RCr95s8gzHlqwAixTZ1yjcepNBeWeDas5Tc72vYOlhKMTSezxtoRPd606yv44Ofz27l+AgrTahaQHEM1mLNZv81sGQ3/1ct4zc6nDcwF7yVO5nQ88ZxAzjHiTdSs8czBu7XJ8a3tq2j+opxm6mbaSXr4jx+ZgIIgXUIPN7l2gKV2gvKlu3nGZBh7JkmDgwT0UHWHfnR07U6SYVQlfU3A631mIEw41hqX87ZmOz2TadAL+vmb2h8wgMMM9CFIR5tAeHyWcBbsVvSt2oecQH3JpXYIio/xqFa8NDEsiD/mfDn76v5pZYQpUvQcpOajpT0ySBgv99e4mORlKHI1MlZKpjr/8mqwJPvN3HZhev1gqD8rlXki/nvNWMKPGKvdZUcJLj+GPPvRn1FOKh0+kY3fHvNDVk61AVpvHOp5AP8mOP5LMuijNanxf6xDw34lsaXU8oEMb4dzbhAol4I03aEgmhonPtozhKTomj/PglgklDDw0Gp0vnMngzAUNE8vLfv6NDYwhBel8TkOOm3ns7viOTe5qFFeoqGHsEup+7PifmKhM/QWtxJVIZTRd9fGNWzmgpF3vbf/uFI7gRnbmy/a/jw1TKqUG3TfrCvZqVbXGjAHC8ei+0zlS4eWnNJJ2LDb0foLAhzUnlcbrSp0j3g2MVQqKMd7Y3CKiWw+8RXLbxfr62RUNwcv/0nFW5W/Y38YSiUa35styyC7s8RwU/yC01U7aGws3ZlOn+AMB3LxW3xGapyQlGE+QZhyaC4+h9lffZSGWjpeUOL/9nP7vjFa/dw5ARwioJ+FR9Hbl6JfHrrJNsfDUgL4/10YpanFQQR1QT03q9dXvtaHtEVvw1chbCJ4HhK/har/dVR2QIxyCutqFUiOAvwDFicW1v6peEqIne+9oPhtxiUwtKnw5wMtczI1KH5IBRszxX+R26FZDyJB07YOlhtsD1VFoh68cuLnXyR20luug8eWUQVgk8Szo3D/pN+VawDOIqy60XLp8ph/MaIouegkVAZvoW+VzVgn6URuKYoX9cETy/Sa4dfGsHKvw/X1nabYLkv3mQ+TEdNQISA+dqX8L391eKA9tI+VMYnCgmVxOsdDV71upUIrpbG/ilusNlBNckdOFmIupReFBwCmV3Xji7+mBc49StnkExGxOLHztAYRRGPtvauKIKGhRyrf1PVjP0tIErn9E6RRe378E5qMwkaBbPDL2+Vjv/d8tegkuMYnN6SHSW2GR4ak26tNXjITH+vMG0a0s9+LfkM/3NmfKsbVfzHBDpw1Lkn9qAhivDE6OwpeBn60EAe3kru1Y78pLz4wn9NexHZfYfw/nCam0Toe86tgLaHO4n8+xt9aDDu4Hf8+WujUEVh8WsXxWZ5TBSJ61Em8aU9ol79tZbqHXu1E0joa/Oo5pns9AD/yeCO62M8Eo33u6+fvWjbMoSuM4h5neEOu707LANCui0ncrfla/xaHIN5jpCu29mROr/UK+fDgH5g0yp+swsht/HrgOzL0yasKiQO/kNeBWIMaZSC5UIJV3dFSgP73ezHF3oKVkYPdnMO9May/JFe1k28X5lwfVQUHDBMrUXfUY85PQ0Ag9iW6yXnTTl8viiSp3Q16xmUQ16UQXpZ25E1zZXx8EG6wYf8JHY5d1UYdUEellWuqTmRdrZbR19ZAAtfr2wGEYSNsKZ07guRBgp2sSutcA8+AA3VSK7dNqvi+a++njCPfQLzpBxxVQZasn5Hwc2omjTt1ySRUx+xyJMgpsSPALh1QIdijnkV2gJPj8Gy7GTrn+JOWoUZ3CClSyLsvWjhtWhhD28LyGW1g8mlivo2HPwL6jqyNQ7D6DuiTfXR8897vsO/97GMy42O9OW7IS1HO0mYZNwsSGDVFOuwMeJavmRB5mM2Ust1dMru27dXDg8E71iBEdpGIZKTcCZP8Lweq/J58JHdCuCevgzjEggIkQWaUQkk+X5YNConkJghTi1Cw1iDIfIyOYlW8mfP4bGmk3QdBf2IHqEhJzxlc65gdfiNEIbbdhdqDK0ZYiVCj+z7PGBiY09gK433+oEhJHrABb+FZHdvSVAVcGk4/iXwowrJ6uDny2NFjObVf2F0anwgEwlZ7wt9rhBuSmg/Uk4PrWXm/nKPtF97RMAlYDiJcLlIP2PVJLEin1Dqg19GSLrpx0sZMS1mQd1Nr/W8Udv2Z5Vll6kH3PBnxekdyawXtrffH6pGdUtFNMYurduZYhiCCzn/nvJewNUrQg/4GdiW632xn+dMfMoF5ACM1jRFVSKos4VuMOVtSdIP+dhBHcQqGVSKEMzKj6KP3stnRmLOZBnPbpwPzGCf6TI3CtceuBoNO5dPrwuXAgkX4Gqtqfv7y1snfqZn7G/inJP8j4C+I7Vfmpz7qAhXGrZ/SrpTYcjMuXIziKLwYTlDEpAUiz0n0vLCoHdz3uvt2wZRNn3Kfp4t+rf/acjqC/KgfJHdCZOv485fCJWNtU90286pLc4DxrTXfbRCSqpFYBmodd9Gh88UU0sNY4UDFY1kva2ocmeZcY3oM38A/5ub1MUFaCRS6QRYGgmE8rHENzVD4g2eiqmV3OUDNfVVjF81+VYmbVv5CrFxextM3VRMwfbsmvPagtsRDKNfrzGxKiyyAWFi1Z0PKu2OvAFswkkumgUfKqqYGJLcwl8CO8XneuohZim9xRYoB6GZqWzifLlVUk+Kbb5Ggoku4K4jkSR7cCqwvgb2TAp9jsbrp82E61kLV3EsJ/FfRIPGbyb2jjZJPVTopFP9xPwjQbBcOPJAe8Uw4PK/riZA28/Iuy3IaixWhQVdFkSFZP0h89o070qQZAK0fIc9mH5pmtg/BXR1RsSJ2ThnO+1bwGgJD0aAZmHOmxZe4rNL0d/XUXbgw0Pr9926U/z70Zg5ukv6clSsl3QGzyeW7aCo2XAcb2njKefwGOdXnqzpeIrM5m8wGhVoa1Cc/oz+W5K/Zp8/Y7j0d3Hc1WH2QQP2m4IfNvaWo0/Z0KiYGheiTy5HkFb3BQgpw68x+XvkEZsUoQyGW8KDIkh/P/KDQ/mospnwgxALz2a/OvhXXX0oIWKeZNIC+K6YJeD+KgnTIb8gulyMqCTPmrVwn+cOZJq+4z/xl/nXl7CfXWqFUV+sDqPOwGUYiifgk17S+DVO/dll42jfRCU6ErhgPC9flfepNdDE9gsPwoh6LooePMJ4XABWusD+dyesjUGeD1FZxd7ol62IFZNYv7x8T7kdLVqfWLYfrCjGnl/2zzzc3y/KEMG7IBb2MmFHlToaEOLFMkoeYLvoJ6WJTMi3ucbaIqIOdAP+1D/0zHLFLogGBTSzFqd3+355DLl/43zPDn/bKhiDD5QrLMq4rV4P5X4WDmJu7jb78iwnEElgpCddPJx7x4+fsSKx/Y0GjVE/nA6chIGDR123k0T3qbrEiodybkeowUhL6mJlYCfsGeRbh4oXX1BeREVqjl6dF+Dojscpw4XeZUrFdowIvlgVVNIOjdrylloMrmZQZaE03NBTlG1o887eq8bjIVjh+3ijky9bdvBhnqeKH7o7vFCtejbbaMniO7wjzvPUsYnRuUdeM72gH6P1Shw5qTBfyGnyNeDoBU9ghj92qT74VORTshjBWw9xEe6KxPsuRlO5X1b6bEMNa3Ay69KqQzYCG770Ru6YekBeaYCOSobJ95WwTmaJTHfDTVZJQOsChH9xIOm7uF/C+mhnLuMm+lfWi30HU/krXrNSTFBl9z6n5ztsaQZFLfjwX6LiRKoyULe3tMTI2a3cVQi5T0YuDHx+SftYY7a4ld/f1+Tn0U/bs7912jT9aju9pcAjcNgdEsDV6B+Re13KWn16y/0gb6y6H72TaGRkFjs8rIWBhCA4Et28EPU6GKOIfFtKbGnwKhSNSoPGGMvyfPyG8Nd1sErOBheNSOCh/85lFMKih72izmwepndwHGuRrefLrC35LVNSkYC61MuQvPHOKrEDWuwwGAAmjW1ZOkHUmlnX+MLdQGEXPCZNvYBJvEe1gu5poVp1ucqJWrfCstbZphvN94J/d7QDIJvd+cak+ptWGAo2hwakELVwUVnO1k0PTs1zd+cxfF9lYjcQSqwT99m03CbyMhcCB6wUki90SsntJOR2m28DbvtLEh7Ii21DMnQZ/Kid+iU1WHbEpb875CgkleYmX1zUMo0OB4+gRZ1wbj3UCFYvEyIMSBDq8cWJdPajRAJxHkT6O8peOGJpLaXfp0/sQ3QkdtbxHqh25h2+JlH5xpUH3lh8ZbNV3Po+3k5+ZZ3AS0WXVqGoq3hktsr0XfCxtPK74VwQwbs3Gr+Wd9nc1dNV331KCTeXU5abkj3FXN0tv6ZkLikE1bnDiOXqpMjWACRFJnTTygD0KDkj+aIzVX2xZEBDA+uW6q14UDEgkGsJTh0rtDv5U/pLthfOh2U+lDjsEWNLIGVsxGARZuJ77QzWHW9qW5naVrx6S4dVi8g3rhPYI7HbShIZP4M0EOI46m+XiAUPRTjR7L/aXOqv/xLA8GzZ/mpdMRbP8yvilpxu3qSqB0ux/LwmCZG9EhtJoVcVG1MF29xHetQ7pqWQyLGLhjev1sjYSog3wuLHszUZVzGUKOiSm1BmCpCbyGizoW5aYav7qCWLo/0Gskv3bT5xEjDfM1t0Cj4OCvW6JCkhRPTRNE30RWnTQnTMkIWGfL3vmSGZpVbutXJ8xvoYCCmzxtTEIu20AgV212W4SByLwyUlFxsGw3LIL1RIXoCLpQT9zP+0w0Vq2NyvWTdJDhXq/spKBJ7mPgKPfBvI/mXrbjv1SBDyidqPeQW/lr9LFF3Rg+AfwPqrMIdCr2l8L3QJaTjBP9wb55fKZwijCS8suED9BXGu1siu3ag6Dd1XLbUzZkyRyo+NVv2xph+FnSTp3Ouv97BXwG1lib4lbMc1ayLvb41V28/WERPwQFsNSdAGYZmEsXeHa2WozbR5Veat/IUwgA+zXtwl73It72yNdP8+2nU6WjZmIDgnod6nMUg5L4T2vjpEC/r6MXdCWTSnf63ToVJzfTCnPqKVAM4L0sVr5RVJ4x+bGmJveTd4MVfV4qhoFSDIn3GfrXGZSiyFqX25QnMMRJa82uWbRBVcC0cLFDIP2j2LD3jRzcrjnmkaFo0MseH1wEhFHqyFTImAomSrv4VzYhiNWD+MiS5LDj69r0INzkkCYwntpZlIQL9QWn4NpgbR5VUZBl6UrfDnYAA+jWyAcbGrpADrPncDwEpVh+6uAfO99E1KIZE/otgO/IoivxiZzZ0mk3jv9sF9mLF65di9dV7OaEsqopRXsNZHsncdZBFWhlfwPD5Tn4us7lE9FcDWbeH+/YZ2Ep192pXPdjhycPjMF4N8URTw/vyAGtBawGDMIZEOd/ogqiPlKw6i9wWEIkeLodP8Kt3AiZC8zap4rZKjvmOsiRdGy1dauF1XV0tdCyluCy8tbfo9llszwJJgKCOSOFF6Wr5DkYCi96KFdEWYDrlUI88slZV/5/Q2fzcDuR8pWCeyCk+5sflPPUPdOYMQNV8xfSRpaC7pIprYvcHGOexh2XzfuFBm0Ff6Bg+8fF6hPmPRFYaOGk0rO6m6fuMq7eiLWGSgi4Hf8bw5Mb5CUR8gUL8sawAwAdaL93YLSpa0wnMov/BpBAbdHLLAbuLxujXRfjGyNo/l9AdTDw/7XIJHx3PL6umykhao9cJqTNO/QnNiq5b+QjhdTtpBkeVI/rquLihXNmpSJZQJCGUENH8hoaWLvh9b88wjQJDySmdC4Q+kz7lMKODIBR1BGhy/xh1uvL+ab+PVbf9u3B0Pp7dMDaH5szgBIhX8tf6JnfUxzW083poLF3Af1cettX1A1dBUD9dblb9aQ4UAYLZZFPHStYPX8BFMJOzbPISzavcq2PuNwLkFoen2GZhnhdtFyqbCmcNorSS3SYxy376Is7s12cEFheMaVUsWca0qWO3EgRzCEdT0fFb9q2olPw1N7BmY+sgOgemM0Lei7esywU+Q+xJ6jFatkCPfIaf4aWZeu7aLTYWv4FR1WfRhY+BwQeHrRlYmDfZVUknY7obHNnV7qcZCc/2tDkDF0dXydAbeJdcGf/HBZp55ljqbwfnA2ryTZVPZQS26aaCCuqr5IEsMOtsC6SqcXoxI40IKGZrBGCm4AczhviMzGxJgQ4k5uZk8QafI/LTnRC7KBuzb8NJfXxLwVALnHPz8FUbpCEZQqZF35QccpvlZNltznx3DRTF5mNPHzF8hmofBnt+fDfW4/FvZxPeTvpFz2e3qA4VILjpwBvTJHu7zDHDFr7IHuTxT6hC0RoS4jMlfelIe1dpAiPm2OM6hPJlJW9erkOk6EG2mBPjbPHBhpW0fNOWEWXRASbhwCNtLfv4ti8wKaR26EFmxa7/TAPpeJrd7OPVVc1VRY+vF+JdtvRlkExvU0LQr9vZRTd1iQZNl6yfJRrFfHri0b9LBpzL0HpyZJLVSngh4p75dBfhgtYN9pANPNhNYqnYID5mVgffYqFkFAi48bxFOqVNM+ase3c6cyS52WRxbEgY/FKjK7Jp+lZDXxewnpRXl1zm6V8yHtcQp6cM6tBEXEUI1CMcFLeZZ7kwJ9u2jf/Lho9PxFthEMEXI0/uEeU1twZ7npsy2z6B0XqtwsIh3lCx3CWyh9GOPIa7f2YYQz3PSOkmitm/RFC2hgUx1qAAR5qPpOkJM9NZdcvQTelwaySBu98uOtLumDXH9uUeBYA79G6rHK3BvBSjklTzWiOGbn1OIzWmmtKcRPxHFWN55LB8NbqcNHAVhJ8+zpmUYLalVeF1aVRsKm0bBpNtUR4ESDwh6sLApJTScGQ+GmviiTfyAfvjwDifoUsBOvWTBfH7x4iAWF0aWuWu/9Pw9o2z0Nc5ZOq7LQflE5vvxePtF2qZu3PaqTtfvLKjpW6gZptlSyVrj1O/6uXcHHHrnPvL3cNQajNZgqBOGBiEAA2DFm76b30WJubZrMzzD9S3jba00fc771VutFL/E2z+BpTK6gXmWUtM+rg3c5WwlbuC/r7oVIV/5Blet10XOyWrwws4z0FL0CP3VFO0teq3AcMqnIGSZLk1i4c+2Xn1mTQxcnKUN31MbwgGhX1Ixjh+MrIusS7S8PHefx0pIQbEHN/V+vxmT81lVWszRzXGReedrzkVbBuuvfBhIHF9fwI5QepnZEYFYESR+QUwXqN5+3ND+k7zWZmxBYMTLlSfpNkcnf1TvArYI773KGFEC+wIC6SFo1h3SDIldeYj6cousoj0aSzRlRmhdBjZNnMHXz+IkvqIoHz1LE7LRTUfqLAPM7Z7LTegowXdq9zHXN2fCvHWDTZENz4joTdZgmN5YsHnaCMPFKoV7N6zKCeKA5Ty6v2+IeljlcyNHBlQN39KonQMcIp1zX6u7E2JpKxlFYfi3ETs5zj/zYeZUzdA1huFkU+himExR0U7WQVo9SOAH+3sX/zmz9NryDjwLLffhMLQ8R8EDJAuXqBUd6y7ISSf2K7HFtqdBuswTOLv57jQVakMSEUIv0HojGYvrM4d6hwauRmAOp94iIOO1SOZelHTzK8JM7/O3KKUuAx0hxXPFuOq2QldFY2HL8Eh7xhE40Lpe9JqBnY3mOxPbacaH5XAY9dAK+QeHz+gjHCdLzXLXx/CKZj04i6u3Y7NC8tTcCiYoTNi7UiFNEGTcz2sDFj8ya54cVpysoCPZNqOlVUI/q4FLgFnWSFAyj10Ajlz+Job1qN1bUCDc7+6ovK8UMMvtF3lhyy/iZFYJLa5QHe2VNcxkPY9HKc7fvH/z0YuP1VfzgfhZoluMwvmBksT7XoVxuqWRbos5kYaSv/+QMcA7Nl00SxVTX4nsWFHMx25ZRgbPfBlXp3lJJv5tX4IA2SDMZ+tjpKiaRQkj+bH0Z0xOrmbWZUDA+LC1SPlZZ/rXhMSgwXkU23a9NsyXQ8CBitm2clgWOX+j4Xt+0QWeQlAWEc6+0PO030fbFhdaQMy3ug3rU7jF69H/YJhBsUAJhCGZT5/QV2EKFlfBI2R+21lhVOM+OZ5Xb76a69FhCC5/BMjoIYFSlwZFT890ls+Uby70wgzpLh6LVWggVpX0gGrEBxgc6TzCub6pG2pL5sCJ1zEG1uwOEwfXbvAQKb4t7E+4FKyNnQo3gLgoRhziuwklIfdezttIDQ0x7G33Ez/LJqnkzv02nDW0IKpXd0uDlnsP6bHbA2fkszbNm9p1P2mrXq1swsFjYV44z7G9qzwMcGMw+MrejQJ3bm9sJid/mNdr32g9SSFeCMERMjhXszbUUo5bJx6L9eiJMCFAJT0RIcRq+uw4isvdtw4L8mAGs6QP6fTdj9VvgcgUv0bDLiOipCt2xHQastW4AIZa8RkvqN7ax0wlan83JhWlPflB/3WuBeC4Gu7H2EMPpTe4+TGd7Hqh0EP9+gNy1FW7wPQCDgu99w9jO+QDLnNOs0o6qNbAfTfbLqcu7h3GsBfaLcPopzjwT3Y9AIZAtriHZkT9oJr1SnjGudJtjAhvGL30Ts01LGl0ZMdQwyXpkE2As/pU06FFKb0xQ4mTBJtJOqPLmpLoS1aBgzTbO1jwbNQYXeHuwYx5PXwMfLPmti27rAo2GwyT37O7tBhl0CTerI2DzGNVdWn8XFxu3G6R6PppT+8dL7RrxYwv1AZQhL3XUfrUlTXGdfje1skGsnl9n5VRwZrve/SiDpP5cTkSMDxCjngIaCbm8oNfpwCPgL5LxwNA8fMSDSi05d2tGtxRRFq7GCzJBieGssHDCSkeJXEnwCdO4uJtL9wXM2bzY+vKyXVpxTErx9nqy7eTbukub4q6rsE5AELVZaeKwzZ4VeGynIZhkPZTGph8wMhB8xOboXVfTDOyOqoyFFSjtLbFaaO69/m6J5e/B/pUAX3Vnw7jQkdaGZ/M3IgGXfroBEDYKtm7dJWgz1ZvqEa4Uh/tWjP2THTGlypt6v19L23aMsuwvOda3fPGj19Y0Uk5S9buS9H07wXFZQ+nfcrfnlNcXxZFfPKBKyZN07dldpNpJgachltEG+ufTJYVeb2jpT92ZF7c5Vhe1Avs6mGzsXdSPzs3dlBltYM1P4f1KxhWbuyC+4hrtDsYz/3qo02sNGFIjajG1HVyr8Fw3w2Y9hidhpHE74gOcZd0CTDxduORBP4I/K8aC1hUmJAibchRwiLhnmbpEqlFxF3KrwVHbbo6SLJLapYnGRRjVnnDoF6D+PFXkWX5VlM8v0uQbjQepil6L1BWpaPObSNrs6/noMNeSY6i2wQORQpUmbt6Th4UJess1VxQdghmkAgKAS3AUucnzdwiVIcH4vuZlQbcR6TUT2d3HymiBf5higfiVm7RLskGbwW9c136SJJaRUUgvYdfG7qHyVpoTGfnaupLWKHsxV82ARBdFVAGzWZRZsnZdCL8dIUGY12LqB50Iqc7TUhJC8tXNMNjZxJ205WbJ09cqJ9JRSC2MiOvxH7UEu7zmM0u0ao4g3RD71uEOfg8jNOZVCJbIU155CVhuQ/fb1BjNMBefUogZedJwCBgBXXPlWNc9+VNfN0e6uy4lcUv/BXwkxvxCVpUwD9FmpyL0/gel9b2Xt+1P1VQbXwgeiEeigJ01dUgPy8SYP4fl3mZG61Iynw77GdzeMR3GL4n5aBHP7yNtSXxCNXnDC6kNU9cSbM7kws7tm1wD7qAgbNvrpPy3h65WwliunWMFzKQ+PLNqiRCJQ3j1fd7HfK036wGwRETODr7d/cYBI3AthmoAqDG3mMZk/GidYlt82fINZmhBlJAAr7EZjWoqWH6DbdElyQS8PsxQml4E/DtSDJb676E4e9Emb5Sr76kukQyt1Y5ba6ClTYckm5nnSLIYWSXRE6e7WyJQUXv4eea7YHGcUgU6pH/EgM0wqvZdak32OjEKFvZ3c8sWVzvT3hEj840FLmIQUFZZ9wIhWjJRIx/x6/vUgrAGaYTM5ynsaEB42H47L75SAsjalSocZal+3SiVfGM70EnFEwsSG82gCknUXGLCciJUZqL+eSQRBAeDJ4cBf/oUdeejL9PWDfjbuvxHX7frPe6Nxzp/TRDeQkxy2N2c7EXnYZxIIOz1yJIwioh+KzXKLHn+P5LGsjo9tADfXqyohbWvSp+1C/jSywN1Sr94e+H+dAEdP8Ff+Cyv1bXfLzbT1V9kb01iJIkZFrGW0e3Is9bucswxbU+IREFrqi2aZNp3B+FZby44XMzpQonQBjM3C0I4M5liEL/zLahAkipM9DZH2g4AZ3EGt8VnetXfn3C+sH1zvVjy7uAJHEiIy52T+ttfKaZP7/FdEPCsd6Fai0rLcKYTUQ7Mn28RwfbhaZvWVu1pYQ+kty1TFkZ0vd05vHAyxDGIw44iiQzb6NiGqE7gLoyQZprIwpsRHoADAa6NfUEw3hNIYQyOo05Krql6tamGfBm8F/VfrmL4qNSsclzjD/gLra6/qsN3rPXkWRpVOwxbGtJkhNs9tXcElsDnw+trsaG4n+1gbRhD7z4jWfbTM2r1eiJzJlZ024cLicnNz1fjE/Gw23piLh+DavmxJHYb5SiZS6DKI/hvJ5lniEsqjTIYULXM88XsT62lNBeF0Kg2RyOswb3l4G04xz8juRb6ybpzZhWTEJ6+UZhn5aZZ6NWpnIgjrS/c7qUWTwrupn/9S1H8RvVv5VfUHkOo9V362+/NnFkKArbg9MxxSdiIc0HdKHUVmZTrp4aRqT2zVMgchQrjcSEZerbF2YV3QH2S9lBONH+atkatHabiulUCuzX+1VK/XXF22zX4hkEJ77eURamyWgq/nZZdHvZF8UD3QOMfHq2BmJ+Em873xJyg0LxIhdjGblBWeBlyp3W2ssLmaoo0NfCE940gI4ibGxw5yPAOba/sOPtf6fAT3d3I2WENplX/6uNPNlYLznfCy57DvQB4td3+HARf8f9wx5eZpRTjlms2XlIGeyIcBKVFC26L+l0TMDfbkTiss7JRxT1lF+bI2TJzgys/IvpPywgOBCuLBo7locBfaTmQeRI/0mZWrTYwVJpu1NDg6aSD9vSpfV83O3IEjP8IF54mUJboO7bfFj1Js2pX7l1oEi3MuJkyke8V9t8VA/hLQxTPPiXJvVdkzyT8VEXWhUKeFbHX4nbUfwuSX4uHWDlLTaw6E+poZVL31hqpuhurpvm/1zlfbmKmxNoPPW53+uHRUvwU2xQQxX/7oUuo9KpMi/Vy7BSVwoMcd55JhhK/oCTVX5kYxY76rpt6PWrR1PIY1zVj364FMTzjIht8ASczYp3Pl/bY7B2Ccwb+EnS0AF6QSueCnFKOCeGYQ/Q+nnbGM7gUl4moF+IQDzB+grtjxB8ksEvg8LyS18KRCI27w7bL606aeUFvamyspbPTDbONmeRO+qzGpgAcgg+R7xw94a6zy7cOBMf2riF+BhKGOU9Sv4ACEgjf32nZv9ho8G5BYuzbwbI7BCjK3lReanFWQXq7ao+pQHvcleR5OczP3uoT3e5n2xYT77twRApbluz9hluFnEGKjrexUcdo95OssxxWfhABo3OVZ8x6AkeXVvTr6XvBNbjm8qZeCNgGjz7kED1OmbGxnR4kZ+WoWo7GueNQnqYOdp35GRg1B/pIb23JjaJrkRXqmOd8gVHY3V8OCOrZ/2DXx0D6Agy2pbua90SrpV9OG7+adVkS9GU9tIupbM0BSQHojZa3y8VSyzl5BLvk3xUjKeGr5KFI8ukLvru2pk92g/tA8O2dudYk3XTCI06+FtCQNLOQq9fwZP5F5/g+Q8ihSutbE9YFo0HEIdLQLhh/tnGOH21Jq0AylcOMHB5Lq9rwW02XGW4pISGc6ABYbbBcnERtlbYvlty3J08vJLajfxeJ8rynpuQ1XUH1OFW7t1IMJajl8YHWEi2ZJsjOBZd9XyF3/4lml9bsYJi3a5j2eh2H/c19JSS4Htbkd8z1ryaykaM08LBEhumMU0LTmkJQRNwlMgAbBkpEIoJBLGMoKiGOF0JTYOYumeOadihQfFQdg+HseRmhjdv1aeRlFghgRk/9DN/qevp/Ue97eZcWHCE/yGoiBW/XiYtso4JYldAhge/HxS89c2kJmYdcoLR3wF8jcDERlKhV15eupKBm+8VBYLgYcXoUoSMYlAIwftr+1T7IFVLeGwzBjrf1dR2kfRUxKve2fOsvYS2dDwIqGeEImmtcR78DEh5iYre5VKinAxJqeLUDPGBDro+MLlTCPOcvL9DO2T81mKqbyTpY8gxirmqKEGtv3TIZwO4lLx2ikXw0/ucQAKMMd32cucFskLto+jaTIUuA/BW87XTKDlHeqtnhKdn8y6bPe9Jbg22442+y68LF9wlxK77da0PEsJ2OyS1KvxKR50j0054T+uXKjeKwK2qTK99D85reuANe+GwFk484a8vvh6O8RGCsQhmpBKSUbjSE4/07s0wB/8BGt6YpxlfYLRHGCh/iOH5cJQiYF5bjRRq9Ur7X3XwXjQXbt8wcP/y+ES0CkIbezw+moj88w4SvoEAwE9Z5hsqSd27DueMiHyxdudkNd8xGOi9tnBMALlN4G89aQhaviQ3Y+HmEzOKxRAZ7fl5CRG/cwXzaNONuE+a5IhFVxKIPUXO+Bj8ZhBVljb/DgJgC/VCISk1HNMesRKVO+lCEmbFX+z/8Yb3gLSgwXV/ZSEt54urQ0jgNCoCqab/xS6gBuB34XmnIMmNGsRbdyetOnyu5s2gl8SDYRSB7aPvhWsyMpxpEBRl9/fsboB/twhErG94uEBL/zD6Wz8hYhZCleP+EqWM8wzSs06T4b5jXSS4/2XvvZYkxpEtwa+5j7eMOhiP1FprvowxqLWMoPj6JSKr6nZP986qmbVds4y0yExqAATcjzv8OPRvOpG6hvzloVTP1pIn7Iedw8ilvE3mjTgejlVNC0b4zucxXs/AwEk+M/+M0l9vrRQG/qrIyTeG0LsGm3Ye2sedu2ywbsw/fJkylFWUtW366bnBhSPDTxK8+cd+4AB/CKCxgVMG58xnxghh9dNyWC2rsxC/H7hhmksf0jRXMG+fnrzZN/l9cUPjlgHtl3HDUcJVbzzHA1X6YjX/FFvFqfMcJwoI4z+yMFg/Zc45VWq0pSzkGz3Joky4Hx8FkuU5hY7rA0l+aoVI+kb5yo4f/gZWjpUGhlk7rOFHXj/Xo3G4l7AWnNxhYfVQgPHhHWI+k+wuAXILhQo2CYRA1E+vFFWm8xU+1BemoeIhGrjYPniS42QJ3F4WJ4Qm2xQ3HTkCK8Pz6zLATTSrpgvjiFjnHphraEaLfGDaT31FUmrdT24/pMiar62rMfd6mOCBuLDmtA8m4/588Qf9ZNw+PN7PIVJfsIXOQO1MuAuTItceGiQwf7GSHNFVZvPr60xuq2w+Jmr2Q5DSE/SyNKuZg/zhft1FhwQQF9cKOON8mT/uMwamCeSOwB8AOT80wbKCosRa3Gj+urK84SvJLSfJYvSbZyCycBTm6i/PhqaZcin817sV2ebKEJiLTuH62Ojnu3iEq4WY9MOw0iCrXQZ/vUcVqmLfGdNvrR5hkzvRMCzpMIBGA2Ou9aRvO+vtPYi7dPwUf47c4op/8F6gVl8qbJPHzhLaP/2PrHwG+bw54Lf8ZPNjCsjAKmcfC9xPg502iZZfGCOwX7qIzD45UZV0Qbp4T81/bswK3rH65+pesV5cL1ms/Darkd7ejX0h9bvfUqr6uX7WfKN1IiiO5ZYV1mMVYFkE9rymPt2f0gI/Ob0YO6kG8eeRepHYPPX4Q0c/vchsZcmWqeu9iA5Yz5yGkPXD3jgJIChzRh49vIh7R+RIdGpym+4/jSJLjkbUxcTaQ5dQtliti6wtb10VnzIF+IDf0BlrbKCSu+YD6o3bMD7BcuuO8ie/EeGUYZqwdbYg45Q221ocPny+DjLyXzR4WyczuASLjCA6IcWEubcwFPvqVZ6FKO3Lpcv6L5qgkCltnLR9tDkHDwnyZ38vBYI9heGZcdKhaisf5LyCD0t724byTykYXfJcpjqv0CAf9mokWjFdKjInzA/9i41YRvZhf9zqk3VurOATH39MmfLPvlHlnNJP8MKsWoQJe/TzXIp/0pOyjFzWKyx42YkU/kmp+64bXnOlZCR4pHubhKh12Rs6DaQRJbGXxsg0rjTfFY6o9W53bORMkeVT5ofz+Y2jhxofLDnA24NO8rn/cT/AP83X5fZXzVWWqU1Hg1l17xJHxLBRFi3U0KifgYhFic3ZOoP0Qiusoth1hPEu2ywLR+JHADCSibOSpqDsbXPrYZa/5IFOEys1qTL9aRy9NeR7nBNVN1W4yIJcYLyvqriOukdBWGW2Ypb+7Q0Us2sJo80G1m+Vtv+PmLn/73zo13fs8CTDUmypQgJJlSiV/ByTbQ/nllYuy/JL6Qas7v8AcV7QlCz5sIE9CKJVAttDzjsIox16OtewmM5/otjPiZ982fLj50QY7EK5/0CZ/hDysc+35RZ10J9H788fDxLFHiRCQCTxxP+8w/lzmCD/IFDo7w8M/xzd62yr/rw9Bv3sq/K6rP4sHIr/AdQT2J2sP7vKvx/9nSn/FghgpIPJu+6v8n3/R6A6+7lmCuKB+BTtf1titO7fTylMiP/E/izDJ+ne+c95PzvW7ez+3LFWyQT+rfukvP/S37/UOuUpKCB070n+2ijqI78fR4Mmq9OkU5NX3pnjWm/1ONzHX+O2jf0/nEB1dQkObON0782StQLXs/C9sW7L2ObBn40D9hTjsDn1BcoCk38V5N7Iktv8QqmfzVsDDOUvY/+Xsf/L2P9l7P8y9n8Z+7+M/V/G/i9j/z9+Gfu/jP1fxv5//DL2fxn7v4z9X8b+L2P/l7H/y9j/Zez/MvZ/Gfu/jP1fxv4vY/+Xsf/L2P9l7P8y9n8Z+7+M/V/G/i9j/5ex/8vY/2Xs/zL2fxn7v4z9X8b+L2P/l7H/y9j/Zez/MvZ/Gfu/jP1fxv4vY/+Xsf/L2P9l7P8y9n8Z+7+M/V/G/i9j/5ex/8vY/2Xs/zL2/098/r/D2Efx5x/Y8xbY8AN9wij0z4R9mET+DUWf+AMj/5Wl/xdz/38+RR/7F4q+AP7772n64/vWKEPOjMPwX8x8QJlnxm5cvueg9w8PHk2XS5LV+X8dG8YBUPuLuuv+4XScQiEa/5uL/9+d/DdbH/q/z/hP/txK77LcJjVKV1vf/Ru6P/LX9p/1hf9d1oFprMFduM99s/WvLAJ/Ji/oj7vKU/VHsq/YH+Xdfv8NZA34b+n9qxvLP+/OJ33dgXfPjH19G1qQk4D4e0hz/lf2QQyH/yAwBOSLIEBH/Oc+iP9rlggM+oNA/k0PxP4X9cC/HvZ/JUnE/2F/6MABOknbchnfQ/YPva74fv73u8x//9r/NR/FX4khqm27r6B+kNA45cNe1Wv7RzIlaZX/MS4lkNrgXDDBVKfj8Nff/2yTok3+8z59y/9zW+5e8AfIKvG/MnUIRPxBQDDyJP6URcQ/S6In/se/6QkE8sfz38iif9j9P78z/Ks4+v99xpBmykHKEGDZgTgsQMt32li2y9smsDjKoegbbwDDD7OYyA6rKb61qutBlPJFwPdpFndrL++rv8Avp+Re/7jtldxIUdqf2wC8cMfP35+dwZhNP5z+n/Pj8m9V+T3pe4j7gds21OkeQ51qU741F+wzwFHxe5ClTs1lyn/YPjT23gZxV+DzQ0zh/7r7D0jlbnFXQZlIEer5fKcnXv0kPsGbF+jCtxmrXtxbY0jKZJ9YKvBNgviQJMhgNvNtOsXn/SAHSeha08lC3YN2N6TZOKgmR5zO2NcJt7ObvN8aI7AG88IUUyyJXID3V+BDkUNjr+B4p9eE3df/PNudwPYWh3alfolITHOcsRARXmsLklhtLwG/jEFu4wYaEtGGUnb8qGiGZieOaif+Sfv0o7ktbjjkrtXkqdUwuH5L0e6dCTymBvgl/dy5HEtTBHeauDjUm7Tv9kzoPq/673J1udD1d9nGTLyNgJr8vAbtHQX2J+q9dwQC0BAfS4LooznY/md5a6o0he9eIuDlLkWeaxba0wv4O2j9ksg/r/k3R//pWv9/eO2/HP3Ha73ev17IAX9r0/nvJASRNk6ANwm7f2LQj5vJuev8eYk+lATPtx/gU8ZiisRQ5J/H/yshjov/24Q4JvP8to/R6Z94sNEolDvzblOJPX72t6A0cRCH8gWeYX63o9Lv/TNFQDuDvsz9Xafv+Opum2/Q/02d7P6oUqSykrD6hzvGaxTqoyd0mMT/fWUJ3sbfpfuHc/5+cvNPNWXvtvqknb5Hgd6Zd3tFt1GfBPIZhXYX/1eL/fN5oPcwz2/ftAX/ilB5SsW/30Y5kir67VuOzUXffrx/cnbCXiH97d339aSJVuBeSxxaZRzgYBSd8gU8swzwo70oDpgVHP3zvUv882VpS2OpXWPp8hZSuyfSZSrSe3R/S5nZ2/ukUaL3VGGtUWH2HZwsMhZmstZufLfNP+/+c9fvncB3v68qf279/+QL7s5QNCdRXEoLlkZzFsVwFsdyFvAH3wIQJKm4a6J9pew/GQilJ1K7JVGWJAM7wbrF2X2G5f3LmcBqoylPoKwUuJaf4y3oNCBjLeoAR8UN2DsmZYgfi6LF6C3LXizHG36kCvPqttfKGBjeOHhEzvV4fpDXoIxDhixIPv3cn8ThcH7cptnylpaXYUVx9EDa9BkCMtVU3GfwZuULYXt9Ov9ToyuDmJZCuIQvstD7VQlp6ySI7jbvvjofMmLPgTzdPfAzn+L8CoZYvvLFbIn3japTP1wA6yIkXy5wMtNQN68xLQLnzLrD2lVUMirNQjKVGD+/nlO3zM+YRGCYLA0+3EH2nQGwHvAlIW4hNRsQSzRg0vDyToOYFzPqEP+wr7Xfat5WAmR6OI+wcJvCWDhY6yQHE2fiIyBim9gyYLOfuj37yhE9VMcnGzZuJTd4stbhr0pW7akKPA1OIRLB8yJLnMa6/fKm20II3WtG3SXRTh/MEfCJlEeolX8a5wMV2MHjrwJxEJzZ3u5y2RfwKs12ULyUKGvIh74b3VMEVvgVN8WkhDZwjvWDj8jJqsLZc9BrCd0sYTq4IkuSKHIe1P7u2/m1mBNwIiXh3FPdq/84UFZ96Afut9PGKue8QAu/xLUNJtiaZoVotLyQxswsgAJpZ9KzXN86x2u8xMyy5MfLk35m9Moya3GYu+/4yILC27U0eu4j9eqcl7KIq6Auicqvj3pbr5AMCt56NUUH55WPbnuuFhb/Th6MLpQTO9K+n+NTLqON6ZnKcwagDDh27Qsl3MACnqjl3T38gTwfQUv06yxbV4DFeoPPn7F16p6tNEfQzpyccJaws2XZEUI1ancOIaFXVIgdwySnzxlJlP0DDa5sP6/XtYfmtXE5cAK6nEvIWAxFXeE/1Rl0dQ4eNqAioqCRrRP4e3c6o7fhQiBQuOd3KhQ27XkGjp9BBFE6zQMGAbh0RACGGxHAaP/ety1PulcRYB0L15ey0RXSDPLSJnq8Lq1DvToHZJqioU+bwBLiawcjPpgeVmh6Q047XAP/yRFy7X6dFZxeeSEfSp/07m9Cr5fSLllaM0i2mEUYHaTv10riIdfDiDcsXmM3Ltm82uE09rE2wgSvUxDhPPKj4SyNkQnGoZrj9vqIr8prtOpfPCKM+UDNHWFrA9fyAIt+9nIM78wWXkWz/yc8K35kkwow21cmcwxtWbck/VOKWdEtm6QbiXESV0UuT3uRRFM2f3KNxniHBSTkjRvLW6pRNG2p5NfJ+XmGyWFGlecK8crMd4XiVujWEoxr6vl6wtt03kP4su+eLqM1ZxSqnKDdrN7v5gSjn3bnYkDBlBXq4zAPhl+gz/777bzc4YEHiK6jol0o/qKK6mKKpOhBBrMQo9YilLwDxvKC+UVYwFvE9slC6bj95pklp1fUepyD13AoAwexKcRL/JHlHSg2HDd+nDrubcUkzetCWBdXWRjESfGICtIOuKq6sMKJ1UrgT7vg7bCa+ukURUDkBfEkZ91Ku4qM9R49Uc/0fizZ4KxIJuGH7b2Cw56tBqK72RNXzYouD318+px4q+edhbv16Y707I3EO5QxFXVWGAol19WGRTgaeTaPs/V0HMvRtlqXR9wIjASmFXnMpuwtXRbYuId0UC6zkcr+mqGs7hURcqxHDleci9Dy06gQIjUYNXt7jRwQDdFr/XRt7yO/zb4s5fDYRsYPgYvoaX7aguYLJXEVEQtB0/iJETMwWYg9QrUfPYRl5NW56taDvGCTzDyXu7pKHbF6UFJTbVOVvVQgOqi75OpBW9XH4mXYXb+jslXQopVDghByb08vIKUwhh3QWw0CgrC1vgW3wuVrzQ4RYbmWDcjofLjFc+yMkqDYz43Qi/lzI/qwwA3SA1Mo0oNHQhb7hqbC78j0ufdg1yLfrM+0SuJPYBa2CAccKnfVz6veWavz1+4GE3Bq8iBymPIbYltQ6AlKlB3PeoXs44CUratng+3YuFnTGmX4SVSDvdSNaZsA97UX45eyW6rUopuix/O8uH5q4W40bhwSP4+x6c02kh5nvdmOn9lqa+n69viI+ykbSPJCgpzwYrK+tkzWBCQjGsVLStQXI9sOiUpMpupsBN4JyGNMTnPDuOQ906RvWx0qW0fnZiuDlsPWqzMKiUDzFnJKz7olAnsQjKT3iSgoLS9goq+uLoadNYnIqCUwVkmL3lsVu0HW+Drbsm6GvGn+WtxcW8LtBgpAeb/DPgfi4iBmML3h6CBmkfcSm1EIUhkJ1Mm66MbydHzN8stPdp8re+lNZUAV6NHyHJVPAbPR435nspA1r2lggY+A7isR+oQJO5CEtgVr/SghLO4H+rNFS+JGF+FAJyfnYabnevSBW8oT4U/+wd51UQMj+ca4l7l94mfvIMI4eWBP7NZPqCULA2cXZ7tOCvRf9Gn9FAWC+XQ3K1THaVHLylefq1CC1JNsjLYfP83F7yN/xgu61XvNj+7R4isTBNjKrPI5ioBQPg58o5nmsBv4rb7U3GhgVNSfqVIzbwLb/JMxC+gecKH1nsqSKpFV98aDLyGV0x9Q/onTsbZnhoMCtf/GY1BaX4QSPn7qDESyyBo7NjkeQzD1/HAmlD5f1bME050vqyXpxPP5i8ExHGSio/HnprjXI/GGZhn1pNOBwGreG/oQDlNUeGNncj3TJ9rNlQ1Rh6IRrHuIY3u4vkGLKNnsduEhLnEGXAS8niw20G0a4TcNuPcBQUanMhWGMcbjZXlBAsLC1w94ZPk4CNf2AcyXSVCTwztanXFuPMHqR+KlPgeG1RjD84h+89800QgbijCXtQYmHSDQqTiKhWwARG8sfX93/rYG/obS9z6OtjlHcjmBO2Xac5WKPiiPt6NSguUqjYWDv6jLGnkKynm3nFSa/C/NtHO0uYtk32ob1jkihLUSXphS5WFnIDOt/hy8Tyunk+YzArR1iIMzUTUhWu8hA5M9RAjML+4Gd+DiWzO3GvuQheUhAoI5OGiv6LUVGApJeSYsmBu7JfGl40tkVjkxPV5SfZJuKpcz6dITloOAYAQzFgBOvoxBS/VrH4hJSWq0ByfB1GA+KypC1BYE1nh+xK8WEizlDSqGFo+cZ4e7wavgeFmXEZ+4VEEid09/I1FrLfFIvprUr6WqIi/ZJm3k8cbcAHuQtNoCIbmuXUiIo7yeAuImYa/FmHMdgtW89QuFbGhCX056AeAKUDfGe1HRDV0ug0CB1vJ5p5w0fjGeN0RxYFrWrr1VkooQvLLrDq7NvxWsi5EJiklzQL/IStEPwXRiMhA81k66jTgCEWohArLj0WUyaRsYtGe88YCOzANtbJ2d4osZHNTfSfpeiTEDipz7KuCcqFjImVzGNbMiNB+VcCnxhZ88XyY4F5p4rTpj6y2atGp6NfPKXl5AAEQfSIAyySGvD8QfPFqjzc6YvIFo+iTLgab4lXZpctAl6sQuhp5b7fr4MNHMv16+pOFgKk46+H52yieUO8D5KgVELHxav4TlAOkcrniTztRMGCjkNZomeX5jU275Wr85B/XatTa3pFKdRsE5Dk4s7/NlJgrhc6GH5DQSUoGBxYjVDeT0Z9Ce8K1OWM7u1kWfJDU5JK2NBSVzhbGKgvPCB5hNfSb0TS7kxpjvrV6WoIjFy0dC7RHOeGTFqDNzA3rHoWPmJTv7OR07WTWS3qV29K3Ue3URrpBAhwsUzLsNJSZoJzcqu7ad39YcD6Wy3wCN7Pr1PSbtAUmyIVV4cjwO+rSQcnUucbhh505iAJsXkuZKZ/OwonZHzMRJD2NZEwcv+FgaYEOwhReNB7z4QltYvzjzdWtqoV3Fk85vpc235qFPFdHzr8LDeugtGXhDekqWFnXjWsve7iIrtTS6lzsnxiDnFecMVVFxJdM/3DztMO7W/2VQAOFyA9j+2JQ10S5m3WwvGyPyVdmHAqLXJtPpcshUJ6w29op/cA1L5FJjX7SQOFjpnAFmtypqSrzqIn7SnN3Hu3i/795XCHGGAhKE8G+HfumnmXOIggspavcCkT6USu9qooHiFFfFEbpNnJVz2jcP80n7Nj6P9YK4V0SkxOmiks80ht1vGDZ68I70g3jiSXEqGdW66IZcvbjV0yMU8NYa9kFa3vy+P9XMpqxKJlDeGC9yMkjVMh7yGXzxlbVvDDKsNHkbb93b0WhyYcy1MPNYhj/W/TjTDp7Y+Gj1KsPm3G9OoNWTt7L7wDTSrVkvOue6jCMzbyVf+5HdrMGDoI+1ixVa4+f10aitvJ1+7Z70m8sLLeSLfLcudda7/rV4j3uQBaKb9jPvDG9JG5fclhlJt4KGa0ucufUf1iVM3+XOwElK9lRl5R1XiHMJk4A7xS1Z/U9WUeHDYHoU4IYxZwODAAyls5oM6dh8uRdsqQ2khmDeBobPNc7cxiU/Raots7sLPaeeeTVdtV26M+dvpLZZ9zNlHU0rN3QVL033FZUdcfv0DwQSYPXZTi+113vBdaCpCOF2iB6tRJaomB56WL0451EvimaYcqLW7NLE6wCTOcxcJlXWwtWmkE36m0UsVAkzzP4i19VO6lH7yLmZ8z0H13llcA0wUah8Xp+wQMe4SNJONKMCeuNjJiPPDzd4ZZbNNqE8Kqm4K9ROD+3wx6Z5vRJHVfbad2JeZXbxqyLYh/PGPIJ6qvk7HzQ/npI4abTyCqeHo7HJWkkzeds3fIwXkjUujetMyfnVPoOkmYZuvo7Fywuiyo8BpY+0jx5H3Csv3jBF58Yry+Ql6cncNppyNqw7jQZOHzs81D4xxcAJL/KvRJbfRtc9D9mBY6U/eV1hyJw++I5+eny3I6hggXA9u1GRpkCZMBvypHizOfJ6oo/qDIH3YljXPkpnXVRXGwAu3cpnVW6T4BZeQK6cKgd+9El82bfV91LXG3jXNV/zVRaoWAHFvVpjrbORlaFuAPjynWI5m9XRMqiu/DN9cOON1QZRJje2EKgy5ZmfaQzqG0Zx4w3O8hzJilypuW1e+8YaDtVovUCTLGOPqQhk3mV18rFmXGBxQLkhJQW8fTTlkvQTjMHZDU0KQmRPPfH3R7fXC80yuwWWh9UT6IQCOwX3tRv0B+S5gR6BfuRrIIExyRoYhLwIfEmJ4O2ZK/NKOl/QvdZoUGVC9ip+3AgaQbyl2be12VDpGsTME+4agst5zOkdPdcut4DXEKmakn5xr855BVEfghkTMrh6xFWGGyoDtPjNNkvpuXRLOrUnO76PFesxjxjaP4fMwwR9Ki5Fcxv+vcjLmTZoYe6dKuI+/mVo7qy4PJMlbY5reyy5vuUE74H2RrwuN1yPWAYZUmbNR9kMunw087NYV8GLSCezHpB1CoNkPazUmJHwhpvkXuY5d98IRB/R+gbMO/prg77n40oYISjSt7kiIGaseRYqrmz9SyPfza2cu2nwg6Cb+jT244rc7l7Sv+xzR4WIYNmgA35pHlfjl+iGp7OmLwRXmKrohIdQtzW8PsZ+o27L08qLUqDCsIuggEICb0l1Nbst8nEOkORUUR2zhpAY+7PtUpxHug8ztaq9T5j5Mdx9mFDdXXMcsUt31Pp10Lkqermnrz0Cu/FzP52JwF/XKB4h8npm8GZv3Nk36IudvYsLrcjHu8fSu7c5giuBq6dznUBYZqJv6MfVxiNzMrVle63G3C9xPG1P6fX23Rpqobi6bwgCo4nlObgFWjvoi9zn+O1wmtdyyxCey7l+TKU/DKNkdFFHwk5+IetHManLYKb9mtU5VxJgHzzGkVBCQ638ZUPddjlbANvk0d5v5dXNWZV+e4DzkZ3b0KlK0L5xXgGH2zlrzBwmt9n8norHnK0diBG8NfLLWziNItsrN+Lej1VV0pkSpEKq9yF2pvlKSTUn9oFW8jJOtATEq9IPCwsU5NOmD27YMihgfFZWhECZhHjR7a8rr4Fv/G3DfWVDKcxb1zbwwDG7b/CD/ABjSUHd9+ttu1s3nw6/+S6rfVP1P89mVz5tezIaGoDwv1YqASj9vPE5j1eQLu2pWijElD132RKADktTzyKhQuvVdeGUDfacdeprLqvmRmEPQP1SDqJU2oNul3Xr/C16XtKbmJXaGOLsFLQ9491LG16ZVkbEmD7Wuf80FaS8Gs1UzsTC4nKmSzSD1WYzBfJoiblbMHzAX2oyKEirTHpH1CCaMEYZ2BMun3dFs3vjVG7I8ENR7qHrpOU2fhEB1r7C9VarHXh774MB0aPvfoo1UnnqzvrBDc0esPDzxD8vPHCn4X0PRneK4kg/PECpBLnM+O3LCAYihJk0o2BoIUQjw1rL4xNHPhe6YaCeQcMWTBV308470MKgheHOdUfDysuDJpPx/XarZGbI3hhjDeSiUtiRdjo2A0RNRw81u6Kz3+KiCsRbe6UZ7AQqq6KB4QrOaPgeK3KUkpDpPWClqoRcIgnyN7ptfLnmUYfGEUQ8dnQjIJJLobju+N3f5EozBEhukhfeTKTxlQEAOLKi7ydgOufc8CX5ZFHGmerB1sSzqwQa5gv97n4qqzBLczg64iIuZZkLNoSkSal0j1yGckTvQr2vO+5HO3PtZlb4ngN/utVSY3mTDp23RXD1lJ8Z8JSIafdQ7N4/n6L3eZE+dypSyow48k6xpp+IQAGG1y1nzk6UlGkObBt5z/pDtEml3Yk+fxikolQThsbIPM6CkUvC03s2stA1e1X5KsQkiDCIzHymzhlXBPk0Re2SJbXyAg7aBCfJ+oBYktsGrzUNezOzUFWTVLGMekMSOWxaz7nFE+4JoRKeSlpfoeZrkUx36Y2WCLlZp2T4VBLkI4uXtulBzKBTEF1neiO3aH66MGA26dk13nZCdKhcfPACgw8Y5e7XkqTJvhieLXyIMn+Y5uNnPu228sF8miWx1J5yzA79pZfpH51M8VylVZoVeVIj03dtmjQSKo724FsXn+Uk1Ld5V2qXhXiI4nRy0K4vlZYZb09JmbIgu4kjw8UipyH/0XMtmLv66FGUV7uMN54m8OSO3Jwlgp53YFLuen2M/WBVjdp8MRcmL392FhE+o6NvSeQVX1y0HrPkB8qszPMivVju7VywtxUMrF7voeu3zaNezqkVRWpZhdkgBwK82tAFm5vDPR/AG6AWJNV9xLoZE9F+RHGLO/LAw5VvZziLImKMthFSp6OG8yD80IvhxPiJf/Bf7ms/zlcwaynvmkGidEl2rSjZycS2658oH3HYW7x+0XKEq5BSNDNTxN4P8X2dYNCF7jOQxi1t36enLu3LewQPBwSWkNT+0gPbQDMRB6GzgilBKzkcCFOEzeilIORESLdSlx0bpAjYQ8A7/Ah9GLrAj1t8QpykS8t/5YwB5qHw4aUBrx97qxef6aE2oobswzToFXzea6GlqgF0faikHQHf74M2+CE2K1+b220pcqduc1n+lEcOZZRVFNp7dp/aE6DgaV/bF5wn5NC8uzrtkxkY1u+YhSdQpqebVUiIRTd6V8Z6noM0dBx9mdE1rvoghJ6s+c2Cj97vzDbIwkfPW8i80Ff2tSHlXKlZTZqDQcjVq5Ysp4OQNaKKekS7DDA63UbvxxSeCEdE4PldPsXNLRJxgxs+69PNQyIcD+kkEKyRE8YeSqRPpIIp2KZBGnxox5IzTpYkW5j+ABMnfxvLlBei/DCVlZGlQ7d8HHRbioKWxE6fOzy9cOtIH6WWDmHZ5043a3vEODLSyJuo6BdzYE2tFZUAoPRmfHOnlgeaoKZufvPUgucUDSlfzXOCv+kyo7cbsfyUQieh36qvogoMEyLHX6kRRWU5xwAJUyXVCom6tqnwrcu+SckXpUXQZy2V6Ho8wwyJFDx27P34AvBmMgpL3585BRK8vI+Vr31PJo1aS69mjtfjkh63YMv9KcLA9A8FmqV4fftftn98+Kujy4P4JjTfn53/pcoI/jSxeT/oH3JrEIy4UYtBjGsEa86LtlGMzwKYOXbDavNh0rUDU0rhWRcsi9cKU5+sChMf84YAoZu8R75sJCptEQgIKoPu/ULnfXJNEL2topm9a5G8WskQlpIB7TQZhlISbTMnRP6ekeBxOkEYJvDJCxhbICC2Xlk08jarpnSdouP9oNE1h1qUYhq4fYzEQ50OnZZzNNpRBG3WY3iKHafLHeF7wjs9rlfyKKtsEyaIfb0eer6jSt69Jk+PlDwJSsDem9991auKUvJOuyHY6sN084aDT5RqFqu8nSDVLTW1W0d5Pa4idz8oeqimesMV69N22RgmTia5io4RZomQN2joytCS8WG4z80Q/Uy9dT07ZESRFVg0qbDaOTqZqz8GvNNHRixDr0mWHFXMQCUwLqiXpBOZPlk7DUxN2fNGGIJikBY1ivBO57V0TQpJYyKXarDNtRCKvsHg7WRT+3BYjRt+H8Kwy/ry9XFZ+eXm3gYvKIEZ+manwDXxfV8J0eiT7/sbmF0g3IJYrS4hxikSwnGs1l08nl+Cz7HMcpyAbl4579Bc9h5mX1++IvOqbZSluiHKqEQaYVSmhmhuVkGK8oWnYV/cmwM4x4DJWFJmxM7OYj92GynSdp6yN0XxZKZXxVsAgNd/MNaR5RYiVtj96c731M4ykd8dc+XbvEgM2Jkdg1igAfFUO7GxtsnBTGp/pabqHVJy/+6xXRqstt92rm1KMED1VUWOwPVl2Er7J6fr7IwKY2sZ0CGWjQJyFOeu8EkP/njmWvOBlX6YiQIP25gmRuRYJwxOqaqTPe5YDRaI60RL90nJjXbK5NBmdFNsiEJsqqQWBEV1RWWDA+STOAr80crGt7HX++KpatvYcPZJ5AJq4anFIWWh7RyHKRXqCPHK14GeiKu9zrW9mGmOk3nORfUxPt5onJbP0G1IELAQ5I+vrAaiqMISV869p6sL6rv1pUYbsZSW7xPfQfgww0duHgEeF+uz2LCaM+wY7TolEA2F+IyC4p3PCRrBFP6FGT7Zj+Q5SzAqTHVFfrxcoojxCYx9vQ783AmVeb3yMSAhslNhYGqw89vhPyQVVrdRcX60iJvvZnjDyim7YLrdYm5IjHTBDlsP3iaLsYAP9nKVFJ9Hz5OXemKIHnmsx6MkRxPm1xwzFfZpv/ge3B56W1B+3drg2IVQ+hpF7i6NAYo999wziRoRb3ndXYSgZw/jyVlzcw10O5Lz9V4aj3AgXV4oM3sb5pHjbjHTu16Aztg5/MPR3u9bMjtlUl0B8255KLyR3At4zzUQ23DjfVW4ktMa0DWpAEaI0jR8aPTyYkTs1TR4hWDQvkANPLwW/cEHm+3K3kZz5Ar0tmJOes3o2NFS4dyoPysC1KHgXXjskdCSoq0R+mZKSk3uJgH2ckwHaEHe3Pb9hhM8Sw1JdiparGhrz7/4bm1h2zQ5JrrfVFSOxbw6sbEP88D1FVzE+pgfn9FMC5FDPHwKvSfdd5olczlWo7OAOqkOP16PlH0f23AtSsUDm5DOJACFXsXnAGI7gIjz8HjdUBIzeHd93FPZaUggUVzWPaJuidsuN7R5dPjX+4jLkRJBwt7jVDvV2oH/qTRNm+bV9BWqOb13mf3wbO0IoBJaJ0F75kk/dN0Q0Lqnvw91O54iyK/IUytYdCoulv2Gsr3+0QYYC3IMf58vBxcedvK1kFleI1OpGS5ASYIc8cvg9sSeQZ5FUbsRJCR+ZgoufpfxpS+p8/UEsV6nOnD6koU4SFw3ojj2Q9qNrE29t7G+r/rc+BAcmclkkgm8qO5mW57iimVv6Yamz3NpQr1DclvNYXSCgbduOC6kQfaa7bXaz1RlXwuiEWK8hiQNCffdLtvXNp7EVgwMYRNpelY3srmClcvhXCmn7Q05z+puwjW51PQ5yblRJ70P1taJtpoZ69uWq/jWItV5f5t+JUvSsxNDn9ucuaDpBAAz209dxlTXxxxhYsyqrEuP14S1ol/NTuusTX9b93hMUTbBpOmA8TzxdvX79pbRjiixFPYudJ6Xxo/YAK6O5pmtrDP1LYChofzZ7h5epJ5gx6+pvsSOkiNoDk1rkqB75yMN8htOAPeSyQJj3n5fm9q6D5rBCa8z8o5BidviIl8R/gEJ1aRl3KOR9ErTzgThGdAYF7Hx1gv3cXdnsHoJ3ahGk3Oeq4sEWomiRmGosOi8VWDiVLriT0S/lB4PJ6McUJpZjDJHOMPaPW4s8X4vRNCNcQJf13tulqCHx+dzXpXEnuCye615vaQCwPscEqndO4AWS4rD3untgKLebo6IN7Qi0vqRbB0ZvIBYBCkWDKcH9lZHHjiG1pvxXgGfv8ju/rMAeSzfugzYm7FnkpTZKFET8dj27c5GFpKM5uBPDwIT0D15wczDz4LDWjOJMisg5AxZkudF9bva6I418xuCP3h394XwvNy3BFkeTtgmlYVC9JLCbmjwTTDIE6Tb4YnVvw7R2nhHMNTdGJxh2Upnc3wvlWvfliCVVj8YaUDY5bzjXHZolnyAITEegcFm6TpGGGPgeABcSjs1L41CKJ8ZnZSgHIFHsKmRhu+NVn1B4WF7iK/36b0jj+fFArbIawIYxT+O7iQyrXPz7CN6GQpl9+v40DCrEUSSmXWyAR7opH1XE8wSfV76fdulWhgYqA2em2NJJvfFqiu2IPV7hp5KNL6yahtduI2iElh6xKwCTJh6bkbigqwcc5BF/JS5zvpucz/ZMfIG/IIGLSBg4z3CpXg2wLF25h/Bt1HCcPBCByhMaY1zA7wu/jFLqnericw+X3kzY8+HBw3FCz2H0jewEkOCeOnG7nwodYzPfC8NSDsiyVsQ7W2MS/25DLfgZDtdgGq86WFXpN21SVjB2tv4QdqT77ya/egRH+4LUPuLVbFokhSf2NHb9H+h0XOmlyVBs+JAIDx3HNdfI6mDXbdlcAaEmoSl5hxel7H9oKiaggyLUJbg7SvnonO2sB2wb1NgAOOv52eNYUk33rnY7/QjGy3gLK9vw0rBmkVrGREgvdTX7dCHWceO3W7eb2PsOwGonNvy3i6Syj90NeJj0iuaBfmmzCFT+Pnm7BeyNW5ugOUMkwvSe9KvNOGfsaB9kqZzHMUYmgbV0ym21Ftpkw1+d+OUaPDcA6aIJhy8771D+/A5GQaWCNK+nEILtqZ9ir65YlN6BDHy4LmiwMLYJTSTVaS5b2d/3T9ZKA6CmU4m3L8eOXu/TWrTqevhOmL8bl8AmTnyo2o6euxsYsRLHPqJ3Ew42re+hU1lz38yEHSqI1uhFvKkd/bhv+FvZn6XIJ17fFWz3o2TxKN6DnfrJwK+v6lRLntdZ9s77c/0xDAvKQvhSTow9w62LUbUz9S1Hncbzp9KmfsTgW3+Y/f3UBA/qwvscW05GcGX5zpZnGSy3VQWeehcchkiTfaK+pHohK4c+Imnpc/nc9w1BanK7jFRAMX8oQNdSrcxupZ4uC3qQ8YGISOnOAqr6upGjOAMVYukIzIxUGcTmKqPYEvhjO8V+3G/3WYWyt7P9aVJbTISebnnw0FIcGAShKCIyeQJGnPjoq43KnvWQZwdrwZQF6TblVRvKWnljY9ZeETde4jg+4uL5vVc5jEeBj6EhNnl14PnDM5LUKMr3I/xXfqRe7+g+yat8OTwSxVKs4gD6oNhz6NAz+KRy7Qbi9aQCDhC9OMtRZ8Aagzt6WeTdmSb7mrlO8fohb1Y8a3+vFQwY/Q6HmDu5AOI7ggZF8CydnF/Td7pI596/NCwaTT45yN1cvFIY1wM8eTq2wWdsOJhK9ONuRYluwo6jZLlNn2W9C08e1jfJLN5PTHSvq2iL+aGfVVpd3TSZOkS0GRaDyxCrThWSWcGT5+1HO8M4Of4Se1AkzkOS8MwN8/c6MQuyb9rXtKL2BHz0JBc0xYkBTc8cHJnLkrsLG3dEAIS+U8SDQeOm8/PP9WVEsh46KXTS2ala3tPWIfV4WUKBAQVIB+rMSXOAJXPyyhmNjpDOUHUl+fxphnCXjC18PJ8uLkqLn+3Oz80p8EPbnmDHRmU285qLSehHMScRPjQhGcHYkfe/gUPZkPoiBzzngId9F12Q19AVBsAj+Lqf7RF7rYMf0VZZwJVtEcgiTr+IZlCnXGhnZR+b/1Q4ZBb1OcvGvFZ7yDeRr/1wMKYom4m39PkzXH0rK/T/SiZ/kBuzff6znaYX+8mNXLf2UUJULH/ZA38RDhxDkdpIJ7Jpo8yFqrVYWlNF3fym5foZ2bxvtIlv4YsCA4XbIR7AL3Ckd8JRZ56lmIA3FC2XZPMxp05t4/Eqy4ZQVJAEMrrVbYld9uBiBBpgWhiL5BN+MzsrjYre+ITz5EP2ovdObzYYm9Q/EglQeAxmW5PbodV7u2/Gw7yUBP6ridKkQ5uedmUgkXJKMGnVB87bpUAmzf6x1M2JglgSaybJ7a3QLCIQyveLi2JQKtNWyEJVOJ4rQFcRPWyojEzpQzVNruKfzCFIKSyds0WLfr5eZmfANRhiIuXQjbQGKkSDsYOhafVZ7Xv19LERBLoLm4/KmeHDp8HHV9d5P1U1xNMe+UEME/8Rr9o08s8IXdWPpRW1j7O67AiIdZ6GnUeJBT3NSX72AkJy4UdVLeu0Ii5WOnuqOEn7jlcHyGEtJGP/IiVLjBJNNcIwCAxqzE1tQWjMXCP/SHML5FauVrNOJlYRchv8JDhow5TrdLv37em2tO4Immnkz8Mu/WOgpZWUR7YWoB83DRq0BxN5SXc082p3EBJxof6K1PPtPwAHRoFOQOMPcGsPhaJdrslVNmteLmxnCztRU2Q9abgnZ3rtUtvKWS1vNrxFCvvo8+gUmYRKg6dQXv5VBpShcG8z0kYpA6XuqbxeGI+G6rO5L79zH4vgIWn6PaDZuuN+a+Otp1Bicfy/m3hcrp3c1guHDLQIPbpBXmSDOt5qavZviwYjacYJLlOfCjNacatkw6tpVkc2yZXMebqA0gdahbli8u796lNt5JvhZB03orU1EXWmmMq1OXshel3sgG59uSWETLTbBzmzbk8SHnpRM5bpNitDgJyzHXMjVGFKUrqKQKfBdXtzd4fWCOVVGk6ULSizBhnm+C0jAHKnChFOgR2ULN3S+Lf7D70DiSERg7EqQr2IKRPfkfLOF4Lc+Ne/thRr4PJXO99jW93bvJgKvmf1MbmUfeduOC4qjz9iYlkts61upailmbad6GuqIbrjKl1OvZQiFqSoiwRQw28Vfk0PUuRLeqV+jO2ap4/UStiSJ20LmGVEXXfOicDmQMssCqjcSJyTBeAqCK9zmd5tnueR8nzUiD+k0KmdPe5NmFI32qt3PD47HxT0FzPcmdGLzKqOS2lurRpWBFjPOuarUqlOotXn9OuRWdFV11MLjCJlXT7AF6AEYoVbu/yFAWCVsT3Kq2lTw2kUm6uXvC5PjQSw/kaGPDcAPIy7ar4rsWiAhZccfO9ACHgFIkR1nN+PjGTDkef0Hp+kbrJjabppD4eNt1S+mNKQbVWO/fWJ2fcVUnyOHLZ6e1Qj82L+tM6vP6KN7tnU5HjopzL0zeEvx/zO+g2r47e5siC4OduKD9sSyZdy1UjX7q2RCmMBNGSarSEyGhCLnGcrMlidSYj4vtROUEQABCNxEplozfS5n26jzmxZgshDVA39KqH9WpjhEFfJx/rsX5K/eSfx0MK5tgRgh7RQCgHTQy0AYRXavLoVqeC1NLC4btUya7MDba1/rmh3hs2rA7YoRQn8LouAUiHa+zcPV2eNE2x5cIEmGn9wToXILFM8vijXbt8uuv2jM0fK2hiLKLjDblOxzpUpXUU+8mgYP0ImBWhCCt6weTROK/WdsIOp9/seI8/yPg0Ba3ao70V31WBBr5Ti2VmtpSySrYqX5IkobHRNi6V+1qF61OOtbLKoUInfoRHAYXuaiMWWgXULmFZFh1Pr28+m1JjbSUZDcVxO1kuO3finnfNVDceB+OFnfYZi0YyIgyxw8Oxa958liZSnaX9ZOLGXFXsmEYp/rhUYipSgkD6mCyxwueiXEy1O65VQmIU4wRj2pCfRiYYdDyUj6kYh9wfJ2C40RcxCHTL+TjvgplvHz+tmqsCK0nOxBxo83RWSW8udTcrIaI+MTB0XMqt+6wE6tZ/4lLFdK7A4v3A6gIrevYTycikdaoqspeCE5/0E0uClw/1AoeziUTGdK2b36D7qhnSq/3GsH7jpNogLjFFcjz6FTM9t/g0s3MdBY4fhlzBKGsDrXkjowAzSlS5hmBJ97bdNAtFpejNv+jXGLyntktldOZ0/9Q6cQRuDIaMr6/QEiwgRLwR4IK0bwT55fJG10xoOTTj66Kmsssi4nmjQuAD3Xf13FUyrqVbNGswdSMYRly5GDsJuI6T7+Clmct+kWyvVz9OBR4aTukp4XIryI2307IEn2nMtE/VQwwr2dz8YrGqpubyYJPHR7y0iz2e5Lk7NQTTkJ9QZVA+uEm2KUMNL6M93UuyEp+R1d2vTSSHW4VkuUEQPtih96x7j5Rebr321CW9dPbzbeXpXBwKEyabVzLwyJanrxXAwGY31iYXDoA7R1OneZuRN/V67PhtTHpxaFq+t0UNLLEcjlM4wliBZJdjuLTDVvv7ahNyvuASy4+tK1C4RXvdLDrDg9FQ+QE5gcgQFFs+pXFVrRkuL8kImve4RNZbBnTd+TWw2zyCKVWuwiK8rRSCapmjiZQ5zzq5/HpmpQKKl+K1fCfOSMolAqs2L70ASKhgTxBiQXsmoUUgf352n16nyiSfbbGIfQRDRSejfFkYErxb2K7NizjM1Dpa00TG+G14JfvrMRbttedBoBeP7zMaiXi11ihnFHW3/hHYbUox5M4iN2iU2XKXBKU+gKi+O68DTGOOJSzHdjznuG5zpzVEJpiaM4VlvutES2JD0MJ/LpGRgLx6N5Zl/kwGcKPamrqVuW3pgHlGR9ZIMcn1XYDqeqx83jQLIZD0wys6PaITUrmmXa4arexLeZ7GpEJReWCA/TiP/LbMjxSNEWYR8EFeDSpDThwI1g4SfYQJno+fZdOnnSqgFv2uIk2pVjh+qbmMyL/xSe69fVGsvygFHPoluFV/ZxAA6HzirC86p6nS4wBr4G/+Ac1VX3Qe3nIkFW1OEQ6SoXas4MHVz7tyIJLQAj4gziRSt9rcbRfZ65VFYw0h5MePTTfMR3JWFMR3jSgM9jjrAsm0KzbHE5kOcjQ8ZX9ZLvitzZY+eaKDDo8eC4UO9czbogbToi3bL0HvLuixnB/itp9V4HEdmuFo/NF9PuPCAzDgm0vXSgwYkwIVhtNiMJHvCinK52DW81lP/qW+b2z12N2peop4vc8uGNrtaLRq2WQmB9ZTp7sGOajHLkATYXy090tZR/0dIHsquobcGybC+tRebNiyj5s3vQ6EhTQRn3DeD55+s+MfzScQ8cMhSwZ5ol96fMinKDRzt+zR5cFIPPgbhcTz3fpdClAti9Hau7A89wAd6ieesWcVaGH3bkS/txYlmJGGohGkdN+sAJIIAAdmWKkkQ2cLuXzXWkxgiZgXlXipC6PDxxOBn/FZgYbyqDi6ZN7vRN6tDdH4oHD3HiDmaUfBrldduaTH5vYgsknXvk5RTTzqjxixdlDWYUPkdRo9Ult7WWKZNLlHiUJCsEdhhmC1xb6NLCLIDJOk4vGcQiHXfQTWgUts6Z+P+sUZuRnQDcoXESntt4gGPh+FFMc96HFRbvqNf+xiTpe3bJQ8JUruAaw+HZGski4YkicKYZQiLwr/qeZ3hWjBskXKrYxaSTp2jYarx6FTqNrSAYI9Lva7UC+K5OtWpRNL3fpSwFesZceOH6gmPDyd1DB7f3w6DsY7aBFfq271PiVc5bJoT3ERbyluZdpDhAf1eXqxQlJ9YUQbyUPijJMYv9zStMEL9OAS9n9j7Ku2ZLeyLb+m38XwKGYM8VsIQxxi+PrWjmO7q/qOri4P2ydPZoZgw1pzLpibQ+A3jaTS7/DnMKmJl3R/sWvh5600hMfrDrdAqA+AXTUSpW/881lvbLhBiNp7/mRYGBsLaiOKL3VVcDSVhsojELOr4RsrTSY25/eEowHqIsZrq3tByyuMiugCWhF2rkr3IV/W3KWixRvn0WfygyIWo900X41figCxXf9eQfQ0qMHrgp0ah79a3G8EaM2vB4snT+z+VRaw8mu2ZayPRd50RYfK7emwQv3sI67fkiVQCp2nImP2f78tY4P1hXeseuOXcsfUfJlFgBIvpMuDfc0iI8f1hf7m7hrsuzyvTJH/tHDxFklyUFinosrVq1qllkjXZ1ITbtv+wioL3n8/RintncKOSDyuOPgdsv3gYDMS3wV5f3opxNOZ41EHeJO3cmS/nriD2UKWs2TxQ8QN5j+PVqQUHq/ebLpvji/i5FM9HHxHGolE7EH7SLPc7I+Plnu4fvP5TjE8tYSN7IxZsO4zE9yMRszTyZqandRMLKMgi5Mni8EGMNATYw3vDcdeOGMP7wloELlgONlEN+crvyT3RdG2pXYiXKboIMAxBZwb6BV6dZsSQaeZrzV2Yv17Od3dJmrNQmlafQcyQjyGIBkj0pmKFv1AL/LW6Meju5+6kbG6uLfDfssGrBnBXiLLh89MqA5Uwq7D7NMEZa1kLn1NA9phkM2xiUznX+dzr+pk8mtBaQbbAF/mEnhEYF16cCGOFD+AvwdEWudo+ZP2z2/4Pb1rdwD0s/8mHpsf9liWIDAmPlu2MtXnRdJJsfA3ITHEDjK++Wtul9Sk1e9lJO8SpTeVWQhQyDz8omK6ATV0rneDTpSSn+7lhwIHCYDT5a5WuudNxrYfA00C3FJG1Cf9EWsEPm3WH5Ko6Ha0S6SBRgZrPlUO+NnjCwrlyxSKkpoXIVfRDx7KgKwx4pvMtBGsD+8fvzf+7fdYIO4H/J7zP/yeW7ncZ8mF6iiZf4tK/erdQV1o8zsCtrosQT67SoTj0ngd9OMCIDHMN7RIA8IeZnL4yKWEWL9HQicmf2/luHxvmFY91cWrEaQMqgfM1F5LL6c1/Oo9JKPAPm5z+zL/k4AUXRa3sUnC9Hd3JyC99XmIdTTCBGxgtM7YekPZUiBkA8RWbpWCjVHELtwHsHkHznoYOZ9WGF32oL6DxUsbH9xv8jzWKC7JhEUCp91knATKCc1kOa5pAw2bisQu6zF4CfOafGvdCofqkJfv/Bntt8H0GbeKi+FuqJV1WDoUKvLraKg45HsDo4huqlUx37JabgfPEJ31sq+K1DD4GTGTcRdb6JDs2KxNcPTqCQcKkpKoqV15HPW6DmoYWSy0VkbKxDkabhp3XCHZFAsY+E2AjYn74q5fyNB5oJv2UJNv6r9eb5itj5MXqR3Npkujzvqy3VPOsd7dLFzNFoQX9KXX0vhjMDdjOZRzJtEqJ4bgKF9tvp99iEfPHEmazutQOo9RJxvLn/UEyIDB4by3cBVd73Xn36qsb7wWpuKba7BV8H0iaPYtemzfPIQ85JK67XbznRTThPcvqNwe79QqxHYLtyrAYiu+dgO/dToQCoR8bOjjfYV2jAw8Y2XTuMeO1/rMaUyM0nuBUOY3GOQSBrKlrE10Ddi5AECPBPxZNzC4JkkYfs9i3OrKb5JIq3j7tS4rofyLvc+E7S4gg1E+MPG3vi6t07VUONxDqd3qs35IrYWAPiXriBzEc3MXMH0jn8KIEPPo321TU2FioCKsIv6FBdBqaRDQFYkiL1i1THRbrz6lbM1d94pYQ+3Vh+1Vqdl93hLHnIwAg1KA+oEbPrv5kzfIL/OD16LEi0wM6i7H94jseGVoMfaYsahn+rWWL0PkY7KEWQdUm4s+o5ml/xZ5F5iobZdL3x3ZZsFYTKI+xwfV58QVplOOm7iBXLURQ0gtxEh/wxASBH6wFD27pS/XyTRl0M52KZoMmLNctvOLVtjD9lUR3vIsyjgkuqEkeMGhrX2ox4DMyGCQEdpZm/pB1wtu8sBII2kfMmzsTjP1bij2gYTc4ybM10iYOwmOtepGrM1mjqQ8rrWGCaUan/vKQK63OC/3CAsfZo+CRo7IhQ5sQdaqZL0Nle2JJeDs3D8jZOXwXbdVKVjBIYJedTH/VvvKch8JRjupW3V3tEc99vws0EQkl/JzVbDKZycfCeRbLw2zXsjTSv9YZPLMIu9mkGnx+1EuVHw1Qp469CTCaw70EigqYVFjTF/9t9kFakPKxjhPaXvX4UGf31gh/vhfxIUwp7y54bFSKT2DY7PEg3PAkTEi+VM3XvV52kHYJrQnrXRgSa97uNfmQcyU6G2KcGpmuoFvMVuat9hcG2m2w3V4kofHUzggp5xGeIK2i5tM3GH4yKF7/K9aVcz2YfphE20ohqyH07/YGmO8rWdXV3DbMDlYJo8Z88AuPvjf6ZcOo7FAMw5Qp8e2H4bI/M2aXIFRgAJIKJ6CIrmKKR/Gw4SOf2VBQIb2x4Ig5sXpIEK11Hmd9Tg+EELFOsF4f/xZeBtnDfMO972YaKDxkQx+3oyPK+YhjMKxJYgZWnH8EWZuvN5EqKwZWw/Y8VmjWnwfjsrndV2jxk7Qgjlub/SImHn23f61uCNVzf0VG61eOKZ6eALcwLzCKhrxebeJzv+kzh/Cllqjw64BFcMyYRDWKVOhOFPA12N8brcP6qUU+pcmfnA7B8AO4GDufaJg7qz289cpc/xqUJjHqO4y8i/16AdaSbkihNXpvDYxW0vSSEVVYo6eq+sM3iUY+aIojZAXqW/k2H0eswZx1P5pBccS2goTeJF/T11fx+jCVCQolcuinjdXZn2WlbaOMI1B1q+ELmK245tWb3HelI9OikL/wfhMjtcLaQ6Zc9/dxtRVUxeMAt6VMOTVDevJZRjzYGN/rVJWMxO9pwqaXhHy5CBgMLXyFd4jJndRIyH0T33lT1qQF9ukVb96KroLx9cc/YIZTRQ+rINF5Mg7d5fx2Dvnx/56I8iaOdzkm9uvDvxVnSm3vnTvQU2qfH9/qUNZ+Zjvk85jTT9HQgLWgT2lUWAwHlQNvTeizwMHavAQ9ODPJyw6zqm1rBDw6YGpi8FNgzNOJ8QIiuziCPRyJ3M6AmltQ0xhXfJFCfMl9Ty5xXHAhHBFfLTFqQ89Zj80D44ATTaf09yeKutz+dJuNWVBrF27C8BPlxrxkQZQoWdjW0/rwoS0hJEr1rWY29cqnId3eEwEC0lcL5OKIpm9BB0A23y2VEZWXVpY2dcmatHbVpj2qHVnL1TYmHOePdS7c9hIX78V0vyKNeQjMSMlW41AFAD1V/ynZ8p79NWQ+vhjShkCB9aboNzMq/XyTfDEUdJ//Tj6+Xw4ZpGFk5S45gSFyOELZ6/PYZJc3QIld3aaitdxPxQhzBfidJJ9ULVYGZWaM2KHYUcbe7D82oQxWYtQyXCLeuQbO8o4THU4K5r3QGYAm7Y2yVZrPPo0bLFT8QHxUkgm45gHX5lGpHj+8thKamwOiUk7TZj8I8hS9aEznvNRGRX3W3bvleE1E4WWaBysgiaLD0njt/84B6tHzYA+xdz+mqzATaKop7LLwUXN+EBogLENf1AD2w2o/pdZ1Sr/y2ASjCH4om95mkJpc0yus1MVY1vCS3aOhuG5vpX1aoc0EQKJaek2zdn3KwFVkgv7jpA+4uiyDI/nbN4imy6aJ2Gaa0RsqE2x8Jnby6cyfriygYGww6+sKZPcaSPTqhFITNm52IkCBn9c7lf44D2XQ0h+1IZL9ezRYNAgyiqTvsf0pArnblln/C48kvwqHYy59o+wVABu4eyB9x9U29U+xSUC9969LkufFyjRYim4qrJOfOPtjm8us1SU9XWtMWepKvEgdoj9xiBrMUr9l5MchfO73JcfIJlLp3dN0TVhdqfxAZd7bctErQYifqYpFusyPBQ9w93hjM0wOlxGjHlW5T5Cqij6UYVKwPmyxjdTf4I7sL/+5Zif2DHImI0NmeYspKpKsqBuCJ2yY9k9tEq/V7uIlaR6NxnfSs63aj674yl54+wnLYkRaHv5rEm4dEPtYQxDOv0+dVSvNczlx2+I+JyutdiYnAE0RVeXwVeQmX/knIEaj3XeEgvpTsqkH+Vz1wsUm7J+1C+LVjpZ22ThAEYSeWzvh2qr3vAFkfN6HrvEM9Bi+dQejMCwbHg8xoOHuTs+FL0OiVPAesR17CoQ0qlzv6ezdC7c+AhDRJVUO8KSwT5O0CPly1+jX5CIYaON9TVZYlVlKLYvHjPVuXVULEo08w5KxV4PuJLayYXUBCuvo5C3U37x+eK4eEwoXzwAQxFkWD13D+6LpV2sC0yp+1prX7pfmWKDIY4yPltBeCYZNY8C+yndtAYfsbsq8Ac78i7wTKoqFhWHOb5DXzpdDDWqKFXfm1HGPyCSjg00r1VvI3gaxRNWEBpLEDy/+Tq9xFM4YQEYxP1cm2iJyfXyPZ0cMb4wwsheIk0RMZWrMs0PN/G1Bf7bFXDhKrTujQsu8xjZSaKy9bG2PMIJCF8sbormF0p9Zjn1kJ7tXnXBh6pi6y1P9SCVJTnqLg2fwZW3QjnQ1mvoE2prnvAhUKkvogMc1n4VbsAJdGsGiiBrDBkJNCER/p7CDGOr8rBbk2HRddfwsxwuBVTTOMYVVOpSGrImMngetyMYIRZ2nZXa974j/HApZEuCqzZQkHSEYmIUSR7JrBVefEE13YTQMpsYriFSsXO3v5LstJmZyJzHtJIuxuTH5aovzSlOf0OFFkBjyLSHecoDx514wmCCcgpvUVcdT1oYaeGKF1pxVCXbNn0nPAmDN0xFAbAp7c4c/+ag+lf8lCq2xk4utqlEb9dj+VgAbxJ545xxs9N8N7peM65wxCJYDCflDFUZz7YN/eYhdJ0xqibwFRBU2pI2h8JB2CNj1IyigDNEWcHC9ADSRdFkDD7XdzsXya+whpxJLvOnqkfX+SBdxXQtcwSQlRDkZcd0SI9azft817DV3KrIxfEaE3DVL5PE+MEZV1tlJiCasgYQx2TTY+Yq7FS88xrrLB1Zt3UoUKSyuepU+yPkuxiuKC129KNQCKxacY/X+8hLW4o+8fBSULIRUoaz8AGjdFxQaaCeLUoHha9VNvsKwOVQVJ+WHIdPxtdaPacgktl9g/q8sZGrVnnhuuMLVfXtQmVvOCWOOOdj9sgkIjX+Pr9juUcwmz3mM3OzIlPPvpRESSFRhIqXxx2LV1NyvlRx0A0R4dFt3rAorKhYkuQFUBt7tBIy7MePtTf6Ogd3WHe6xS/eCnV1TV2GonlgvCT5TJ2OBvAYn7GrOxvWrgSTPtFMdwW3fJ/2IUMyYw5+BKZI1FdJsyZjp/Gk3DswxKk7SRAkaqvuWIxxI1NYoSRAG4nDPeSDFVQFWaEq+52rjgGYC4k2P6+Y0zM8n1S2+lhGFP9gj0HkVKGQZcV0zCDL6MwtoYSQS/N9KP3BLoFoZDKn9OTDUZ8r4Y+7O87NvfV3INN/ZX2S/4Wy6XspgJ42Vwes5R6QJlUj4Army/8IPhAZjMD/eIUDwJ7ha8X+Dj82EZkvF1KYecEy4scwuuMldvfzhS4cDMOdBsuoj4cFgSg2/wbiBwoF2LB6c09f/+g7Y2n0FzvhT8waPmsmwV0uCVUhwUs6GETBQ/VfOsdATpT4o9dM1cpf+s25tDy+4mP6HMunqNopvL+ZHHYof5R1/9ZVBtq3/1wz693efqn/aCnnaI7qQ3brPX0lF3VaXovrN3Ppt3Lp0fP5Gr6LEIfiqFqfzzd/X/tfrv9/6zb/1HjjUN3zyKGVWvnn9//+75/3e97B8yFa+a/0r+k1fd4dqBH/2/X+UaM2Ib8P/u3d/odONPr5ZBx16g2zZ7CLZ5K//1Sa0eCKkeCVhHGjAO3kf3ve/6OP/czXf/ms/+k53W/Sx//xOs849kn95zn/dbz/jPmzTgTYtFq3e8YcSUJXynp6Vf7MxRFH6oO/fu/2rI2fPjWc9Wb3f1/nz7X+jJnVd1uGup/0+b2XDxJRYGb0vvsm/Hi5ggIZvnObzXNbPj4N30ccz4FMPz4sP77N2zmNpsJc79ifq4BRJN4hfueS+Ix6oLr8f7rzMzto8IAoHPrXO7v/fuf7v7/zM45NHsJdOrj/4842RwPlZ9PzwJpVv3kftO6g7qn3/xzj/+LpLO///3R/rXTC/U9P989d/5vZsPj//q6v/zAbv7vyXz7rg08u0Vcg0XvK/6M1/oBUGkpRc0xRpnIgozIa5jRfzOiFYvPM8u9n2l/3slrzSkLx+Z7qpwi92P++l6jfum6+9rNuwQh8fvaiwc5nXqB3mPRWS1/vMAAK8T899H/5/N863v9ua/62c38pgAN1evun+f0v+vbP3EYATdAgFpTYneoKol+Y81o6fSaL4Q+HHgUIFJQIqkSGJzxY/UFq8vVJ263f+m/riPKlbZw01kLXq72I4nHH0hbIy1bgQFgxEM+jxkZSb+kiEQ1fhBlHyD4Lk2pO46Ge7YHkBkXDpLdH336bum4jv6+C/EX3R7SlTiMvo3UC9AT03+DoXmpFcxwQispcc2cp1I84nltnQBdZLicoZqUEgUQ6z3tl1nNmEzlNnN4bnlDyeG1YNMMUSBUSwS9oZ0TonBI1iOMFDgjbIZsCfpyldv/5rNtmypbvbXVWRWEM2bmLUWY+0F2u8yS7A6RLzvKHnFDQzJw3mG/Q+zmCHN6a/jlE7XhrznSSK3gFSZIQAjPyvfLml8GUKQvEzFieseEXs5s5ApFzn+PaeSNDN9Lr90JidDi1+/boe29G386Tpa5B4y5rMmj1ZxTSTKTjoWEVx8tKGgZtPqP+bd6LWYPqM5MvA0P4srZfgV74Cz1yoiYMHvXTX1uggBLCWF67Z1Zki0Y8GLn3m6keqn/Du1IDPkkn5A1/qspEwN9CFq1TomyauiekgjSnexqWpP4JqKN3DA9kuY4LwmCtXP7m4a3Y45DJGP2NSLI1qjIPLHAhjG7v5GoOD4FM4a5DkLvdjgjnMPveltcMRgvL3ku1LyAZmRDgQ51dIXdX63cskYvoOhwojTJpAT7NxXZxtAR3zAzLFCvik7RvWTxPESozHnu2YYz/Yn9g0Pd1bQTiRisqgHwSi9N0rs6ClkimzFKPblDo5QIx18F/neqDmPMWf9sk2VHY7ojN/dJr1bS37TenkGQx+PbBUpJvrdJe8+k4aB86VEp5oyj60kZkfp7JME0k6Tg+usEk+aCfC5pfJ+kjSzIS4LgP8SfQjwzS9O0TSR0PRySMSP6NYqbZCeQtIFFnfioYEoULuetxCji1Yu1XMNfPo6XebU4D4HqYZmLmg0WPEY4qu4MQ9jKOaEGfr2GePEnyoQnllo19VBfSNmJ+JZdSIj0jHsep557cfbDHiRXkjsntu0/d4mcS/HaNZZP/0Xz8hL4gWiODTMfXM4Y/74iPyeHZjEecGSQYLj0ild++bww0dz5DTrYBYhYSHivQRWg5ZYCnHPmF04v+PRanwJIUQc45bRjhxshkUNb4sjxI+ZMqATwMjhrcf2Y4GRlS6Zv+yJ43gDBmAwxhfvP7zfxCrHLynohympA2xD7a8sxw83GnmJmxh3bYKH2rom2EoE4Yfb+aN+8hp23Z83YS33iv/+yF4m3or0Cnj7JL5ThnDbtHOdEciAMknLLme6Mr5fy9c4oLWEwWUBiA4kUBq5odTWXCh/XFIBzPx4wXpcE4ifbhx81xJf7zVN3E4MQHz7VmtpwXNd6MLGHU0L6ubpQdLRUSBNQQTiRRBYO3QB8NkzNmC3+rLzzc3bIbt/OgYmY6p2Gd5gjGGi0vENRx11QUA01Rt6yq/ZPQ5MNM/bU3bdhJ6Z2bQyzkFuxbA5tNbtgBVk2MpsxrG2wVexMo+XVF/qEVM3chW8JaQ8zhRuFpIaXpxrI0DIruTBpBPbb+MsYKQ7LnaXrLdekV2cSPnWKbYKsSnldAfvao3glGgwKhaPsyHkrCPBo61vn4IYduU7J4vx63OXe2OKcJNs1z2qFg+RVe8u2/hijgNwPvzXvjDE6GKTgicwyy7+gGdR/TvsTjXFsqWRomzgMaHghedayFogzNljJgK5ommvJCUPmMdklbTc+IIL/c/ePBzQWyj0QGmpbEnh5INNc/rJd58LJoNUM0suk6CZjcZquc7utj4be4YGUq6OBFVbzzTspPc9DoHpe/xvj866RTMO+gsikLMUzfII0oFvDXvS0Ja8eJY1jkrCbBzlvOlH0cHJ14BAsBHSh0WJOjWgNQ/M981z9ETTcIUFm4ujS9+Phah5CV8SWl4JXz0bby5wQgnUK6KEniS4f297iq5sdsGBKVmZg352kDBaoDBvxS93LzQj2dgxS3Bnd8mhU8D6AD5+N4e3mQNE+lWppDr9bCTfg4MCRsZp897IKqBXbUUPtoAmZxN0VROHsrO9/BHBzeT8LVuXyqSWGOGFOPg+jTfwVE7gQir+9jI2jkXleQCRClMxQPECUZSBAyFfkIv1ttP2mG39uxUtGyJ7twmLavVoGi0GDxSoeZtAlsizwLH7vlJ/PjZjrCsWRe+bYHyvPcZ/q6MjyWYhT5LYo3DI2U1yZKDyqKuOJ77FG8MpzNOhdZSV/K7kZFGoZx+pINBw5jFa2GzBYMyNuki8ixEC+no1eCTIcT6hp3x30NDcPtI42+ILVLp/FQZFFRAN8FRRcTwA7rFuaOIkf+gMoAerZjYI60kYr1EQtgFTRohR5nsKAghv2iheNjR2t7jV/kZgMG5TqC/EjCRCjbZ8U9ZIvzXnQGSBJjRniA6P2M/meMIGKnPbLlr+q0xjS/5ITwHuDz7QxOx7FahzWTGSZz8Ft7J4ptJlvyBjhKAumxQ9uVXuC3uEqDuDLfdKcmTm7fH+yPN48ajtN+JfL7mOI09+qTIkWXgjN4+dfAinzZaa1Fhe+0SiTWtixHasoRp3iMTKoO0AB6WrwsWxmJbCWS9w7Z5zC123rPY4b29iLaZNuzdMJnx2ZyUf7Gz9WYHR1GLaY+jD3C6uHodMyaAa8YmJpPixooEsFq9u/Y1gK604qWlI+1CBPMtyjDPkbaTtkwkLLPK+Qum+cOLJGKM3nlOW4kaNPQ/Jd59pi2TqJZbp6TeEUkW7CW4XuqSsX6jz9c2WqIQF2QlrmMhg7mgIpubS7sxsCl/gXtFwuPkTJ5a/G5RQ2UWQ89wbAIV+d977FPtMkoVSr9T+avtiXQDnQjNqFQqLBH18ufQM62in0h0QAQsvfV0xflvKnJj743EyESL78avBDQBgC5kjyHjRzsn4+yvEmkPp8UiSChncLMU8+J1Q4kFL5NMapM8FanV15phwX2mqN0y3oMkao8YxfcK/awl5RDxOSdMfIAt5u9BKvyoSrWQW3s2U0Tlq/KNkavt9T7WUmueDfuHgs/U93Jdi+2u4vKDXkNKJqlf82d92z+pkzkDcehI415/jWOjMwstPqQBKH6HFMMPKillwNc07EWQRu9orI6eYghBrzhdYxmrDR31pN+G1gH0PlNsObQQB+Snw9G4RAgdclynwz/qcD7GxO1IKAcvt8QKv/x3swvY95q3UUrdvo1OpmB5c3iMoOI8p2ls4ItWYLxBa/NkMV74CXExE7Et6FoHW/GUphXd+/n6n+Q3j9eCm6tdPcRP9OtmWpNbL/TrwDKIrpmDnTHB8F2do6TbydI6/grdwIFDHzpO7/Th5Dc14vVNj/hBsC/K35VzsG+FfelSN+XJchoWgItEvnI8AgGmCmrlviHkaTsWRVyxAqXCCfGxsaToUkjA79eoPtGJP0GEiwq7XnOzMiHztmqI2LhqMa9S1rPmh3khZ7z4mcrU11TR/N4jMdHV5pPG7LM6/SeIYsz7P3wKiiozJLxyBtHJuicND7HdUb0FjsPKEY6skt9Q/sIONLDRLLu7Z6JFEXQKmKeRzwcvSnpQ02Ez9ncTZ5v2Ob6fNC+IwYY1RLqu94IkYdCDMs0HlL/Aq0VC6KpQXtExkOxTuBlet6FVwd/5g749coEmfvJ9yC0BENqI9aM3wQs3rCzaJaKt7f+QepLvMg6eJzBHEdd917xPKuSZiKnECas68+6yK5vmC1prt9XCIf7miu4NL0G/41Y5oeukNnw3QL6qG8sLSPqS7GvXrWghZKG1x8HHFe1+zVJxraLuikgwoRpYZqqKRKP89Z3gI4bLB0O5g9HIxzml9aMueVLL3YasNFwO4GahXesHKht0ev7+XQiR11ODXBGzoz+UnUl9KALE6mkd89aYWKFIQDUnt18Z/qioyLhBaQFAPGiywPFoPGStA8cqCgcTyOJZsPjiABC5C3Ad2QIxdth+HxXR6E3CXFSyv4ekfkFUOckcuJeTU+v3kgbqFpsSPIznGsNY71IoqB+xyHti9MrNLd5Ay/ZhimiNNohKk8C90XSb5aWD1+iid1hmGhfA2Hw+D9I/fHiNpfH5rR08nh04buIT2s96eV77p5zFmFYaxacoztaG2We3fWmmVny8RdLeXt51uXjAnFnkzDP9gRWcvHzBby1/PmVqa6T8aVgh6ZodGBxJxiEaFi6da3tcA5tnjoL4jKh3x56ZiJGyVuShpRFoYhZRElVTWPiYRMd6+Dam3oSdChu2ejoa1jVY3+NghdzY6dLP2bygaL+9lJdbm8HpR+IwVn6WD6lh8DQyxFW9E/L3vKFSHNSFDGzJi1vqY2OgrQI78yOb7KCtY327naHoXFB02oEs+V9syHOKhaJ1A/No4UBCMwD6OA6UWRjLlEhUB/QtQN7gn2hS+qsjXg5815piogwhc/51js/GNy/DZXO+heWnFx4RNlSUnozbjHPBs1rVJyvQuyb5BO+iH9a1dVdx4OGqenQ+tP+1qr1ZkDC2S95GzKE5ohjTYWjB5Y0z87WbnUttyTU86/C7Gb6IF0UkZXyDp5FY2nRUmbo5uRH3UcJzvGtoPKWagwz+el8Gt3AMUUsq8OJakhLBwV8hEAMvQRdoviYrI08wfbE5eU3/biu6fpFOJhGgrlynZjiLJe6rcK1Gr3HKUQRGiWRrflKMS4h88aE97do2VRlKt+sPPrGVvrzhdxaYnLGJDMFeO9N/95rdjpfTwd2muUbSJlJ7lWLn+FLfVwRJBOb8Xm4oNhhMi00OLk1NneU3WzgElI2dTv3HKzpW2BorvM2a7bJ6/VQYr94fzkFbq/0QPVJZSLLo/34ez7U6wFrePIJv9+J+TzWvNLIteQt7A15LUgMuWgm4WFXfzdMKB+2KFd0Ib/q/IMF9pq9yV1rQZLsNW0HwL7R65W8PSHZX0XSpVLg0JNIvMuxHoPbWkID+N82/hJG3JmuFrttsQtX5dOiBoR06pYfoJtxR+B+0TosCXxQ7nci2GcnqR4nnu/3SNVBrB68Kgqz9FI3gPxYaoHlm494DyViu1T/MC71QlajAPnBaKeJMfwGWbp82HrYGUK3e8M8iwyz/hQbibmRX+SO/pq6NuA7VCaF344M19YzZ0z0hl15LFWRIRBKcPTF6ULQ8TC/1WB4OcNP12DxZFEoCyrXyNHyPhtNUFUaIRQV57QsEKCbevhpEIRL1vi8OKqSxpQeiNUdn191xPNvq8ttd9Oc9cVYxLavTCTcbAZbVxylX6zFPB2Q1URGBnfqQ7l9NvRl8WJZzninUOWRGhQ0Hs9oZd7Cef8Y3inq72Ihu77Sknh5a/Gbo9eCDuvIkh2HrWQiOfsKvEPzeQwXiB0SZfMWh3Fg7xeI8Ey/5k/qFESfM0aPnSfrGzJ4lgkeuzPBj1xz9cq81m+jFlsveqsJXujaBqynDGHrLvab3MLlo7u0h+GsOLDnbNCJTmKUL681OOmOedxfcBlTcbyb+++oJO5X9vzW1pc+v8FRoeKpH9DhSjvBCZgUM5bP5KUd5hM1d1RohrmnB7eWuqQEkLcTuD6qJ7ctImGifmuVnt2McQqYIDBNGwvqZ6sx8XozIHKjWaJz6cEDXbj3eJ1BDss+K78KKJM3OnYX6UC9MfO/sWnjeAL7mESnr+Wd0E1kKIH4laoEFkUUAR24FTR3Exwr2cyKSxzkdmn4Rk5KNWvbD/S+rBwFMchsOriYeKUggSpA9dVdJYEdhv8NKL38FK4yVVFlfYXNaZR1OufAy1q6nWTZYUKxNe3WOl9vpUNwHOnknm2DsrgJD83GYea6TbeOKN2u0QERTHHOQETn/GIWWXkLFlZRUo6gHVSsRQFjMx6lIkafa+gOx5W4xShlyIVffidx9rqcvfjkHMQrQCCOhwe9YzylGwTbNDeCL5bgd7TImIqSFYgrmUhkiuPxUrCMSPa/CILsS/6EMc4hcFXmVSyOleOvflVq9IJgIx+hXtP5U0yMsZnotkqUOGczNFnFaJJjHVu/oCTIT2j2h/bVPyhLkRNV67d8D/uxe9uOeyGWBHvxoQeLD0mrZRSWJF34BNNSC0WVTQ+XfdggkOKqBtNUKD++Krr3g1Ub1hx7W91Iy6F/tsaHXPcPFjJtqG2LhuKmifrzbm0jshL8Sam84UjYuSl96Cyw+xA0EAZ6V1Jy//FXfKWDJWjPW4U3Um2Sh+oEZUf38vdrvJt0GY42Tcsz7i2m4cf3EXdn+iBVrlBCKzRlaEmruYG/l6Z/Au6qkJZZQA5RwUoBxZUVuOHOHQgQPaxPFjOeG/AM5l9xsUFUjImOwYR9BE3NXKxsWMlT3O2MucgosLVE+ROqrSLi9VgKEJUJGOIzhA999pW0eZOT9QEtoazOMY2XjoZrb19yFmnRBt9tPf9emAe33HFFyfkqYylpY9khO3dFndgbney7YmHmveSk4PPIK6JRPILo6MC0kDAWxncoUkXwdeA7UawUPs2ys34dwWqn7p1ND6gs/ZKYvMn+WT2LKVKQE1DBcsF8xgwO0vgldPqqm9jCkZhoXWY74yTLeweBjDzIgb3yJGkdwdZyxo5u8Nw8KDktdXqALBVW4EX3rvQrGKAkGITxfDAm1Ehmcsk3Hky9mbY1EwzjvIskguVtY5oRwhBxBhLBRH77cuTp5ld71AQodMLS8PQY2OpGArwnoXO7JpDC7GayX7iqh8jNwX5Sx7/6/jOKsLpxJJVIvlral+ddAn/E3rQ95IthmvANoZVgSSciNdy1W4lIvvz2XdPVJ5VMZ8WmbG7WsSoHCdGmDS/WiCZPZyW8BnJ7zCgwyQcw/HOmMgNVZHdB733oGGvL/L6fC6Kcvtv2bJCm5nXv7amV8sXJTgulPXWzPasgMgFNMF/uPcAGy89oeKaMHoFrgszU+zoaMAdvJ0LztNInP3HjayAmrMKT3IZ9EHtu/LpiXlVlZCUTUkBQhu1rPHbsybeMo7KhxAYMRMjMbIYPGrWjSDIfPzE8uGp9N69jjilUoGFdAOUsQaAbm0xMBc3F4j1dhVARCkWkdLj6BQ797LlqFTEEv+lYcLE54gJ6R0Esbr/tz13Lxsq4dWW7WPuBbzw0jsf0DZmXyFZmZzBt+xnCbsZMT7uFHjk0q1t+BPD+slwaKvFPUnwy5Ur7xYU/+PVKeNcNccqGbnwjdbzpPoTFcE1KlyMpJk3E/PKISfs57wLm0XJjS6TQvk39vBltQC93nl4tlBXrzkNFyHD2QSpJhEJ2H71VtF4PW2m/fcM+JL0/PLPAdg6haaKUp3YaLVTc4AL56BlDl/j10CJ5X8Y21IeBFwS/pIpzt2KgJrk0E8ZRjRBd0sc/X6Iees1Kc8TYSDIsMwGBkiAgjyt3xkpxUX6Z0IfFqPygkkdCNA6igTTHMeeFkXcPeUZZAyF6bAspUG/0k0gGjqQDi9sqv2+6vIuQ6pwJkgaCaZp92wBHdjNaw+8lxEl+uH9q4iCmCgK+8YwOO3DwoCQQJMNS8JO3ZYVok6FVx9pjnRHeJUN2UD73H7KIIOhxvbcUxEC/BpOyQ0zAhAxJW+n8CW6wzv07+eT7W2L2T1ZuJkB6q8QLK0jIgfa8I9ySYd9AC11dkr3N29FWPlz/l9eNJPAsJ3sIIDymErtrjzlJ1+jnBxzZZBwGM+WOXH3v4B6/I3DXVTdsCh9q+w6Txv4VaSsNMAkAGXi0BEL/C+CL3hAye5028IaZtDDadPssPXQhmVzDPmFTTOf2tcxXZvMPKH3BqdKhGsYMj6EsPMt6L58++DVhrEG4Bngq8QcPVRm4E3ht3gZ3xhMLpK0z07PXAuS1X+7vONm3WvOYcaOLh7yZt1x3Hf1MGH//2g8WW2chDPpeN5gGCo1+sywzr1CN2R4ltB9cFbGKg9s4FOajqArYbuAL9EL//Gsp0uRLiVSm3daUcQFz+yjqJKe0CKNTsaPnjduNWVpMdaDwZs9f+RaaqvS2nCw0cHHC8/rPjJVHqMQlGLJrj4D9R0EQw/zzAIZ829vvSN0V5nOWp6iprMu8KbX4Is9orw6SGKwLxnXoLHFgHTz7Nh0ny4GOO2tWDMaenxmRf6cgc6nSRjvfXJ+fxAyIXhD1NtvfSutxqHRdcktQjn4s72EB/2sNYJGUeeUofTRkvbfMhX2BaDldE+e2LxbjHhx4Zkh1JLw5OLQvSBgsQBsV2jqg7x/XWcRCL+thKnUkog4Yw/Qb4zpeiEtih/GdC46HKWbL/inVUbCc7h03IpEHYAVtLExwFZPr4qGLF5ZZNAGFFZxp5VyeW06c+K0/q7sdWptXvpjn3kCnhARJms88I33F2jUVp7g8m2BtJISNjVKSrg62G5/Kxmb7A1xAs4E4WLxTIl28p7guAorAbg6WrR73Iv5Dg5cGlEh4sDw4+VjcFHdLHoyU8aLruG/zigTZqwmLNaGDGh2WU6t26Z9lbwy2g74Y9NJ6UGsqB6BAvyck6PXtnHW6zwJRu85JS5WoP2UjH9uKv3UM9XCCpgjvzc4Vp3DoaPm0w7FobhyKPX8STfvSKzxC38pxzIyvVscbCT0q/IpqD2xXwLnrIK3TEoCBeVGfO7rIyNSA+cQ7fLHT2GTAmpn7iYYJsA4KTPWAIj/8Xpkj4I14WZlsCwp6z5vUMTpjJfqAdSHsIX5ZqpChFKDHBRbWqfcteqzt+7IsamYhfwiAPoJ4tgdCpt8lA6YEQy3YUkHRMb1DCWon/rPt4HIvC4PVoutKx7XOwqMuxs/XvVBkb+araJaUm6iiF7CioqlkS44A5Nmhax8DG9jWtsjry8h6zurrpjDZb8i6uQhmLAUKPJQ/gpCDzFimxX6vuuk8kz6vxFwmW6P4r6rz2DL5e6o5FvpFGMp6CEzZRVD0zFzkLH6aif5O63ys6c1Klu+F/sxDod13qR76rQLnEJRQzTM3sbfbjagQIJmvzZHNKi+a7MIAJI09FAsGInlAOkJQ/GbIpPImSR+Qy5k/Y9zaCm3iW0vZsU7dlH1iouGnYQW4YiViAFqUlD3Uv7YENWd11A5o3Y7atWx+Sjf7EZP62p+meyZGUxvzBpCv4X0uEPyVkHPM+jercQdchYMBsCK5vnhRFGg3nfa9gVutSc1lmXBJD6XNru20XYrTzXFgix5aXH5hP7qCzxnbkqamslxXtPLjmEdxoK/E/o13NC01dLJiweT07doFbvEXUhnTlWRL8YJYSM0NzmKJ1rV8EDZIV0Uy5ofLkXB5VuJm+qHg4yB08e3YhuJzktUN2nuY+fGNopjefBlYxN8ZZ7u79wRwvd6MQBVTiDo8CGb5U4X2h+77MMIX8CBV/aZoatq2gGXWpKNmS+xkXpI5/fSAgN/zK0eyA9RXNujRD1jzzgOX9PLxHAxiP8sWKEwQqkRiNAYLcsS1L9kS3qmcdTKYpVA61h5C5q+SdUVvwyqQXIFJhaMsl+hu/4aWB7Hkx33b3z1GdJsPtjGxIPcCwGJoikT2SWt65oW57Ap91QNYNHj6bb49Hccao2p1+NjAOZznnQ4njTx/iL9kKoPvqMmy7f3rNqUeO9uvTI8nvHuwSGxaZYf1lTkDB1yLl5hELO8KA0svh+RAuUz2buC7FEFWOcmb1JxZquaia/epJ6c3fbt6Rwq3ivxhRaUDzPkVRQvbOoA2V7M3zyReh7La6HheRPjvzIkvFk7w99a8qiJF20zCKPD2OHDI89f+fyxfH5F42gNXU0WJYsrhQ1aUOJfVQ2ZFSkPvyoXD+80LNYMi/MvMDzr3pLR3O5CRWKXfwUvwmywrbGqZVFt/RQ1CYMYWkr5UGmR4oPwCoObYeCSeZpmJmD0T5PYTLyFfj07YcSUmtwOvVbShUkTix4/XEuX+AmiKDa6IWF0JmHoEXqDsmfEPxFhvXsE3/UA0ZhFUSPJf1tcR1HutOEZXhe9eKxHEVXEPjIJdkPn0+K4DKkC8Z6r1d5C69lp2IX5fDN6rhsa2EnRHH9TvMHszllJpECK/UfZg7VIey9trX1MJA1v2E0Iyc3Z78DxkfwmRhuryxBa62T849ho1OXfdkVCDefy5wBb+4+JPC24xdXq/ovwLw8iStbnyE9oshDvjRPxGGeUFyuvkA+DTVFN1sNRVTy9fAnxsmqS6JhZ8q8Jjtz+XBLx/ZbncujHgoy+fzQzio/HEqDQEXKA0MZBZr8vjdYbO8SbMm7fgk1EoWbUmNpofBu0WIzt8FbS9mNVIn6FBdwjMmvgFiZYI2kVSY9NxU2KGOssqJFAL8ZbkAuETdp9J2Q2Yt55ueKc4LiMDTJeaqOmAiZdPf2K6QxwnmJl5h6UY41R6JQUhOzLzuZRyGea2pHA05zfGuo8naZGNOZE0By8IVtgvmcijN2UBdHAd7vndBpO2d4QK58gfX9MX+LNgnIrhFL2RCQWGKX0ITHu3hBuXTZmjDy8CM1/bh9LUB/4Bq93vol/s+bUidnuQndec5Ssfv0KK01egs3vKK4vCRCQDDaDezMKpdEYlMP1eQuEkMHmtnx5SkRmuf/jTtfWBldeb2KTe8JHOpU3LQmcXETwIFkeqq2prngW6P9D+woJAwLlF9eUWqQZzrpQeoYUB0dEf1nJpegtuksZ93iNjioE3gknrWBSrqgZQNxMdwZGjgq643vGYn0SG4/tsGUXvOZJeNPHGaGqJJWZHwLG9YK3M9J2NUgCdApjdcjAyed2vfcfkT5BUv8Cgon1z9es3LA1bobD8GI1Lrr5CcEdiD+cdMdwZPAQS4Pz4S/Aw9SHzi3nPVXp05Qtg2VNyhV3LInEEb/NrWPoxnrHtqjCx8w9t9QuWZgRd6mjbfEBZxI6+v3G2lqHWPGuTM+w5vaOriPJ6JbxRKH+92BYswmA/fVCReNPiHT1L/Pn+FLZTlVFwDx12QNk6dwN7bworq3/tpZo3kC8yEOkG4uIBuU0pz3pNFOgkuoLLXY3/rXudOX2jNAu8aU3rta4MV6iRdWw0gFW5NabSQdloyeqKuzR6CYb0E87M6GIE88a4ivaBYRmQF8x2CfrCOgRfXocuiS6TYGT7ui54X3bPEWIBSSMZjKwPalMVmqGIPJ2lghAMv+Cp2ImJpvdo+s4viNpxMj/xNz7o+lgZOsz3hukzDlAMEWMyigD8Gpn3O9fugdBszPgiLpTlKm8KeDXaXzDTQzkZ5BI9BBDBtIorGBXZi0TTu5vIoOynVQSjJI+4G55gr0WBAM21jZM+m4hDr6Z13LnwZCjCSlSBugGVd8IsxV2vZnIvzhfUvvdiWu2yzh+HqhMrZe3sV+7pmseR0GL20afDdsW89EDAe8+l7aVZns8C4SxFXazZ/Gu3/XbpsKJZFU6SHj1mpdpdCDWCezXvAyPeJKKJv9Da3ifAIY9mPKU3wHcix5ZBfWrddGnvy+EwPeICZfBEA42koMq2h58wtuu8afNHJXnOupo5nlM2qYMgzMpTW7fTOOiLzYadQJLFmWPzilu+IkywxAKN+AoBd4pV9JCqtpbCRqEzad+wrqBxq9ofE1pjc8Ff8YHUXnQevz7DCO4BYtQJ3vkBRoTksF7uhi1sExXAZfvrVtF7YeU+IkOHo3YjfX39raxZqJo6EU9g64CVBg0heyUy2yltcr9tlHVDAYlwwF7WQ2J4olpI6YT8JFUBw+BhXYHGE2zRcf9RV2NQ4PbNgMWy/iSEbyLvUbHkD1+e2PU4hCsX1dVAIdYKHTa6c3lK0hdp8R/AtEY63fScoMehBtadzvIJytPwaFXU0/TsAYSUanGR9f6y1CyOiWl29OUN/IZmUepCOYxo+gvI6WCwyhRdAgaly8/Z+cJGyewpBT0Om5Imf+Gz1/AO8OFgWSjtZ0Kci4Emgrc21zmsahG2ycEPqWgPWyOv+fkHNRGm+yQHm6gbBIZkyf26SG8duRymk8F57yIRVZOyWA9e1MmF08DRBSJwGlSpKtTMYUMPOzymA2gOhY5v7n8o//5ON8ZQGGT7cPMWUa5p213xEsT7dzjDFjDfOCLc3sjqzG4+wEgtVd06Z0uKpyAs8oSDXNfeYM6EcOJbCNdDEVthXjLB8SoNctjx605i1aiM+fiKjyFoDSUTjOZvzyZ9VSKt+Tb+FWWvVxMENzQFr72uARcFPixeM/nUDdMs93EY3u9wQaiJnsQofBcSadpZtbUPmlAv9lPX5jaihnfmu+XNhT1bI4i0JegXd0oQ4XiBJGz1Vc4ahMXq3IniIH0V/Pvz69s78HHLx/ynUFjpBldEffni41p4HdRyiOLc8IEKjgwb3GiAYL/Zy2C9DuWx5lqGES+aysgKOCGf+bIEeVvjZd0whTMrt0KVwmIPHcjvEOG+OBK8yg/w/LFg4nVq/bTX0gjhKl9anTH+LFmWInkTgOmqsvjsFu3u0j1+5RFhUUfS7iHRVYybPGZPe3nQGBDXFaY2+ZlBVCwnk5G1OQTFYzjGrLujEHNtbtPREtJjfI78cnVg1tQE3vvk2OHWQydUAhXJawB5QNg9gEn6cVf+TZ9RkDKAa9m6owDXDaIXWPUW2RByT2B+my8H/gB4xu0bdxdr6nrVNDAJohoZQ2RXekUpDjDEmMBr5qGK1YIJdMnWlulXFD1M83J4+MEu0jzmncB70GcLGmAzdaZdP83IWIcsGsP/5ukqEiRHguRr9i6GoxhTTKmbOMWMr19F9ezOrWu6s5ShcHczcxo3gaLu2YtxRumnU5tP0Wk+9qb1VpQceJJouEJuU2FBZf2fckeAau3mWA6dyWfyV6tF4MSfGyiM6XNbdJYyMsecQx7NhL/YEXRmWvTg1I8CVSfqH7bQixNcGj2fiNC+nVoodnaw1HYFuLeuu3SJOu99VXL9yeyA5PaAICKj/GSerTVFINK+K0YyF0yqq66UZTNSj/MG7jOElgtM/yO6WIH14fs5w7XI4acymS6BX+bgT7iE4yel2ki4h9fSStncKE/UVNDnstMGsEFh3eibmXtPv8CzGpqOewRO/JJOAG/ytgG6s+xV1sLqYsk0s6M78Ro8W1oiNzMh/9x6hssGnLO4cuZVwloZqTr25LW4THFV/CWhlTbmLFZ+825yXDdWA/qncugjR2cYBv9tJC+EoJIuHkN2+3Bif1gmBCiN6POkxIbsNTWOuhbMSu6HBA8sHHoE1DzodhiwH2yzcBS8tJVxZfZWI4xVwGPrkdndm940hL30J3dJVImkPnl3Kp5LPTSF+vPjDMAhmmis8Papmo+VDxdlwXRP9ZevYBNARxQpVkE2cvpKwy9tq4DFSeWz71Xlrcxway9oKJyCfb4Q81oUuM0SN8N/z7CXt9n4zpZhLxkr/wp4vaAKyx3EaCtgXwgm8TZ92SyrJPQevxD7V/8m27v+FkFRrOajaw9g4kWsqllrPH5+A4i8SO9i0oZvbErVl4jWs/PFJoXl+pitgJSiqIRh7VDHg/6Guyf7cgDnyc8A5j3Hd8PTH147WT8kN4RRNVfBKlYMoPf6rwxdoqyffWPjZOiuRPwc5ESY7HuP0qjOvfI+sYByqSN11bcVMuD2EtTflaQyp7SPGT6S2d+TxYRwRM4LzL4iR3jgr4k/evta08ImNDCxyxfdM4jFDIzgGm4+IQTcgv6mcAKEUHSO+AX1VFf4HbS4NKrfi2O+UVSRGu8oMDef0taPJYkmx2BhAURzmUNYTylZe/8RAK5+gnC+nM4xX2orK0A9z0P19U8yGxN8AV0ywAE55HPiM3YO7OUuMd+i7qfIJIKBTsYsztAHsz4dgyjjET8zPbR9f/8te7KlKeB7x6rQvliMkHbB4cJ1j7GkVHjbk+aiaXxaHiZwRAMFiTbrIh+2JFvV753fRCbuUJfnWurxV2TmGsaVOBuj2VvrGgsfcJOyl4hQzUBjcFAj5jmWTNLOBoX6be9GiyKAbJHtrB8+GGuVi6xzDr5C6gtagpH5A9ILiI8KLhzF6Ue0Z/MQFh1FUAgQtAJEHDQ2HCqAS1H/IFNFmNqWrE0zH6E4XACz/1T+8DKN4ERqlaHtGkL7El4eN2aFZ3joISAyb913X0fKGzsr9mBY3jMgX1oZ1Vek86YbJh9o/IMcTLXPkO+nt4Tuh0E31siBQTakVnclfSwToVapZblQ8lJcr0PKtnEqKQzL/ociKUl9rSFPeTwAX6qioLX6M0RIQbYevjvBY88WQsXVFl0GSsYXqvH1+VSfAwKREHY3t0J4hgk/Pt5kcvWUW/b3uirJ7O1MLb1gaS0cuc7+zl+KB3REFG2husIu8JRhpKruLB/gpTRSxWp4N46KG3d0+k27vzFGR8DCv0qlq/MbUU0+IpvgFMZPqOCqu0Uj0FC52Oufwk1Cm58irrgw4BK0DZCnwuxhoWxnqPFmEYo4HDvq3o6+2+1yZ+zUKmoaHCOGqeANXYiFc/SK+Hwnhp5MTge+W7rkRRD0Y+NJDPpGwx2cA8a1PMJZNYswKWGZAwhnH76mMHcAxFtpIxkydSI3VvrGt3Db6mBuGolJ46Nsiy0KBu2T0HGNfdu17xya6c8cZaJFr1T4VP8bClAYizjb/fVhIkKox+zr2oVXDDtbBM0puP5ySORE+vEzio+CBWpYb/HtaHtKkZdoD5zGiMwbnsvkR8h/UmGNFpBEWNt8fVXGqxKayWpVLRItBIwU6C52HlHy2pyjb+q9MNkUVXBZ6gWoFrof2orSTWmgABukrUCZmUIX5CVvKND8xP3ybsPUmcTMKy7Rd2BBVWZRdl1RvkZOAYRO1C/HxGXGiyqQPdXO75qEdU/5TmtvhJwBYD71DwQzyNnvXxrhB+o8uPYpXfxjYbj6KUuKoECCySQiUpIfLJ8e9L1mUHoEZR4A9fE1/dx4/CWtjxzDEQtaGTDEAW0jq0PJ+oyvNcfxIi2TB0CoYXPvo7dZUBy3J3A/40avASmMjaUVJtbkgPcw+gUAus/rEKh1/Tr5GA6b0jDMoiDyOZi1+dOAN2/ecQjzYZ8QSIvK7TKhansTz7RusO9KQkavh5SKqMDetTlJ5o8o1V9ZO5VWDPMf6fvDbiABmC6/v7YwTrZEzPXm7wZcqj7/TZP+ApkrUOEhGtTSpLyPHppqrHK65P2oL/tPxBJrJdwWxC22VJelu5JtmqLSImvXh7O5u6xo0OaUhtNP4O9R1PwVvnTWtPn964GsMFgTI5K/PveJYmImUsqe9CLMlx6HvyYAhK6H954w0K+QVYpDpSimbM46JyAE+Z0HP0uVBMeuqy0eCOusGKoZOHYX8eok4MbfcjaB0IsOBWIMa8wGPf1y//0hfCB4AZEb5ELWF+ybG6EHtZCG8AJE3hbz85BV+14SJ1rqU2YmgDUWId+aeAps9ZfEqs616tU9k5cwhlB7vFmb5p9dRVAB9HZw2b3ExA9smC1TuGyMKrSkUzfjDxLc/XKYDUj9N11bwgmBEvFMTy1+daE7dILP4G6CtfGIbbc8iEn5+WpU88ExujPyb1Csb6hqbbTM2rbFctKrGRGK6L14nTsInuhcuua9XGXO/cHDz+Y9SqXPJoGmsfTEuGFLPXX2JPGi1vOrp/qRafcU//ViBzfYWKmY3PTpb+bmSKcc+b0anlhKoYaEcA7xccBXsUfjDuujTOiJQfhBG65oXlPCXju46lPx3kJznrCDv3SMiPLxJVG0kP3423bmGJwDtZonHecOh2FGThBp+ZLm72+DjevPQbEubkn8pDT4Qu8huP3FZxacYSt+RfmHPsFZ/sa+dqRYaeoN3poNpAUbqvsVKwFjIb588TS21YfRXWKatbzmWiJa/cI6DGPFCTo3kGc6JjiIdDY8dcJdfzWCPonJSTO9lrvHoDaEKntlcYwEej8+lHxGSMjvQhtgk64pwQJFfKWamjKs+BrqaVQ8ijIO9ckjeUxJCmepSWy26gFoFpsj4609Wy7N/brbyPwzR3FD0shGQcbh84gjGZW3+m09XxztL2fx2GKoY0I0Uxn1kd98Zoj+7XhwiJnr0KK9/rGR/j81VGZGRdSrr3+jt9SWTsf1tpL5Cje8Ud2/3VgiprOx8nBvC1kOeAAVZGlocMh2dN/vzzCAmvMRurIqzSav8utHjJV1s+P+0c15AfmVxP5Mz2N8IL3ig2ow/MxGZaozGQsyk9d9BT7Q8dSIpD8kWynyzuHIR/7wnwFK5egpbtdNd5564uuPOWfKo+6VWxlA0hRTcUZp7fBKVYG9fctdu10kn5fsixCSdSIXecgCs0r3VlrEtHpaJG1feOuOmKBqU7MmKYMOv99GQj+mikMTTiBeVNf6EbMtfyOT6AopolhGgKEDAPOs9MANSOOwAy7T1ujNf3l6hvj9zTu/J+7YxW9uIRDrQF9H/sqth6Lu3/D+RXbeF1NCZb74TPGih5f0HjycSX8qfw7c2xbzgrf9fKGiET0uxJRO6kIcHMWP7lwQvc9BpP2NYxbF7or+0GdVqNg0j/E2rxZJg3q6L+NL8O/zU7UYUsOEzLkROSEUaXrOQOF1DuHGHuf+1L35hGqBOD49G5VfK7Fs3fzN6i8N/f55v9+jaeaDf8SdnNCYE8HSb1HLZXoo+QVzv3NkiqSFnMnfzGvgv4Ds9nA/3V2Sv5Ym8cczjoB5JJdTYkWZiPTcJkckvIRjCfbsEGRm+pdK1pE28cKuYx3LPvaRcHNQoOgL8NG2lUKrAyIB7YF8U+A1su8vzwtLbG7r6r+5EflGPA/2G/NDAl0m2odg/3pBuIJ2rl5Sp089Xp8Ub9uqDYC3ZUVsec7uG31/AxsY3GFurjnoza1ewhghBeHlnYTbEKtjBm3HE6Lousx8N0DPtg5gkxeKTp/nvQDaM0BkmcIe8ISk1BTGCs97CVVWfuSOU05JNbmJaTKHVX4JTcXdRXl+TbQueSYQExu74dGKajj3jpYI99WwhcfPxZOvpvGru7ho8bKrtUrAuD+W8z9gRuNjqF/Rd2Ar6i2CTPRvGle39sdJoCNaztVVv7XmjwBN6AXmRu3PzbO5OU58X0o17Ko+JWWHJjBJxIi5PtshASEvyaXFHuWjWBiGmhRnbLJEhXzVTdok5uvI5apDCdEcEF40++11M95+f3ywtOgTKZ1ixP8cf4lQ6vamX2HII55oA0BH9RBAu94tmYR1I7/FZOzo7vtfxlVBSTCYKKBdswGlpCHUbiS54TYKXI3LxYJB+AZq7sCW35dfTkkXIgGOZX+DNIs2PbZffGCHaLkje0AN5J16HokzHVEK2La0KqTFfUsHpQZjyadE1e9tRg6PeDl0f5iiwRyX6J98WUY/SXL3eU4+cPhrudEltpO3uTwTjehl0YTrti8903xvLPnNdaVR0//W/zH0UvA2fP7mNeAhQe3XLteHoSKls2vthSny1AP+9ov5bkKnI50EFnyk4GWaSAi+hobD0JGcXSBSPSCvgyP0CenMx8nQLea3zv7XLxCYJNgo9zfdoLwNiZLdNuHdBBYJWB5lew4YrY3oMefV6yExeVY5plKnbsLrNi6+N+6yZQoGlv+R/1HXuE81k/uAYy1sBSpDflRHf2Dv2YGH47PcnytBXCB89RmaasTFVW9jgYcWals73m6clPRx+Mq8h6askZ4wQfHkKptDfIwHWPwqgluJXQRWEH/jdOtEH3aHd4wVsLva5QP59yu+bDNvnXDNM517cbqHJ49Vtcl0I44hkAsEOpD+ZGSCBp4SWnq0PsKlrtRe9yavjRafHoATWm22sFeouQWT73GgTksb8oMYxXxdkUTREHgLTZMMvlAnHQTE+L+qAcL2EqQN49yEJNug47Qgz2M8Uevo156qwZRb8Q2iiv5enumaltOgML82uvXsYMo6PJtoPkLLodPKjAj6Zf+6MAHz3uEGCeCAAVWKOmwcmNLCxaKpvyY/JdVeV3l/zMfUMmGpgs3oceOFSI0u1d4xjmnEquRi5+bnajIlOQfGxoJmiq9cQ3kjuQ7o/vSfoe3pKNl9B8VW3s5K0X5JIcruFS+nnfD4NvZr1yPa75jiQnjVEcOj69dVBqXD0NlfGdnLSGdLIw/lSNzitUZUSXLlc3CnEf6t/c6kSos+HwZIwXMCUGvALpoZ0PKTwKP5qXGC5HoSuQfkwsiNTZqVxLXUjU3oDnZtxwnkQr98iVy30106LGakrZC2B1iQc03IT9lv4Kv4v+VnX+coS//3QAdzMrb5UooLcIxhevlZdHsZtPa1iyvUZUKx/Xj64of5QG4AaXR/mxPXtEOKMACgIpqfyaeYRv8SKrf0krfA8MZZrDeu4ZF8yDwZWbmEcDNqse718W6crcC6wIhBUcXW0er1FH66VAga34aqPq7KxGtjf46PZ5uyNKvKPMW03aBEhxx/XhGDJ4jqhaS7psIOWm7oz2MXm4+gujVlle/WK08sA2UeLsfHM/tS4B/gqwgOPZSOr8Qx8IfNpY59NlNyWEMX9TO/x9vFh7/Mi6yHHUbTgkq5C6ukQniW+ECbxCbt69rwYA8Wn3XaknpBnCHsmn4BP8NC6IBtD/UYqSfoatYfqnJtGNwcelfqNEuoegdPX2n1Ez/Xs3z5JTvLqzZlme401eapUSHXf/jyK+RirOOq9uv6Y/j6QVfMiQ/Je39px5qTfxOJ7JdzOV8v7gQLS2TBirCk7Ri2ndjdOpnSAJI9nLdJNwpIxjYO2fT878MKuxEfthM6W2TYsAdieK594bJqeH+/fsYRRjssad/YxLW8KE1Ah+L9x/DX63EQI94ekfh+jywqXFJMQV1gsQBQxd5fWtxAscWan3+zdoqZ9jGDmJiYLOhbWaW67drffu1EJVNqSP6OJ+CS6Ot4q4W2ZGY3t44vsP2zQP0aPbBWdzmbsjJNNWYBlZysxS/xrCTEZN9JGp9R974oRZkO2jdHL6/Mb8Qno82ie63Nbk9ePotQobUe3P2TaS9vivu+oRFD5lNQdGyRJI8fSnhN2sPkQmX+m8vb988Fx65hwQNbay/EMq7t+qJ4RE2wcL5UfJh32k9+6Hp/HXJTM701HBoK+j45rmiGgp+2/yVgQppjYF/HFaWnlOKbdsBftQ48VVuRxezqcXxvruKN6MlZv+F1mwUChitOmqahTWQyPCMyM5ozqvc4G8M8UePId/MEvbcvNvk5qUScBA5cdBZRWV42Q51p8oa4kkuJA4pE3/pyXfNV510g2tD5yMw86oe+Cz3cwxwSuRJjXrgvTjZuhUyfbhTGLsm99CkPBM28CR56QeZi//n6pGLppvfNOYGYNVfhMctj24mqO6UlDwPYBPXNMuTdNPPRA8IEBo29pvdv4NgLaPiNKxlfRUQFMWxMcOquSkl6KVMyBs9bZwwK7VlV/TwE2UaQA5+a/VIi4K7ciUVZjeHhF2hLceviG4vCUXz20M/wAUyUA/vInITmmiNQy9YTSjn+keBgeIF4kJ7JhitcFOGr6mDgk4jc3KEgogK8rgA3owa1LMibsNSVD8jQluHneHGxwQ4DHnGc6tgKphnj8mcq8fNZW0CJPoQrvKz5N+Rq0TVc0rv+xqN/hUPbC/1DoLLg7gHqLqS8/r5k9/hLk7cHOwoR+z0JrgPSz7gXImvqh8pxNP+nUDCGtwWqpNnEKV2aVl0hW+JCWmCXASZq2KKPLDI0rWNM/w3Ur0nhmFxYX5x0FlIy7R7FFb+Uzdoxl+d4ENYrjSbib6o+0igosAw/96BDL6cFSHCBG86HxIzzOdr6HHrUoLJ0NUNmkSEHti+zX04rCqH70VKpa/qXLWWdLSg3tUachMpfaVRiusr+sF3ZG1xA8BdmMTLLelYd7VTa+1cGc6BtgJzhmS/7k/STRJQqQFpivk8drvE/FW2kzL71I4dS6s8VMoRSvjail3lmeC8Sh9AeJYpPXrIVTfkbDnwML7/XUjPpvM1/6vqyrv5Do0+hiy653oHxhXqnEJ0NyGvu9SnP9mz87i5dpvgd448QxBHOoS9rr90ndkYYKfBHROoL5Dpw+/O6o1OD4a/ElMUK3udcJD2kePlL2BsCXAcRxTbq5wgVLKwquBXh7XncNH5UFRWmTj5mqhgWVOxyIir0iji2jGN/uIeaHpUitdZ6EY9mtOggBSeBG2v3LGk4aIrZFF89KoUnuMHaERL66esi7wpVzLBaTuaJd/o1tKH0WXEm2tLtBJjiAYZTWdrdMZHSdJUlSjDrLca9xk70J6Oov3JJp6cNFh0MEtagvCfO8W68T/g3Uo9UmfA9mOEelPsZbAESahpzjLRQ8H0CKNN9Oa+SF5zbL35Su74rzOZSuI8IuwqGZy25H0JU286u5heGFCMDMnNQtP113WUg3gOCebS3yVNRx+C5dQuVle2BOuzWx7hp6hsWBtPG5zadDg4JeoN6/peGUEtaBO/AEyQ4bNF2VZEzO4DteyhUu7nZvr8dgF+0646VYvDNzt63ptUqhz5XhryYL/pbhdT4kjSqMrodpjkxh1ZU1t1O/RFO0sZOuJkkUTOaCrqFWdQfd5/F5wpRSswp+FH++BRQcLlEIR9Dp1ND7E/QmWSAh9c++TN6y+OSAasG+SlFgz9FRTVhlX4B5jLCUp4+g05Wrd+YbR2d3N6MXOwc1SN/+R77AIqopd9Ab9QjIpb4zsAzl8ysRiWIwyEBAyaUlBCdnfNtiuyVuH2y5ZEKS25yyqcqN2VBCPh2sv6103RQq3hyIU6JFk87ZneOvThbyyggyGX7KK3KoCs2e3r44obS9tPo8jHQn6RrhtuKLiepsdH2it5XdmcT48vRl2lBGeMq62O0gyRIXFCD4m/d/3fPpBbHfbmFBtAc6InbgBEHUtgOeS7Q+39PfTlO/vt11k9sY6CUxJ7X6o/GpC0GSrhT+/qqwcFl2rOrSEhQi40w6JRyLZHs8Avuqb20jLWWVfGM5iABhX/i0N+/M9WiSeNLfeVTV5dXsNmwtCT3+xkwr9db9I1Yf6HtZPHmB3wON3QrNGx+pYbzUOZeLtQP4WEsSu6dSkoE3ZJwo1Pdz3WJisFtivm6/Wv37IOlyFEJQTIHZroVf41mIw+/Jnn3m2IjCFP+HPzC4mKdKGaH2GQ/UMbWZ3BKw5dJRvFUEEFLMZn4GOULmqRmSy/qHLyTjAbAhWzUQY5klpCRG67R+ABTSiHFlzGG4peJfXrir8Ttl6CV5OynjtpzmsYBXMgw4n9Ws1Z0g++nOq3mprXFQCfG12//LMZKldqI9B2XyliEqiD8Dvfm/V1tH7wynZtd/ix9T06g+EE7pEVqEs+pgKhHKoXUMik1wfvGp+gLv93bOU8ScQ2v9d+VtLLkkcyVKAksVUPyHim/i1looVAKfAAVR7kDfyqeFkYZUYkyFtq2JGgUhWhXIOSWj9Rn3gEXAZkCbJOCKCuLqwYaFFKlWYw0tf/lWds4hWNhbFFsILylDyOr/0aUlMUA6VzwOkZBQq4JvZyxwoFOlmAKjVc/zuJ5juv+OKoRP7oRfmHbX3rnbDCvKKE+QoXSinIamkVmuMl2mVu+ffIQQit0zVRMf6/fZzxyl6FKnj4sBMZ8kU+kCq9WHq4TrGf4ysUf4OyIOePh1NzbEcb3ojpLCPk4YdakYzxSMdsuiaZUeZPK/B8T4k3E50HwsdMZhSY6PKkkzEma1PcQIYmlmIpzG1HmKHTpEiCWgOeuvSEYoG7HMEm8SEnu9TuASIM//EQbOaSfOv5iZ124g0dxRKfQDuyVjarN+1TaRyY/OBKF/eHK5De0UnDLtEcLQ6xXlSnQcEuPuDxxYmnWoDllF5f03L6qggvfnjxGEPZ4HZjS1166gQQE+9e6B7QruKbE75XTLQhsrvfceqaI9AMCQNJj6eud8mjUr3NMlOXCzzVr3b+uCMn5vn83egBeRhpHCMy7/n63LabQ5SX/7087wh4CW4ub4ZAGuPi6BIwkJLVBDK1PWZ5yx+7X0HsBh72Rir/Bu3f/PE/ehLyOu8I0F/UkJdyhnmn33W27TlWq+9sUKczzzGQoQaDQUBM/x8iAwSpS5dp+re2NZ7oyzIkNyzTNZyu9Hj3xIx3co90rqJbqR5PvX6KgZgl3vvGC6G/smdENVlLKbhVtoCakI40o/4OisCnUwWkDFPdXoxUB/Nfz9KLHF/mI+l9/D1EKs6QLLldxDBBSKCcs7XLG/SsE8fzYG3GZEvqq67B+6rmYNEQ3O7jGzpK98VzsRCbQvt9WXbztZ2tY12p+W/Ae4G46okTq9DwtSAm9TIiN/BEk6H+I1CYOuFdc/yUQ4/W46hr//ToCYS1v5CjwrpdYlLzHFnHqBJ1jhAaqGecVYoqgB9oTKGgeSfxbT51LChhGMTA/0Q7O0C5vIM7BOncclzrzldTt202/zp4kNawUXcuGqK2dLC5Iojtc+mXF6FKd4w/RRVSVOtZDy4gIgcWzFeN/dIs+/1aqYBLqIvETkMk4uByBx72Bqvtdi9NjhATbF158FkVIIeDx5THpqm3A6UIpKBTLrB0k6s4vtIjUy80E2x0ysNuLhdq+da+xR5h1qYc7wM06FPQnJYew01XfQJRB8bu5e0QrCqHgwPO9P+jZEFXt0ffU6H0eoqbpo0bFYED2QVB1qyzip+2bRP+9piaIZeyOvuZo3+om66XWZ+t9TuqgtBkY0/29cXBhrCzBHgWRVeRlpfClUuQ21ZgmOmzoL3yBGvkZUWjWU5L1xacA/2INFerrAafMEfyVxldQKYoFgBGhOW95lyrtJPGS+6QNCn4KDnEmP0hlJ9wX+Sv6fgwMPyz86C0YRE/d3qjAb1IYKAw+ha5+3Uxi/kAFjcyMti7sd+0MKdVDingtdk2VLCyF415OEKddZeJTzV2CnOV97LMfY+lyhpiyLdO9p3N0/Xf9lL/2DnrqIqdoGucPB4m38aJ87M9S0D1CByndlXVp/zV422rs/+0Z429GXV4IP9ydkT26z64BsKsuCzR09GNjhRwm8xcVpEKdz4dxAAZA2v3+VwhwcuhWf6oPYbBjWadWKdUbKnz29ZTpIg1SpRul1F70/m6h58Zl3gw/cKGKAUIM4Q2pE/pG2AQMPBZJqGKD+aNeuygG9efMOwZ1R876GbHNNV4Cnjt3B3GOyDEvp0fSd3vBMPX+7h1Oq1nRStIkR9rKWJH3u7V2SLFUwuGWFiOMHHTawfYXmh9NnOOkQszPHKkqudsV6qzo756v4I239muH9sp2B/htojZejTXTe1w0ROpJWd2Z61phE1Jmv9wlTIOq4hh1Dj9+olF28n9wTnM8VX2Kn9LMk0IDlr44xPbziEcYob7QfvpcscNKfllM3pc5+sg2cfvtRlWIvzyU1SVCUsz4frFKzN+40qV1sKyTRyxj5h8CNrv4bvBu9A0Dj4xiSx2JDl1PHTLsHIOOukKJHpEWNG9P33KZH4KTdaDwe0KbsDnGtR2XLD2zH31Iz8BgK/yL+5qmhXXiux01L/45Fw3dq46TvaExsNK/Inv5WMrWB+4py7XiUneBEQ0FxhahNNBME7f1Yz95q6vOYBzNfc19M0UL7AhHEKOS+bvKZfEioFMR1xfz52WWbTJD8NpT7+ivnKVndy+Eudm/WiD/7lG7n1JrROQAIZdtFL4EOIJKfrXxox0niuEWEtk1S9Nd0ktMDbvvb/1J49Xv/SW2jG2r6+4UoDSqPKRkkfuTBSpoMhz3/isI1dC3mXoi8Ko3StLHVm9pDwgzrmT3byh6TRqs9BGxPN5HQTvZTafUB02IVJu6UaA+EfPjyGEJBxg8vyHx+sTvmZ//bTbP2DVS42FuMvHDZrFLyszM7XwuUjrn2LUh0hJOPuwi2iW5rWjwvPYsSmHgihAfzAJAFd1da5N+g/bkv1a76jdqYpsqabAaZGO6l+Hcn1TswOw+ELNrX1UHmN4y91qeEgrmLqneOCHdRHObzHMmfFxzL/nUB09N4rOmasxEzYy5P4fBLL5+aomqtot4WlzJdWC6we2g8raW2HnT6KTOSB0XOyVnl0QNZNVhX1nOD6JOuvvOf7QAEpEn9DcTZJXBjEEWyW93PrUSv0VnkNRLNwYzLFsrgNss8I2FJE+Bvi79MyIZHcDOgMLvI2fPKq83aSyhxKFfgoZU6AmMLzk4R219+5/aUc44bRn1xYXV6SJzSe4QG2u1SPvnDCBsvhJUGuw7zTO4Hy+lo72LE3PGx07lb90i2dmVJOCPDuCBe1476RlclP1VNnS0G1h9GN8ktasK3gvq7C0gfUg/4KrEW8DwxAzP+vWLBb0nQQT0AjWtvw2qw5Gi6TihmYAV8GgQGEcK3z+Rcq4tnJfRkQv/FzYtrins6NX2YXEk/BQnuami8tE4lxBEdRXD/aPuNWif0VoevFpkewNpgdDkAS3MeH4Z7+ZhFfdc5+QoeAARTzFl06Ft92VSHcTEabagSymhIB4RApVTzs+FNsGKyu74RE7z/usa0rjEytg7FtvNtJzNvroea0NGPscdhPt2nONotsqAiFmoGRkqVgyt3r5+q5cqSJM8luPROe95fh4IullQKZdGL4kqgVIFnzXEpxXv+WImx5y1dOMUkzgduBB0+ZL27e8mDCAdQCCS5dfSCzc6a5KpfwP7lRsVpcY9fX4CKbqFGcDYHKqJzlC3j/D5re1nKRoTw3UvxvJz32rCaN8zagUBwaBicuY8RJ6hM1bnF76+DjLrbUnLSIPWqnsIYF97AMPd2A9OkkbPB13NvP0/YOkvtYH3oTOcO4e0RTqcPKuw3rTPAucGX0FSmGAi+scLq6VmCx61Bzf3lc6P5WUhaaUJAArK27baf5YU48XWgCU3JYCKKriBLSEiNpbDI18Sgq+RyS0n0ccIzAJF6uiX/6cFcHz56IQHrQ+eYrU7b1ClzR1SfvRtZ500ciErW1aICwqHYCSQOXRDxWFernIxjPVQgj/OCw14ZDlykqDN6a0j8/ODm+X80Nb7q9vwAO7KkYZfF456+YVR9fOGtw85cFv7i7UejItnd/2+dD3lVprIDYLUKA3/Q1c986kagjq3z3wLvutTjiHCcy5Z6f1bGr/kiWn/3i/fEMgfyTMwVGMMf/LnZSkwfmyNjNdb6Vy/GYr4UkmqSb/uvoRbZn9PsdQHM0mNbZdBsRwbbN8GsTmTmQibILF+zCxugh3n/OthxI6VkzOY+hRkN2QfLMvW/b4Iww3/NWGvuZCBInSs71UMn/uvWTUXtcO+9/7hY16O+oeI8sS0YPtmnAZpB4dMJi362apsR1tgQfJIIMnfXODOZZIW8gpeYSPHly0jggBrMxoQCm6bhwEUEEaiMPAWW9xsgYLdQuMGMq0GELqFlTU5O+oBHC6NP7RbY+pnLba04qZ+29ZRnY0kNLuGLwqXrPgXVX00wls6WybMyjs1bWNp9V/rcpvMn+/mwHGlTSoOpAArE/42ZatwskZ6tMQCvRz4fJyi3oP2JuCpWUNkbuZPsRY2xkpopuKVLj6ADsXwXenievUZTUyxqOuNSi8Zwkd/5SgQnVl//iaHH5Y3Vdbk7wHyWMHUm2E+KN/OFU9hWSxkBumwZAXSzA7Xd10bBAoKPh42ybTxWoZqHdFaP1+9D5Yk4O9igJ0kOqG6zyCw1pctVGpmXwuWievcF2kcmo6VQeE0p3skn8cjj5KE0hK0FZhp6JPVHTj3IGJdbuRS4WEmEtriRyXGgF196lOxWX5IyPkFiPOiZHgotsDN776rvvT2aRMWJObWZ/jrJK/smORaqBcFEWgTu4yo/kHwhJ3deIENYe5aL6nootMGICMEV1RvYWbdQmzrKgFl5aqtieMQvQUtOpetAvcDvxTc3yo3mFPKfE1CR4bEHPNxnQ0mt2xZpYWRixxTZjDbIl3VyZVoMIora2cRC6w/pYDNsUMuvM0Nh+Yo0BAx/8bqyEZ87Bw/l6z3Bj3MIFxdfWL+Peqw+fWcXCXFNMZ/yuoq6yWqOE/WFSPv9gvCZbhv7YvLlSkZ49dIJUbgiEZ27oQngBASiQ5s0HZrd4ggzM+gLqzQud7fnBOdREPc+aWX7Bg0dkoaHwjM35R8kMgXz5ZprUIlui/4IJD/lhcHZh4YzPtgdfCa07px0zqatBHQ/aC7Gfzm2Dgi8QbL2GjDlmI/OVbta9fVHFnsXbK8JjCFSuiX6oZDyiyp6O7YbTXOVPHwv+kxLY42wD7U7GR2Q+5tMHKHVWbdXIwZfj7AQCZ+5PJi2lZSFbyXWqIjBgrevK8w+4yMIus0M3nTw30gE78DWP5nTrBSZsrHeE0qQi7Qwwg+V85XyVNPGd/kFUSXik3aJRXCC5qOISqkv62rDC01MlH751Zta9V30e+4gt9hDmZ34RH0huSbfQ82B77koXAZn21IIyvwfpGe4cU8f9zQe+nm5ygrp9ol9Fi8529VUl3D9WZjJGjgrFKQ2mANWTndr5201CEy9Wz+tUHzA/rFGCAE8K3DyWIQLuP3Z+aooTAgfziFhlyY8nsABZKxM5/ol+5q+FhVFm2MuNYRE6d+uYgq7gb401nx8o1ySZzK4gAirVUvlOUrRjX+NyckWUC/FaSKsOvlg73T+Z25p0toA2Lwp2KmRkcVIb4VNrGZ7aLeMV6L4s7FMnl2XtJi43IijaAE7NeXxMq6OAAzfTxBvqHdKbOr5THGlR6hscO9Mt7J0rnKrg0znChrVXGxPG8MYPkPQPbD5702vaqzcCgMAihpcsACk5HiTt9GTkcM73zSnSVjPFbC3Ej+6fgFZRXBlxwZ8S9pRZPe/YChKyy+U+aBpdff9idjt33LSHKGf30ycAQM6Nh/ufuMIVt8JVrDaj/qIDnHx1cVPwS0leaPRK8+Nkkz5phuxXMKKCnLFQ+7lG/fktpTDCnD9Vxr/xMIYUBf5G4O+9lx3eug9DdOfigSH8sA25JpX066Ass2RZlI5zx4cE+l0svmsNaVZYQ+5QcwhZJV/OpDvj4sOzAUEcxh2nxVlj/CHQFZNVeTZYLeY3VySmjVYrznbQD/kC6UdvZWdLJFLPs0CGp1V5Ja3/jwwkPBIaeHXYX/9lYj3y+vaWkoZEXRiiezLs/krkECEsWhJhqYHWRSmSoEcl16UEazGDWhcH/JeSsYxdSfJ8lhMMsdYV16aux1lQw6zsGLV9QHtjboHCeQzdrR+cy/ppH8WP49AD8supE8UsdhWek9u3Vd0+VvkaW2VVMl7JyQY9zOWL4JfdXLGnvw9hpOLqqh6LdIfng9QNuchVwoCBDr8oA5bbQufNoQ0byDSWXv8cd6Lk4vmkq7IwauSv/aci1yhxitxTfgET4wH0xaPB/s1ipR4HvZrETfz1dFKwIUNd52nis8RX6gw/d5r+HqmfmSPx5oVn6eY3gUeNsybb+zqnFLx3zzUhZeAbXG2XIu7chanwXoSXjM4E0Z4I0vnLai4n75KShqbxZcfRnai3xVF84NawRvUBTn0dWh+D7Ih6S81MOOxuXZXMY9ch5wwS4/ux4S9Esc+Ma3idahPvKAk0unhMbeoz/xgoP2kk7k1MCb0wcFaw/Hdj79lmiZCYG3+ijAG91FYfEeZbdCZapq/tpzTk2+M68XzCYyTfZLUariTVWtElDfXqUOhs1t03ZrztO5PAmyosmH9tTwyRRgXtTZ99M23YKNhRZAb+Pf8Iq8CpBbVU0kU1heaQ3SHRIQkdrA5tNTLg6tgItZ+tyLFA+r2Kpq4s7T4KRcQYUo17ALv/BnaXywm5Sb1Vu3wdF8SHj93w/Fra9JEutEUhHK0iVBkzOTEtxN1B4qcV47Vu1KghBmzExydxF7p5unxYGEZNW8fbgUxni3zkmyudRQHkzXXOiilMjkCZO4h8hSbnPDn1uepimP/Z2d9LLUzatWVay+y2Wn2Hqm5WsEKgT8cn9kO/3761dFodkC99J5nm/Xg062W9pbtNDGFRapKNgpnYM1WyYzx9ui0bfsLkb1PsUO57fF3j0hPUW90S7IFPV9Dto/DkbCtBeusMTK2SAiU9qHDeDjBQ6k6n9gnZ899G92vFB89zr8MVaNAjWoyxFABP7meSaps802fywWx1vXDsi12ID6c7EvDz2b3Fj0XBFLBv6F9sBNqcc9/7yUVVQjnz18XxkD91WqLLfA+5aKd0ex7OSf398SoCz2eV8kklk6P+brxIKKhyit7fkx+X1Gv3AlXc6Uc4Sp7GeNtacz4B57l+oQUuV3q3ejvaPXN85lb0RDPNDnqemn7ebXqgmKgFoSIx1bN/Vf15Q/L24c2Kirikep3ocwhvgOPn6/os/yl2q0og+0XfCvYkZM0w0Q69hiQkUmlIYqDopBrMntA/ieAm0+ZoWXvpqMwV52Lvlju3+jysLVGWMsEET/JnXxpR+mfhToAr55hC6KMqiQGMw3onlNdPu/SgVVOqxoIvco5Z3cZf2LjR0/ygO48mWkj1xCrzxHs4Jt3Tp5cQ+PLDMXMcark4OOsiIUmk78Ro/pb1pL71fsGZ09N7ghtUGO5sHep70WEb+qNneRDdZhxNSVGf6Qr7+px2naj7tHLt35cCHmcWHt15Wd0Bu4oc4V/M3PHdBYHxbuOx9f6ycLayzYPhVjf4PNcTRRi7+UMh0231EJXuQTGK1z+JbAI4ptzWWBXNDa4V+XuRFYT/LRiPDfZjPoOt2/g3MOQNNC0moZ7DPWi7ck5Q/dC8FWvyBQZrLncQHoxhxs7uHqPzExxc14YwIWDkaGnLWLivCpxydLcW6WCPBlPMJx0/pHyoOd+23bzyo/Zh01uBGveZFBi/8y9feDLpkZuISsTY4mktRrcprEOpyY8fOo2mX6KcHOTEdVGRqoHL3vpSBujZpn+wPYPCxlgdvkXJAdSP9yWFV0dKeMBeMP34EYIrDVC17NYL7mqQe5vb8aX9Znk29shrQPgMc9hYfLv24NbXadpShEzqoBztilqNPv92szlAOpM4gNc8AgsXNLrNrkB0gyGQRyM+d3o+4eFtjYZXakLnbmx59IHXXsOLujhzukNpIacOUU/7fM/pBQ1f9rXn4aKLxfMxFyh7GDOmDdM3N84Ikrbo2i08z1R7OyIqryjq7AF2BhNcvBZ4VXFDp01apUk70OlfK4F/mQAx0wqj9w/TRIk8HOZab0OLolNswxA4P+OkHIZZtHL8FDpJ28buTz11uPjWLU7gpv5giiDRObTCgeGir6K5+pu8SXB6SbeB/7E4wvTt6iQYKrm5I91U9shqaqMJP9wtuhvOzmOdkNzSqtQBRBTs4KZ7mtQoUoTSco5YcpSL1p7nl8j+Hg41vyBJ5hYb/kVqq5p7P/SzHa9ENUPsMvtF0D+3by6EZHm4yPoyhLsyjrLl/Y8ZPJjomfICSKjvP9rfSSykHa121FuADe1exXUDabt03SCOFZCncJFynl3v4GJQkPghJu5YaXhkOxVzYg5e+cAbIu5lffJSA61KTmum7+5bUdQmZDsI7V++iZqeLmlBYnKKnzjBqKUi/MCYq+tErXRSVXA/AaW5j/AP/mNTGma22QYN3lpakrlHwGZf51HU52uccFSUz6+gI1ky8JD1+vYh7/MKpwnp+NPD8qAMETKg03ZSrMQ7dS1L3mARRUNuvglYtEMtexPfZwq/iwni/mvxfS2T3Va8ul7n+bXS2scOiwss+XVdZmpE+m6hlr70HzOOHe4J6MYCrufXJA1qwkxWCJLlJGXQsN7mP+jSNyjZ/pnL8NaZThx6RnjjjUhs0E0fir/wQQzHDJT8bbbtxuO2iJa3Z/y+ZZ+rC1+xz8diNvdeTpaOvkOpm0v5trOj/gyvaDsSA/2r1Z/WCEmpbCyGNHLlP5j8P12OtL/IvQLat9Ji0l7cQsbeZzPHMwJMAdqw0ikK4Ca5oUYZ2RqV/XDRKgcn7vhjLqAE30k8nD9MdODocIJSysE/xeibUE0qx6lI5ch/nFFL90Zz8as8ToLHKiwPhfKL8+miLjufadjg7xrapyx/gcxy0X+5eg+MlKdueJR75Y5eECeeNB7JLL+tQ496P1A2nrb3kKBJlxrQbVDEL5ExHmUcDNIYWbYyh6v6deXQBCillXeeC8SpAs+WnKUM+zwwaHFTne2V+nuH9Tzm/V2zNVTJLPLgT9P8FvhjwvdRbr6lUku2eNR6XjRQa5i5Pn3vnwqQ/0JPL1MJboCZexRGl0BEXG6sQuDapbDJ5LaMvAQJRszbP1d475OinWb0SKxzdMB/xGBYR7NuEez0kZUNjF7BBY0cMe7ZhQou56zotgNDymu0aBFT/gHFRA0P6u0JOOiZcj81/os+CsFbOfIuHQ01C6lSsad7qWW998gaYYT25q5RFIwVuZPb+2PPOYvZVZsRg6r9cOY+J6d4xqtVuXKCnfe0BVl92M7A6UMTtJSwXKH47e4Oj8KbIZ7tU3Q6EwlMIhT0tsb+uWvc0DUQJFdBYM1VSb1VaNxlTQDzbQ967Voluowq5OvGRpcLmLvzdot5N/ycGnkCW9uGG9u3FaGcdx/9Ks1JwvFQhw/S76HN1np87pzDKAlxH5+dbZ8QwbphgXtBj+dOjHRaj2x0AZcCBN5u6p9rmZGs2H13U6JJcORZpPJccaJdd17II5QFb+uLawRB2DQCtMshnK/yI+sIlsQtgkxX+qTs00az5K29PcWKHkEVevC7SniUhd92E/WMndUBLilJj55UjomGct011Z2pIT9q1Twe5YL77gf1+d/BCXBv9On9FLHYgt/PNvDLfwYH/T5SIS+32bYlryyrkLXAsSj+dixWjHgBLirxYUjqvof5CrSnqEJQYi72f7cgy3kG0wQ7v0axUvSfDFbOOiKmOZZK1DwiyEKvu6tVrLUQNK8/1AvlkmS0UOhQerCB8BVwqUY4Usd4/OuRWmb1/k8tN+UZplhEJoo4Af0NGvWzF93F82kMGzKyyEtHeTQ9ffatt/2WWvjCGpYzNYUwTYY1R7lOZr/ZoAhAcZs5xouxHKrxCbda+H/m+hMPRj4l5B8f6zL9SLPK+M59R/H1cAnYJttJAHE77Fx0Y1cf7U5/dFNKuWK3p7fLYv9x78qtByZf1NpFOi9ojFIXNaX+xi74CPzGuYuBExLyCYjmUyfWg7zslgUVRg7K9PkgM+Bfm8rAkKtLuKTsfxO55UjbmPIe4TRHmxlEodPE5K0Yg7Vl+aXCEkAYiuDzmgeh5j+6VTuHNdRt/5SzGy8PpSU4OUtSbesuHjpdi2V6pWO1BxlT3+A094K0dceJt4qs36y2T9mbHzrBXj++tRvgme67f77S+uFjBtXfLir0eTqakMH8MyxJjzotP9X+auK8tRJciuZv7x5hNvhPfiD4T3RtjVD6nqN2uYPn26pSokkszIiHsjw4AzKhCmhSPv8eWzvtWlJVAjvyh44O6JE3a43hOTcAL5qydje/M8vlCa4ChpC/NPRP0ZyGU0EasemPlLDTiY8xey05Vb/orm0Nz+fIgJj0Dx8A5msF5K+vvT+d4IrUso0V2Jh06SgBAt0Ri0LLdIom3z8zxOoaUt6VRj7aA8yckLbIEfHDDpOIhERs6NYnlx9l5puBYmC78bo3r4bW9HBHDqJoLs2c0Wddwbjedmftcl1x7esC6y7F93pA+jlBM1cdviEQh48ivLP7ODzfAwFGdO8KDnhk9x7UaLDOtF5O1g4oa2Fm/ckaZ8b6fp4SVGdInUBSkuiH2YdA3XWabwGTcZzQ7iijbE6C8gn7H1a34+RWmXo6YtGEJj/yo6FPDlAMdm1keJvmWFtKx4jkVcjPS9KFRKte8qK/dauL646/IKqOQD9b0Z4avRORh4ZRUg77l4BSBdEGx1S5tvv++MASsJ8tcB5Kxfgzj7JX0c1QlLUjF/Mgjy2nX/ZrA7Ozl5zx9u6J2r5aoxZ9DApEQG0WMpW7+/7NZ49BbBuUa/bEVCZbSaZGrosK+MnqWhMmU1cd3Lo4fjrSVlbNbpHCgP9eHCNXY7qS+6YeVFn9uYjxhVnX19IlCil3XplSLgbE4ZWMeTRe/WjyVZ0zdC47OLsuX9QoV9XIJlP+tJU189ZSHvpro7QmQsNdHJtHzB2+tU9dib6jdPCwO3XMmc402j4PnxUHcOF16NS7TcvKBqLgr7QbTyrHGz81pt0f8mqfbowEkTVsUWTezbjg3FmtfO4yTft12ijGV9dnaBKSlw9N+zUprBghncdT9id0Njr1wwdw6knaLiZwekSJQzrpv3wrhc1JcTlmmLKlLT10Q4DvxQd9Nr0v31pV9Ou1j2wWwN8CO6tcWIES/AlQZbW0i2mEdqaQTUIOPOLQ6qb6Mfdw8YW87tmWCmYWtTSLYicXFzLW/U5liLF9Y5m3L0Lv8OlXsGTvHPV+8CwDB3e7giPxzLtCpx+li3DAaRjZhTwriqMyBabzesarqui33IeKlWcKATcHsstKg0mQ6/vSvLKWTdcBA7Oywg+VwU5IA1eKw/ol+k/xdygV5gt4KoPUwmTI1hx/jrIkkTQJ6Y9BGVJPZDcn8uIF+LV8r9SWlWpTrzqeGV+ZWQD33GKOa+5mGUgMqHJk/rHIxWv9dWHKmQmtoFJRLlCUa95V8xObg7U1Yfztcyyb4WnWmuKnjfDVcNi9GbnSnzNg746ADUFPMFdshZzcvZu1BWEelUvXy9HxgI3MLGrWHLW4Sz1OC4l+9nu1e26U4ULDAyR0xvVSLV/Lot/A15TW13hJb0pSplouhZe2UTAyrADB+jJuusx3Bgb09iCTYLiOhIP3rH0qGRq7yy3ZMUvEa3tIiXyE6YACETK71IVWofHMED36hwjtpQh0IDsZdukbXC3yEm0vYdi2nefyyyfwEl3HOteB9HaSe5KJ6Rq381IzJNq345+yN7ivKllJRV0z3bUIoiN7N99u0EtUQAqOwKJ/HnzXIguX6OMOuliRz72FoiAC6CpTGIpQX6MWZsf9iBSHId2L9cEdpEdEX0hSllNRMidO58vgLvFjauFpn58h3vlUds8pqqYuXzQRAytamx0fhnPGxYIR6Vaup13lJ8ZYXfEY64aeOWVI+sg0/zSSDmd17r6urk5zUesYqRN+oVctn7wTO38s80jq0soss9cZE3CGHyOjNPAKwiz+PbNqKs2O5a+urrrzVsKoTzsaRAdMxPpZ6iZ7uSkEDvdb+Dsjbf9FTLhUB2GLyeMiam6ttYdXPUtHNDszPoXOMSKNGntEjrBHReIccUWija8c+e7OMeUUaqaOobLpsDt+J+s99kJZ911xvYy2DkHoGoUeQjkSt9ZKpOHpsuF4xW0gs79/NtfY/Q7BEWCO7/9WpgWYsBqeTIB+fsMlo9yzP04VA2jd5eqP5+FZ3Xqb/mTF0yYbNvqVsf04GBR4XXQifPaW1HsbSO5NupegUHooy/slOGOb+M0zYcKsBBZfueAm/gUmfdZy5xKoU27ZmKmKJKoS5Rg85UD6X8Vj5yhNkNnrQhdmNju/ABYB3yIPcBp75ZklPqtAExEGHCf+E2tbLKaTqAUrsyapUyAdBfsW/YGmlDr3aLRcShWTqcoRX72FxWsIsDUoRMRPaP1g+1FICSQEyL7sGC7agT7z7LyAYtnxXopHSR7qV+mJc6PUhsdwPdmwduRVWsWhGa8qBpbtoxZuBYvf2vE57Duwe9+ER+e77Kb3y2E0nU98O5hiBulZCoek9FRHeG/Omix1JuJFuSdVW2BnFTGw//ytyhLkYJ7IjXDJQxDdb30bq+X9RNf74kAum/3IyvkMJy91HulA2dcAjCX6kWgG25ELtPtC54ta8Mz7RlE1L96TVRmK87IFWZFaWHefns+5eV6uLCDzCsigvxFp8TzPVrrWHIXJDmXAwvn9Fozzf5aRZpUEcmSmboy/AkxWTDr8UgtVDWaU9d4dlehz1GQX3ZzgdwXLWa3xnNkHHtL4f/qqcHGpPvfpOkkE/VudfJIuaHyslYS3ZCfotoYWxnxmFEelA8z5/dUAL89cLyOZlRrMjjZz8hfnY8W8K7/FBi6lJDMjJgSJuWHaa24bdtd37EYmTXOEbyzJCwwXhL0kUnFz4kPuo1RnHebzhfyKQiTiralyEuA46KQw7Mk2f53S7MBJ152YUfvkkUnyJB4T2h5KF7VPqwi3Q/J30YUy9EsNztQHYhBxNBmIa2VSs+VvmbnF4LDnWvhL68aAHOaIokfN6BxmgWMwlN6HFVzHYOKq07ZKAgLz2MSFPxic/3yh9YGEqq+yh2XD1V8wvBac4YwS9zpVcSYAqX3NHiT2pnduJOM1pfZnE5LbDM358OZBeRPymrBaq2O0jI14NPo67ppSLi3Orru0oOL8bf0JViAtdXo3LsGTuuurYx1vHJh7bmWscfHep9ucJ4494RgNsfUz7cLwTsm2G1UtJLXYsMrY32xh7Ftt7NxSKnVcD9IL9fVrSX3h/Rgt9hr5QPfZ/Yn5kH2n5nmkyhM0eiz37z576SAq0Ujvf5nXYle6kMBdE5kOtRKnrkK6GrQfGRUlBI6ipKAkhrRZqpAwcUKLW7ZTjwDAKu0NQaRbvvi7xT4iIh4HnEfxbJSdRYWuYdeN12tV8WXH7LMdZVmul0pSQKXCXfYtswbLCNxK/slPDQ5PBygMKUsTdAYnQHzPn+mXlptGzrl4+mtsKcqPMgLWEZfs5BdB+Wvf4wC6y+fg4MQ7jum37FY1O9q20FEeLps8E6p+vKOxdrdclPdvYchYuMYcsruBAoVoZN8s0ZKZW32PeGWuhapcYZ60ygJHxSSiVw8tBiLF7qhDIsEQhWoQ3yA+FN6JvzCloGo/S+Rxxb8Gv3CjcZh3QWUb0Y+8rb6dvSpMnissoJZ+YPfY8fNJgh2kiM+Hc5VO/ZvzVzj1jhhOySbtGGwd/7LBeU9zm6zXvfBabPK9/++uVzoVEWI4S65jb7hey/NtkvyXJtfBK2D2rbV6lUrpxdnpvtnzf5Gr9y/CWdv1AvlNZh5RcRAkfisc1O1ufBnyx/14qZFuSU55heWC4ZZHzQaBb/mqTk+Cz/0AbtDi/lpcyEFlAcSdDXe2Vmxh9ZE0bO9pfliA6+I0mLvY4Ko50O5g+qbZTAkSGwkA7WFGl57WHv5WOpkI800CxcNPbLIqZGLa33o8EsaCqb3f9YSRt7P4f2KyX49y8F/7z6VxBXNXCitKE1UBz8bHUZkNw8hlb3G6VcXjdjtVLbr9+rgNK0ZL7NzJk/J7+kt4rGxEfgQbc0VqK9cOYqAVZyMcDRCBmYVAe2RRzXsS4/OCDkxZAg8Ndvw7pUDnOSoUz1PmTP4pc0sSLLf8+DERutwHSm8wP769KEwMLsApr/iNAekRWyaUSh0IoqNsM5jRHsr7BmP3dP+ezXiObdmPnmoyNnnOWaMSMsFduvKQLTW0N+A4ObYNksG21F3JzU3BmIZRo0BZ/baA7ou90ziQ7oF/eeyVQYnJ55oIAZP6i588gDrbOa6LNY6TtfTKloABZKvzhxHGr4np4r7p1BQu/TPvPM0iZAV7cFsbgvjuSqWN7rwwRWg0aJt1XVGi+JwnnWi7mi+M3gKVVc38jIoEjA+mpjmP3XTGDk4Cm/RXXns4078Ws5AAnrb9Cn+lUyuboJVEK8IKpiceWsbmw20RS6t/O7baYsRYyK0XonuJGy9gjXlX7mhoY+pX2afXTdMOhRwET5PS6LZBz3MS6I+ZLU8zhAH2y2EcdiI6O6TV5t2rXldRiPATUe4tM/t/mmC+iovPHPbUQMoiQKfquwNirSQHSQL21tVjrfXx9KidTJTvBc68r790O2kpzd2w+28xv/3zgMzBCNitwloLdqafaiI0C8FTd//eQxzE8SJQtFCHiA+EIpjTtUAjOS93OmZBy1GRJ7p+nSOGzJ7XJhtgl66Lx3mMSJbgVdsjsLxiJK0qFQxk8Jg3qRbFLVrhvNf+fRbAu/IcacDgn9dQLnmsLm840VXQNHBGkvXaa4gL8hdsx/jZ1/o4PspFnpAxUe++sJposwj57zThCm0nS6k1MmZsAHmFWkjErMyTapX1qslKimaOgNYeHGslZySGjQyfeZ1Zvd+E66ldj85ciRxsM/P2MbImtGyxvSphqqIm2JPaPjf92jddpZ+kLT288UivB45HXOO8q0RlXwcwfizH9XWgbDAtcnhdQzUPau2WStxyp54MpjdDFtdmg7GMP21T8K0ieVci/6Odfir74mkX3fZL63yx8ALF0fKCwo/EA/meH5JoPXNeL5/+7mzK/+aG9105XeoM/j+Hcd72U90AznL+zEUtvqQ1Q0xoA/rh+Yzgvn3oryPyj/PyibJmtOPFPB1QFrOgf0ksoRXGi4fiX45fPqAm/5hGPez/9s3rrJC/xEihwxlB0vRWIoewBabLNsLNF17D5kMBSHOFC7d+jgn0/XWc/1ry+uOoLo58YDCHz4DsQ8BwhA8vrP4jNHLSjF/RZ98iPmYVKn76/YkX4qPHNJy55E2Wv+9R33RTGhHAip7aY9yHKvnZwJlRt29PONv+LPsFdLy799OkC6EHLfFMekjqYJW4XfgCT0bmwpdlvHOH6S94IWHuSEZYeaFfrdh+Fn2+9jYm3m/9Ef9ph1pgKvbA44N+hP/AlAIJX6/pXAcn6tMYu1oPYEvVE0TOA33f9KgnQPZwDWSNvjAq3oTe8d9bfATBlBUiO76izbUoPvQgNbYfNgPfRWKVUt7d8MxFx1BALKmNKVzweww7c43Bv3qhwmd9k+NxlGef6y2LM+b845m9JOTBwXGL02WUfJHemDjczvy/iqeuNAKCPH/0Yj7jQaBMdj34hH3bDsb1CtRI6txp6RFhBmG1+UGqXv3KX42rwjRhAEcJFWPmRBgc49aiplunPV//IFhCmYJLj58XeN8VxTKYjaotjB6q41x8EM5+2RCyUQaubFoZgo3EdiSVBUbpyrDhyn/H2WZaj/e83Yb+u1EERyzp57yPb7eRLwBVzB2lcSgb6gouy+CSnzVBBHEDzDIVbvN3kGY8pXBkSKbaqEbzxIob08w7VnKbnfbdgqWHIxNJ7PG2hE93rTrK+jxs9nt3J8hOUmVCygOAZrsWaz/xpYspv/q5bxGx3OG5gL3sqdTOifxnED+IMTJVKxxzMG7tcnxre2raP6gnGbqZtpJe3en/czE0AIrEPg8e6jLVCuvaB02X6eARnGnmniwDARHWTdkbWerMVJKoSqrL8ZaK3HDIQpx1L7cs7GZLclnQC9rJu/ofExDzDMQGeGeLQZhMtnBm/ZbknfonnEBXw3qbwtovDfOFQJHhpbFuQ/E/78fTW/1BIidwlabhLTkeI+HgTs99tLfCySMmSpOjlLAXP9P1kVeLIsicvOXK8XBOV3rSJfzH+vGVPgEXutivggwfXH+Plu1FeEg0Knb3TDt9fckLlDXZDGO5dKPsCPOZ7PsizKaH2S7bV9aMC3NL6cXCaIsXQ04wKJeiFM2xEKoqFxrtac5U2Kov37JIBJQgUPB6VK5zN7MgBDWfPw3r6jQ2MLQ3hdYpPjpN96Or8jknubhxbpKRp6BDufuj8n5isS6qG1uJMoDKeIvr8wqmc1FYq87b/9w5HcCc7c2H7X8OGrpVSm2qb9YF/NSrZ3pgFzvHgsts9UsnhoxcWdiA2/HaGzIMxJ5XG50aZC94BjF0Ohjna0EoVVSmD3iS9aeL9eWyOhuDl++zoRblb9jfxhKJRr1rabZ0H354jgJ/mFxqr2UNhZu1KdP0CYjuXitvgMVTmhKMJ8g7BkUFz9j7I+eykPtOS8ocWv97M7fvHaPRw5AZygoF9F7cjNK5ZPb51ku9aAtDDeTyemn6SAIKKYgN77tctrX8sjuuK3gYsQNhEcT8jfYrW/OipbIAafQssqlQjODDwD9v5YW/Kl4SIid77yg+G3GJTC0qfDnAy1zMjUoZ9ByNiey/xaboV4PIkHTtg6WG2wPVUWiHr2y4udfJHbSW66Dx5ZRBWCTxL+GIf9J/2qWAVwFKXXi5ZPlcP4jRFFz0EjoTB8Cy1XNWCfpRG4Jstf1wRPL9Jrh18awcqXh2tru02w3BdvUh+moyYgQsB87Uv43v5qcUB7aTWV8rFCQjnxKqPBK163EsHF0tg/xQ02O6gmuQMnC1Hl0ouCQyCz69rR2R/zAqd++QySyYi3WNsp+kZRxKOtvcuyoGEhx+pLqpixvwVE6Q+9U2RW+T68k9pMgkbB7PDLW6Xf/77y16CS4xic3uIdJbYZHhqTbq01eMhMf68wbRrSz34tnxn+58z4Fjeq+I8JdOCoc0/sQUMU4YnR2VPwMvShgTy8ldyLHflJefaF/5r2IrJbhvD+cJqbROh7/lgBbQ93HPn3N6ppMO7gd/z5a6NQRGH2axfFpp83kcWuR5nEl/aIavXXSqp27NVOIKGv/UQVz6SnB/hPCndc/8Yj0SjLvnr2om3LELrOIOZ1hjvs9u4wDwjptpzI3Zav8WtxDOY5QrpuZ0fq/FKvDx8G9AObVvGbXgi5jV8HZF+eNmEVIXHwNXlliDEkUQKWCyVc3RUpDex3sx9f6ClYKT3YzTnQG8vyR3JZN1G+UuGqVRQcMEytRd9Rjzk9DQCD2ObrJX+afKi/KPJJ6GLWU+gDeVEK6XllR9Y0F8bDB+kGHz4nscsfV4VRF+RhWfmamBNpp7t19IUFsPD1SmcQQdgIa0J/fCHSQMEudqUV7sEHoKEaybXbZhU8/9XXE+axOjBPyhFXZaAl63cU3IyqSdN+RRIfqhazTxy8KbEWALcO6FD8YF6BtsDTY7AsO9l6nd1xqzCDGyR0ToS9Fy28Fi3s4W0Buax2MLlUVt2Gg39BXUe2wmEYLSPaVB89/7znO/x7H8u43OhIX74b0nK0k4RJvpsFCayKYh32jbiWL1mQ+ZiNxHIdnbL7tvTy4YHgHSswQtsoRHwSzuQJntdjxWcefGS3ArinL8O4BAJCZIFmVAKJvzWLRvkEEjPEqUVoGGswRF4mJ9Zy/uw5/K3pJF1FQT+iR2jIMU/ZnCtYHX4jhOG23YUaQ2uGWI7QI1ueB0xs7AlspVGuNQwh0QMu+C0ku3uLgyKDc8PxL4EfVUhWB/+zPFbEaF79F0anxgcyEZPVvtDnCuGmhPYj5fTQmqfuL/dI+7VHBFwChuMIl7OkHosmfivyCSU++GWEJJt+vJQR094sqLvptZ43atv+rLLsMtWAG/6sOL0jmdXC9nZZUxWqWyqiMXZu3c70hiE4kz/fU94zuHhF6AE/A9s+ep/t5zkTdb6AHIDRmqaoiAV1ttANprwtjvvhM3ZQB7FKCuUiBLPyo+ijcqln5M2ZLOPZjVPDDFZPl7lRuPbA1WjYuc/0unApkHABLtaKur+/vHXiZ3rG/ibOOf78EdAyUvul+XC1inC5YfunpDsFhsycKzeDKAo1yxmSgCTY23Mi7ZMZ9G7Oe7V92yBKpzrv59mif/ufhqw+Iw/KF9mdMPnq3fkLobJvrY5u2zm1xXnAmPa6j1ZISDULLAO17tvo8JliKqlhrHCgopGsthVV7jQ1rhF95g/gf3OTuncGGokUOgGWRgKhfCzxTcyQKMFTMZXycflATXwV41dNvpVJ21a+QGzc3gZTNxVTsD274rw243YEw+jXa4ytAotsQJhYdeeDQrsjbwCbcJKzZsGHgsomhiS38JfATvEfPfEQM5dKsQXKQWhmKp04X26VxJPeNl8hwURncNeRSJw+OBVYXwN7JoU+R+P102bC9ayFqziWE/svokHfJfP2jjZOPFTopFOt3/wjQbCcOfJAe9kw4PK/riZA289I2WZkMWarwoIuC6JCsv6Qem3y6XKQZAK0fIc9mH5pmrd/CujqjIjzZt8fttO+GYzm8GAEaBp+eNPCc3x2Kfr7OvIOfHho/b5bd4ovH435QXdJX46C9eLO4PnYsh0UNRuO4y1tPOUPPL4/1ydek/EUmc3fYDTK0NagOP0Z/Tcnf80+f8Zw6e/suIvD7IMG7DcFP2yslKM6b2hUTIwL0SeXI0ir+wKElOLXGP898ohNipAHwy3hQRYkvx/5waHUqmzG/CC8hWezXx38q64+5BAxTzJpAXyXzRJwf+WE6ZBfEF0uRlT8SZs1c5/nDmSavt9/4i/zry9hP7vUCqM+Wx1GnYHLMBRPwCe9uPErnPqzy8bRlkQhOhK4YDwvX5X3qTXQ2PYzD8KIas6yHjzCeFwAVrrA/ncnrI3B5zNEefH2Rj9vRSybxOrlffaE29Gs9Yll+8GKbOz5Za/n4f5+UYYIyoxY2MuEHVXqaECIF8vIeYDtop+UxjIh3+b61hYRdaAb8Kf+oWeWK3ZBNCigmbU4lW358hhy/74/e3r421bAGHygXGZRxm31eij3s3AQc3O36ZdnOYGIAyM56ezh3Dt+/IwVie0lGjRG9XA6cBIGDh513Y5j3aeqHMseyrkdoQYjLamLhYGdsGeQpQ5lLz6jvIiK1A96dV6Aozv+Thgu9C5TyrZjRPDFKqCcdmjUlrfEYnA1hQoLpeGGnqJ0Q5syLVeNx0OwwvdRopMvW3ZQM89TvR+6O7xQrXg222jJYhneEed56ti80blHXjO9oLXRejmOnFT4Wchp8jXg6AVPYIY/dqk++FTkEzIbwVsPcRHuisT7zkZTuV9W8mxDDWtwMu2SokM2Ahu+9EbumHpAXm6AjkqGyfeFsE5mjkx3w01WTkDrAoR/cSDpu7hfwqq18yPjJvpX1ostgyn/Fa9ZKSYo0nufk7MMW5pBUQs+/JeoOJGqDNTtLS0xcnYrdwVC7pPxEQb+c0n7WGG2uOXf320+59FP27O/ddo0/WI7vSXDI3DYHRLA1egfkXtdylrUveXWSIkV96N3Yo2MzGyHhzUzkBAER6KbF6JeB2MU8dmWHFsavAhFo9Cg8Y2ln8/4DeGv62CFnA4uGpHAQ/+d8yiERQ97RZ3ZPEzv4DjWIlvPl1lb8lsmpyIBdamXIXnjnRZiB7TYYTAATBrbsnSCqDWzrvGZu4HCLvibNPUMJvEe1TK6p4Vi1eXiQ1S6FeaVzjbdaJYL/t3RDoBsducbk+pvWmEo2BwakELUwllhOVs3PTj183F3HsP3VSZ2A6HEKnafTcttIi9zIXDASiH5QqeE3E5Cbrf5NuC2vyThgbzYNsRDl8KP2qleUoOlxzv3d4cchbjQ3PiLi1qq0eHgEbSoE86thxrB6nlMhAEJQj2+OJHMfhRLIM6DSH5H2QtHLK2l9PtUv32IjsTOOsqBamfe4SsSlW9ceeCNxRc2W7xb38fbyS+sE3ip6NzKFHUVj9RWmb4LakvLvxvOBRG8e6Pxa3mXzl01XdXdJ5Rwcx/KchOyp5iru+XXFM85haA6dxhvuTgpsjUASZEJ3bRSAD1yzoi/6EwVXywe0NDAuqUoFQ/KBgRyLcGp3grtTv6U/JLthfNhmQ8lDnvE2GJIGRsxWISZ+F47g3VHSW0rU9mKV23JsGoRWeI6gT0Su60kkfIzSAMhjqP6drGY8VCEE83+q82l/vovAQzP5u2v1hVj8Ty/Im7O6eZNqnqwZMvPaxIT6Su2kQR6FW9jKmCbq6VHvWNaAokcu2h482qNlC2E90ZY/Hi2JuMqhhIFXXwTykwBchMZbTpUTStsVR+1ZHa030B26b79TJwEzPfMZp2Cj4NCvS5JigkRfTRNE31R2rQQHTNkoSFf5T0zJLNUyr0Wjs9YtYGQMmtMzVuknVagwO66DBd5v8XhkuKLDYNhOeQXKsQvwMUSgn7mf9rhLDFs7tesmySHAnV/ZSUCT3MfgUe+DWT/snW3nXokCKmjtjav4Nfyd4miK3oQ/ANYfxXmUOg1jeVC55CGE/zDvXF+KXyGMJrwwoIL1F8Q52KN7MqNitPQfdVSO2PGFCmvbbTojzWpFXaSpHOvvt7DXgG3lSX6lrAd16yJvL8VVmw/W0dMwANtNSRBG4RlEsbeHa6VojbTfor808pfCAP4MO3FXfIu1/LO1kj276Ndp6Nl3wwEf0io92kMUs4Lob2vDtGCvtbmTiiL5vSvdTpUaq4O5tRHtBDAeUGyeK28Isn7x6aGt7eUDZ7NRbE4KloECPJn3GdrXKYcS2BqX67QHAORJa92+cZRAVfC0QKFzIN2z+IDXnSz8LhnmoZFI0NseD0wUpEHayETIqAo2epv4ZwYRiPWmjHRZfmAT++rUIFzksBYQntpJhLQL5SWX4OpQXR+FYaBZ3kr/DkYgE8jHWBc7AopwLr6bgBYKarQ3TVgvpe+SSgk8kcU24FfUeQXI7W502Rir2wf3IcZq5ePXanzckpbUhYlvIK1PpKWVZBGWB5ewfP4THUusrpH1ZQBW7eF+/cb2nF09kmXP9vh+IDDZz4b5IuigPfnB9SA1gIGYw6JZLiTB1EdCV9wEL0vIBQ5Wgyd5lfpBk6EuDSL7LVKjlq+sea9MNpnpYXbdXU117WQ4rbw0pKm399yawZYHAx5RBInSk/Ld8hiUPRetJAuC5PhI1XIM0t54d8fepu/m4HcjxSsE1mEp9zYfF3NUHfOIETNV0wfiRuai7uIJnZvsHEOe1g23zculBr0lZTggZf6FeozFl1h6KjRtLKTqus3rtKOvohZCroY+B3PmxPjKxRVA4H6ZVkDgAmw3ntvtyBnSSs8h/wLn0Zg0M0hC+wmHq9bE+0XI2vzmE9/MPXwsPoSPPo9t6yeLCtpgVovrMY0/Ss0J7Zo6S+E0/mkHRSZj+Sv6+qCcnmjxkVMmYBQRkDzZxKau2j52JpnHgGClFc6FTJ/IH3OZUIBRy7oCJLg+DXucN/7q/k2XtX2ZePueDiVMjWE5s/iBIiU8df6J3ZWbZrbeJSaC2dwH1XHrbV9QFXQVA1XqcpfraFCADDbNIp46drBa/gIJhL2bR7CWbV7ZexdIvDHgtBkqwfmWeF2kdIpc+YwWgvJbWIj37cv4uxuRXZwRuG4RlWSRVyrClY7diCHcAQ1OZ9V/6pazk9D8/YMTH1kh8B0Ruhb0fZ1meAnyH0JPUarVsiRZcgpfpKa167tYlPgKzhVXRZ92Bg4XFD4upGVSYJ9lVQStrvhsU3dnqtvobn+Vgeg4uhqeToF7+Jrg7/4YDPPPEudzeB8YG3eybKJ7KAW3TRQRl3FfJA5Bp1thnQFTi9GpHEhhQzNYIwU3ADmcN+RmQ4xsKHEHN/MJ0anyKzbcyIXZQP2bXjpry8JeCqBcw5+/gqjdAQjqNTIu/IDDpPPmTdbc58dw0Vv8jCn2vy8QvQTBvvnrjfU4z7fwia+dVIi57LbRQ2FyEd04BTokz3c5xngil9lD3J5ptQhaI0IcRmTv/SkPKq1gRCztDjOoTyZSVrXK5DpOhBtpgT42zxwYaVtHzTlhFl0QEk4cwjbi3/+LYtMM2kduhBZsWu/kwD6Xia3ezj1VT+qor6tF+NftlUyyCY2qKFp19vbRzVxswWNl62fJBvFfnng0r5JB5/IUDk4M0lquTwR8E59uwLwwWIH+0gHnmwmsFTtEB4yKwPvsVGxCgRceN4inFKnmPJXPbqdOeNd7NL325Iw+KFARWpX9CuHvO7N1gmtKL/O0b1iPqzlnZA+rEMbcREhVIFwXNBinuXOhGBLH/2TDx+djlJgY8EUIU/vY+Y1tRl7npsy2z6D0p9KhYNFvKN4uXNgC6Ufewxx/U43hHiek9ZJErV9i6ZoCQ1kqkMFiDAfTdcRYqy37vJB69DjkkgGcbtfdqTdNWmI6889CgRz6EuoGq/AvRWgkFfyWCOGb35OIfZDM7k9jfiJKMZSft7y0eB20sBREHbyPGtaitGSWoTXpRWVobBJFEy6TXUUKPGAoAcLm1JMw6nxYKiJz9rYD+iHD+9wjC4Z7FRLGsznF88OYnFhZJm79kvP3zNKR1/jnKXjug8on8h8a4+3X6Rt6sZtr+p0/c6Cmr6FmmGaLZWsNE79rvW9O+DQ++Mjfw9HrcFoDYY6YWgQAjAAVrzpu7nMcsy1XZvhGa5vGW9rpak+71dvtdL7Jd7+CSyV0Q3Ms5SaVrs2cJezhbiB/77qloV84RtcsV4XOcerwQs7z0BL1iP0V1O0UvRageGUOiNkmc5NYuHPtlp9Zo0NXJylDd8TG8IBoV8S8f1+MLIusi7R8vLc1Y+VkIJsD26qLEvG5HxWlRZzdD+4yJSf9cNFWwrrr88wkDi+voAdofQ8tSMCsSJI/IKYLlC9/bih/Sd5rc3YgsCIlytP0m2OzudRvQvYIrz3yt+IEtgXEEgPQdPukGZI7PJD1JdbZBXt0ViiKTNC6zKwaeIMvtaLE/uKotR6msRko5uO1FkGmNv9IzehowTfqd3Hj745E+atG2yKbHhGRG+yBsP0xoLN00YYLlYoXNmwKieIA/bh0b28Iephlc8XOTKgaviWRO0c4BDpnPta3J3wlracURSGL42388H5Zz7MD1UxdIVhONlkuhjGU5S1k3WQVg8S+MH+3sV/ziy9srwDT0PLfTgMLc9R8ADJzCUqRce6C3KSif1KbLbtSZAs8wTObr47TYXaEEeE0Au03kjG4vrMod6hgasRmMOptwjIeC2SuWc53fyKMNP7/M1yqUtBR0jxXDGuuK3QVdG3sKV4pD3jCBxoXS96TcHORj8787aTlA/z4TCqoRU+NQ6fUS0cJ0vNcte/4RVNe3AWV23HZoXkqbkFTFCYsHe5QpogyLif1wYsfmRWPDmsOFlAR7xtRkurhH4WAxcDs6yRoGQeuwAcufxNDOtRu7egQLjL7ii8rxQwy+1nn8yWX8TJrBKaXaE62itrmPF6Ho9SnL+fvuSjF/9WX00N8bNEtxiF8wMlife9CuN0SyPdZnMsDTl//yFjgHdsOmuW4k19JbJjRfEzdssyMnjqy7g6zUs88aV9CQJkgzCfrX8jWdEsShjJj6U/3+TkamaVBwSMD1uL5PU6078mJAYNzqPYtuu1Yb4cAg5UzLaVw7LI+RsN3/OLLvAUgrKIcPqFnqf9Pto2u9AMYr7FbVh15mavR/+DYQbZAsUQhqQ+fUJfhclYXAWPkPptZ4VRhfvkeF69+WquR4chuFwLkNFDAqUuDYqenuks9fTZXOiFGdKdPRYr00CsKukB1YgPMDjSeYRzLakbanPmwInXMQbW7A4TB1du8BApvs3sOlwy1sZOhRtAXBQjDu+7CSXh472c0kgMDTHsbfdjP00nKefO/TacNbQgqld3S4OWew/psdsDZ+TTNvk0levWSaterWzCwWNhXjjPsb2rPAxwYzD4SstGgTu3NzaTk2vm9do3Wo8TiBdCcIQMztWsDbWU49aJx2I9eiKMCVBJT0QIsZjqHUdxuftWYUYezGDm9CGdvltb/RaITPZrNOwyIkq6YkdMpyFbjQtgqPU+3wuqt/YxU7Ha341JRUlP1ui/zrUAHBfD/Rh76KH0Bjc/ppNdLxR6qF9/QI66aheYXsBhoXKvGdshH3D54TQrp4NiDdyy2XY5cXHvMIY9024ZRuvswOv0egAMgWzvHpoRtUY16xXzjHMl2xgR3jB6yZ2Ya5jT6MiOoYZL0iGbAGf1iaZDi5J7Y4oSJwk2k3RGlzXF0ZcsAgdptjJY8HTUGF3h7sF883r4GPhm/di27LIq2GwwTH7P7tLeKIPG783aOMg8VlWXxvriPsbtZrGun/ZU7nimXStmfKE2gCKsXEeprgprfFdhua2TDWTz+j4ro4I13/foRR0m8+NyJGB4hBzxENBMzOUHv04BHgF9l44HgOLnJRpQaPt0t2pwRxZp7WKwJBucGMoGDyekeJTEnQCfOIl7b3vmvpgxnR9bl0+uSyuOWTjOVl2+HXdLd3lT1HUNzgEQqi47lR22wasKl35oGAZpP7mByQeMHDQ/sSla9dk0I6ujKkNGNUprW5w2qnv/Wff48vdAnwqgr/rTYVzoSAqjTs2NaNClj04AhK2cvXNXCfp09YZihAv10a4VY89EZ3yp3KbKb7m0Scssw1LOlbp/Gv/9wrJO+rBk5b4UTf9e0Dvv4aRP+NtzsuvLoohPPnDFpGn6tsxuMs3YgJNwi2hj/ZPJvCCvMlr6Y0fmxV2O5UW9wK4eNhsr4+rZuW8HVVY7WD/nsH4Fw/oYu+A+4hrtDsZzv/poEytNGFIhqjF1ndxrMNx3A6Y9RqdhJPE7osO7i7sYmHi78UgCfwT+V40FLCpMSJE2fFDCIuGeZukcqUTEXfKvBUdtsjpIvEtq+olT6I1Z+Q2Deg1i7a8iy/Ktpnh+FyPdaDxMU/ReoKxKR53bRlZmX81Bh73iD4puEzgUyVBl7qo5flCUrLNUc0HpIZhBLCgEtABL/Tlp5hahKjwQ30+tJOBqkVLrzu5qKaIF/mGKB+IWbtYu8QZvGb1zXfJIklpEWSCVw68N3cNkLfRNp+dq6ktYoOzFXzYBEF0RUAbNplFqyel0Ivx0hQZjXYuoHnQsJztNSHELy1c0w2NnEnbT5ZsnT1yon3FBILYyI6/YftQS7vOYzS7RqjiDdEPlLcIcfB7G6Uwqka6QpjzyErNczfcb1BgNsFd1DqTsPAkYBKyg7rlyjOu+vImv2kOdHbew+IW/An5yIz5GswL4p0iTc3Ea39+5tZVrWflTAVVGDdEL8VAUoKuuBvl5kQDzr13mZW60Iinz7bD15vCI7zB8T8pBj9a8jbU58QhVfQYX0ponriTpncqZ/bZt8B10BgNn31zF+b09crcSxHTrGC+kIPHlmxZxhEoaxqtluQ6fpN+sBsEREzg6+7J7DIJGYNsMVAFQY+WYv8n3onWxbfNnyDWpoQZSQAK+xKYVqKlh+g23RJckEnD5GKEkvAn4diSZrXRfwvAyVqav1KsvqcqR1K1UTpuLYKUNh6TbWacIchjZJZbjZztbYlDQe1hfsz3QOA6JQjXyX2KARng1uy7xBhudGGXLu/uZJYvr/QmP6NGZhuwjYlCQVyk3QiGaMxHj3+/Xd8kF4AzTiRn+JG9DA8bD8Nl985EWRtQoU99pmuzTiRbZM74HnVAwsSC92QCmHEfZLcYgJ0ZpLqb+QBJBeDB4chT8o0ddezL+PmHdjLutx3f4fbPe695wpPeTFOUlxMyP2f2Iveg0jAMZnL1mQRwWMcGnvUaJPcf3X9JARreHHujTkwW1sO5V8KN+GV9iaahW6Q9/P8yHJqD7L/gDl/21uOajbOui+CJ7axA5Sci0jLeObkWet3KXYYprdUIiClxRbdPG07g/Cst4cUN9M7kKx0AYzI+bEcCdyxCZXs+2oQJIqTPQ2R9oOAGdxBrfFZ2r1+eqw+rB9c71Y8u7gMTvWEZc7J7W26inmT+/2XRDwrHemWotKy3CmE1EOzLV3qOD7UzTt7Qt2lxCH0nuWiYvDOl7OvN44HkI4xEHHEWS+WmjbBqhO4C6PEaaayMybER6AAwGujX1GMN4TSGEPDqNOcq6pejWphnwZvBfxX65i+KjUrbJ8xt/wN3b6vqvNnjPXkfipVGxx7CtOUlOsNkXc0tsDXw+tLoYG4r/1QbShj3w3iWebjM1r1ajxzJnpk27cbgcn9z03BifjIfb0hFx/RpWzbEjsd8oQfOPDKI8hvN6lnmGsKjQIIcJXc88X8T62FJCe10IgaZzOM4a3F8G0o5z8DuSb62bpDdjWjEJ6eUbhX1aZp6NWpjKgTjSXn7oXGbxNOtm/te3HMVvVP8WfkZ9PjBafLf+9isTR4Yssz04GRN8IhbSfEAXSm15On3UU8OIxL55CkSOYrkRm7BMffvMLKI7wH4pOwgn2l8tXYPWbhMxmXKB/Xq/Sqm/rnib7Vo8g+DE1zvyzDQZTcVLl0W3l31RPNA9wMgnZ2sgZh1721lKyA0KxYvcG0vJDUoDL1XupNJeXsgUWYa+Fp7wpgF0FGHfBnc+AvzB9hd2lP53CvxkdzdSRmiTefW/2siTjfWS873gvOdAHyB+LcOHi/g77h/28DKjD+WY2Zqeh5TCjgjHUU7RovuSTscE/O1GJC7tnM+Iop7ya3OELOmZgpV/MX3NAoID4cqisWN+GFAtNQ8iR/o6YSrRYgdLpe1ODQ2aimu2pXPr+bjbkTlm+MF74WUKbYG6bz/DqjfJh/qVWweKdMsjTqZ8xHu1Ta16CG9hmOLBvzSp7xp/UhkfdaFVoYBndfwVux3F75Lkf6QDrLzFBhZd5xpauPSNJWaC7ua6af7PVd7nq7g5gcZT9V2uNYvm4KfYoIYq/t0zXUalU2Veqpdiua5kGOKUn1QwlM8DTlb5kY1Z7KjrtqHXrx5NJo/vonr0w6UgnmdEbIPH4GxWvD/ztT0Ga5fAvIGfxA0doBe04onwTgjnxDDsAVo/bxvDGVzCywT0CxF4T7C+QvsjBHU8+HmQWX7uS4FIvM27w/ZLK05aeUEllRfWUs9k42xzGrmjPquBCSCH4HPEC3dvqKt34caZ96GNW4iPoYRR3qPkD4CANPLXd2r2HzYanFuwOPtmgMwOMbriF/XJtXdagHq7qk9pwLvcFSRZ1/Ozh/pkl/vJhvX42x4MkeC2NWv1cLOIM1DRUWa1Oka9Haep47LwgQwa/VF9xqAneHRtTb+WvhNYj28KZ+KNgGnwtCaB6nXMlH3T4UXWLUNVdjTOG4X0MHO0ZeSkYNS19JDeWxObWFeiK9GxTvmCo7HqfTgjq6f9g18dA+gIMtqW7mvdEq7lfThu/mlVZEvRlPbSLqWzNAUkB6I2Wt0vFYst5eRir45rFeOp4auk4cgyiYuWXTuzR1vTPjBsa3eOFVk1jdCog7/FBCTtLPT6FTyZf/EJnv8gUrjQ8vaEZdF4AHG4BIQbfuptfCev1qQVQPnyAQYuz+V1LbjNhqsM55TQcA40IMw2WC4uwtYK23dLjrvzCa+4ciO/14k8v+cmZHXdAXW4lXs3Yozl6KXxARaSLdnmCI5FV/2zwqV/iebXVqwgW7frWDa63cd9DT0lJ/jeVuRyxppXU9iIcVo4WGLDNKZpwSktJmgCjmIZgC0jAUIxgSCWERTVEKcrpmkQU/fMMQ07NCgeyu7hMObczPDmrfo0khArJDBjTT/zl7ie3tfqbTfnwoIj/JqgIlb8eqm0yDomiF0GGR5cPih465tJjc0q5ASjvwP4GoGJjaRML7xP7koGbpYrCgTBw7LRpQgZxaAQgvfXVhf7IBVLeGwzBjrfVdR2kfSUvVe9s+dZewlt7ngQUM8IRdJa4zz4GZDyHBW9y6VEOR7iXMWpGeIDHXR9YD5OJsxzXH6Hdkj5rcVU34iTx5BjFHMVUYxaf+mQzwZwKXntFIvgp/KcQAKMMd32cn8yZIXaR9G1qQpdBuCt5munUXKO9FZPCU9P5102e96T3Apsxxst868LZ9wlvF3361o1EsJ2O8SVKvxKR50j0054T+uXKjeKwK2qTK99D85reuANe+GwFk484a8vvhqO8RGCMQtmpBDiUbiSE4/0rmSYg6+BhjfmacYXGO0RBvo8xPB8OEoWMK+tQjK1eCX9rzp4L5oLt28Y+P78qCNaBaGNPf4+moj88w4SvoEAwE9ZZgnlpO5dh3NGxGexdudkNd8xGKhcW/hNALmN4W81aQiavyQ3ZeGmfjOKxRAp7fmfHCJ+5wrm0SYbcZ80yRGLrsQQe4qcURv8ZhBFmjT/DgJgC/VCIc41HNMesRKVO+5CEmbFX+z/UcJ7QFrQ4Lq/spCW88XVISRwGhWBVNP/YhdQA/C78LwTkORGDeKtu5NWHD5X8WbQS+LBMIrA9tH3wjUZGc4kCLK8+3t2N8C/WwQi1jc8XKClfxj9rZ8QMQuhynF/iVLGeQbJWSXxcN9vXSQ441dOpK6hYCFfFd3a6oT9Zedwaql+J+tBHKRrV9OCEYG7k+NNhyZOiZn1L0p/faxSFAbrS01+MYT+PTisS+q7N3fZYD+Yf/hlyjB2UdaOFXyuL1y4KkxTYOXJ48QB/pDAZAOnDC5YdMZJUfU3c1itarMUbyRuWtbSRywrFNwWsJM/B5Z4LF5kPjqg/WXcCIx0119REIEpTXk9uOT25dZ5jhMFhIm7Kg3235hzQVMafSkL9UFPqqwS3h6gQLPQU+R6AdDkl17IVGCWaXb+5W9g5VjpYJu1wxrt6rrfZOMKqbQWgtphUUW+APnwTzmfKf5QQHILg0oOBZTAu5/SD/qarjQitRTTUfmUTVxuSZESBFUBX6/KE8JS7Qe3XPUNOsOL6zLAzXvWLA/GEbnOfXDW0Iw2RWL63/PKlNJ6e+6Qytue729XY95NWuCGuLTm7P9y915LjxtLuujTzOXqAAh/CW9IeM8bBbwhvCee/qD4d2sktdZaitmj2XvOr5BEgiRQlZWV+WVWGg8cxn1f+IOhWKcNjpXqwkcMm8gI1M6AOTAp8a9DhUT2R1aSLTn30fj4OqPLKhuPgR69AJT0BFyWpBV7kF+5X9fQIRHExb1EjLU/mT8O9QSmCeT0wB8A2V9pgkUJhZE5OeH4cWW53UeSm3aUPpFPnYHQxBCYrz55NgzDFlPuxetL4uozvcF8+BbPzUK2T/MIRw1Q+SvDSoXM19R587WrkAf6OTH9zIoI6swOu25Kug4QDey5lyt/6Ky9rk3cJP2Wf9+5+fn8wnv+o/ykwtbZ054C64v/yNJjb9vKA7/llo7E4JO+WYwe6jtbjb4tEik+MEbkPukiCkfx0kPWRPkU3Ef2dWNOdI/Ze8/O+dTyM1ak0nul1a21dn2fSO3iW/rx2M6vnm+Mhvv5MV2ywiRmEVYkYM+rD8r5Gi3wkzOTvpMP/7kRiRtKNaU9Nyb84iLjpciWQp/rJNmgnzkD3eaNu3ASQFDGeCNaeJL2Bs9u4VtVXsn+RRRFtlW8ygfO6pqItqRynhR1WrWHRCk0yAf8hM6YfQ0V/DkeUKtfhvEbtFu379/zG2/8vRsGdB5NSH/Li2VOthBQ8UGGXsyA1XqznYNztx5EJySoOLYmiqAfvSpwEK1+cunS9oMm6NuQ1HbyIl4ZD3fR7Tu/FyLOvcWOSnn5eKiz4GfCHeum12UbKl+jYDXZddjyfQY6SVizHqn5cD5uY8R+pX9xIccqHuz1S/Xm7AsrePjm9QlbfOeNMuPv7QBP7KyGqLiHX8+lBYoZ7lPPp+2dA4sdycH3lLpP3/CKL2Q9wkLNXeTboypaXWOANKJl7lRZhcHu9afDET1fdEd73pA4IWG/cj4/cfRQ7YGWA4LVaaSQeZuzAf+0UBXLj5k/OLYybBXmHnsT2RKK9opkIrpKf21ENIws3tLYWyu+xFmSmgbX1+KVpkGPfwkAVjYwTlbvCHfZ3FqQZrHSMUlkJgZdJF/E0V66cu1zvGyGEpM4UAtM8B4PTEOcI8fNIp1RU/twA83uasSqo462S6nu/yoz93/mj4k/e0cgWY7migckknSB0NHXZ4rlYvz0Uoqi+KR0g6zu/wBxXtAQTVm3gCu3m1qKXAvZqx+EO0TZZzcZ9j8Q9OuLWzYt2fH1RRhcQvj/QNj2ELO+zZbpEnXQj09vt28YAlEwAl3sTZA3/OsO76+Pb/jt6/1epUv5/Sco9HWtzKqi/D4cBPsGFBK4HM1fl4pfH/Y5G/8MAaCig82a5seIPq9vUJV+/Wbwnx2+5a9fpidStSslBxH+DwL5PquoWbOv731dmJd38/3CXEYDeFm1UXH9nwEUqJKoeURx1hj9XC1V312fx/2y9O31hQZ8wETJq5j6tUvZvumnz62Q/PP3m3vQTVWA3y79cF2N5iFLwKzz6siuYTOfR9I/rkI/rlyvy2UZLmLQX5oo64pva5xN35IehKHuwz+SvlvAgl4IaGj6KP00MYY+FaMggE2EKMnivn/9A/42dMXfyQYoQX1DYYqCSZzCcOwHgd9/WPHfcMH1Axz/mQ9Q+Bt5+5v4AMX++/igrdIU/OZf8AF0cQEE/Qkf/Prb/zIrNNWW/eMCwkv0j6r/NkTdUmZ9dw0u+1b1f403vn7dRK/sH01f9P9Y2u888nnif07oay745w8M+fsckuve2fRj/r8hzK8f/G28dnHXN4KAMewSNhDgt9/zGol/A+bFH4UOBn/Dbn8id6BvEPx3yZ2f+e1x0dte+gks6h9Z70NuwAYcfJFvL6/VtIdrB18X9ikCoqNc2ub7x/My9a/M/z5B9LpSTFFaXbT+DQcSWYRngIvyqml+cx1UzcsTcP1iErs6wSNg9J9LrO/vmixfvv9IiNqqARRn+7ZKrnnYEUi0gVT717H9eF7Xd9nfyQ4ISn0jKRgjfuigP2ggBP+G/swPCEJ9I24ohv1QXD+zBgxBfxdj4P9eEPXrZd90FxW77j+lQRrN5YdDoL+iov71av66UX/LVd+lX3tc3DSU36J9Rr6VaT7/kjTr/PX1P/CSgJEY8mf8933df88MQCx+/n5i4dtf4aw/8uvfx1QE9A1HoC9lBhMISfyeqX5s7d9wFIp9w3DqCwN9xBL6J9rtb4M41L/nqJ+0zW8WfuirbvkMCmP+A+P+jE1+aMTfrsHtNxqqaouPZgJZFe2cROAohIuW6Bcg877NW/Fb/v1ZhMF/53LiCPwNpvDrxddqUn9YzZ9BKvInAgH7u+QBCf0/tXrRZU5nYErRMPwyZ9NWJZ/EUPp6a3+9/YXr26jq5n++rn9xx/9emJAQ+Ofv5ATiwpcU+auxAv8eOxDYn2EHHPsTXvi7QCoJ/z/FCz928m/XXu3jqsl+uS79r1v+CzV+o1AS/VVM/16uw9ifSII/keN/nyT4C8jgf4GpmiLUa4wWeB86uMe+JU2/pvl0sdu3Llu+BMufGiYfr1HTrEfVPy6j5Bepn6rz+lnU/MJcBPnFmS4w8MUWf7tFixHfCJj8VZv/AVWS0DcU+s8/+GeDA/kTL8dl3eJ/F+eQ/55zfo/00n7/M1j5k7j4p+brT2hxmvp9vn279nk1ZCkN3oK7A5pdcAq8BMRFwVO6fkk+UgD770GCf5AkeZ6kN/LPraJfmf5/1k65EcQ39DKif0DKP2AQ+E9cJNA3kvzxbWCn/E8Kor8AKP8XCKIvEfNtr15Vm6VV9K0HRdEF8H4A78EBT9+2/adKxFKunzqZn3/jj9KLkjL7Raq27BfgJPmou5sA3yBoOP7xZ5/+3VKJor7hJIaSv+Md/GfWwYAx8j/ILtTtJ3aRu7yf2ujDA9fevUjVghZt//ehzMcoYa//XIv23wxgfmXjfyV2/pps+04i+J9vm79bYGE48Q0i/1P6oL9jOgz5duG1/1SAP2tAFP4TBYh8+3Ei8H/Cg386w78Anf5vGFHpxWmgqO7HggIXf+G+X/nlkpFLMWW2+fhg62z6zo7/esH/q/z5t/ML9Q0mKIT6YVz9wWVycRNO/CvE9GfHQoDLkL+JX4j/dfyivv9/wirAg3+7pMqvzrI/YCHiZyvsT/0x3/D/Bofan8/pL5wVAZIPP9MI/ifUuEHJlzM3in/cAfrXyh5Dv/04u/xOGoT6WdfjP1wWv3Ne3/42Vf8zZaxs7tcpyYACi7qLt9vPyRNkNNECQMBPlPs/OOq4/TPtmlKfA6q/dqTx73Xwf303YP9k/X9e5/+RRfzTQf4FJ9P/k+geCMcL3H+9vQkAbf+LSvXOJ3ajZGkVxEW8NljrwBfeHqN6fPAf34teCOQZry6iOS6k1vL2sKH5bnfEV4TFjGp12CqsXfWRqNchAuw2IQEdThgjgJd4IY15fbCneX7aRjWKpTsuLJrXvSSQCjrgegnusMUtmonHotqvT3OlABqUd/JpAuNA1AZ9okidN7kptFETJZpJPWHMfaSbNGh/oU/jOOJr3IlHEtzhPIzDVQs8z6dAqottjhnhAJ7IU8xo9tc6QiA0VRMzPaK2x/u1GkxWrlU06xTaZqsyZDfk2DQ8C0CcW6o/HRBGl0LNug6bIWSBd+N6fae9VYlLvfB4hlX58AFCTGzNbXjTs9BOh5MqiCq9LH3ftR8+5kXPQOKYCPGn53pLnQmEscJ0MA8C3S+xHvu5SYA6+1TFv6QPxdKUvncBw1hpkqi8ILzNuKcC0BSZeXfK9XPJMAMkzmSizvTN+kQKvjr0jtvdMtDqFjsdhmsbqRJ0YkBmVAyAIK4UGlWXZS8/q4c1nxwQ4j/FUIoMbtqzmd5+GgFqIBwLm48MPRQRl/Ss+lTTN8Bl73Vu2xOntReTEjhBQLBBiqcUOfBMH1STSo9ZBi2NmCwN6ZutIXXp9HeQIwJbCx30WZ5fQyNWBH1gWFtHF88HCXun10kmaVfX7zCDNxOKpiDAuzJNmYvPIK6eb2eRNh8NzVyhy8E+52kjsykDBi2BJp9au7OTKriIgFrCTIA+HO1I7Cw6C1CIkbk1nCUhC8iuQ4vHcLLvrJ32jAqcfNdX4vY6Fj5cE9cpTYPZG/mgoJxq1ORTfa0Dkdlaer+Hn07PMSF91WNiJkKm+taI1q8tMwgB5Cxzhq0nmlJrdIlTgnMA5Qf/ZN71Ta63PFJ0HOvWLvGV9m7EUFi7AjIeiEBtWZYHi4WgbGGR9Y7g3EZhTiOBygiAie1qKyTBPDcvGc4lrlEh6ENcxJFV2xPSAGVIbQKk8mx4Oe/Qlj/aSc+scz/TYEDuElV1oWhnuELxdkaFkT0oZH3cBuHT2MybRZCuIeqoNrPZLdKD7GZEiUwyhBAmzIsPn/mWuPMzNfT5qZElabU9yPsqAm5ic4S/ZZ/o+Hym08l1TQTkkvSGTzbInmV4I1VuqaaVtaY4wZZwNdlNVg5iUT0XuxMtaCC71Wbg9lVcC/1k9/eyFT3hgJvepI3eYT4A9cwqJNm22N+IXsrcdYmioFNi1JyjWFB0ZCl4S0vTpDHGN2WvK7XQKRYIq9/DKlx2SxDsRolXeFho6SfSF2tybnMrYSszydip96N8ji537VhOHnkhYVK+5yM6yrdDOFedyisKDKK8neL9aQew5lOPJ9MFZugiJZ+03jbq3k21FcxLbizYML6KkhL9DIO1xTZLlyaXtvNEojoQjvqMklA2QICuhJ18Cspq1IBb91VTzBVjW0h5RvrynpEnLnebPsIoi0SHKTkpL1yTvuxhwIakQhzPNfTlUs2xwMNRZwXSkbBuvm7Bq+Ee9uOAI6/bGBZ+O1KWGpn6sEFMDJDYI53DW4O/QDsibq3QVEwsOQVeZ330FvyOe3eSgR6dac+Knj46gmNJ7LHGEZOFDSXAvcoulWrSn1LuNNorNNzM5qeWoqq9DwNjrrtRM4lcIGjMlpXlkLRM0xgEgQqj4aRuEahBVN+9GfY58eVVaubJ7Ca/057w+vMNS44IAWLyfq4xB7reFPfGzUpaCEF3STXJwYiIa0xueGOUtppxGyXpQ477hxF18p08b6iLkQyg+JhFg0dxpOMVsSufA2YkygmYhUg5Nod7N9q2ghlS/wlkIzwoXPXUMxWTbyw3emWXhTJdiLb1KQdfULvW3fUtFrZHiirwDRgu/iCaXTyGajG+JlAWN9krfgppoEkc772c/DrTRF1BPGYauE9blhdvWjkcQRCmgTlGREdboMUwiSyvGt9VyA14wikCo7BW9aF79Y0GYfIHu0w+Med+CgwlDsb5mtof00MmU9RQaepNwkUkCnmsJx1/yUMj3B6PG9rmPgXiJDnhXZQuUgcsiT90fcZcz5BdajUZlZmpjBes7EUd+kt5BMEhd/ucxCOSMZOFo8+bsDQmI23v9pJfRmI2morlCXH4g662I1y32YHwrE+4o+d1KcRJSdhNimPoGBSgUcA3Nw9bNpALqdwNAph0611rkh3Nyd56DfjJ93lLDXgKxDqFEu8N5sfu5fXHkkiOMVvRNhBLRpLZcp4bIU9j8OaqnLUpgqO0J4xiSnveYuMBos+tLUgxXTu0bBKH5LWmY67iMKbzF4WVresPRpHTLofIxwgbAql3bgXF0/LpESCSFBcVDzKEpdiLJkryGZlRehsBQf7TJiexSnXP81ZHIeZJ7wYLCvzRPrU5ySVGoJRJB3lVEOnAu4gnSOSlhglru6D2uoC5pMcfj3lgjchKDTKki/N0xlpXqOAEhR1i1++phmT3vX631g0gl6COVDGTt7YsxZ4DLtvHeOtxIh/wA85eL6lttYO+4faOVdca3YI7On6KuO4B9EyebxvIZIbtsLiZRSXl0QETBhKxWCi45iPVvY7cbvxaqzcOPdlQh1RnOyrvGI9ImDC0Wg5f4Z1Qk7kbm1a325tBXafBD07A47ekmil5N4SRgfTcNkkf5W4HKNCFFLHhxAvE7xROd+lIincfF+GH5HRkIYyux+tYkjstc9LpJ1eBJyX4fODCNnWhgL/9m1oXeUliDgjR04pRxfNshSxk6qss1u0LznXUWrTMQS65pFKj5sveXdoT3HSKd0P2J8cMwXkos9o17dPcZ4VryNIfjTwMCrCRrUxuobtss0KSjpng0H79qTAbpe/7osQy9DCn2hD1CIeZZYP76AsGryKtYWUo0MH0nMng7OK4W1Os/vR2X1LYSrPLJHukvVV5WAXEa28qZY2s7uhUufFpqeqI3i194LjQrWT1ipucmR8mTnFxgbeKkp9zOQQyLJI8kH8PUHRmrQnnU+ILqNuBfzCi59zfQjFdi8fJNpAGdx6ErZNpyu9cN7rZeT5nz5sZXSA6dSPDkCmQa9u6tQ7yCD1MQjJUaw1DeVExYhpS87KP3HqcmKmojHo+92cqFgw3TCsBXbZet1Jsw57RsbPc5oWHQp+5wCgKW5R7o3MBkOt2FbVk6b5K7nqSyvCVlKjvyzQxumLj5SCzyY7MWPJNP7PFSpdwKM36vXyysJvNmhNThwoNefOud2i0VmF4rCFR11PlLheZujvCw9t6sHYSMg7By1NkmTtEu2jyVJqDXC1OvXQtCmUtn0Yp+uCZEM83YWspWl8W+CnNoAiEcIu2sKJopC5sRPApTY2ftyRUHxOxoeWNVl9zX+ISDXLlbB3iEt11VTc6+fuFEGyDCBojrsiUCSTZJ0+TNLrWCS8E5+p0340RmUsmfGQBmZ/Law/lwgFIcZX1W84VksgWKWf0zvMUgSzMnwlKVTulSagkySB2ieHKT33IbCFVD/rkfIg71Z1vqniXQk/xZLVdSurYd9MNgIlx2QukSJmb12XAxrhAIEBMfn9MXJ4/yKVDBmw8ORm5Oe8cscDm3JW378LOk38Rr8umWTzLuXAhyMGKdVCMpsjMx81lO35ITFMMGhI3daOZDsVdD+XGswivKCSxO15d1iiGMrPtAbDqiV4N4CQgcn8hIF48dG96bbaghIPoONjRRCTGFUpScbMFcloU/ZB9lEf4kng5fPVY2OgBNHZPIv0TKpOR4gzM3qg+Eak6FmdN2bScJ8xOuGtnx30q53gAaycX7xMs1jTRhVRSjxy8ZKO4KrihF5w/S88pHUs5nt5IyjfRNv21Brspmy85KZhAwtip9cR6BRiO+/Oh9mqfxN2ouWzNtblbALgGBAAT7Wsu6kTPyi+YeMNP5XkfPHWmkPkQUXISU83Ko7LVbwBkycbGKVO9tNY4jDKAXkBKNFTnUkDLsSB4nYfq8VYzNzYQPKkKBrLfYhMygoOyF9R3LjuxkHTBRo0jH9UlXv3t+TDaoIEOrJNnqh23T05krAcVOrTNe+HgPkGLXTFngm2tGaQEC510qFLuDm7zqAzFLYC8sEn8SM/nKmh7qLGLZo30vYnF11t4hEDVkbXtuGiHL/HFBQCgPlNJWJ6461tj/tD0ZiLcXXgPoSPcqZzDzeeadY2czrO63d4hRQMKuLddlFZuIltPMHPxQSOHXHLbTefnlA4J5Xx6TdtCWwALKpwPGZDAdGmLJ46McH+Dx0htQw++DfzwFtyZfK2OejaXjdK47s3YHrdOTUhqmLPOYzsJ5JOtpX8r7xt+OJVaByMyG7H/MJJerojaM+pPURgZtcQg9O1VVi+LrPHi49ACUDYxG7jkUTLwRKKNQo5IZAbK8kKjxLqgsmWkScnj6q227vdrA6Br3Eq6BsxLsWlGBbt7cEi2DyCj426JS64n8V5O7/ylunaGbJcIq6undt+2cEJd6InP5rFO6uwleeCbPoygT6dHX/ULNvCHJNpotW381ASrJu7RIDSf+i43ZHMjWDAe/tu9dNA682KBkIZc8lWpvD3cGOGqe2ltl1RhtnUQJdV59JooIG1sT5T0Qbw24TUOfb2Tmj5Qb1XtDWRbZDBuNmbwu9rdGX2y+vp0vJEIIti/Ecq2D+bbxHYoGMJH6QNjuDg7OpizveyjjSF1hGncl6CwgVm5AUrHqYqRD9q6Bq0KwTi707bVSOntxurHTElGFozK0WDhWh4vxKtPfO8CqofuxxAYXYwbCY1YA3bcIfFVsSS1L4t2WazSyRxEMr7Do1qalScu46S9iay8vxXTmu2nfyny8qku4cgRjV8Fl7qiyWBPoJa2VTvtMSw2M7HGoaFsnwIr81rG1ujawzNpClCoj3JYev4rM3eJy2Fxh9ycl62O2Rld4y/UWB2pX9ByVwbRjeODjJB0uUJaGq8XfBKlh39X2u58FCGKqfVwmfvvwU2Eu5jEgPWc55uhRlQsX/xo02hOLT5ssVRqMucg2cJaMRjfPSESCyamSVaoeM+vqVf5euqR/E48fahkWSClqHxzbFJDhfvdt09jorr3NfbdPyUNC8VGtxxBa5VtxtDieN0pojzfA2kLF59Cot9y871y1bt/lxIO0oGr53g4XjcqRKDxHe14K+hL070znbO0kEkets70x3uWbjhlyMWnspK/0F40+y+sdmmd8LTR20rVhF+Wxrz3UT2hk39aaKzDILVPvJZcqy4liG89qpAdV70OUkmaeYuYJbHol28QeOlcFlIUiD0bFF2Jtt7YvLgK0m1nTCqHuynFM0y5OoMcWLEB4mBiesiUh8Ub+XmwUjAyluwDf1Zf+NF78nTZRGaJdxKXwynTrczKmZCWkWbTol0jqk2qLZtWZepKrkLYChwKQ1/c7dS7J2+idVxNIfo667e43itz9ViU02Y7iEjIt4/z5qjp/FiPV5XPOjqbJRzwtvKOVkF96mWQCS/71px5ebzLhxKHTbpnmWIivYYww+a5rbJ/8vQvmg39eiHCS7Y4LVy0x6dBmxLrEZ0/LXmo7GwwD/ElYPrROGycU3m73PpsorMsmEUAnyGgfoVewtOVk6O83HPDyk+Xq3k5p4JpNjegl2cQ5BEMvozIUhdJJu0+9RSNFetoVGulQ3RhaZkdWJOcLITVLsBBmk/KCYf3SEN30IWhfwSSS0VJZhKj2oSkrC73I+pRCrfMuismtIYShcbje0k/xY0ZMSk+gOfSFC552tNQy9qFzb4eRYIFckZHOtsqKqe5Vmdhg6m47fOy2IsDffGOmSkg4VLjuazSCkg64AfGS/LUc+UxWWx8l3gzfr/K6kYm2cF7rXSx5QswJudb4RjywrmTe3JdfEh079sOOr3mwiDtoJplfbl20nN7lWXAiud7tTlRMWvLaYA7A2KO2WqKQOJlApaYtwO9cGNVIvTC9ojd3lTmaY/Q03nxC3Z/uUSTyfYt2EnJuzuHO07iUaL4aiaaQvNvhpgISF2PlTMUMW7hgbKf4l1gsnC6c60p4tplsTzVk5w5LMGMTrBzs3jXaTnzN82WEyEj7Ah5KJlUkKMWPC4IlFnTYbiwur3RuTCdiV9hsVvdFVXPAwXouBRpMTzfjFduhkfO14yEeqPaPrIe7bj4piLoxNuECGve+saYtY65a3yYIsqmeMXYVM93UrdncFmVXe7TcThp3ihWDQ3T4/E+Nfud2ixvWy2MvLVVswmJgbN62J73CeKdXOhkVhtHb1Qn2g4KqK/VWE979tJksRU75gcdnsbJZzK+8hZNzDdNQzqhbY/Q0C4NlNwGsxafx5u0SwI2NNqFctW+e5gVcsib2EAzRsFQz+31LFb7kkfRmFeHwveVf/jU86boqsOXPkPi3D1YHUoIDJA+bkonqo2bAyq5X3yEInE7nLiE8OBDlq8x7jLKCmd52M1DavvamDzq4JuovIxUyGrOaBpeT49sHm3goqQ6vaK9AnBFp/G367z5YMKeZoua7/Tle70tHdSj9ySuURjaajO+qB+kG1HH+DSfPMJxr62EW7Oxl4fopilHeAk8DPrnqBvVEJFClVVMneNaCubRojdrl3tNQYtTnC/l9Wi2/Mngu3eXdUZKM0cGvr9AUTbSN/HGDIkF4L51NhB9S0lelHNjlFBONYkm96ogekfyGUAC8iAU7hz4KjCBCz1PZve+MAkEtycHHDECoTwP7wb5m99IZ28cZZ0QCpQ3UdsLDsVTZqBPCZFYDCQuJW0gOHZyJQyT620Hij9sD8h6dum1aidhjot3R8l+z9kjwIKDM48s0nwP1N3JTouKFjahqwkB9a78Oi+Vu+b2D+h6LrureWOhNTujRw7WL+cRT+dMsnVZbEdaaEpkW+KUOClwW7xwBC1CQ7BAggVAOMB1PaEe9uLM21fK8k+9nv/7TzR/5FsQ326/ifWAfh9m/yfZoBfxvlHIzyegOPwN+W+ImP3zaf10Bmo7tMbRjwvx/NvT0J+PKH86xfwv5mf91446A/AfTv5qys1VsjF8jjrpQLMtSKanGb0kLrjQ7BccOq8XD/7iCvZQGVoZE9BslmHSwRNKyOdhVW+1Lbax8quBN4bGwffMc+5A9a5cEhFuUpEvMhGe407FMw6qQt/awtbFwfvY96DQJitZKvDrO3sqzpTclJdlyXAxojQy564ai+5yjd6vQReGpLye9WBbfPjrPZPWag1b6VPJ2vWK3FIkRR5dcj5a6v18k4fuvLDHSb8fp/x+BNfvK/jMfAwKg2K5fl//uPdv7s8/A61O2uYaT7PFFfN+iiEe+sqWBiYlV/Kv3//x76/zu+bguBAltyWUSjT+eFNr8v6VPnUMdoco7I+TX1WWWuJr7mz1+3vJUrnEInbBLA1yW+93c7uesX6n3xqCguxIWSYseTxqektgC0tEd7t+c8aI9w5v3oX3w1ou+t/fn2XQ2D/W5BzAev3Fsf6rcVrDsw3/5X0uOrbP6mucv6X3F80vPuFhTX9ZzUXz29O3xKSlFvlrLfYwAIbwZ24XbyhNcqPgpNWaP97n615fNNPbZk0Qq4yv79mgbRsDVubRNsOT698WL0Oqa55afT2WCw/1MpdNx4Q0N9x1Nzy10zzUukAtZ9+uuwAq4pGPnakoXFT3FIv7V0++VgfxlufFYb99svX7J59//ckXHevUh5u4s356ssFS1xoymuMAnlWGtPVeVqdssfNPafwXRqc7/3503zkdt/7V6H596l9ZDZ3760+1/8VqfJ7KDVzSemUqUm9PpLaYw5gvfmTMSKSgGNH6GKELE1ILtaYPzaZ7xxfqa5U/n92/P0t/ae+nL1zXFDe+UbPx+71Efvi6HoyLbwEFyo+8qNHjWhco8p+t/qLeke+t11iv99T6m9+TD+RPZM0POcdSH5njvizx+s313S/5+JGXoDkcOOSkQHPyp9EoFi+4mTYtudkmkuCDs/nXngG7Nb8hcnABPoYqmiWR3mX8Wtu1HV6mIL3vKyv2Fd+0SisgWNgwlA5wTvFpqe0Jx16hPfF4UdlTUF0Bpk0+KWc6vpu1gzjGp0UkScGEswVDu45NsxKDnQEv7YUokBd5qGkeLCPoxwWOrDFky+9Zve8QAhrOnEkMtT2GpfrhUVmSSk8E1WMcvwUPjnPypGW1OjDrMD5X7ElK/XtFgwkmwRks7oF6toIaIFOMV6CTqGeCo4TbKn96ZMRGW5bLumqS7jprlRSBH0JGaqGklnZUkz44gtnAGRAxSSUxggQcIa1RV6W2o8+I8xJrX8Wi9uhujgexgCmIonjDUTXdCmeyVTqPmRx8h6MN2KY3Lb1BxNSm2P2yfLump5bhfQuR7rifp0OdW927Rvqcq2oGUFSjkeKLCnEiUGFXM7LpJDkFHx8rcqijWauAD0njck/lB8ZwC1D45o3sKV7hKoe48Qz60/EIzvf5e3O0gnghAQcoF0V00df7CW9yBar5UU/ihMui0G7gnc8gVYzndV21uJgR2niO3fysTHBAgZwh3BH50s83Gn1J+WcdItnou0RCqSEgiJda5Kmngxuh1Ot8vuvduUEaf1Y+iHBZ9wBjUeNcZ3sC1EKTaC62GRzqPHHwo8YobmdTPc5QJGbBMlkAgzWKhw9tNiwMycETE1XXhAIvn69IEo5DgPKEQ69tGILIEx2wErctS83jJ1KQHuQSaBjHU3FklEjQeRI7VI1AtkVs0t659qHsHpS+sMggiIZEN1OoT/tRKZqxrp81hUSdxtYSjQnupefGko77TrnQrpByhCCIfe9v0zUmVdNuz4blghMskpuCU8zJPgj3Nj97PAPn3KD3N3vrxHFon6LS76aAq4H0oWJyN56QM4MDSq0sYEjg37ez6kePVQrGsL2puoYWO6c2dsDgQu8aqmnUsfdwUBgNdGPe6h7MyPUa5oiDIGiVy9ekb4MqE9cedQspF5/iRfEwjB3rYM+d2Q80IzZUekVtbGUfkeC+llDSuACYwdgBDeDsTAJuj8FRu685Yv1zdwzawY8E4lWL6m+F+4rOT7+5i+TEy7tpmYiFMvTG7ympglH23Mw+sjbqs4NnCBInppRSVX+lJcLLK2yemfiS/bIHd52peOfXCj97mpDbut2TawYQSq/ABTtF3HbSBHCwS89oxPNxvL18tLzP1wrXpTWG9ISyXmEg1KlcVq4PrCMksuuIc26HoRvTeuBDuFVfeyGL1IftPag9b2IpTBnVaBFW0Dp8/7T5rIcTWUjzx87J3kBiMh/DDviYeLSoNySWcBd+zCpuOi6q2uQdxgik9UsrxeTwa1TNSGN4iaX3etJNm+xPWhJRsnvZ76aXzHvMP2+gw9tI4IXXOTNU3lEpoVf/w33+bm26UVuNA2UT3Zg1Y9a711dI/gbuFmuJBeEyZJU1KSr3wO/SrsXu0moGbMbUxk4+6l+G31ABmU2s6A64JkRi2l47Q0EjHCEGS+BMzZvY9219MnoXspiaOXefvD/Uea5pBNnoOIBaFNyieMs0wRyH5szv96MgQCSjxNTeWjw5TgZ11PcieqIgYEII1oF2EALmEN/Uj0sPmdQrJrLIvtTm1BjCFD/RcZriBgHslznPoR1UgcdOGt7qaGVVVoJJOCBSFDLO4NNtd9zmsJ8qXSFyVcM4cGTn8U6xL5ksd/Ua02ArahoSc7xXuPT9La4VNd14yba20oHrT382PEHBWXRLdQSSPkrGSRx4nu8VjdeSZplPsLj1WpjN4KL+kL1hecwozyYLzoyeeVnvFLKFeQa+lg5mPHrTBk54Eh9FHyt0x7MZvN1eOa5vGL53s5RUBNh58xEzl4Kjng7OQJ/eN93y3IvFM9WYpoflKybuoeIg4HOxKGp2saXyIT3hclLGCrO8r/lHCUAP8tYEz2f4flyGZ78oWqnVNIFIdMhp0/jVDg0FeqmxrTRTDnMnhLXGTJdieMcB6MAsTWfLd4LiyPgep5D90jEN3nf05teTy+xGRlY8098RY689erZWWZZZY80b10RNDN4O3Hqw6VgR/BTQ2iP0grId+JvU8HhanfuKU7dzWXwgQ8TDF3Zw0tN9GrUKXICdr/t2UDS3vfpCQfKWaPxuXId7kX4C65zcpMf7CLZFmviX3HKf06VmGtzUJU4eXjvCcWw5DpYE97kQBO4LwWqauuXvVRAvVBSw2bBvQbjQrMGYb6IQB9Joelnsun4ciJoFRScFvSaSGf20wJ4FloE4Ke6dHIT2mf7jzp5hW0Fdd7q3+jHfKouKwy5LgiwDugsK3rQHm4yVaRty29MLVHrQtR09rafUWKj2kAdcUCMFsh/e/OlAaFP8XhrB8nr3w+1kPBphG5woRX7E5bVcMOe2hmkrmB0kCiHNX0D0vKhf9gGEb5RDvLh3ceh9nL6lJ+5cwGdoVPaBodUDvmt0N2qd+zI2PFsn4kWcAEeJIFhhv29yy3NrWMReWGgR1ShPMzXOEv3S5kHNsncFdLTb+hijWLt9ZjEyZ6zKScBddL8NzLhUgsw190LAl1ee9+SY3szsEjKx0kEdcCk6SbLQIvESCc7ZJZdFlWZtHYfuXqcTUBrzOnLTv3ZsImX5h37Wnd6Qrr+HZEkbPazs5oMKGc3jZBVV0nFWPFnEmbvxKU+ZQWdcUKJc6jM/wtwLoZlLSBsx43tiUto++zY4dkefYnY87TTF1CdS1xQ30Nceuy+joOWrYz6dLJB0+J5gW6yI2fKrPlyYogtA2b57YtF3pNM6RLAqbWZWGs4fAw2N1cyhhESc9/BYgxpK9Ms8QdEAU6Zta9EyWCWEzOUWwMtbZXxqhp83A5dJhN+Ct+1+OpwXocs/7wAIGdviPGb5OMnRDYaTDm4iJ9k1lvFIDYBcThzdSnTGR0fpziiQZRnfAoh/jX7iKMfI3Pebzw911iu0FymjnRb3XQd7zZSbedm7QJEv2nnngl7WS8x+AidpqYNfqzF7i1ySBWMiBnrtphFNF3ntAzsSWzfJiQVr+s1h4GupG8lohddmIVJNvDsESeLva+dcm7/On9KKYdAehxxn9z0t0TOlXEYCX5T7GAINqj/yDq6o8B5AK7UgkjI6N1XwONVp6Lu6UOxRjY9TRRuAzk+c0boaKglu2mmZvV1D7hm2TDBgD2ruSgcvUCHVjyIIkb60N4hlpu+ve/OmZCMe1EaiYWnV2UTFg3RjqCRjcganXd55JbfZueAlRIdmwL18Qd8jWpdpuzm3Y3HLW+vutozpC9WUQjmed02p8BUAtg/Kwpt68h6mq4OQoil8Dg0vLr0OnPEg/J/LXZP7NP5L3Ue2GFrprwD8W8KgsCY6FOxAEq4riZBav3Ake0p7ggWgVAGTFHP4wUhicnGFFDD8W4Cf6sqEo3oXexq2beDVFgi3hnidjFuO1RLiMucMxRRQv1fC1iL0i2c7aaamNPvIyvhxV3ptv4RH+ZDr8uUztH04F8nCBI0uuwryCi2nHeLEbiN0jHcuxR604MxG6pG0uCdvJYK2HthIlyWSNJF1PMUggBYBdRz8stHrnNqVJ18e9Vmn6Yqulst5ryiggVDNobZpVf92mRDdPPa72NqgaPd8uyveaw/Uy8Q6gJZpOQteTOxaO6DXCw0cSI6uAyE5IKlx0yfsxGHhhM35rivY63yUt+otvInKu5TBFAZNEy1YmhTPeiRGH8b19xdfJO/BT+Y4fZxvH/a3JZUxcbQ7N7rpWkkVt0l1rQwqlQiN84AcSMZuFR2aSbGzvxRwWFTWoBG0YWRVnUG4BlP8OBZjIOzH+dgAOq7RuNvpLxsNN2mw9kfIzgM1G7HHBN1pekrin6G8I4ZOLdH166cUNCnZwQkx0Q9beci+A71RgXy21lHJdCjTOIDak5VudJs1ZMDbIKhC+pxW7wgK9W/xXsKegsDh2BNI0l2KCCBETgf2jgQh2KvrymExZWoVb2ZMGsMeaAOAOgee4ueiOY8iur085R6qonSRc6lgtBUIBETzmITxZh8FkhqciuVMTWdBHGwQmT49yyaoiKGk3RUpfDNpOtgWj+8c7gupX1rcYNNQG+dG6vfGj7Lw0JeDmodjc8wj8/3qrsMpsiGVmqegzepdS56lO+ty5KRJk/YzxB71k25AroBQzG46g1lLJchPZ5ZRHUjYpEgK6RjM9Do+6OZmWSrDn3yDI48Mf2vQZw9dKxEixCmKXcwgUEDPgqgomjpysIb0lffe6mrkH1D4YoK9rWDlEbpL4Nn0iR4WdYnJC4q6q61Y7Pbq5LbDO3NuQ+kQLwOGmne/oIA5UeouH9zNGLlpSR3np/gK9ozQcedI9uG5AN5GWms9fV99Q+OiepPuDEkXJgVzC5SS4pBM/UQIyRhcPWVJnXKE95QLdG1AnqAD9BYbfcVtc9qKuyzc6MxlXT1KdxpzT1WhktZGnwfr70Ey5+Sj7teQY7za7mVzkPFtFV3cFbDypVgPy3SgbqwbpCpfH17VIxrE07k5Z0AqX+9heFfg4IIl9bWz76ey5OvTf6SDTG9afCFd5CbJ+eldTKPfgzlPkNVM96oNnhjLvXiF0xW1m4iycSlkDUevYJgH/FRUcW4gjwtuEE3NXvOUXVS69xzOtPjbSU/qUl3j++PhoGsRZvNlpLMjn6tX4S9F71xKIQiQ4BkYd1fO+tmnI5SPhuzFxApduFrhUCe6UOUAWZVIp7RGJDLQ3utjOJfkMAfnAeQ0w9WQPBGsXQllN5ClJYADx7q/BudlG0zE2R1+nncmNeVNq+EckldlPbYU8PTJ0xTbOKs+GcTbvkxiN4sGVoZf73hHHqNCB7pDueFwXKbXBdawZ+kPw0iXlzQv7sSSczoaQc4LhFRZSCJiflMNK8rnl7UoFVQm2VVaop6xJBGx3V8gts0e1x1g38C2n5HDPzc7ezax6JnUKOBR3le9d+qzrwL9+woHXA0bzbqH1ivb+HfhUsIdREBXL66DTtrqgfpFKj/HsU4+oydvHI2oOKxwRFFPVl6o7Jwi8JNoKytAfgw5w9LJBZyD4KGRK18Wl/K+LWoGItGCjcJ7f/CSeC6Zqtto/GG0qnZkCar7X1IzVcFBJQLmwK1Adyh0DEemBFf6tWZ0EMGW1OeKQOM3kjcfs9n4IN5gihSvs80OpI+NsyMJfJ6R6Z3odadcKZws4uBGkmFKSTwOEiQ6GkzNn5Pa5YReEe907nzitcs7/YkxZl4P6dWcFKsPKHMzjHci4FYyga0r9OLH16IdJoiavfU0Zla7fLqM70rCm2FYNYqhwiHukFc7HH3P0xectpfgHYP2zGaiaYv7M5yjexix1JJRfhXokmkyhYQ/j7YAc6jLS3AB3yGe15HQ9R1z2sDDM4KhNeTBCy6r9g4zjfrg01iS8A6z0d7HuGarhbaXoVaytRWcRQMTeq8d2pIqvzZvZnie/NtFNnHz/Uk2YcdcoQMZhSCd7cU7qIa+1J/3Vsdsj+rzh1cScwtjiu6L/ZiiN4iuPx47tFvihrM8Koa07tJpbvjpSE4N6Wt+6jy88x5bhAiQt+lZLvJ4noZw85/KUCnUZCW0mcE4jt7vfUZ+ZDUqvCMaeG7uumC+H94FXdiofx9eCksuI9kZlEgrFVqzuCNOn7hDqBkY9oRdVKRie46eVB2osicMYvGEBQG5gTyEApqaEQ7lZGKEOfRSI1ddNSXEijGMC3q/9RQBPshk3NkQt+PpEw9avZt3jqO76g4e+cjLzJLHIij0gV/NWl7GY/Kc5EW9RkkyaV94acZLP+xIbm4Ydmuklnl5eXbiDpL03cQ260Pfg3h99ybwYApTAjw6x4DqROHMqF8Ez7ynP0XHBR5lEg4hA/oxVdDp9wt+CkFMEzM388Ax0j6kxOaeRye8vRvEcnD3aGhHbjre0LQV57L5E4wr9rEg6p6wEE+RiDEsnDOGFoj240GQXNEdUdrcebZInILB0LwHxecEsX5kOBO4N9IeD9BqRwjRCW/WQhBZc1XvkoJSBMuYxuMNPb30gCa3e9nthbJk6anc2zXd/LZvIsO03jddhJ1wf3izC4mLrma6KL6xEabEFxQUBtW9jd0AjhRLUem6QLjeLqjW9ZZ7t6RopDc9Jfnu8VJLYtlK1Kdf/n2d7wimaYg7bfra3xacO0iFU00RPVa59c0Zti4DDbiBokJ8nl/6iisegAWNaS2wWqw0YldML2+oVhoGNarjudtfcZwfYavTNddHe9gc8YVU2Uz2dV+ToDkuphoe3vdH6bHv4vaiZ3CGKKM5j2Dy8ukPb3U48B5WB4Oq1wM4GnXfYbZCZIgKpkr7bQCN9ZQtjF9IY9hstDZLIGVSwPMB+CSLALcvSQG8Mh6Nl51/mc+uHNcRMeplCMb/YOnaiXvVMtaBmARKMMDVl+OeM33hljMsSCldJDQmDDTZJfMsyAONkNE4CwamozkleJe72QGFYAFEBTt693F1pl2TJJQbtnRcIwiFzMVJclT27i1GbJ3JeIHK3M3x0RmNj9TT6SwGZwIKYBfUpTVvJ9TPgU5bNCOTmSIdLPNkJKyoO5HnSbcLOTDv9Pl8mbxxT2kjOMG4ORDKlj+oDtIVWIbnh/OOB14FcYPAjecCmpA9kUg5VzswGdGvl/ZEUdZ5E7g3RwZ6V30Ywg9PxOnAfdmmNJ7cYvR3HvJNP1edRwhkdS0Cu+dJpUaF3zKtmYh2ZosWIlYTtcGDQJ195ggCtKpNUcGfwz1u8+PMgT5iTsro0lnVNPiEkILXxeMm1ux7058CYbuvqKKKMhY1c0HHZKqXvsg78XYfVyxbAoo4zAV3ashqUTVDRRfA8PKIJRoqiOYNRVvX0PqauG07ZXg+Dut6bZC64h5O5CiFPGBEc/fFLbaSLSkg4gn6PQxs1MEqw02If8T04xMvDU6movdegzWIzABJ4+Ixuk8rfHf4iBbYMzVgF/iea7cqaLso1CSnfbIEKUlthYWmMbq6uhcG9DSABcInWjLBO4UYQSBql57oLly1RLW9TyGJ8BT84EF6gOc91FXCx4xiQ+Ec3xlf4DKJx5S/uBkGfeS5omchBEdUyFvoFLAetSHAF7edRnlWkrrQVlUYFvoq4RPz1f0SfV3iPCU9MRKYMtzkxqzqRI2bjuwpNClrunvwZusWBeVY+czKRH7H7WzBJfa2n5xl+RhpQCe2Eg+sbkpcp9k6pvKeEJ71pzMPST1f5XFmMIfkK5PfsvtQV9fMKBWyrWm0X1CSLRsHZT7NGjshPwMEMtogUpBq2Q35NbQ1cxnp7e5oGbqxN4rCc2l8jb2OCCuc3cpHQlM59r7MImmb+5f/6DqO592czI5ND6F5EOd6RFmy5oO3WLqHLTx8p14oFu9rUYIl2sMRAjjkMflMGDHM8oH2XVgI8hIRHQKiQEVIhmJZ+nijxNlCjppXR7jm6OqTHJAd3xv5CA1gbj0fIio/M59szBESO5yu621dgY1sJdQdO2cfI7juBGkgHvCpAodvOCHdBhQ8CIwFh2Ex+CTSdR+pE6RoGKOvEtx5S5Dh5dfzuyTAcapfzjUGPtBBpWOmC3EYlyBxzc0v5wZjnggA18OHxQwgos8JB8dbOZbp3pPoKMfZ/fXZbSuIo61yojU4I1jzy9b/nOsGIhjLwew8cI8p+GYZfUpQFei9Bcb/7LtOi9k9VaINPCMFqSLL8lANEusq4/Sf9bWkIBatBiIBIAOHEj/ZOsBedDqf3qq4hldUo/jeoF4X6yEzQad3tPTrbDzWQdfsxOAuUGrDsdwgd5TuLkGZOboezWXrcSA3YPH8xcNikds5qEjAk8C0OQM8GXvqn3INmmMsGTjXti0bOIcipeJQ9URm5xbRkVQ1DXUtGHeyQJHPxoOBUGh4n2AZSCT4rLJE274SMi2C3z9wVUALFn6FPj/tWZHBRg2/8fgxffRrLlCELQcK/VqXmLaA5VbKyijFlAAjY7Yhx4kZtZbrdLEj8GpMg3TydZE7a0pkd3Bz3HHackLz3ZfDHJDsvQVA/iOf7mFfA1Cl01hrMMYF5lKGI8kxr/K0zu/hmziCrdgJvNPfMPaAjhwD0sExTs00k5T6xBgXNMoc5XSTgI8zYmP5FWxc/S4r9cswoPBqnYyhuLcYlFsWsT4Rlrok764D/at3gEnytDDlNuiS1pmnzHgDbzlV4ce6zTpt7SwYM6SYIlbvLNJmBAwY0ED4V+VR58fWmYXskVfdmD9uAbnDKPo4Ubbh+DDHNxjbWG+/LMVk3spc6XndbKKwFvDUAxy0MjDOFnT6EPaH8EYTncIhv4CTez7lx5riB3Y+Lu5+dS+DkwfUsU4Qk0+AQ5pymm5twRgVGcaYNH06Fz5xA+3FZ7yY6KaWhYFORglUQL0CP1i4kQKVRWNYZR6JoycLS3qLOQFXUmDSwCTiLywPml0Kq2ytzwsjJZxgmVakvQNecipcZzRoJ3uTYZXiNbcX26udYSI2jbzvbXN3LckDYZstLkL20JjLeB7ZTWkaM84VvCrzWtrXBYseKOJgOEXiTsRMBSuzSK+7lMkySKrusjGVz/t9oBa4h4bCNLWEKxbT6fFHkLkF+drRTd5VjgbHOq9PxqwTtKn5EGiJ7FAXj3ybGfsaFPMRtO1A/CeQDjJMtsBEvux7eQqANuIkeTR0yGsdZ1T64AjloAR8wW//H09Xse440ixfSQxLMVpM1k5sMePTX9Xp+W8vZr7jPm3LVZWZEZFQIX6bqpChFKDHBRbWqTcVPdb2fVkWNbOSfwiAPoPvYg2ETCclA7YEQ03YVEGjHn1AMWrF/mt2cHmUxYfVovtOx63OwrMuxt/k3ChyNMtdNGvKzVTRC1hR0VS8x2cA8uzQfYwBuHWcbYu8vj9Zz5l93RQGO4Wsk4tgx1JQJkv5fzcyyYxpmOx0103nGfR1x8Y6WxrFT6rOY+vsH6lmm+iEMJT5Epiyi6Do3bnIXv00E/2D1vmvpjcbWSYr/VuGQnueUj31RwXBISihmmce4mj3B1EhQDLd3ZaNKi+a7MYAJP16KBYMRPyCdISg+P0jk0pCkj4glwt/fXFzL7SZb03lwDp1V46ZiQZQYwHosFSJGIAWJWUNNfA1o5qzOmoFtG5F7VY2xB8EOL+kvvWX4Vzxp6k/yw6Q78f73UD8lZBrzPqE1bgTrsLhA7Aiubm8KAq0k87H0cCt1qTGus64pIfSbtVW2q7F5eQ48EUvLS4n2I/u4Hd9LUlTU1muK1r545hncaJubP2tdzSvNXSxYsHk9ONYBW7yN1J95jvO1sKFWEjNP5zJEq1j+kA2SDdF+iwvlyPh8qrE3fBDwceBdDF1bEPxOcnqH9p7mfk5RdGX3n0ZeETghYfDOXoChF5vQaCKKUQdHgSjBK9A/tBNLyN0/7rL6oSiqXnfA5bZ4o5aTLGTeUnm9MuTLQykxM/4AKivbNCzH7AmyQOH9PLxGj7EcZUtKEAmVInEaAwW5IhrXdkUklTOOhnsUiidWw8hy6RkXdFbsAqGJcCkwlGmQ3SP/0Dri1jy83ms6fgiusUH+xibkPN3VSy4oE32SXN+94W5rQp16wEcGjydmqmnv1+NUbU6fH3gEi7LQYezRl5/iL9kqg/fUbNpWcfkNKX+tfe/Mj2e8J7BJLF5k23WV5ZMAWDtFuOI5R1hYOn1lGwol8neCXyHIsgqJ3mDWjJT1Rx06371bPeGb1VJpHCbyJ9mVNrAnd9RtLKtDWhztXjLQuJ1KKuNjudFhGMA4U9YOMPTo3lVRYqWEYdR4B3fwCavvxvMz3XyEYmnPfBuqihRTDn8yIoSl7J6yaxIaehTOXD4JLxQMyjCu0Z+0rknpb3TgYzEJnXgAMAJWVbY3DKptv0VNQiB8TWR1FVpkOGB8huAmnPnke+8yEzEHJkgt7/vGvL1aIcdV2JyO/BaRX9Uioj97xu1RLm/AZpigzsiNkcCrh6BVyh7d/wHMWbCK/iun4jGrIIKSb5rTragPlvFMboqTEetRBBXff8anayCzOc3dp1QAfSeudaTIHWsrexC/LkZvFc/GttK0BP9UL/DrP2zlkqDEPmDsidrlfJYPl7rziUMfBmoUhKMnN1fPA9ZEyHSUF1e2Eo3xw/H3FGTc8cZCTVYxr8Q2ML/Qvxlwi2mzokb5RMMI2vW5n/X5E6F8GSciD8oo7igvE4+AT5NNVX/u5rY00tXgM9dk1THwIKpKjz2v8lAgPdvLJebDyjzF2+fzT7ET+OJUWkIuEBpYiCzXpfH+wrtMyGMhzfhi1EoWTVnNlpeBu0UIztMCtrezPZJ36VBDwjsmjj9tXJBh0hqbDruypehrrIKCdREvDW+/24qPhZSdgIm0dMd7xTbYWSA6VIDNWyw8fLlz0x3iuMMMwtvsxTzuZReSYFkR2Y+l1IOwzymFI7GkmCs80aSFtmZC0lz8AX/bpIFEYNHH8oE6ABcuDrtg0FbB0KFS+SP7jyBeBaMczFcojcyocAwpQ+62sVuDXcumzNbH1wCM9z9R2nqC/+A1+4P0S+O/N4Qqz3Jzmuu0s3HSUhx+g509kh5ZVWYiGSgAdSbmTiVLqgEtt+LKRwMF2FbPz2lIvs4/unP994HZl7vYpN6w0+61jYtC51dRfAg2DdSHVXb8izQ/YH2VxYIAdce1bdTpBrMOVJ6hiYG+qz/sJZD03vwkDTu8x75pRh4J5i0/opiVdUA6maiLdhyVNAV19sekwNEZfs+W0ZRskSSSxMJRlPrV2IOBMv+nZWFfrJRCqBLALtbDp9M3o77ODD5F8TVnzCoaFOuTn7D0rAZCusfo3HIzVcI7oyt4XoihruCl0ACnP+dCB6mfmR+M8lSpWdXgjEg4iU5wqFlkTiCbwOSCfYf4xnbrgpjK//RZr9iaUbQpY62zQ+URRxoMn2zrQy15j2b3Mda0ie6iyivN8IbhRL4S9qExb82zx8qEgktPtF7xN/X57Cdq4yCe+i0AsrSuQf4e0PYWH2y1mrZQb7og0gPaGAJyH1OedZrokAn0Q283d34U93rzOV/SqPAm9Yw3W1juEKNzHOnAazKzTGVTspCS1ZXnLXRS7Ckv3BhRgcjmATjKtoHjmVAXJjtYtTFOgRf3VOXRIeJMbJ17xs+1sOzha+ApJEMVtYHtakKzVBEni5SQQgfv+Cpr/0lmt6j6Se/IerAyfzCE3zQ9bH66DDffwyfsXkA3L5kFAH4NTJJkmvPQGgW9pkQB8pylTcEvBqtCez0UM4fco1eAohgWsUVjIocRazp3UNkUAawjAejJI84Ox5j7qpAgOZan4u+mohD76a1naXwZCjCSlSBugGVD8IoxUOvFvIoLhdqk6OYN6us8zeg6sRGmQc7yT1d8zgSmswx+nTYbpiXngj43ktpeWmW54tA2GtRF1v2gug/iJEOG5pV4Szp0etWqsOB0E/wbMZzYkRCIpr4J60dfQwC8mh85/QB+E7k2DKoL62bby25bQ7TIy5QBk/8oJEUVNn+8hPGcuyENv6oJM+Zd7N8l5SN6yAIs/LStv36nPTNZsNBIPFqL1/j/rZ8RRjgiAUaMQkBd4lV9JKqtpbCRqEz6dixrqBxszpeF1pjS8Hf3xOpveg6weeYEdwDxKgTvP0HGBGSw3q5G/awjf8mYViTU0XJysp9RIY2Rx2f1J38vaxZqJo7EY9h84SVBg0hayMyyy4t8ngslHVCAYlwwF62U2J4olpJ6YL8OFUBw+BhXYHGC5joePxR18+gwG3CgMOyiX9VN0Teo2LJn748s9t5CncuqtsHhVgztNnoyeU5Tl3S5H+AaY10uus5QY9DDbw7neUzlKfh2aqop+nZCwgp1eQiM5lYahHH2DA6+vYGfkezKHWgHEY03bXBysMqU3Tx3yyW/FrsCf6UzJFS0BuwKWn2Vz5zhyTAh5NlobRfCHEpBpoIEm2pc1jVImyXgz+kor1sjbyX9w9qIEz3i082VncILMma+3WRPjpy20wni8AZEFE1K6v54kWdXDmtArIiCBpUqSrUwmFDD9s8pgNoDoW2bxz/KP+RpDvzURhk/3HLHlGOYVld4QriE4JSnD1gpm9EOP0nqzOr+QEntVZ1a18tKV6CsMozDnJdR4PZM8KJiRBupyK2wrJmgu1VGmSz4+TMYtWojPHGit9H0BpKJhjN318jdSuR1nwLn0TZ69UYwT+agtdeB0YI/RU8fLdMvvSPYZTHOAxJEq4INdOzGIVJIZGGlVV7+6IJ9WZ/dW3sI/rxrvwwvaWwFnP8G7eBTrhdAoXDBUnYalKuGshidW5H3yB1Cz75ReBvTnzc8zHHwa9W+ocror50+W8tuCe1nqK4NHygFvuMDE40QLDfHGWw3afyenMtwwiXpjKyAkHIZyaWIB9zvM0HpnBm4zaoUljspQP5EyLchCOBW/5A5P8KBl6npgjYURohXOVLmz1+f2uWpUjeBGC7qux7dav2dOnxdfOIMKkzbo+Q6CrGiV+3p7keNAbEfYepRf5AGyObk/HIWhyC4l/4i5lPRyHG1jyGrcWkx/gcOXF1YNTUDL73xbHDo4d2qAQqktcA8gDZPYBJ+g1X/kNfUZCC+ROspdsKCN1AvcCqRGRDyLmA+20mDvwP4Bmnb5xDrKnbrWngEkQ1+gyRVekVpdjAEWMCrxmnKlYrJtAlW5uGX1H0MC/r6eEnu0rLmHcC70G//d8YHJ1pt18zMuYpi59h3ASKumcvxhmln05tPkWn+dib1ltRcuBJouEKuU2FBZX1f8odAaq1m2M5dCafyV+tFoETf26gMKbPbdFZysgccw55NBP+YkfQmWnRg1M/cAMKq/5hC704waHR84kI7duphWJnB0ttV4B767pLl6jz3q2S609mByS3BwQRGeUn82ytKQKR9l0xkrlgUl11pSybkXqcN3CfIbRcYPof0cUKrA/fzxmuRQ4/lcl0CfwyB3/CJRw/KdVGwj28llbK5kZ5oqaCPpedNoANCutG38zce/oFntXQdNwjcOKXdALYydsG6M6yV1kLq4sl08yO7sRr8GxpidzMhPxz6xkuG3DO4sqZVwlrZaTq2JPX4jLFVfGXhFbamLNY+c27yXHdWA3on8qhjxydYRi4a0QcCiGopIvHkN0+nNgflgkBSiP6PCmxIXtNjaOuBbOS+yHBAwuHHgE1D7odBuwH2ywcBS9tZVyZvdUIYxXw2HpkdvemNw1hL/3JXRJVIqlP3p2K51IPTaH+/DgDcIgmGiu8farmY+XDRVkw3VP95SvYBNARRYpVkI2cvtLwS9vA6DJWKp99rypvZYZbe0FD4RTs84WY16LAaZa4Gf57hr28zcZ3tgx7yVj5V8DrBVVY7iBGWwH7QjCJt+nLZlkloff4hdi/+jfZ3uUCoYRiNR9dewATL2JVzVrj8fMbQORFeheTNnxjU6q+RLSenS82KSzXx2wFpBRFJQxrhzoe9DfcPdmXA1hPfgYw7zm+G57+8NrJ+iG5IYyquQpWseJvxu1fGbpEWT/7xsbJ0F2J+DnIiTDZ9x6lUZ175X1iAeVSR+qqbytkwO0lqL8rSWVOaR8zfCSzvyeLCeGInBeYfUWO8MCviT96+1rTwiY0MLHLF90ziMUMjE8dbj4hBNyCwA8AKptF54hfUE91hd9Bi0uj+r045htFFanxjgJz8ylt/ViSaHIMFhZANJc5hPWUkrX3HwHg6icI58vpHPOltrIC1PM8VF//JLMxwRfQJQMckEM+Jz5j58Be7hLzLep+ikziS8kZYxZn6INZn45BlPGIn5ke2r6/gd7M2NIU8L1jVWhfLEZIu2Bx4brHWFIqvO1Jc9E0Pi0PEziigYJEm3WRD1uSrer3zm8iE3eoy3Mt9fgrMnMN40qcjdHsrXWNhQ84SdlLRKhmoDE4qBHzHEsmaWeDQv22d6NFEUC2yHbWDx+MtcpF1jkHXyH1BS3ByPwB6QXERwUXjuL0I9qzeQiLjiIoBAhaASIOGhsOFcClqH+QqSJMbUvWppmPUBwugNl/Kn94mUZwIrXK0HYNoX0JL48bs8IzPPQQEJm37ruvI+WNnRV7MCzvGZAvrYzqK9J50w2TDzT+QQ6m2mfI99NbQvfDoBtr5MC0R1Kru5I+lolQq9SyXCh5Ka7XIWXbOJUUhmX/Q5GUpL7WkKc8HoAvVVHQWv0ZIqQgWw/fneCxZwuh4mqLLgMl4wvV+Pp8qs/xN3wadje3QniGCT8+3mRy9ZRb9rddlWT2dqaWXrC0Fo5cZ3/nL8UDOiKKtlBdYRd4yjBSVXeWD7ApjVSxGt6No+LGHZ1+004CExOOgIV/lUpX5zeimnxENsEpjJ9QwVV3i0agoXKx1z+Fm4Q2P0VccWHAJWgbIE+F2cNC2c5Q480iFHE4dtS9HX232+XO2KlV1DQ4RgxTwRu6EAvn6BXx+U4MPZmcDny3dMmLIOjHxpMY9I2GOzgHjGt5hLNqFmFSwjIHEM4+fE1hfzPFRaWNZMjUidxY6Rvfwm2rg7lpJCaNj7IttigYtE9CxzX2bde+c2imP3OUiRa9UuFTlf8x58JYxNnurw8TEUI9Zl/XLrxi2NkiaE7B9ZdDIifSj59RfBQsUMN6i29H21OKvER74DRGZN7wXCY/Qv6TCmu0gCTC2ubrqzJeldBMVqtqkWghYKRAd7HziJLX5hx9U++FyaaogstSL0C10P3QVpRuSgMF2CBtBcrMFLogL3lDgeYn7pd3G6bOJGZecYm+AwuqMouy64ryNXIKIHSifjkmLjNeVIHsqXZ+1ySse8p3Wnsj5AwA86l/IJhBzn7/0gg/UOfBtU/p4h8Lw9VPWVIEBRJMJhGRkvxg+fSg7zGD0iMo8wCoj6/p58bjL2l95BiOWNDKgJvo0DayOpSsz/hacxwv0jJ5AIQaNvc+epsFxXF7AvczbvQakMLYWFphYk0OeA+jXwCg+7wOgVrXr5OP4bApDcMsCiKfg1mbPw3YefOOQ5gP+4RAWlRulwlV25t4pnWDfVcSMno9pFREBfauzUkyf0Sp/sraqbRimP9I3x92AwnAdPn9tYVxsiVirjd/N+BS9Rl0wHRfIHMFKjxEg1qalPfRQ1ONVU6XvB/1Zf+JWGKthNuCuMWW6rJ0V7JNU1RaZO36cDZ3l9XfqL40nH4Cf4+i5q/wpbOmze9fD2SFQ7Af5K/PfaKYmImUsie9CPOlx+GvCQCh6+G9Jwz0K2SV4lApiimbs84JCEF+58HPUiXBsetqiwfCOiuGagaO3UW8Ogm4sQA+JBB60aFAjGGN2aCnX+6/L8IHghcQuUEuZH2h6HJG6EEtpCG8AJG3xfw8ZNW+h8SJlvqUmQlgjUXItyaeAlv9JbGqc616dc/kJYwh1B5v1qb5Z1cRVAC9HRx2LzHxAxtmyxQuG6MKLenUzfiDBHe/HGYDUv9N15ZwQqBEPNNTi19d6A6d4DO4m2BtPGLbLQ9iUn6+GtV8cIzujPwbFOsbqlobLbO2bbGc9GpGhCJ6L17nDoInOpeueS9XmXN/8PCzeY9S6bNJoGksPTFu2FJPnT1JvKj1/OqpfmTaPcV/vdjBTTmNqpjc9Olv5uZIpxz5vRqeWEqhhoRwDvFxwFexR+MO66NM6IlB+EEbrmheU8JeOzjqU/GeQnOesIO/dIyI8vElUbSQ/fjbduYYrAO1micd5w6HYUZOEGn5kubvb4ON689BsS5uSfykNPhC7yE4/cVnFpxhK35F+Yc+wVr+xr52pFhp6g3emg2kBRuq+xUrAWMhvnzxNLbVh9FdYpq1vOZaIlr9wjoMY8UJOjeQZzomOIh0Njx1wl1/NYI+iclJM72Wu8egNoQqe2VxjAR6Pz6UfEZIyO9CG2CTrinBAkV8pZqaMqz4GuppVDyKMg71ySN5TEkKZ6lJbLbqAWgWmyPjrT1bLs39utvI/DNHcUPSyEZBxuHziCMZlbf6bT1fHO0vZ/HYYqhjQjRTGfWR33xmiP7teHCImevQor3+sZH+PzVUZkZF1Kuvf6O31JZOx/W2kvkKN7xR3b/dWCKms7HycG8LWQ54ABVkaWhwyHZ03+/PMICa8xG6sirNJq/y60eMlXWzIximPYOL79jE/kzPY3wgveKDajD8zEZlqjMZCzKT130FPtDx1IikPyRbKfLO4chH/vCfAUrl6Clu1013nnri6485Z8qj7pVbGUDSFFNxRmnt8EpVgb19y127XSSfl+yLEJJ1Ihd5yAKzSvdWWsS0elokbV94646YoGpTsyYpgw6/30ZCP6aKQxNOIF5U1/oRsy1/I5PoCimiWEaAoQMA86z0wA1I47ADLtPW6M1/eXqG+D2Aut4Td+ziN7cQiHWgryN/5dZDUdcHsX+RnXdjSqjMF58pXvTwkt6DhzPpT+XPgXvbYl7wtp8vVDSix4WY0kldiIOj+NGdC6L3OYi0v3HMothd0R/6rAoVm+Yx3ubVIv9GT34ZX4J/n5+qxZAaJmTOjcgJoUjTcwYKr3MIN/Y496fuzSdUC8Tx6dmo/FqJZevmb1Z/aej3z/v9Hk0zH/wj7uSExpz4AcqOlsv0UPIL5n7nyBRJCzkTwvwXI/9Kqx/up7tL8tfSJP54xhEwj+RySqwoE5Ge2+SIhJdwLMGeHYLMTP9SyTrSJl7Ydaxj2cc+Em4OChR9AT7atlJodUAkoD2Qbwq8Rvb95Xlhic1tXf03NyLfiOfBfmN+SKDLRPsQ7F8vCFfQztVL6vSpx+uT4m1btQHwtqyILc/ZfaPvb2ADgzvMzTUHvbnVSxgjpCC8vJNwG2J1zKDteEIUXZeZ7wbo2dYBbPJC0enzvAdAewaILFP47yIFUmoKY4XnvYQqKz9yxymnpJrcxDSZwyq/hKbi7qI8vyZalzwTiImN3fBoRTWce0dLhPtq2MLj5+LJV9P41V1ctHjZ1VolYNwfy3n/01qPoX5F34GtqLcIMtG/aVzd2h8ngY5oOVdX/daaPwI0oReYG7U/N8/m5jjxfSnVsKv6lJQdmsAkESPm+myHBIS8JJcWe5SPYmEYalKcsckSFfJVN2mTmK8jl6sOJURzQHjR7LfXzXj7/fHB0qJPpHSKEf9z/CVCqdubfoUhj3iiDQAd1UMA7Xq3ZBLWjfwWk7Gju++fjKuCkmAwUUC7ZgNKSUOo3Uhyw20UuBqXiwWD8A3U3IEtvy+/nJIuRAIc+7s8pCra9Nh+8YEdouWO7AE1kHfqeSTOdEQphqHsq0Ja3Ld0UGowlnxKVP3eZuTwiJdD94cpGsxxif7Jl2X0kyR3n+fkA4e/lhtdYjt5m8sz0YheFk24bvvSM833xpLfXFcaNV0BEhFDLwVvw+dvXgMeEtR+7XJ9GCpSOrvWXpgiTz3gb7+Y7yZ0OtJJYMFHCjbTRELwNTQcho7k7AKR6gF5HRyhT0hnPk6GbjG/dfa/foHAJFm3U/+mG5S3IVGy2ya8m8AiAcujbM8Bo7URPea8ej0kJs8qx1Tq1E143cbF98Zdtkyf+T/yP+oa96lmch9wrIWtQGXIj+roD+w9O/BwfJb7cyWIC4SvPkNTjbi46m0s8NBCbWvH242Tkj4OX5n30JQ10hMmKJ5cZXOIj/FgAFwEpxK7CKwg2D/En+jD7vCOsQJ2V7t8IP9+xZdt5q0Trnmmcy9O9/Dksao2mW7EMQRygUAH0p+MTNDAU0JLj9ZHuNSV2uve5LXR4tMDcEKrzRb2CjW3YPI9DtRpaUN+EKOYryuSKBoCu9A0yeALddJBQIz/qxogbC9B2jDOTUiyDTpOC/I8xhO1jn7tqXoHisYbRBX9PTzTNS2nQWF+bXTr2cGUdXg20XyElkOnlRkR9Mv+dWEC5r3DDRLAAQOqFHXYODClhYtFU39Nfkqqva7y/piPqWXCUgWb0ePGC5EaXaq9YxzTiFXJxc7Nz9VkSnIOjI0FzRRfuYbyRnId0P3pP0Pb01Gy+w6KrbydlaL9kkKU3SteTjvh8W3s165HtN8xxYXwqiOGR9evqwxKh6GzvzKyl5HOlkYeypG4xWuNqJLkyufgTiMEEe+bSZUWfT4MkILnBKDWgF00M6DlJ4FH81PjBMn1JHIPyIWRG5s0K4lrqRub0B3s2o4TyIV++RK5bqe7dFjMSFshbQ+wIOeakJ+y38BX8SCATF/nKEv/90AHczK2+VKKC3CMYXr5WXR7GbT2tYsr1GVCsf14+uKH+UBuAGl0B9A617RDijAAoCKan8mnmEb/Eiq39JK3wPDGWaw3ruGRfMg8GVm5hHAzarHu9fFunK3AuhKgrKnYOlq9nsJPlwpB49tQ1cdVmXht7M/x8WxTlmZVmaeYthuU6JDjzyti8ARRvZB011TYQcsN/XnsYvMRVLemrPLdeuWJZaDMw+X4eGZfCvwDfBXBoYfS8ZU4Bv6wudSxz2ZKDmvoon7m93i7+PCXeZH1sMNoWlApd2GVVAjPEh9ok9ikfV0bHuzB4rNOW1IviDOEXdMv4GdYCB2w7aEeI/UEXc36Q1WuDYObQ+9KnWYJVe/g6SutfuLnepYvv2RnedWmLNOdpto8NSrk+g9ffoVcjHVc1X5dfwxfP+iKOfEhee8v7Vhz8m8ikf1yLufrxZ1gYYksWBGWtB3DthO7WydTGkCyh/M26UYBydjGIZue/31YYTfiw3ZCZ4sMG/b+LurSvnBZNby/Xz/jCKMdlrRvbOJaXpQmoEPx/mP46/U4iBFvj0h8v0cWFS4ppqAusFgAqGLvLy1uoNhizc+/WTvFTPuYQUxMTBb0raxS3Xbtb792opIpNSR/xxNwSfR1vNVCWzKzm1vHF9j+WaB+jR5Yq7ucTVmZphqzgEpO1uKXeFYSYrLvJI3PqHs3SlGmg/bN0csr8xvxyWiz6F5rs9uTl88iVGitB3f/ZNrLm+K+b2jEkPkUFB1bJMnjhxJek/YwuVCZvwB39/1zwbFrWPDA1toLsYxru74oHlETLJwvFR/mnfaTH7reX4fc1ExvDYeGgr5PjiuaoeCn7X8JmJDmGNjXcUXpKaX4ph3wV60DT9VWZDG7ehzfm6t4I3py1m943WaBgOGKk6ZpaBOZDM+IzIzmjOo9zsYwT9Q48t08Qe/ti01+TioRJ4EFF51FVJaXzVBnmrwhruRS4oAi0be+XNd81XkXiDZ0PjIzj/qh70IP9zCHRK7EmBfui5ONWyHTpxuFsUtyL33KA0Ezb4KHXpC52H++PqlYuul9c04gZs1VeMzy2Hai6k5pycMANkF9swx5N8189IAwgUFjr+n9Gzj2Ahp+40rG4BaJIIaNCU7dVSlJL2VKxuB564xBoT2rqp+HINsIcuBTs19KBNyVO7EoqzE8/AJtKW5dfGNROIrPHvoZPoCJcmAfmZPQXHMEatl6QinHPxIsDC8QD9Iz2XCFiyJ8VR0MfBKRmzsURFSA1xXgZtSglgV5E5a68gEZ2jL8HC8uNthhwCOOUx1bwTRjXP5MJX4+awso0YdwhZc1/4ZcLbqGS3rX33j0r3Boe6F/CFQW3D1A3YWU19+X7B5/afL2YEchYr8nwXVA+hn3QmRN/VA5jub/FArG8LZAlTSbOKVL06orZEtcSAvsMsBEDVv0kUWGpnWMafFP+SKFY3JhfXHSWUjJtHsUV/xSNmvHXJ7jQVivNJqIv6n6SKOgwDL83IMOvZwWIMEFbjgfEjPO52jrc+hRg8rS1QyZRYacBW5Z+nJaUQjdj5ZKXdO/bCnrbEG5qTXiJFT+SqMS01X2h+3K3uACgr8wi5FZ1rPqaKfS3r8ymANtA+QMz3zZn6SfJKJUAdIS833qcI3/qWgjZfatHzmUUn+ukCGU8rURvcwzw3uROIT2KFF88pKtaMrfcOBjePk9lppJ523+U9eXdfUfGn0KXXTJ9Q6ML9Q7hehsQF5zr095tmfjd3fpMsXvGH+EII5wDn1Ze+0+sTPCSIE/IlJfINeB25/XHZ0aDH8lpixWsJ9zkfSQ4uUvYW8IcBxEFNuonyNUsLCq4FSEt+dx0/hRVVSYOvmYqWJYULHLiajQK+LYMo794R5qelSK1FrrRTya0aKDFJwETqzds6ThoClmU3z1qBSe4AZrR0jop6+LvCtUMcNqOZkn3unX0IbSZ8WZaEu3E2CKBxhOZWl3x0RK01WWKMGstxj3GjvRn4yi/solnZ42WHQwSFiD8p44x7vxPuHfSD1SZcJ3YYZ7UO5nsAVIqGnMMdJCwfcJoEz35bxKXnBuv/hJ7fquMJtL4T4i7CoYnrXkfghRbTu7ml8YUowMyMxB0fbXdZeBeA8I5tHeJk9FHYPn1i1UVrYH6rBbH+OmqW9YGEwbn9t0Ojgk6A3q+V8aQi1pEbwDT5DgsEXbVUXO7AC276FQ7eZm+/52AH7RrjtWisE3O3t3TatVDn2uDHkxXwSEqRdgSNKoyuh2mObEHFpRWXc79Uc4SRs74WaSRM1oKugWZlF/3H0WnytEKTGn4Ef541NAweUShXwMnU4NsT9BZ5IBHl775M/oLY9LBqwa5KcUDf4UFdWEVfoFmMsIS3n6DDpZtX5jtnV0cnszcrFzVI/85XvsAyiiln4DvVGPiFjiOwPPXDKzGpUgDocEDJhQUkJ0ds63KbJX4vbJlkcqLLnJKZ+q3JQFIeDbyfrXTtNBreLJhTglWjztmN059uJsLaOAIJfto7Qqg67Y7OnhixtK20+jy8dAf5KuGW4rupykxkbbK3pf2Z1NjC9HX6YFZYyrrI/RDpIgcUENir91/989k1oc9+UWGkBzoCduA0YcSGE75LlA79+e+nKc/PfrrJ/YxkApiT2v1R+NSVvs7xIE+/qqwcFl2rOrSEhQi40w6JRyLZHs8Avuqb20jLWWVfGM5iABhX/i0N+/M9WiSeNLfeVTV5dXNfrppSW538+Aeb3eom/E+gttJ4s3P+BzuKFboWHzKzWchzL3cqF+CA9jUXLvVFIi6JaEG53qfq5LVAxuU8zX7V+7Zx8sRY5KCJI5MNOt+Gs0G3n4Ncm73xQbQZjy5+AXFhfrRDE7xCb7gTK2PoNVGr5MMoqngghaisnExyhf0CQ1W3pR5+CdZDQALmSjDnIks4SM3HCNxufvPjVI8WWMofhlYp+e+Ctx+yVoJTn7qaP2nKZxABcyjPif1awV3eD7qU6ruWltMdCJ8fXbP4uxUqU2In3HpTIWoSoIv8O9eX9H2wdbpnOzy5+l78kJFD9oh7RITeI5FRD1SKWQWialJnjf+BR94bd7O+dJIq7htf67klaWPJK5EiWBpWpI3iXldzELLRRKgQ+g4ih34E/F08IoIypRxkLblgSNohDtCoTc8pH6zODybRZkCrBNCqKsLK4aaFBIlWYx0tT+l2dt4xSOhbFFsYHwlj6MrP4bUVIWA6RzwesYBQm5JvRyxgoHOlmCKTRe/TiL5zmu++OoRvzoRviFbX/pnbPBvKKE+ggVSivKaWgWmeEm22Vu+fbJQwit0DVTMf09fp/xyF2GKnn6sBAY80U+kSq8Wnm4TrCe4SsXf4CzI+aMh1Nzb0cY34vqLCHk44RZk47xSMVsuySaUuVNKvN/TIg3EZ8HwcdOZxSa6PCkkjAnaVLfQ4QklmIqzm1EmaPQpUuAWAKeu/aGYIC6HcMk8SIludfvACINfviJNnJIP3X8xc66cAeP4ohOoZ3s2EZUbd6n0j4y+cGRKOwPVya/oZWCU6Y9WhhivapMgYZbesTliRNLswbNKbu4pOf2VRVc+PbkMYKwx+vAlL720g0kINi/1j2gXcE1JX6vnG5BYHO959YzRaQfEACSHktf75RHo36dY6IsF36uWev+dUVIzvf93egBeBlpHCEw7/r73baYQpeX/L+vdoQ9BLYWN8MhDXDxdQkYSUhqgxhan7I85Y7dr6EEXCPRSMXf4N27f54nb0Jex11hmot6khLuUM+0++62Xacq1YHbQllhnmcmQwkChYaa+DlGBgxWkSrX9mttbzzTlWFObFimaT5b6fXoiR/p4B7tXkG1VD+afP8SBTVLuPONF0R/Y8+M7kxqQtmtog3UhHSkEeV/UBQ2hTo4bYDi/mq0IoD/ep5e9PgiH1H/6+8hSmGWdMHlKo4BQgrlhKVdzrh/hSCeH3sjLlNCX3Ud1k89F5OG6GYH19hZsjeei53IBNr326qLt/1sDetazW8L3gPcTUeUSJ2epwUpoZcJsZE/ggT9D5HaxAHniuu/BGK8Hldd47+PIxDW8kaOAnu9xKLkPbaIUyfoHCM0UM04rxBTBD3QnkBB80ji33rqXFLAMIqB+Yl2cIZ2eQNxDta547jUma+kbt9u+nX2JKlhpehaNkRt7WRxQRLd4dIvK0aX6hx/iC6iqtSxHlpGRAgsnq0Y/6Nb9Amq2VlMQl0kfgIyGQeXI/C4N1B1v2txeoyQYPvCi8+iCCkEPL48Jl21DThdKAWFYpm1g0Td+YUWkXq5mWC7Q+aBhAPU9q17jT3CrEs93AFu1qGgPyk5hJ2u+gaiDIrfzd0jWlEIBQee7/1Bz4aoao++p0bv8xA1TR81KgYDsg+CqltlET9t3yT67zU1QSxjd/Q1R/tWN1kvtT5b73NSB6XNwJju742DA2NlCfYoiKwiLyuFL5Uit6nGNNFhQ3/hC9TIz4hCs56SrC8+BfgXa6hQXw84ZY6/O+3qCipFsQAwIjTnLe9SpZ0kXnKftEHBq2ARZ/KDVHbCfZG/ou/HwPDDwo/egkH01O2NCvwmhYHC4FPo6tfNJOYPVNDIzGjrwn7XzpBSPaSI12LXVMnCUjju5QRx2lUmPtXcJchZ3sc++zGWLmeIKdsy3bs6R9d/10/5a++gpy5yiqZx/nCQeBsvysf+LAXdI3SQ0l1Zl/Zfg7etxj6oUmX5m1GXF8IPd2dkj+6zawDsqssCDR392Fghh8n8RQWpUOfzYRyAAZB2v/8VApwcutWf6kMY7FjWqVVK9YYKn309ZbpIg1TpRim1F72/W+i5cZk3ww9cqGKAEEN4Q+qEvhE2AQOPRRKq2GD+qNcuikH9OfOOQd2Rs35GbHONl4Dnzt1BnCNyzMvpkfTdXjBMvb97h9NqVrSSNMmRtjJW5P1urR1SLJVwuKXFCCMHnXaw/YXmRxPnOKkQ8zNHqkrudoU6K/q75yt446392qG9st0BPk3UxquxZnqPi4ZIPSmrO3NdK2xCyuyXu4RpUFUco87hx080yk7+D85pjqeqT/FTmnlSaMDSF4fYfh644BbqC+2nzxU7rOSXxeR9maOPbBO3325UhfjLQ1ldIiTFjO8Xq8T8jStdWgfLOnnEMmb+IWCzi+8G70bfMPDIKLbUkejQ9dQhw84x6KgrlOgRaUHz9vQtl/khOFkHCr8ntAmbY1zbccnSM/vRh/QMDLbCv7ivaVpYJ77bUfPin3PR0L3qONkbGgMr/Suyl4+lbH3gnrJcKy51FxjRUGBsEUoDzTRxWz/2k7e66gzG0dzX3DdTtMCOcAQxKpm/q1wWLwI6FXF9MX9eZtkmMwSvPfWO/spZenb3Qpib/QvcSc3uUbufUmtE5AAhl20UvgQ4gkp+tfGjHSeK4RYS2TVL013SS0wNu++n/qTx6vf+ElvGttV1dwpQGlUeUrLI/ckCFTQZjnv/FYRq6NtMPRHY6o2S9LHVW9oDwowr2f0bil6TVoABYnm8j4J2sptOqQ+aEKk2daNAfSLmx5HDEg4weH5D4vWJ3zM/98BUgIxdIzUe5iYTP2wWu6TMzNzO5yKlc45dGyIt4eTDLqJdktuKBs9rz6IUBq4I8cEsAFTR3bU26TdoT/5rtat+oya2qZIGq0E2pnsZzv1JxQ7M7gMxu/ZVdYDpLXOv5SmhYO6S6o0T0k00t8k8Z8LHNfeST33w1CQ+a6rGTNTMmPtzGMzi66eWqGq7iKfFlVwHphvcDipva4mdN41O6ozUcbFTcnZJ1EBWHfaV5fwg6qS77/xHCyAReUJ/M0FWGcwYZJH8dudTK/FbdAZJvXRjMMOytQK4zQLfWEjyFOjr0j8jktEB7Awo/D5y9qzyepPGEkoc+iVoSIWewPiSg3PU1rf/qR3ljNOWUV9cWJ0uMpfkDrGxVou0f84AwuYrQaXBvtM8g/vxUjrauzgxZ3zsVCigyJOdXUkC/ugAHrjntZOewUXZX2VDR7uB1YfxTVK7quC9oM7eAtKH9AOOSrwFDE/M8Kxfv1jQexJEQC9Q0/rboDocKZqOE5oJWAGPBoFxpPD9Eynn2sJ5GR258H9h0+Kawo5ebR8WR8JPcZKbKiofjXMJQVRXMdw/6l6D9hmt5cHWItsbSAuEJg9oYcbzy3g3D6u45zonR8EDiHiKKZsObbsvk+ogJk6zBV1KCVwIxBIClVPOz4U2wYrK7vhETvP+6xrSuMTK2DsW2820nM2+uh5rQ0Y+xx2E+3ac42i2yoCIWagZGSpWDK3evn6rlypIkzyW49E573l+Hgi6WVApl0YviSqBUgWfNcSnFe/5YibHnLV04xSTOB24EHT5kvbt7yYMIB1AIJLl19ILNzprkql/A/uVGxWlxj19fgIpuoUZwNgcqonOULeP8Pmt7WcpGhPDdS/G8nPfasJo3zVqBQHBoGJy5jxEnqEzVucXvr4OMuttSctIg9aqewhgX3sAw93YD06SRs8HXc28/R+w9JfawPvQGc6dQ9oiHU6eVVhv2meBc4OvIClMMBH944XVUrMFj9qDm/tK58fyspC00gQABeVtW+0/S4rxYmvAJTclgIoqOIEtISI2lsMjXxKCr5HJLSfRxwjMAkXq6Jf/pwVwfPnohAetD55itTtvUKXNHVJ+9G1nnTRyIStbVogLCodgJJA5dEPFYV6ucjGM9VCCP84LDXhkOXKSoM3prSPz84Ob5fzQ1vvRbXgAd+VIw68LR738wqj6ecPbhxy4rf3FWg/GxbO7fl+6nnIrTeQGQWqUhv+hq575VA1BndtnvgXf9SnHEOE5l6z0/i2NX/LEtH/vl28I5I/kGRiqMYY/+fOyFBg/tkbG6610rt8MRXypJNWkX3dfwi2zv6tY6oOZpMa2y6BYjg22b4PYnMlMhE2QWD9mFjfBjnP+9TBix8rJGUx9CrIbsg+WZet+X4Thhv+asNdcyEAROtb3KobP/desmovaYd97f/iYl6P+IaI8MS3YvhmnQdrBIZNJi362KtvRFliQPBJI8jcXuHOZpIW8glfYyPFly4ggwNqMBoSC2+ZhAAWEkSgMvMUWN1ugYLfQuIFMqwGEbmFlTc6Oevi77R5/aLfG1M9abGnFTf22raM6G0lodg1fFC5Z8S+q+miEt3S2TJiVd2raxtLqv9blNpk/382B40qbVBxIAVYmMMAGVThZIz1aYoFeDnw+TlHvQXsT8NSsITI386dYCxtjJTRT8UoXH0CHYviudHG9+owmpljU9Uallwzho79yFIjOrD9/k8MPy5sqa/L3AHmsYOrNMB+Ub+eKp7AsFjKDdFiyAmlmh+u7rg0CBQVvD5tk2ngtQ7WOaK2fr94HSxLwdzHAThKdUN1nEAMyAYVKzexrwTJxnfsijUPTsTIonOZ0j+TzeORRklBagrYCMw19sroD5x5ErMuNXCo8zERCW/yoxBiwq099KjbLDwk5vwBxXpQMD8UWuPndd9WX3j5twoLE3PoMf53klR2TXAv1oiACbWKXEdU/CJ6wsxsvsCHMXeslFV102n831oEjqrcws24htnWVgLJy1dbEcYjeghady1aB+4FfCu5vlRvMKWW+JqEjQ2KO+bjOBpNbtqzSwshFjikzmG2RrurkSjQYxZW1s4gF1p9SwObYIRfe5oZDcxRoiJh/Y3VkIz52jp9L1nuDHmYQrq4+Mf8uddj8ek6ukmIa4z9ldZX1ElWcJ+uKkXf7BeEy3Lf2xeXKlIzxa6QSI3BEIzt3wvu7HzESHdig7dbuEEGYn0FdWKFzvb85JzqJhrjzSy/ZMWjslDQ+EJi/KfkgkS+eLdNahUp0X/BGIP8tLw7MPDCY98GC677ZtG7ctI4mbQR0P+huBr85No5IvMEyNtqwpdhPjlX72nU1RxZ7lyyvCUyhEvqluuGQMksqujt2W40zVTz8b3pMi6MNsA81O5ndkHsbjNxhlVk3F2OGnw8wkIkfubyYtpVUBe+lluiIgYI37yvMPiOjyDrNTN70cB/IxO8Alv+ZE6yUmfIxXpOKkAv0MIL3lfNV8tRTxjd5BdGlYpN2SYXwgqZjiAoJwLgPQ0uNTNT+uVXbWvVd9Duu4HeYg9ldeAS9Iflm34XNgS95KFzGZxvSyArsL9IzvJjnjxt6L938HGXlVLuEHov3/F2VVNdwvdkYCRo4qxSkNlhDVk73ayctdYhMPZt/bdD8gH4xBggBfOtwshiEy/j9mTlqKAzIH06hIRem/C5AgWTszCf6pbsaPlaVRRsjrnXExKlfLqKKuwH+dFa8fKNcEqeyOIBIa9ULZfmKUY3/zQlJFtBvBaki7Hr5YO90fmfu6RLagBj8qZip0VFFiG+FTWxmu6h3jNeiuHOxTJ6dl7TYuJxIIygB+/UlsbIuDsBMH0+Qb2h3yuxqeYxxpUdo7HCvjHeydK6ya8MMJ8paVVwszxsDWP4DkP3weY9Nr+osHAqDAEqaHHCByUhxp28jpyOGdz7pzpIxHithbiT/dPyCsorgS46M+Je0oknvfsDQFRbfKfPA0uvv9idjt33LSHKGf30ycAQM6Nh/ufuMIVt8JVrDaj/qIDnHx1cVPwS0leaPRK8+Nkkz5phuxXMKKCnLFQ+7lG/fktpTDCnD9Vxr/xMIYUBf5G4O+9lx3eug9DdOfigSH8sA25JpX066qsGbyEQ658GDeyqVXjaHta4sI/QpP4AplKziVx/y9WHZgaGIYA7T5quy/BHuCMiquZosE/Quq5NTQqsW4z1vA/iHdKG0s7eiky1i2adBUKu7ktT6xocXHgoOOT3sKvy3txr5fnlNS0MhK4pWPJl1eSZ3DRKQKA410cDsIJPKVCGQ69KDMprFqAmF+0vOW8Eopv48SQ6DWe4I69JTY6+rZNBxDl68oj6wtUHnOIFs1o7OZ/41jeTH8u8C+GHRjeSROg7LSu/areuaLsA7p9pWTZWwc0KOcTtj+Sb0VS9r7MHuNZxcVEPRb5H88HqAtjkLuVAQINblAXPaaF34tCGieQeTyt7jj/VcnF40lXZHDFyV/rXlWuQOMVqLb8AjfGA+mLR4PtitVaLA97JZib6fr4pWBChqvO08V3iK/ECH7/New9Uz8yV/PNCs/DzH8CjwtmXafuf/sfdeza8iWb7op5nHU4E3jxiBACG8fbmB9054Pv0ltWv3reqqNnOm+5y5EaOK2n+BIDPJXPlbhmWKCtdfmKMd0oeXgK9x8tk/zcDq6gfYk/CQwevcxWvnsRuSjDu5mlHUWn9w+dbQbslXtuD0rQ9gBQVhGqwXFJ4beZGUHdvYVls8mz5xm5x6/GHk6vryCPpWHPjaMYjGpNRnj5OfVvLea4eWwgG7zSHuyK6AlXv1EtZspmGq3RIpiQaKmwqXBNDozDKdtymjeRSaLKf3fk6p0TGn+YDZ6EmT3SfLZeGkikZyqaCTqY1hU0MzrIqzX1wauUlWp32zK/ioPWBeeLF3a8tLh98f+gFiG7/JK9LCRU5Z1pBEYnmpeZNWHwGO1LgGH+/PbFMyOJtE9fyIYT8LjSxH1jT2ZsxllIdyNfvhP/yev1XsJJ/1bM9LbyoO9Ljxr0RxPdBIYh5JykdZOidocmJigjuJykZFzm6GoplJwMLeExOdrc+e8WIroSsiSTEtKhfDGG9VKUnWh+w9e83SPnSWi2R0eVHYQWT+bNK3MzU8TVM2W+6teGupi13MslAEn8OIsXmP83sTyBDA5W5LVrr8xqui0KQDujSvK2g7EMl2imuDZsowwwLluyv14mDFeJKJaS/+4OhGG6KvLsY2s1xC+xyRjqJubucmknyPg3a2jREx5RZXWGLmDMCRKUVlXXi7BQdSdlT4xU82+s0d/8iCtfJKRq9QYA1qUwQoAt98nlFsLpPBbx+d4/VjBcq1UAP/c6HLt1cyWqFgWwIW9fwt2gOYkrdzKu2YleR3Otn4OjNv3JGpPF9cO8gl+/TDp5mq5bcIUBI6vCMQ0STuqnaDmFvwEKU0HT9EpTo4mSW+nom0DzCVlPpQ2S8G0LF9yCYhFk472yfama/qxLnk5miIDeI8ldduWOkxKw/pgeoiI25LO3aBpT3VW27sWb8tsksq7kG8+/B0Vaeb0evzfdWo+yq0HHBZMAOmvN6A17HZiAqMJ/ZF6Ga9UJGLCvQ9CVocTPeO16wxb/YwUtEZmjXwCx2XJ4zRARP9Zuric8eLHd99PfDFJl6C8AQeEr12czS79k+nLGTgpcMKGnIOYto+26S7ZWPT8VMXLpwn0vkW8SpsU9HdZV7a58fabDJPLOQ9HO3TbSndR6Fxx090G7/ZWjqnYHd/77je8qgFMhUbttXm+Aj4UTSphSzwC0a0lzTBKnnjTTWM47qdHXK8TJXzMJvzKqcqjIheAIWah/vNn9uj4av/cMG0BXr5fMzhw3CoEPsmNsfRSM6+r5Rprw4Gyb0lH/fdmJujP3hEMvQpz5ADmls8sJgTgV9ROrx9vFwMBp3Hs9w4cwNqmkfqDYOpQ/WxP1Feomv2MOQAMMrkaducC6Ixe4O7uOprTIxxLVwYl4XdgSEn5aB8fOzwUZfMkyVc/DNs3rAo3SWm7sqVy1LquapVfo2/wznNEujj3Jr63dDxZHouIiuNo4kotuuUJrEWJyZ834rmM5aSuzLjVhRvBXiOnucnI06FmiZDBdo8LCauVaecm2xId+uwsmC+zDx8vL/yHeAhD7a4hVfNnY5p7MC7va+PL+uwURBqHu0AweMcvc3ib1hD6/XFUhTyTIoeTthPVsVBEBgMZULyBHjD5DJIaJ4iK9fpBl4yvQnkZPZgoc4OfrChxaxIla1Mye9I5bfsMFmDjZukMpAKgHKKB2CKbCIqO9/g5auGvPPeJo/UZAy3cllrT0wHIHHBzb6/a+nrUvQk84u0pQvwACwsJyloyzt8z6SLRqbq5AZUyuZuyYfsaZeRnZ7rxl4c3+yUJ1KHo0tkwBzTM2jZPh7p0+DR42Ej4koeJ6J+Y+uxQfCbVeK1FEGUfmSjEcW9t4yW+TW2h3DrAfEinNt6ucMtJy9+L8LFST1t2YkMhqYKL3k6mb1Cad5OU7S+FT3XXUEA7+R0b3o2hScRuWa6+fNiMvJV1+c03NOw8eEp2g+eYWEn52aqPse9+75iNOiLKByG/9BGBfa3mfonOhhkuG1ZnmtZXrXphx3U5Glq+A5YomCaQTnTn/jpxl3VFIQFxLuKDR7SYvCGRr49eBK9VcQFSjqXb6Kkx4WghFVY3qHgUGjnNXjlb+4uMn+04LWKwOhQkYplWWnAKyuETO+Hvs22+ko0GdfGONuBS539riA/tr2UoOhDKV4vQUplFyxjA/MqwDe7DrGX0rgR1h52HFuPnE+gxDmOzUwOazsgkYlvLJCT5yHi3o0q2vZDRn3su7qQuyoDIXhExf6kNIm56Eb023t7AAsqm7TwzPkCmb6wNbRxPVNZ2xHS8hbpjI7qlM8hr9/KrjqWmbRXGPutVVaa/xo12X7PnQ1Nw4jbvbUzD02yzp0DZs1ClN4s0frS8FK8N6dq33RE1rvUzL1ckFrqSybeU8SkFmwiiNqZncuFYIaLyifetMNyGm5DHJNVfhZbf/VLs05uub7T5oVcLa3vXPskjWCxNLMEULZujA45/mpPsooRcpw/Bh7b0ieVlhz+Cu0uxwOEblhFHZWYNCItNxh1uya3jwAcyzXyIC0JVhTRx9p3IgeW5UbAyhmcNfWuXDR67UzqxSU7mhzyyOHHPMI3Scw5MM3KW24+Ky89mKyMV1ZVmE+ITgInPBgngNJDVaQnnirBuLWIoxeFNYT7MCyp0N0KihPNZLvvuO8IRep9IHvYiFW0WIcapm7QS/DaOsj3B0EmXKNAFYNQzkh4qe9yk0fh2uAJdnlVswWEkGx6yTwArxy8LCkVqa+myWTdTfdNe++OXViDmHMa+bQ1GROfe+uB+B+3nCDbjs2PfnQykpyTwqPidksGqYWT+9o68P7q6VHgq37I0R3OQ5FSaB/y37MZWjTwbnnzXETrbwxwyUbbG2flmMCMsW4hYjw8YdrlF8olrL321nCKcpfCDmaFQIkedmuGiBJelm3eEoyCh3RbS7DkuJyJPhC0Owt0p0Pi1pH5AFI/OKuHrJpFHLq/pXbmstoaj8/5WpwHTTH2s66k60E+7JlZ02NJE5tZmycrZH1rd8r2HrnOGvxKbuePH+U3HVDFYdQDuwLLmBHFuQSlF0cvsL+X0lPz1iJIUMjzRK9P4xxbm6phT21DJFcSzA+GKrLBKrNCYzKIB+vpc1UqwcrkxyqPvKgrcL4K5c20m9E5nq6aPcVXdsKv9sRpaRiGNaBZsd5vVcDFX2fWpeg6mVVKJ/oboIzAT+eLHXavZrLhg2b91w59WQjVlAyUAACpE2uNFfVkKjTtb+g0SS7uszgdc45951zbsh/MBGZl1TIeH79lEGiGSTZB+dLnXYNIRoSNYryUX9REs9olNR3NDQVKbmFxQ6AxjkRsWRerYjl3QpGHU0Li5APxwmz9M56FrnxSwjhflLua+i1f8GXwIlXiUOByd5hX/gLGFv76kYb7cWHf7HI+iZVBnY2ftDDPDFfcyOa5UHo3g0s9wkBxM9OSXl+Rq4g6hCV6Iu0m4zDfVvY0QA7t3KlkPCfBgxnvgyren/GptIiXeFBhHKdSKSn6huJ03ZAgSZ5ilkLexkoP9YFLGcqxjyS1ttY8JaZrbsmlVEo/ThJCIpThgW/Q1s1LNqpWmfSke60SCyHNWafQ8S1t++Ptsp2HkNiyCaxID9hmZGMQp2MONCCEuwnz2dFmIaQyE+p5rfruW1AYKpmwk1C8U9cPdUueR8Jz8o/mMmCnYGvF40GGb+EyUEWY1GoPbolmVlLp1WzqEnD3xM8S/Sz0b0Y6yW+2UOgTs3GENrQ3eEvsmglrAbNdgmlZJnn1TcuZCSwIEox94yQ5gCmIemtNkKuchb+bptPypPyeuhDiVNdPs08uVe5lxhSNWEMR0OQMIRGQ6DqPA1bPbWgCOoZby2JeK39I78Q7AmqskbxShPP5dvBcaJojlosVWHGlNfwKT3jz9Dnv1PBYmV63JutMjJEmjRCegU05GhhXuTpNGRYfkG1dtMPApslYk3r1rb+FkLP93QLvqICbFo4Eg+Kwjt7GBYCRrxc8MPeEEdufwchE3IP85pMx7GkaFJQmOEpcvSzxqR8M8jNoiF71zLRQPQ7mXEE2urSKb9IcmtvumxhvdyUbb2EG68Sou5LWsQdo/ngi3Ra4Z0YRcNES3v0rzXSSaJrsOPbj0dC6eMjha6ds0cxy7APfcsCo4sATGTlWiuWFyVZib841Fg7qd3nrt53hE8CoGz2etlGvfssFaDjVU1AVXLPb/fx5Pp3z8tV+EDOiIi5D2N0HHn3T8k9sbzA8DIWp6d7Sc83H+OtC8xTrBCQwMWFFG51/X/5LWi6z7uBPiKgiqT7EMCe2flRfuMoyucNY0aC1EJc3HkYvQPkM9W/x89GP2wzVjMf7URvfjA45fJrAsJl2fqSuaS5+ZjzDfC5Euk54lFK5bTL77F7erHDnaedQwbtysL49pVY5GFhlJUDvmXC6IFwQbHX9NV1O1757rCDIbwWQo1J6YXIKet/LAxbFfEpSCLKbeVtS2JrMjLymhOs782y4csgY1NUogUHUUEzn5RvdGg7252Geg1M0AiEzr4pkKmg3zpSexL7UnnJkWadN93vwiopQq+LJlW7Vh/Pm0GrFLm/7mRccbmUSwS9b40x8kKKXteiZIuB0ihlYxaOP2s6JLurj4qPh0frpJ1DQxzZ83M92VONLVjpKR4K6vFpCYHQ5Usm4UOBVOWQ1tMcq4OlHz33OaMrwupbwbL9Vdw5/KLVFNNz0QeVMeGw70TynFzeZymwIzhLFrxsDx9djlgxBw5ZmqClWOzceJ/muaSNpKKqjNXJMioGh/5qkQnM/2Js7r5vsLmjopBPmjp40YlRINqAUCc+Ua6ctf58W6jwjlmny0pdjZSRME75Vd82u401ZaMVsPrqxM2sN7IhWpTOCzz/g8gXrq0c2mE2+Yh/AIGNNDQ6yb6OJtbmM8cyMiWDGfm1i6Kn7wsfKXlkt1/ucK1hrrtLeWXzgSdcEjOLJorYu0DA3oz99xxuKuCxwep/XFAaejZhZwLisMsBbb3vr5XieJ3sr44Vcwq5KwM3+oQWpTlU4sM80o5B5xYHvbP8BwefC4+mybx7rdv/r6b9AFsAFds2JysaehPZi2CFcLCSqXcgWos6nosi4ldyvCch5hTNlfak0LWOVSSp4Zr4p5D2HeedTV/EwSkDFrSaP8+QOerdVeujLkBwbOSUQxQFGvWaLEO3clUqzA2dzEaWLTqcvS37Yy4rLb51R640psiZ0eX8HqinmPNg+Y192xl65NAtIK6uFEtxiIDALv68X9gkEOI3fHKc4TrrZRRNvRM4CJrOH9FpGYsXP64e/ILuujJZ4RV0hi6kg2PpWGkSPPmCGD1GNNee937HAFlmCTV3C3+NEbVnae2cyL63XKLrKYBU6oQjsiD0gZGRFhZTF5pYjeGAbfRzDq6+8Rw2xp6qTlcRfHibQxhUKcdYlOtkpAIQ7rhGufS+MKBOEw7fU5fX2NU2vFHO7aU+SFkqKWTne0hWlKHLVmnvfjlBDuECVneEoTAKWA8H1k4/pykvg2JvXEi4wEXzqN/FpAD6GjOH0GyBJrgX7l8s9g/BPnz4xqSgnQoCOjc9mYN3ChlknU+d5hVtpE+tzjmWhdHjX9ZhKe7H+8IN5GLBE3JCqqVXWUHype8sA+9y4cp9Y9fWdj7PxQUxBVqnybGbHOeyhjJEXaufPonPce26fX9Y4NE8B/Vwj59v9w4uUI7UfQKvIsvAy3n6ar1clLur8LQ0bP7xp/8SAdLSklA/BNizxEUHBvF1uUWkBPVbP/EG2GDwfT0yI5eA9q9rweh0rmh5ua73PByU41Mt/tQ90miFTezSQv+HJFm3D5lPvWHrJAVzUO66H3WoEZPk8qrZ7Y8qbeXYIRA0C7wtc4SBjefDYeFpgtKKaG5mTrXMwQJNN6MC5/1urgWV1BoSSIwnOGYU/27r9VvtdWl/0qqBqoOSt3crf4kxtNGKTo8trF9LuG/dzu4EOnns1LcXSKpKth2znHPAyXp5m4WX8ZxjXfpeBHFQ0wejaPReb8zZxkVlKtGZMlM/kZQy1key2mrxLxVI6yO6lF3jSmtjeK9t6twDWIrfk3uPUkkYZJY8rIAMBJhwFN6iZlQ7NBCq19UT14kkA6S/fVmz2X30ntx+dCD2tMLn3K9+G+tTdTeiR3GN8srtR33vFQChxhThvb1mwGVQi6NKUrNHiXoFWjD/i9aluzUseb0lss1zVnnpuRmWsnBGasqFxqpshZOBQvpzF9I4+6EAtPoFf76ac2mFbgUQdx5sqCOJmEfHLYMx9un0/k9a/OeVKsgVZlUXzJi5q5eFvmjvUwqgHO+AVA6VMjXWdP8+BQl10spAIpH5jM5ZHDD/bRLpi1jO93vW+qVqAbMt52HWgVc7LXfm2NeOpQbIzKiOFOaoJQpVZQbw1L4cNvlGpFv74CgyzZEG8zmcEc35La7yfnBtnXAh/kuHdHAGZ1B+xlwfGjyZoYXiSYtL+W2KQ+lD6YYxtbht2i91MQVYMMwE6rlxOQUozZFg5n91RqvEWjcmgW0XR42N56lQyD/m+NFNWf5oev/r0Y2gmxmQEupds25ksTwT664llUzShWJ6F935CnHS/t4R9Op7IVMULSUmXIQ36aTKVAQeG0To+i5Ftbb6je4YeK4w3JJ23z9yBhBteQxTnnZpzHqmYh1FJO0+IS4GhYn+62sGz/GbkWoRO/NOCb32TyJM8QuEtop59e0N6vwl0N0WdF1IK8tCtdUe2RwYmgtDer7Wc8aHMAnJUPjjUKhF92v4HGKMpknB4Exr8SUhFNKKHWdKayS1f7f4EAHmqnk9qkkMky5ndYqEnytYN7Lh8yNoCwXHGvN1v5EonRYAVfjLzFSaxkRqRNU5odWr5aTaAMy9fDGQ/An9QegOgtt1JyFHdpJbn+JQRYWrUOSij3Q7xADpj7MF15SDtW8oOs/paGX1Psr6puMZ0BpMKTusxXLi9u6D7fcz6S0HAvulnPSbt2NJJT19pe+hQbO2sTMgzWga6H+R0nxntxCARdDjwOqm41feR/bJ5gPYbU6cSnZoifXSrM3Wl6L6Kxx4cy7hJqSIzFERngK4HMe+QRUTnN8X7Uk4hsSVJEVBaS1KLTdilQKrdNcWBZRDoCnX1omgrOMkrJk4SApZH/MuRzEgOxc+0AavbJnefD/4MniHWli/NbAtReHDl8xKammHddSC+aacet5rsnSYAzCcWAEmMbgE735KJFwfd0L/xaHLzmCJ56sWPV3jJ0QvWrWXPX5kFlpWvAeP9OK+LVsKhLoNynYGHeHxvsNZs2+LKhEr+ZAc72abE+e9+zUo4f1DsE9bIgHvHVNZgywU10DmLtTlU6YMS8VEqJNfMPJ3RebF9FF6BQLAMrZDjPgJCXU3FbRiMUrsOMY2HU1mntz5xSGUR2Q6x5bkejiGOr6fwmZ8Rp2W3+h7e0mCKvAZiwJfPLtv3/q2Ya8By02M/8eqvGLxcR/FBeYejm6xzLMD67CJw5oXPHrX0eXtQW19a9yG7xSC7T/Q5Vz7ymltq22axkM6M/dydbUlAKsPyDBfS/OHqhdIqLH09QmBf2NfJTLvM/UHLy1wy4wc5nlNIf1gu6p94/6JZfNFI0XRY/lYbXpd3Soo0ES+X4kiCPoOZmRhnYDUYOZpvlCPaO6Yofox5kJjXYWJOLxvvAhgyHiykgjVFGv51a+/FzamQROxpFs5rQ9GJsZYLPbgRTIfGot6cRI+a0P4atJWY4INvCP5xdooblhUwojSe3lMcfG/1J1BysxCarcWPuayqh3Km1m+91wdK06IWaKk5JQf/iS8ZDYnkwYNqaaxI297ElQ9YygQXR32kZ2IV8BZhmIeqSHCgkOd9hMCL03hVIe3a+IRS2U7IjsVPcWQFll+OnRHqV46pTOu4xmLRxIOF2Q8o/iNAm0+WyPoicomWZKHuj3HwYWeGX8bde8yn30I0Qa1lq4MO3Pso5pQZYDFfv0URmE7vswsw3AhLp+e7KYmLE+srBb5M/UvCp8afXPpqtlSkXVrhgomMH73ZMbcooIW31Nza5I5WaUV0aSh1rSPElN8DDqWenDD0FXyN9xXXxiCenTT3PLO0BqSrS4dY3BEGcpZ0W0kYV69RP7LXspzDTyRxtq4wpx8GDB5T+bn47xTyH1hXrgyzfYsJDBw8Zpcgb3y6cgd+fnaghHUXqFOtFEwmrw8qIhSIKllcOsoLmzQ0hq71WNZVe4o+I2O02j4sX5o7hGsLJ7W8tzrGXZwmqvp+08MDE57B8PmI7/3ahw+iKaJ87Duog83WwpCvpF81kdLEbVOc+/tmoO9b8enubpb4Ayoqr/zdjYBBlEjBgQy/BknsiRZyxLVJC3P51qEUSZVsH7aln1kX3MpWlLFbk2Abv/I/x/HG3sK7JDcR4FYlTra/u4g949q3njyGOVEkpZ4AAQsQn0vF+/IkV/Of2zFRTxw1GBIL4vhTm2zBbc9cayJ0V3l714gDXXO6YDcWjEUQxV2i3l8QBvki2aisLMuffryPZhs4gBht3EX0Wwmcq3ODz1ZWsN448hC3wmLyE9gbQlP7tbDzd3SQEdUzvaOPm//aD81CmBvn7AO4qdStamaUhr3hHcwqUvgFZqar2H0arBCpOq/pFWHhWtdnso9oUMn3ntWLXflWvKRQ+8bIke9b/0yGxkPmlH6uSBO/UBlpCuweHf+tHq3S5qfLX2qTjJ4AD3tWZbwpjbNful9zIM78vFJ/MywwfVJINQGwt7Q6bWxWylzrOfgn06T7awNjWBc1kZAuKqXrox5TJXzzaxLpEpDZ1nx+CICF5QDAgrwE+tIMz9cpPM8+z//szZyUbm8ueVWl7k0f+/7rdbyddgAZjq/biS43ZUKUNMaAj+W4mqngXCBJ/4Hy/4Gy+dAvVnVl9xGM/Hp869JVe95nuKGrbk4NWVF/oxukWj9vWM72e8d/IDfSQWP0yfoFtIcgainyHWStnh/sEG1d/Ue3/heK/7hwyz5Ldvy48Hsv+vgPlOsOMRu6bAFZ7aFff8UR5BdQQxPcdf44BQoDgcO9Spfyxyma+nGqzKqiXH7e+QuM/jgdzT9OFX9pHuDyj07BZj+4rG1/juH7HYGq9O88B07/+hxRu2Y/rvsPhGjvntm0ugERmn+dmPvstA7Lr9P1v+bvBN90DcEIqPLz88f7W/Hr328j8xj1/+5W9l/nCrTTD58uAk/9vRv6p3qxss9WgTp/EF/NyXAv6T2vRNSN9299PI+/GQiQtr5j+Vc+5f8Cw/2vdPBfnYDnffv9I7NFVRvFVVst/8cn4P/i0//1o76GKL0vZqM26pOqv4U4CKzQr5PEDX2yfm50SM5/MOq/ffq7s36eRX73ZMiNJuD8XlZLZo1RAk7vnwgMrFy69gtR/0WMQ+/jqK1uGRHlb5VoyT7ggqptuaEdPt9hoPn3c58HAFclUcv8ekNXpSlohf0jUML/WUhEKPgXivgdJKJ/gokYRv6CkH/ERQT5N4EiAf3fBMXf0jMGQX9sxi4/Vb78SpbmA1R9+H6VeZb79av2/fo/dPifoEPyd3SIkP8d6BD9Ax3y0VzGQ/RJf11o9dYolnuyfmLUfB+AaQI/3WOoQB1o6KtuQtYSLffv31N/tdifYe3TDAwF+sdrnt5D+F4LDm5+kbVslDTFt42fK9cPPVibefkMTfab5aSQGCWIv/zi/Tq5yB8XPsUzKsXu88UnSqt7bX/zG02mEEn+0+T2K31if0Zu5T1d131R1P768P8CcoJ/L+X9mZiH0b/8vOq3lET+RJ5/PSlhfwJpf0UFv1njuYzSYf91Sv6y4NCfzeDf3Jh3GyNouTvuJRzLX6LPZ9hn5Jd76asxSxlwCFoH0wT9AogiBROIgV76YUm+hIH/Oa38Z9f9D6iSpAj158T1F8T5PfH+StH/AvKgCPgXmiYIGidwBCZR+nfUQlJ/pBYS+oWifl4NoxD2JzrCv41w8P8hnP9fEA6OEb/g/71Ih/pvQzr12o1S/zvKgfGfhIOCBf1e+cwi0CeO/23O9seF/q/RV1uNz18f80/p5k8ITbg/KPpH4qS/n38NraG/52Ew/kdU+glUv6Ul+t9FS+T/wNC/BIZ+0hv8ryGTG5Fg+DeIBP8eklD4vxkkkcT/kNF/QzIi/4qx/Z6MSPq/l0iE/hNEBJjG+CczjoP/fl2Z35z/8fkzjYn4fv5T04xAydD3WbJE8c/hQH9fYfkruzTxJxoLCf0J3GPYv22O/8wE81/RWv+TiipKoDSa/jOKapJk+Hej/Kd245/aPn6nWf9xvf8+Mf7jzfZ/ZDVHL+yJLW/+n0+IVt1KS35E/NlqPo4l69MvhSLQoy+qPvt7dgn4H6/w71fqb5gO8jylv9vpj3YmJEn+Jtz91cr9Ndgvw/iTxPRhrpZq+FPe8PqrC/7CI/5oBfvdK6l/grb+9dTy81fod9DwJ9op8WeCIPxvoyXkzyTBHybN+D/+GdMs8fdMs+Di+6L8B5H9f9f9cc5/08T/bqdOX+VVBkx5zLp8LVHRlzJ+Ps7nT2y2Pwb4h9PxX5/7w4l/15Nhf286/zcf4V8LA39L6vntrqb+5q7+UxCJcOhvamz/vq34C0LQv/n81ZsT8o+iNkHRv5DoH3fnv8LM+Oebk/zHbPunQFx1UQHm6/uXmcdbYPkpXP88yKsDLPvfBNB4WJah+5tL92sPfBot0U2tPw4RYQTv9LjKZTVzhxSxGIAbwdtyyodT3N988A8vcUwA/laSPvZfRwP/bZmQxHxmLCEMcKLdLaG97i+vx84w3KGyjDwl4n2CZdPRFUrIe8Cq1r232MLLUKSr0MKx2Gd+fPgD0/pySUS4TcVHkYnwHPcqkfFQFXjmFnQOAY5jz4UCi6qkZ0Hc1+ypONNSW74djuVjVG4l3lnfHLZLNabcgy70p9yE9WiZj+AvbSadCRLQDenT3LWK2lI0RV99cr06+gxP6tDsBn9dzPm6pPPl3/dX8JV5OBT4xXLfX/9s+zftP0L/XSdde4+n3eKKPUMxIAJP3lLfoKVK+sv1P///y/Pdz2A7EC11JZQ+GeJ10mty/mV+6hhsCVHYX9djVTl6ie9n56rftyU9yyUW8Uvr35DTub97truP9df5WwOEXl5oWSYcdbxqZktgE09EZ7vvuWLUPQPEtUIvqKVi+H37HIvF3rEm1wjW658c698bpzmGXfB327nnsQurH+P87Xz/mPObTh7wW2vM9p5zJPRMMenoRfqxFnvgy1fofp/tpg25TRAaTrp3+9ft/Gjrx5xpXbsmqFnG93WWA8L0wMq8unYM+eE0HxKkOsb1ru9u+eBQHQcxbAN6O8GuOcH1voxDrQvMtPftbgXMIhF5+JWKwj3rrmzyf6/ne3VQdwlvCvttz+bve77++Z7veaxTD27j3vxDzzpH32vIvm0b0Kw8pp3bmL28xfbfnON/YnSa/Y9H9yulE+bfG91fev1nVkPj//lerb+zGt9e+ZFPOrdMRfp0RXqLeZz9QY+sEYk0FKPvIUaZwoDUQq2Z420xg+0J9b3K39+UX/vSmvcZesJ9TnZihJ713+8l6kvX9ajfdAtmoPziRY0d97pAkRd2WkOfkeeu91jvY3r9zf3UC/0TrPmJcxz9xRynMcX7nvvaH/j4xct7bb9p0WjmRupQb2XzITjZ+7PkRpc8BQ+4TTd79s0zgKCSr9oPli7aJXmeZdys3dqNjSE8T2XlxKF6tJ3cCSgetCytgZw/BQvc0V3h2CtsIF8NnYWC6ggwYzyScmZixaht1Na/lUApGibtzR+7dWrblRytjAQBEdmANtShprm/fHOIAW95HN1yJat/eL1x9ZXEUDfgeKodLp0l6TNEMS0mCMR/fT3mOu5d+0YdxNeKh9RzOFfM/8AUcEIn3PobROmjn5ioQCoe1wAerMgqgZ+TWO++/pjvp+bYa5UUvhdAempi1Dvt6TZ98SS73bIbS36eJTmh33I3Neao9HYMGXndsKaDKhH4HinGdJDL+nVvBP6NaroV9sdSmTxmc3ANz+iwxWzvFIHIT5fiynEhfTvQy3giAdofynXZ9LXVg6On4VxV87deG/PTqzJOBDroa1Yy7CSnYZBEeniNdTS/KxAt+uZzV32MrO4UIGPKie4pUREqjzrx/P66IxOPIT83+12QDfrD1zCKmGKo9wvepAq4ZtIhecFlUbwRcOSxaBUTeV1XHSFm5Hu6pn4OKwNEIaBXAPdkvgwzwmDNM/+uQyTpQ588MXr0SbJRizx1vx6fGN1c4VnvoKrQ46o84AC97j7OYfq1ztbn66uaRHOxAZd6NiTATa1eIFdbva5AJGfBNDgTxK3QD/h4z7qJo/nXV1LV3kJBlGETPYXjEKA84bF7G34DvkFCdZbflqV+EBdaUC7k/PB3LY6MFkkmT2KbrlHIMsntufeOdci7C6UNHukk2VLYZgj1Zb0q+a2v63dNIVFj8LXEYpJvtFxf0mnfaQfaZUqKUBS1lAH5JF/fXCRsOd6/wCI5KfDG/lgH6SBzOBAg5gFUcWA5pBensQtFedgNgVD950//zxCyZx48blnAkPA4kasaJpeTC1a33E91Dy22r/fUg0ySmPLG3sBHdID9Qm8hEGuy+zN6f4d58iBJRuXzNRk6v8rEdcCc4pmLoXjPeBDEtnlw187uB5aRG/Zsoi7+hkI0u9MswfPNAydfAT+gEeRNe4IAsNFW+x/PiA/hbuuMTRwJ9FBNekAKp4kuDERr3FNONi7yzkQ8kKCTUFIKeCwfAz9zr6yLhux4sCRFkJ+UVlVvZZ6km1f4PLPxjf2SC/e9IbvXjxUOB4aUurrbk/sJIIxZvzVWI367GBKk5nmG0UTk04Q0HlYqM/BoLs0pYD4Y5xY6Sl+yoKseSNuFRlYd8TZy6Jr+WQ9iDLbqx17IIvVluS96z9v4GaSsqncoJ7x7YgfZLpN6vNCFMn7unOwEiAlSAgskiPR4YEW9ofGTcODXrBKG7WCqRSkwTqKdV5opLgU/RtVODE6UeKrUH82wqOFiniJG9Y0FEjAaSvwIEeCsPZFE4fb2DJUK9kyY1ftSn7ebm6bXZmtD2YdpjZo16t0dKjQ/QQS+ucSC4CqSvCZF5RyE8tzfsbN0bx02YnrjPh7mcTM2VgCzyRXbAdUEaMxYa6/LWESg5GgKvPF2P9yJrCGr9QGHq5mteJTyUue5ZlB0Y2If6jDQRHFKDMkex9uez/NVkHXw9X931yLkeQmET+xFFGI0cFj215GxURLmUc/QjpsPGXQTk1lk3Wzz0+rCJw6x6fOJWxSQX2aHYzeqwgO/GHiro5VTuSdMwT6ZYpB++RcIQZ62ORg+lSaTufrGeRAm6D7sYl8ySerrNWbAVny/0Zh/uIXDKKe4VvQHeTwtcyttuD5B/i0iAQm5hI7uSTR9layd2PA8KxVD1M+3aYRgceu1MNrRwbwxO2FpymjXogreiMK8rHca3YI8A5eloxFP7me7tTw28TDstUIKkc3gcGtyQttwYu/nZ1KRYOfNR8zeDI4ObYKFwm/4yhLuxeIaasww448oL+GlEsDVezFpenbwpfIgLeFzSsILo1TW/MsEoBeFtH4YBufrVjeHRX6X75oh0ScT8O/PtIL0Uz32TR5nmWkmH8ZOCmuNGw7NPmxQvok1SsPe8p2keSpW4hSyGg0HvvcY4tUfh931jKoe7KCg+l67zGyukiRx+pq3joEZOLwdhPni0qkiHx+feb8C1y+78YE82weRVte+EjRyLYsHMEQ8PGEHeUl68psbl/fxq1G2g2b4rRkKGc07svX6aR2VIgXRm7OdG8ykTGBbpIkHohrCz81mWsLQnrw0NjvK81w5jeYTHnLB950GxWuGRvJzFcRbKvK5bNw3P1gYTmeNkyzEkdJbEHHRD9NI1pwCJAStJpMZu/c6G88Cx0L8Mx7sHARPGt5L4a6gq6C+vxykfs1IZdJx0GeJn2XfSAb/ZFzYYM3svaHInt5CpQvd29F9D7QaC9UePAAV1GiB7oc7o69vFoPHXur+0pzDiFysy6BcS5Cl+JgIaS0X3EbWIO0Eo4dEIWAetyB63bNfDj5EbLRNNvxZHNoQp+czJOxb8BlblXvhWPWClTfTT+/eafSNyNYP2ZAXkKPEb7ZrZZO6B78GRewGxTuiWzk0Uv0qsR/c3K85TpFBKP02xDjNWV2YxeiccSr/BHVwFWRkp6USJL5VCoFYmjwfqClFjOwGmVjuoR5k9bSTZGFEshFJ3t6fDofJ7drZNtM3l+3Tb7Y5csO7d2zyzPLv/JkKs6H9oARUyegDLO/Giw7Yt8tLKian0yy7kkiwik6CZFYZdMUFLUqlNj8mmG9Qhr1BWo9ZzxWT0vK4Uwf1LkMxO0IrTXE1ROua5kfm3mPKMgnvfLWN0M78pwYrCb7Fspgtf+GHC1v0PojBVRKTUdD+3aOCWb1ndmXg/DUy0FTNPEY+yUsJjtWvoUS71RMM83H5s20dVvrrE6VyqQPiJVLpIggluRCdkCj0sfmn5UwgOqsInEeoAEFI3xb7NUvHRU2OP16Mj4j806rx7IHWQJDLyaNfyV7/8ijNngSqLGPEhx7N5CW2fEyssiPeY6yzQWbcSJ6stFB2Dew1Q2rnZe99Wbrnzr0W7NZeYg4Rwihhnj3crPrsLlJJFayB6ti9myYsXaR18K1I7JwkJxe8HTabhe+lbp96JzSbiT5r8uxRNIl/XTv73vx1Hj5XHIf2OOB5axiYJzPT8q0kPIpyn76Z4rVX3sMVHSg+tNIL+pQnG1EFl1ftllHUheaOanpdKtYC6fwi2HdfQyXJf3ZG4pB7yAPLlQkO9MG3szJ+A+L5vCiCfkTFZBHImcsojdKetKTHo9o+Gfi5alyiEn66sXSSsTlLMM7DbhJktm/xEmICw+cbT9D2iNEkxmqv7VicEumc3ZJwbaHbUiinS3nLFbECge0rZRFt/XFfhqOBquefIBzbh7gMGkgP5X/DrByDB5OPpM4rW/R36YGMF6wpjDJnYGPBjRTpOE8RUuuGQLPwuSe4D0LD2aSYg6+MJCY3VTx99nEKcKiubDCpijgwsGV9a5eTTg09NCrueO6dkLc6p8uGgHmDHHQmqd002z9n+pNmX6yMX4o8vPcbPMqXVJeNxzLWYd9TFiRYdOtVkFu8c8YmLxyZoGNS+BR/MYI966lLMeKenHIEbQPQkW5NJGkj8whF34cWAbNt4tbR65ze5fBRHvVVp+mKrabDu03kMwBUc6hrO9VDbhWin6dhFzsLpMGeEUV2m91XbxXrAFym4014MfB77QBfL94g8cjk2BCagynVEe2DXwQsXLAxK5qMN9erRKpTOMnKvZnBJ/DbNlrwNCnCeiInDya08wddJOfoJXOcvq7Tg71tSSVcnKzeiRDtXdIF8lEdM4NKOcLi3KdGirU6WYNmSuytHww4KCpzfJOMrmdVnUHEG6Yf01RMvrAf1wtUdxFqLO535oeORhigCo1wBNw80rMeu6zfX4YrJ94VSDuqa/QS3XeHT79NqR5OyA/zsuSX5NnQiQlU2JlHJTGBxBBA1P6Y6cZ0WUv5DwsEZwPFi853FIOGU1RK2JVROJgGEk367BvxB/Ea0HeeEIo3fV+OiyHRq4gYMaWPu/8egahzEClxLW/7VURI48pKoIrPezqXCsY6gURBvKxB6if3KtBU51U8Z2sm82N/g6g0dE2LpCOWfu6OSBObwTD+triP3uZ/SOo3F9e5NHhPc/sc9taLsuDQloOex2OzjSPzvErR4BTd0ErN0+SqVuWdhKUza1Jkp0mbDjPEHXXItCA9nVDMTjp/yzOWGNAMl0kdKdigKRrtWdxw+4ffz+2yVLr38XSeOjLifEPfPXSvRICSlyj2MYtCPjMLoiy/1YmH3+hQuedWV9PjBQUN6+9dBcuvwFl812Iu7DDpGyZvUdRZLdnktqaXup7ojbkLnod4KzD0vHvFNwtwqTkPXzFiFHkndZxfYuPvGakR9pHsY7gA2kY7c708Tz2haVHdj2aPSR8kBYv4cknzaKYCBeYW6OAqlJ7qJ0cfrnwLXRvAE2yETrHVVsIyPluhSALCZA7naFG6M7hzqTKddBYWHpy3+8mcU696WAOedWtrkIxRIrZVdAhHwMtGNl+mYUP9VLdoVTZfWtUiBgQcOzmvQ+qj3oNAkWH/Fkvqe2crl7zka+i90lFitnd8S7oo8pTyy72JRlP8OU/Q1Uj3qvNDnOObh8xrstp/yLJ1aHQNJrdg2Rccyqo4t5DL+wjE0LPbhpKDPZWBJ9iOOO30om/WNZ1fCwdTizCXLxOTHflcNYW3FIN9MwXfR/3Q1xVHyobZYyLsEY1Zw8YyUzjvwqYvbKHLETIrkUmZN5lIgHuvr/FaksMY7RfAaZavIelDclYllP1IleY3kVw93INzsw0m40yBw0thU0Pa3jWcQ9Iqr8eWApq+HgzNtfaqfXTytG6V2MmikZPg5ox39DXJjK/ZtBOMx6163cIaHpbeOE5MeaN5oZBLzmtYBNkNyElgoomIe201rtgjv7XFZ0FnT6tKS8zVlyQiN6UBKW6sad2B7OtbVhjZj3CzsrCNRdegJ4GI8qEa3EubPRXw3yYYCTVo36YSmE22Pc7CoQUFxNlXDd9DF2MOgP2ilZcTeC9dUfjQj1aUbU44omigKjeQd14WHh/Rklcg+bHUDD8v3udtlAj0XP6hccknsqgZSH/rbzQxeKObxHPJVv3GEC+9U99HlmCa9wM1UzU9yQ0Fz8CvgHfITAxHxhOutHvNGD+CzeeQywJDINTDeM1G64GQ3U8ku71l9CC7yDTbT+GRZ1SqkINmlytNUEXsIxQVpPTzQYDE2T0DHs2bk9rhhUEWFSa3ga1uL5VfA42b17NpL5rTRoxFdP1MBMJMQK1xVhjEr63lfRggPxMyMLhR7dLlsJ7zFE6W5dQohgqbVCC3tnlGydMGTrsbeCe/u7KZbLtCCYM5UoKIo5eM9ipfexoGWzyJ8OgK8Ax1eQMXsB0SeR0J/dCzlwUsPBMYWksdD8Hh1MFmP5M2egyeJA+b3Rj3q1xz1cJYy1jL2doJ9vLNKHCuPdZR6mNtT3YMr8fpoJu4ed5HMmDbWKEDnQQ/na3FPeiWudmfe6pTtkf19dMqiTuF/omUxXp9om96l+O1Q7spbgT3wMSA0RwmzXUvnahPS3lvL7Vf7qXEJikCydtwTQd9hZcuIF4oj5VMf8yEMTKYIDBFGTLqi9WYcEYMsNwommCcL/cWXbhoOA83hZ8O+7QyKHmudGDO4o7aQ+KMwVvH8RB2MJGOrTkK6dpXJVcYxSKEBQFFQK6mAvq0ExxIyYcV5sBN9Vx11JQUK1bXb9H71FIU2CCTaecCwgJv24UHVJ3tmRPYrjqjS73yMjOlqfALbXysRi0t0/Fx7aShm+n5NBhPaN56ox1WJLUIjiPts2MbN88uwkaTof9w7frSdj9ez8EAFkzhkwCLzjFiGlnYM+YVfpgPDJAbKuGBsQmPUj7z+lTQ5Q0LcQl+zJAzPz+AYaR7PROLD49eOF0E4ni4f7WMLbX9Q3+/V4LPZhfUXxGHWBA1V1jIUCRjHA/mjGUEsvtaEJ6O6EwYY+wPrkjsgsWxfCCAPV+sXxnB+g5CWdMRg6q+AfYh2rUQRM5YVeUpYzTJsYb+OqHQTQ/o4/SN1d1SlvQMZaVb083rhjbSDfNENBG2g/3lzg4kLpqaaaJ44hNMiw3kFzrdn/quA0OKKatMXaD8YBV057iL0i8pFmntQD8952jUkly2EvOYxlPWWUHx9xt1Ppu2DshC8Acl86ohYscqdZ4xw+atoAEzUFSI4fWDX/HFC5Cg/lkLvBarN7nLhpu3dPccRzWq47nfmzjOj6DTmJofoj1oj/iWVLlM8jTv/YTmuPjU8Hgqr9LlzgJpmBm8Q5Sw/IHi0gLYcGv2BLAeVgeLqXcHPIM5Z5CtEBVggqEyXudDU/3JFtYrnlPQbsx7fqIAa4l8BDbJwiesGymAVcZliLL3bvXZkeI6Iiet/Nayf3FMbceDaurrSH4EWtDB2cZ2rpm55ZYrKKhnujyxmNSxZH8aV0EdWIRO+lWwMBPNKflweMTyaRT3IdrfMcUj1JlxDIqUEXzp+VYQComPk+SorN1d9Ni8kukWKnMnJyZ70r+opzFZDN4JyIBcMId5uzupfl/odEU7sZkhMv4yf/SEEzU7ct0ncksO7JmGYWM8dCVldP8C4+ZBCoj8RfeQJsMSPL/sMx4f6jd/JBAtwJxQA5k8c762YSpimuYdYhhnnyThzpGOKaoHQ8ThigTjO41lPKeLX/RBeUCe4eWq/QoAVtfiN0kZneoVgWTv9kN2M1d0ELkamAU6+ib/O3wfq2pDlIlwVOIuP64c8CP2ovU+ndX3G74gtHho4oGINXduWiiQltNEFV2Usfg2FmxKPvUyFHkvIsq04tni0+RhLIRdQ2aHqRkmOkAML4/4yUAF2Z5QtPUto62J03WfjMin8Zu5o674lx3ZciGNONkqnrjFZrIlBUSG2j2gkYt6WGX5D+odMfPyQZvfGkHnXoM1iAwfTePiNTmhGZw9MWEFHqY67ADbc+1UBWMVhZrkjEeV34oTFR4Y+uRo6l7oUKgDDeSRvJMPvNOo7vvi++YT/S1XLVFt7Z+AQh80/HqALO+u+1LXJzFlNBcI13Rmj4KQKCKmvcXJcOiL57KWBRAc0cHDxD4+59IbCmxx26WXV/VUF8asCt3EmhK+cE/db+jrEzt8aomewLTuJAi7qh962jR0T6GPvKa7C2+WZtJQjpdhVibSGXezCZf4aYW8aXo4pUMXvpIvvG5LQmO4OqbzgRTC2me+7xHDpjyuDObRfGVzJFPGurqfjFYhy/xMVgMl2bLxUOYxnL6TUuijkN75kYxWy65LzdjV7K2kd7v9zrCNQ2iayJ9TMw0aKqxwhpSvhKFz/LzVouc2D4336nv+8XByKjs2LYDmUZzrCeOo+uGfYukclvDy7HqhOWKoxSf8ZFwC/VFgWLoSVgyyfGQ8Bxb8vERFm4Ro4GXM0hzHHKB2VwfZal4dwZpjq0fxADuAORkwEuATKmj5GNH5lXlUa0yQ2BNMXW/rCnRkM6EV/Jo9nOT7C2RScoFNFRh8gw/ab4DBg5o84GXYN1VtpGkeWido0bL6UCWEfT4h3c3v/vvEJwh6WK4VFLplR5WJ2T4gYOIJiWtu/DBusMaFAuF6/JKYDiD6+hDg9VaOZ5obkj1t27u3hv22fotG52Sn87q/5reu/32v64tgLAe7P4B5TCY2Ux9SEtRY/QqObDj0/Tvm9lSONtDHj9TVy0vVKbyv9MsL63tJ73NSDSABSAY2LQLT/wz0Rbv3mK2Ka3jF3vRj0OnmJj10JpkU5DSus+lYR+1tJTp/C6UWHEstqmBMfwNlZmtaNJed+y1Dv7je4uKxyO88VCTf0vHAtKODnvFQA6+tk7etLxl4r22ZFjAORXLFY+qFzjYSMdGzalv6XjD+4gAjn/UXC2HQeIL80iyF+t9VfjKWJwdshxLKV1wVsIKDm8B7fPasyGC9hk8ifn2+/DUXaNKSfJlp1iVmzG+pO0menjEtwOiUbehx4Xr9zjWm2FF41T/j83rURW6vKZkp3yQ0tt2V/y9P17HlOBIjv2bv9OZIb0XvxBu96L39+mVWz24fZl6pqyUqMwFEBJDAgpVnqHxLsGT3EQH/jwIRw/j3AB/5sfYGPOMG8znLU9Rc1mXelNr3Jq/oqE6SGMwbxnXoKnHgHTzrMWw7y2nwFYyKwdjrtyAy0DgTLlXa6OCb+1d//hEDmqj3xZoqrceh0nHIPUY5+vW8pwnirzmAQ1Lmla300ZD13roU1g3Ucromrv1YTcY5OfDMkGpLeHNyaF+QYJydaKFCWwf088d1VrHQy3qYSx2JqBPGMP3BuI4XviVxwPjBBefLFLP1+JXqKJh2l3wbkcgDcIJ2Fia4isl18dTFG8tMmoDCCs60cimvPScu/NHf090OrcUrE+Y5DxggQ4IkzW9ZkL5irZr6pri8GOBsxISFjVKcbjZ2fH6VhS3WD4SAZgc62PegRLpI5m9dBBSBPRwsmz3uRfyPBl8aUCLhxfIsQLO74uzxi5EyXnRsJzHuSJC9mjBZAzqp0WY5tWrX/j32n8GyUZdBb63vNN+R/2YA9oQEuVNnb/NzFYjadXZaqkT9Kxv53Dc80THUwwmaIryEXSpO4dDR9GmbY9H8cyrW8os1baI3eISmyraNjK822xsJPSr8impP7FDOD8+AtE5LAAbmRX1u6yIjUwPmE0nosvPYZH9dSY8LDWPgHRSY6gFFfvm9skQgGvGyMlsmFPSeN6tjdH2V6G+yonCE+G2qQoZSgB4XWFin3lT0WNv3ZVnUzEr+IQD6DL6LNRAynZR/bS4x1IRNFXT/og8oRq3Yf80OLo+y+LBadN/puNVZeNbF+JucG0WOZrmLZk25mSp6ASsqmor3+AxAnh26jzGwgG9ti7y+P1nPmX3dFAY7hayTi2DHUgdYtz8CyUFmTMNkp7tuOs+grzs21tnSKH5SdR5bZ/9INdtEJ4ShzJfAlF0ERe/ORfbqp5noH7TOfzW92cgyWenfMhTa85TqqT8qCA5BCdU88xBHuz+ICgGS6e62bFR50WQ3mCgpfj0UCwYifkE6QlD8/pFJJSFJH5DLhb++uLkX2sy3pnJgnborx8xEA6ixAHRYqkTQRI4tKWuoga8Z1ZzVUSugdStqt7Ih/iDA+SX1rb8M54o/Tf1ZdoB8P97vBuKvhFxj1iesxp1wFQ4fgBXJzeVFUaCddD6OBm61JjXWdcYlPZR2q7bSdi0uJ8eBL3ppcTnBfgR6WH8tSVNTWa4rWvnjmGdxom5s/a13NK81dLFiweT041gFbvI3Un3mO87WwoVYSM0/nMkSrWP6QDZIN0X6LC+XI+HyqsTd8EPBB8MMxaljG4rPSVb/0N7LzM8pir707v/NpQJeeDicoydA6PUWBKqYQtThQTBK8ArkD930MkIXRJCqTiiamvc9YJkt7qjFFDuZl2ROvzzZwkBK/Iz/JkOWDXr2A9YkeeCQXj5ew4c4rrIFdceEKpEYjcGCHHGtK5tCkspZ99e4L5TOrYeQZVKyrugtWAXj9mBS4SjTIbrHf6D1RSz5+TzWdHwR3eKDfYxNyLkBsBiaIpZ90pzffWFuq0LdegCHBk+nZurp71djVK0OXx+4hMty0OGskdcf4i+Z6sN31Gxa1jE5Tal/7f2vTI8nvGcwSWzeZJv1lSVTAFi7xThieUcYWHo9JRvKZbJ3At+hCLLKSd6glsxUNQfdul89273hW1USKdwm8qcZlTZw53cUrey/4cXV4i0LidehrDY6nhcRjgGEP2HhDE+P5lUVKVpGHEaBd3wDm7xAzlM518lHJJ72wLupokQx5fAjK0pcyuolsyKloU/lwOGT8ELNoAjvGvlJ556U9k4HMhKb1IEDACdkWWFzy6Ta9lfUIATG10RSV6VBhgfKQXt68dx55DsvMhMxRybI7e+7hnw92mHHlZjcDrxW0R+VImL/+0YtUe5v62+s/R0RmyMBV4/AK5S9O/6DGDPhFXzXT0RjVkGFJN81J1tQn63iGF0VpqNWIoirvj1wClZB5vMbu06oAHrPXOtJkDrWVnYh/twM3qsfjW0l6Il+qN9h1v5ZS6VBiPxB2ZO1SnksH6915xL+m/sLvqCRs/uL5yFrIkQaqssLW+nm+OGYO2py7jgjoQbL+BcCW/hfiL9MuMXUOXGjfIJhZM3aXAGvT4XwZJyIPyijuKC8Tj4BPk01VQdHXfX00hXgc9ck1TGwYKoKj93/vSXg/RvL5eYDLtOIt89mH+Kn8cSoNARcoDQxkFmvy+N9hfaZEMbDm/DFKJSsmjMbLS+DdoqRHSYFbW9m+6Tv0qAHBHZNnECiJYIOkdTYdNyVL0NdZRUSqIl4a3wD+YQ9FlJ2AibR0x3vFNthQPtwMTVQwwYbL1/+zHSnOM4ws/A2SzGfS+mVFEh2ZOZzKeUwzGNK4WgsCcY6byRpkZ25kDQHXxCcsL9kIo8+lAnQwX0617QPBm0dCBUukT+6M7hcIgbjXAyX6I1MKDBM6UNg27s13Llszmx9cAnMcPcfpakv/ANeuz9Evzjye0Os9iQ7r7lKNx8nIcXpO9DZI+WVVWEikoEGUG9m4lS6oBLYfi+mcBK4vNZPT6nIPo5/+vO994GZ17vYpN7wk661TctCZ1cRPAj2jVRH1bY8C3R/oP2VBULAtUf17RSpBnOOlJ6hiYH2on9Yy6HpPXhIGvd5j/xSDLwTTFp/RbGqagB1M9EWbDkq6IrrbY/JAaKyfZ8toyhZIsmliQSjqfUrMQeCZf/OykI/2SgF0CWA3S2HTyZvx30cmPwL4upPGFS0KVcnv2Fp2AyF9Y/ROOTmKwR3xtZwPRHDXcFLIAHO/04ED1M/Mr+ZZKnSsytdgGUvyREOLYvEv2apIJnwN329HtuuCmMr/9Fmv2JpRtCljrbND5RFHGgyfbOtDLXmPZvcx1rSJ7qLKK83whuFEvhL2oTFv5mEP1QkElp8oveIv6/PYTtXGQX30GkFlKVzD/D3hrCx+mSt1bL/DUZDpAfcWwnIfU551muiQCfRDbzd3fhT3evM5X9Ko8Cb1jDdbWO4Qo3Mc6cBrMrNMZVOykJLVlectdFLsKS/cGFGByOYBOMq+m908oC4MNvFqIt1CL66py6JDhNjZOveN3ysh2cLXwFJIxmsrA9qUxWaoYg8XaSCED5+wVNf+0s0vUfTT35D1IGT+YUn+KDrY/XRYb7/GD5j8wC4fckoAvBrZJIk156B0CzsMyEOlOUqbwh4NVp/TV+Hcv6Qa/QSQATTKq5gVOQoYk3vHiKD/ub1eDBK8oiz4zHmrgoEaK71ueiriTj0blrbWQpPhiKsRBWoG1D5IIxSPPRqIY/icqE2OYp5s8o6fwOqTmyUebCT3NM1jyOhyRyjT4fthnnpiYDvvZSWl2Z5vgiEvRZ1sWUviP6DGOmwoVkVzpIevW6lOhwI/QTPZjwnRiQkool/0trRxyAgj8Z3Th+A70SOLYP60rr51pLb5jA94gJl8MQPGklBle0vP2Esx05o449K8px5N8t3Sdm4DoIwKy9t26/PSd9sNhwEEq/28jXub8tXhAGOWKARkxBwl1hFL6lqaylsFDqTjh3rCho3q+N1oTW2FPz9PZHai64TfI4ZwT1AjDrB23+AESE5rJe7YQ/bWAVw2ZqcKkpWVu4jMrQ56vik7uTvZc1C1dyJeAybJ6w0aAhZG5FZdmmRx2OhrBMKSIQD9rKdEsMT1UpKF+THqQoYBg/rCjRewETH44+6fgYFbhMGHJZN/Ku6IfIeFUv+9OWZ3c5TuHNR3T4oxJqhzUZPLs9x6pIm/wNMa6TTXc8Jehxq4N3pLJ+hPA3PVkU9Tc9eQEipJheZycRSizjGhtHRtzfwO5pFqQPlMKLpLpjThMEqU3QxWJQuvxZ7gj8lc6QU9AZsSpr9lc/cIQnw4WRZKO0XQlyKgSaCRFvqHFa1CNvl4A+paC9bI+/l/YMaCNP94pON1R0CS7Lmfl2kj47cNtPJInAGRFTNymq+eFEnV06rgKwIggZVqgq1cNjQwzaP6QCaQ6HtG8c/yn8k6c58FAbZf9yyR5RjWFZXuIL4hKAUZw+Y6RsRTv/J6sxqfsBJrVXd2ldLipcgrPKMg1zX0WD2jHBiIoTbqYitsKyZYHuVBtnsODmzWDUqY7yx4vcRtIaSCUbz99dI3UqkNd/CJ1H2ejVG8I+m4LXXNeBNQQz7bpl86R/DKI9xGJIkXBFqpmcxCpNCIg0rq/b2RRPqzf7q2thH9ONd+WF6S2Et5giUthidcLsECocLkrDVpFz136jq3I6+QeoWfPKLwN+c+LjnY46DX630D1dEfeny31pwT2o9RXFp+EAt9hkZnGiAYL85ymC7T+X15lqGES5NZWQFgpDPTCxBPuZ4mw9M4czGbVClsNhLB/InRLgJRwK3/IHI/xUMvE5NEbCjNEK4ypc2e/z+1ixLkbwJwHZV2ffqVu3p0uPr5hFhUmfcHiHRVYwTv25Pcz1oDIj7DlOL/IHbi2xOxiNrcQiKf+EvZj4dhRhb8xi2FpMe43PkxNWBUVMz+N4Xxw6PHtqhEqhIXgPIA2T3ACbpN1z5D31FQcoArmXptvI3ehnIE1UisiHkXMD9NhMH/gfwjNM3ziHW1O3WNHAJohp9hsiq9IpSbOCIMYHXjFMVqxUT6JKtTcOvKHqYl/X08JNdpWXMO4H3oN8eNMBn6ky7/ZqRMU9Z/AzjJlDUPXsxzij9dGrzKTrNx9603oqSA08SDVfIbSos6G+6MnDGBKjWbo7l0Jl8Jn+1WgRO/LmBwpg+t0VnKSNzzDnk0Uz4ix1BZ6ZFD079KFB1ov5hC704waHR84kI7duphWJnQQfmFeDeuu7SJeq8d6vk+pPZAcntAUFERvnJPFtrikCkfVeMZC6YVFddKctmpB7nDdxnCC0XmP5HdLEC68P3c4ZrkcNPZTJdAr/MwZ9wCcdPSrWRcA+vpZWyuVGeqKmgz2WnDWCDwrrRNzP3nn6BZzU0HfcInPglnQB28rYBurPsVdbC6mLJNLOjO/EaPFtaIjczIf/ceobLBpyzuHLmVcJaGak69uS1uExxVfwloZU25ixWfvNuclw3VgP6p3LoI0dnGPY3kXsohKCSLh5DdvtwYn9YJgQojejzpMSG7DU1jroWzEruhwQPLBx6BNQ86HYYsB9ss3AUvLSVcWX2ViOMBRPORT0yu3vTm4awl/7kLokqkdQn707Fc6mHplB/fpwBOEQTjRXePlXzsfLhoiyY7qn+8hVsAuiIIsUqyEZOX2n4pW0VsDipfPa9qryVGW7tBQ2FU7DPF2I00EtU3CVuhv+eYS9vs/GdLcNeMlb+FfB6QRWWO4jRVsC+EEzibfqyWVZJ6D1+Ifav/k22d7lAKKFYzUfXHsDEi1hVs9Z4/PwGEHmR3sWkDd/YlKovEa1n54tNCsv1MVuZ/obhhWHtUMeD/oa7J/tyAOvJzwDmPcd3w9MfXjtZPyQ3hFE1V8EqVgzq//wNUmYzibJ+9o2Nk6G7EvFzkBNhsu89SqM698r7xALKpY7UVd9WyIDbS1B/V5LKnNI+ZvhIZn9PFhPCETkvMPuKHOGBXxN/9Pa1poVNaGBily+6ZxCLfxP/hptPCAG3IPADgMpm0TniF9RTXeF30OLSqH4vjvlGUUVqvKPA3HxKWz+WJJocg4UFEM1lDmE9pWTt/UcAuPoJwvlyOsd8qa2s/A1hDtXXP8lsTPAFdMl/o3UgnxOfsXNgL3eJ+RZ1P0Um8aXkjDGLM/TBrE/HIMp4xM9MD23f30BvZmxpCvjesSq0LxYjpF2wuHDdYywpFd72pLloGp+Whwkc0UBBos26yIctyVb1e+c3kYk71OW5lnr8FZm5hnElzsZo9ta6xsIHnKTsJSJUM9AYHNSIeY4lk7SzQaF+27vRogggW2Q764cPxlrlIuucg6+Q+oKWYGT+gPQC4qOCC0dx+hHt2TyERUcRFAIErQARB40NhwrgUtQ/yFQRprYla9PMRygOF8DsP5U/vEwjOJFaZWi7htC+hJfHjVnhGR56CIjMW/fd15Hyxs6KPRiW9wzIl1ZG9RXpvOmGyQca/yAHU+0z5PvpLaH7YdCNNXJgKAup1V1JH8tEqFVqWS6UvBTX65CybZxKCsOy/6FISlJfa8hTHg/Al6ooaK3+DBFSkK2H707wWDCzXFxt0WWgZHyhGl+fT/U5IBAJYXdzK4RnmPDj400mV0+5ZX/bVUlmb2dq6QVLa+HIdfZ3/lI8oCOiaAvVFXaBpwwjVXVn+QCb0kgVq+HdOCpu3NHpN+0kMHzgCFj4V6l0dX4jqslHZBOcwvgJFVx1t2gEGioXe/1TuElo81PEFRcGXIK2AfJUmD0slO0MNd4sQhGHY0fd29F3u13ujJ1aRU2DY8QwFbyhC7Fwjl4Rn+/E0JPJ6cB3S5e8CIJ+bDyJQd9ouINzwLiWRzirZhEmJSxzAOHsw9cU5g6AeCttJEOmTuTGSt/4Fm5bHcxNIzFpfJRtsUXBoH0SOq6xb7v2nUMz/ZmjTLTolQqfqvyPORfGIs52f32YiBDqMfu6duEVw84WQXMKrr8cEjmRfvyM4qNggRrWW3w72p5S5CXaA6cxIvOG5zL5EfKfVFijBSQR1jZfX5XxqoRmslpVi0QLASMFuoudR5S8Nufom3ovTDZFFVyWegGqhe6HtqJ0UxoowAZpK1BmptAFeckbCjQ/cb+82zB1JjHzikv0HVhQlVmUXVeUr5FTAKET9csxcZnxogpkT7XzuyZh3VO+09obIWcAmE/9A8EMcvb7l0b4gToPrn1KF/9YGK5+ypIiKJBgMomIlOQHy6cHfY8ZlB5BmQdAfXxNPzcef0nrI8dwxIJWZgIW2UZWh5L1GV9rjuNFWiYPgFDD5t5Hb7OgOG5P4H7GjV4DUhgbSytMrMkB72H0CwB0n9chUOv6dfIxHDalYZhFQeRzMGvzpwE7b95xCPNhnxBIi8rtMqFqexPPtG6w70pCRq+HlIqowN61OUnmjyjVX1k7lVYM8x/p+8NuIAGYLr+/a2GcbImY683fDbhU/W/eaPcFMlegwkM0qKVJeR89NNVY5XTJ+1F/Ey7AL9dKuC2IW2ypLkt3Jds0RaVF1q4PZ3N3WdHgmlMaTj+Bv0dR81f40lnT5vevB7LCIdgP8tfnPlFMzERK2ZNehPnS4/DXBIDQ9fDeEwb6FbJKcagUxZTNWecEhCC/8+BnqZLg2HW1xQNhnRVDNQPH7iJenQTcWAAfEgi96FAgxrDGbNDTL/ffF+EDwQuI3CAXsr5QdDkj9KAW0hBegMjbYn4esmrfQ+JES33KzASwxiLkWxNPga3+kljVuVa9umfyEsYQao83a9P8s6sIKoDeDg67l5j4gQ2zZQqXjVGFlnTqZvxBgrtfDrMBqf+ma0s4IVAinumpxa8udIdO8BncTbA2HrHtlgcxKT9fjWo+OEZ3Rv4NivUNVa2NllnbtlhOejUjQhG9F69zB8ETnUvXvJerzLk/ePjZvEep9Nkk0DSWnhg3bKmnzp4kXtR6fvVUPzLtnuK/u9jBTTmNqpjc9Olv5uZIpxz5vRqeWEqhhoRwDvFxwFexR+MO66NM6IlB+EEbrmheU8JeOzjqU/GeQnOesIO/dIyI8vElUbSQ/fjbdmYwo5ilVvOk49zhMMzICSItX9L8/W2wcf05KNbFLYmflAZf6D0Ep7/4zIIzbMWv+BvlBUDe9Rv72pFipak3eGs2kBZsqO5XrASMhfjyxdPYVh9Gd4lp1vKaa4lo9QvrMIwVJ+jcQJ7pmOAg0tnw1Al3/dUI+iQmJ830Wu4eg9oQquyVxTESuPvxoeQzQkJ+F9oAm3RNCRYo4ivV1JRhxddQT6PiUZRxqE8eyWNKUjhLTWKzVQ9As9gcGW/t2XJp7tfdRuafOYobkkY2CjIOn0ccyai81W/r+eJofzmLxxZDHROimcqoj/zmM0P0b8eDQ8xchxbt9Y+N9P+poTIzKqJeff0bvaW2dDqut5XMV7jhjer+7cYSMZ2NlYd7W8hywAOoIEtDg0O2o/t+f4YB1JyP0JVVaTZ5lV8/Yqysmx33j27Of2MJE/szPY/xgfSKD6rB8DMblanOZCzITF73FfhAx1Mjkv6QbKXIO4cjH/nDfwYolaOnuF033Xnqia8/5pwpj7pXbmUASVNMxRmltcMrVQX29i137XaRfF6yL0JI1olc5CELzCrdW2kR0+ppkbR94a07YoKqTc2apAw6/H4bCf2YKg5NOIF4UV3rR8y2/I1MoiukiGIZAYYOAMyz0gM3II3DDrhMW6M3/+XpGeL3AOp6T9yxi9/cQiDWgb6O/JVbD0VdH8T+RXbejSmhMl98pnjRw0t6Dx7OpD+VPwfubYt5wdt+vlDRiB4XYkondSEOjuJHdy6I3ucg0v7GMYtid0V/6LMqVGyax3ibV4ukQT3dl/El+Pf5qVoMqWFC5tyIgBlJTc8ZKLzOIdzY49yfujefUC0Qx6dno/JrJZatm79Z/aWh3z/v93s0zXzwj7iTExpz4gcoO1ou00PJL5j7nSNTJC3kTAjzX4z8K61+uJ/uLsnflSbxxzOOgHkkl1NiRZmI9NwmRyS8hGMJ9uwQZGb6l0rWkTbxwq5jHcs+9pFwc1Cg6Avw0baVQqv7m4zugXxT4DWy7y/PC0tsbuvqv74R+UY8D/Yb80Pi/6Y2EX+zxjeuoJ2rl9TpU4/XJ8XbtmoD4G1ZEVues/tG39/ABgZ3mJtrDnpzq5cwRkhBeHkn4TbE6phB2/GEKLouM98N0LOtA9jkhaLT53kPgPYMEFmmsAc8ISk1hbHC815ClZUfueOUU1JNbmKazGGVX0JTcXdRnl8TrUueCcTExm54tKIazr2jJcJ9NWzh8XPx5Ktp/OouLlq87GqtEjDuj+W8/2mtx1C/ou/AVtRbBJno3zSubu2Pk0BHtJyrq35rzf+bsa0XmBu1PzfP5uY48X0p1bCr+pSUHZrAJBEj5vpshwSEvCSXFnuUj2JhGGpSnLHJEhXyVTdpk5ivI5erDiVEc0B40ey31814+/3xwdKiT6R0ihH/c/wlQqnbm36FIY94og0AHdVDAO16t2QS1o38FpOxo7vvn4yrgpJgMFFAu2YDSklDqN1IcsNtFLgal4sFg/AN1NyBLb8vv5ySLkQCHMsAJqiKNj22X3xgh2i5I3tADeSdeh6JMx1RimEo+6qQFvctHZQajCWfElW/txk5POLl0P1higZzXKJ/8mUZ/STJ3ec5+cDhr+VGl9hO3ubyTDSil0UTrtu+9EzzvbHkN9eVRk1XgETE0EvB2/D5m9eAhwS1X7tcH4aKlM6utRemyFMP+Nsv5rsJnY50EljwkYLNNJEQfA0Nh6EjObtApHpAXgdH6BPSmY+ToVvMb539775AYJKs26l/3Q3K25Ao2W0T3k1gkYDlUbbngNHaiB5zXr0eEpNnlWMqdeomvG7j4nvjLlumz/wf+R91jftUM7kPONbCVqAy5Ed19Af2nh14OD7L/bkSxAXCV5+hqUZcXPU2FjASvm3teLtxUtLH4SvzHpqyRnrCBMWTq2wO8TEeDICL4FRiF4EVBPuH+BN92B3eMVbA7mqXD+Tfr/iyzbx1wjXPdO7F6R6ePFbVJtONOIZALhDoQPqTkQkaeEpo6dH6CJe6Unvdm7w2Wnx6AE5otdnCXqHmFky+x4E6LW3ID2IU83VFEkVDYBeaJhl8oU46CIjxf1UDhO0lSBvGuQlJtkHHaUGex3ii1tGvPVXvQNF4g6iiv4dnuqblNCjMr41uPTuYsg7PJpqP0HLotDIjgn7Zv1uYgHnvcIMEcMCAKkUdNg5MaeFi0dRfk5+Saq+rvD/mY2qZsFTBZvS48UKkRpdq7xjHNGJVcrFz83M1mZKcA2NjQTPFV66hvJFcB3R/+s/Q9nSU7L6DYitvZ6Vov6QQZfeKl9NOeHwb+7XrEe13THEhvOqI4dH16yqD0mHo7K+M7GWks6WRh3IkbvFaI6okufI5uNMIQcT7ZlKlRZ8PA6TgOQGoNWAXzQxo+Ung0fzUOEFyPYncA3Jh5MYmzUriWurGJnQHu7bjBHKhX75ErtvpLh0WM9JWSNsDLMi5JuSn7DfwVTwIINPXOcrS/z3QwZyMbb6U4gIcY5hefhbdXgatfe3iCnWZUGw/nr74YT6QG0Aa3QG0zjXtkCIMAKiI5mfyKabRv4TKLb3kLTC8cRbrjWt4JB8yT0ZWLiHcjFqse328G2crsK4EKGsqto5Wr6fw06VC0Pg2VPVxVSZeG/tzfDzblKVZVeYppu0GJTrk+POKGDxBVC8k3TUVdtByQ38eu9h8BNWtKat8t155Yhko83A5Pp7ZlwKDscwigkMPpeMrcQz8YXOpY5/NlBzW0EX9zO/xdvHhL/Mi62GH0bSgUu7CKqkQniU+0CaxSfu6NjzYg8VnnbakXhBnCLumX8DPsBA6YNtDPUbqCbqa9YeqXBsGN4felTrNEqrewdNXWv3Ez/UsX37JzvKqTVmmO021eWpUyPUfvvwKuRjruKr9uv4Yvn7QFXPiQ/LeX9qx5uRfRyL75VzO14s7wcISWbAiLGk7hm0ndrdOpjSAZA/nbdKNApKxjUM2Pf/7sMJuxIfthM4WGTbsgRiea1+4rBre36+fcYTRDkvaNzZxLS9KE9CheP8x/PV6HMSIt0ckvt8jiwqXFFNQF1gsAFSx95cWN1BssebnX6+dYqZ9zCAmJiYL+lZWqW679rdfO1HJlBqSv+MJuCT6Ot5qoS2Z2c2t4wts/yxQv0YPrNVdzqasTFONWUAlJ2vxSzwrCTHZd5LGZ9S9G6Uo00H75ujllfmN+GS0WXSvtdntyctnESq01oO7fzLt5U1x3zc0Ysh8CoqOLZLk8UMJr0l7mFyozH+zuvv+ueDYNSx4YGvthVjGtV1fFI+oCRbOl4oP8077yQ9d769Dbmqmt4ZDQ0HfJ8cVzVDw0/a/BExIcwzs67ii9JRSfNO/8bWtA0/VVmQxu3oc35ureCN6ctZveN1mgYDhipOmaWgTmQzPiMyM5ozqPc7GME/UOPLdPEHv7YtNfk4qESeBBRedRVSWl81QZ5q8Ia7kUuKAItG3vlzXfNV5F4g2dD4yM4/6oe9CD/cwh0SuxJgX7ouTjVsh06cbhbFLci99ygNBM2+Ch16Qudh/vj6pWLrpfXNOIGbNVXjM8th2oupOacnDADZBfbMMeTfNfPSAMIFBY6/p/Rs49gIafuNKxlcRUUEMGxOcuqtSkl7KlIzB89YZg0J7VlU/D0G2EeTAp2a/lAi4K3diUVZjePgF2lLcuvjGonAUnz30M3wAE+XAPjInobnmCNSy9YRSjn8kWBheIB6kZ7LhChdF+Ko6aPgkIjd3KIgIpiCzAtyMGtSyIG/CUlc+IENbhp/jxcUGOwx4xHGqYyuYZozLn6nEz2dtASX6EK7wsubfkKtF13BJ7/obj/4VDm0v9A+ByoK7B6i7kPL6+5Ld4y9N3h7sKETs9yS4Dkg/416IrKkfKsfR/J9CwRjeFqiSZhOndGladYVsiQtpgV0G6Khhiz6yyNC0jjEt/ilfpHBMLqwvTjoLKZl2j+KKX8pm7ZjLczwI65VGE/E3VR9pFBRYhp970KGX0wIkuMAN50NixvkcbX0OPWpQWbqaIbPIkLNSEJk4rSiE7kdLpa7pX7aUdbag3NQacRIqf6VRiekq+8N2ZW9wAcFfmMXILOtZdbRTae9fGcyBtgFyhme+7E/STxJRqgBpifk+dbjG/1S0kTL71o8cSqk/V8gQSvnaiF7mmeG9SBxCe5QoPnnJVjTlbzjwMbz8HkvNpPM2/6nry7r6D40+hS665HoHxhfqnUJ0NiCvudenPNuz8bu7dJnid4w/QhBHOIe+rL12n9gZYaTAHxGpL5DrwO3P645ODYa/ElMWK9jPuUh6SPHyl7A3YJoMK6LYRv0coYKFVQWnIrw9j5vGj6qiwtTJx0wVw4KKXU5EhV4Rx5Zx7A/3UNOjUqTWWi/i0YwWHaTgJHBi7Z4lDQdNMZviq0el8AQ3WDtCQj99XeRdoYoZVsvJPPFOv4Y2lD4rzkRbup0AUzzAcCpLuzsmUpquskQJer3FuNfYif5kFPVXLun0tMGig0HCGpT3xDnejfcJ/1rqkSoTvgsz3INyP4MtQEJNY46RFgq+TwBlui/nVfKCc/vFT2rXd4XZXAr3EWFXwfCsJfdDiGrb2dX8wpBiZEBmDoq2v1t3GYj3gGAe7W3yVNQxeG7dQmVle6AOu/Uxbpr6hoXBtPG5TaeDQ4LeoJ7/pSHUkhbBO/AECQ5btF1V5MwOYPseCtVubrbvbwfgF+26Y6UYfLOzd9e0WuXQ58qQF/NFQJh6AYYkjaqMbodpTsyhFZV1t1N/hJO0sRNuJknUjKaCbmEW9cfdZ/G5QpQScwp+lD8+BRRcLlHIx9Dp1BD7E3QmGeDhtU/+jN7yuGTAqkF+StHgT1FRTVilX4C5jLCUp8+gk1XrN2ZbRye3NyMXO0f1yF++xz6AImrpN9Ab9YiIJb4z8MwlM6vR32z1kIABE0pKiM7O+TZF9krcPtnySIUlNznlU5WbsiAEfDtZ/9ppOqhVPLkQp0SLpx2zO8denK1lFBDksn2UVmXQFZs9PXxxQ2n7aXT5GOhP0jXDbUWXk9TYaHtF7yu7s4nx5ejLtKCMcZX1MdpBEiQuqEHxt+7/b8+kFsd9uYUG0BzoiduAEQdS2A55LtD7t6e+HCf//TrrJ7YxUEpiz2v1R2PSFgMl3Kl9fdXg4DLt2VUkJKjFRhh0SrmWSHb4BffUXlrGWsuqeEZzkIDCP3Ho79+ZatGk8aW+8qmry6sa/fTSktzvZ8C8Xm/RN2L9hbaTxZsf8Dnc0K3QsPmVGs5DmXu5UD+Eh7EouXcqKRF0S8KNTnU/1yUqBrcp5uv2r92zD5YiRyUEyRyY6Vb8XTQbefg1ybvfFBtBmPLn4BcWF+tEMTvEJvuBMrY+g1UavkwyiqeCCFqKycTHKF/QJDVbelHn4J1kNAAuZKMOciSzhIzccI3GB5hSCim+jDEUv0zs0xN/JW6/BK0kZz911J7TNA7gQoYR/7OataIbfD/VaTU3rS0GOjG+fvtnMVaq1Eak77hUxiJUBeF3uDfv72j7YMt0bnb5s/Q9OYHiB+2QFqlJPKcCoh6pFFLLpNQE7xufoi/8dm/nPEnENbzWf1fSypJHMleiJLBUDcm7pPwuZqGFQinwAVQc5Q78qXhaGGVEJcpYaNuSoFEUol2BkFs+Up95B1wEZAqwTQqirCyuGmhQSJVmMdLU/pdnbeMUjoWxRbGB8JY+jKz+a1FSFgOkc8HrGAUJuSb0csYKBzpZgik0Xv04i+c5rvvjqEb86Eb4hW1/6Z2zwbyihPoIFUorymloFpnhJttlbvn2yUMIrdA1UzH9PX6f8chdhip5+rAQGPNFPpEqvFp5uE6wnuErF3+AsyPmjIdTc29HGN+L6iwh5OOEWZOO8UjFbLskmlLlTSrzf0yINxGfB8HHTmcUmujwpJIwJ2lS30OEJJZiKs5tRJmj0KVLgFgCnrv2hmCAuh3DJPEiJbnX7wAiDX74iTZySD91/MXOunAHj+KITqGd7NhGVG3ep9I+MvnBkSjsD1cmv6GVglOmPVoYYr2qTIGGW3rE5YkTS7MGzSm7uKTn9lUVXPj25DGCsMfrwJS+9tINJCDYv6t7QLuCa0r8XjndgsDmes+tZ4pIPyAAJD2Wvt4pj0b9OsdEWS78XLPW/bsVITnf93ejB+BlpHGEwLzr73fbYgpdXvL/vtoR9hDYWtwMhzTAxdclYCQhqQ1iaH3K8pQ7dr+G3gM47I1U/DXevfvnefIm5HXcFaa5qCcp4Q71TLvvbtt1qlJdDjQiYZ5nJkMJAoWGmvg5RgYMVpEq1/ZrbW8805VhTmxYpmk+W+n16Ikf6eAe7V5BtVQ/mnz/EgU1S7jzjRdEf2PPjO5MakLZraIN1IR0pBHlf1AUNoU6OG2A4v5qtCKA/3qeXvT4Ih9R/7vfQ5TCLOmCy1UcA4QUyglLu5xx/wpBPD/2RlymhL7qOqyfei4mDdHNDq6xs2RvPBc7kQm077dVF2/72RrWtZrfFrwHuJuOKJE6PU8LUkIvE2IjfwQJ+h8itYkDzhXXfwnEeD2uusZ/H0cgrOWNHAX2eolFyXtsEadOcHOM0EA147xCTBH0QHsCBc0jiX/rqXNJAcMoBuYn2sEZ2uUNxDlY547jUme+krp9u+nX2ZOkhpWia9kQtbWTxQVJdIdLv6wYXapz/CG6iKpSx3poGREhsHi2YvyPbtEnqGZnMQl1kfgJyGQcXI7A495A1f2uxekxQoLtCy8+iyKkEPD48ph01TbgdKEUFIpl1g4SdecXWkTq5WaC7Q6ZBxIOUNu37jX2CLMu9XAHuFmHgv6k5BB2uuobiDIofjd3j2hFIRQceL73Bz0boqo9+p4avc9D1DR91KgYDMg+CKpulUX8tH2T6L/X1ASxjN3R1xztW91kvdT6bL3PSR2UNgNjur83Dg6MlSXYoyCyirysFL5UitymGtNEhw39hS9QIz8jCs16SrK++BTgX6yhQn094JQ5gr/S+AoqRbEAMCI05y3vUqWdJF5yn7RBwatgEWfyg1R2wn2Rv6Lvx8Dww8KP3oJB9NTtjQr8JoWBwuBT6OrXzSTmD1TQyMxo68J+186QUj2kiNdi11TJwlI47uUEcdpVJj7V3CXIWd7HPvsxli5niCnbMt27OkfXf9dP+WvvoKcucoqmcf5wkHgbL8rH/iwF3SN0kNJdWZf23wVvW419UKXK8jejLi+EH+7OyB7dZ9cA2FWXBRo6+rGxQg6T+YsKUqHO58M4AAMg7X7/KwQ4OXSrP9WHMNixrFOrlOoNFT77esp0kQap0o1Sai96f7fQc+Myb4YfuFDFACGG8IbUCX0jbAIaHoskVLHB/FGvXRSD+nPmHYO6I2f9jNjmGi8Bz527gzhH5JiX0yPpu71gmHp/9w6n1axoJWmSI21lrMj73Vo7pFgq4XBLixFGDjrtYPsLzY8mznFSIeZnjlSV3O0KdVb0d89X8MZb+7VDe2W7A3yaqI1XY830HhcNkXpSVnfmulbYhJTZL3cJ06CqOEadw4+faJSd/B+c0xxPVZ/ipzTzpNCApS8Osf084hFGqC+0nz5X7LCSXxaT92WOPrJN3H67URXiLw9ldYmQFDO+X6wS8zeudGkdLOvkEcuY+YeAzS6+G7wbfcPAI6PYUkeiQ9dThww7x6CjrlCiR6QFzdvTt1zmh+BkHSj8ntAmbI5xbcclS8/sRx/SMzDYCv/ivqZpYZ34bkfNi3/ORUP3quNkb2gMrPSvyF4+lrL1gXvKcq241F1gREOBsUUoDTTTxG392E/e6qozGEdzX3PfTNECO8IRxKhk/q5yWbwI6FTE9cX8eZllm8wQvPbUO/orZ+nZ3QthbvavFsi/e9Tup9QaETlAyGUbhS8BjqCSX238aMeJYriFRHbN0nSX9BJTw+77qT9pvPq9v8SWsW113Z0ClEaVh5Qscn+yQAVNhuPefwWhGvo2U08EtnqjJH1s9Zb2gDDjSnb/hqLXpBVggFge76OgneymU+qDJkSqTd0oUJ+I+XHksIQDDJ7fkHh94vfMzz3QFSBj10iNh7nJxA+bxS4pMzO387lI6Zxj14ZISzj5sItol+S2osHz2rMohYErQnwwCwBVdHetTfoNrif/XbWrfqMmtqmSBqtBNqZ7Gc79ScUO9O4DMbv2VXWA6S1zr+UpoWDukuqNE9JNNLfJPGfCxzX3kk998NQkPmuqxkzUzJj7cxjM4uunlqhqu4inxZVcB7ob3A4qb2uJnTeNTuqM1HGxU3J2SdRAVh32leX8IOqku+/8RwsgEXlCfz1BVhn0GGSR/HbnUyvxW3QGSb10YzDDsrUCuM0C31hI8hTo69I/I5LRAewMKPw+cvas8nqTxhJKHPolaEiFnsD4koNz1Na3/6kd5YzTllFfXFidLjKX5A6xsVaLtH/OAMLmK0Glwb7TPIP78VI62rs4MWd87FQooMiTnV1JAv7oAB6457WTnsFF2V9lQ0e7gdWH8U1Su6rgvaDO3gLSh/QDjkq8BQxPzPCsX79Y0HsSREAvUNP626A6HCmajhOaCVgBjwaBcaTw/RMp59rCeRkdufB/YdPimsKOXm0fFkfCT3GSmyoqH41zCUFUVzHcP+peg+szWsuDrUW2N5AWCE0e0MKM55fxbh5Wcc91To6CBxDxFFM2Hdp2XybVQUycZgu6lBKYA8QSApVTzs+FNsGKyu74RE7z/usa0rjEytg7FtvNtJzNvroea0NGPscdhPt2nONotsqAiFmoGRkqVgyt3r5+q5cqSJM8luPROe95fh4IullQKZdGL4kqgVIFnzXEpxXv+WImx5y1dOMUkzgduBB0+ZL27e8mDCAdQCCS5dfSCzc6a5KpfwP7lRsVpcY9fX4CKbqFGcDYHKqJzlC3j/D5re1nKRoTw3UvxvJz32rCaN81agUBwaBicuY8RJ6hM1bnF76+DjLrbUnLSIPWqnsIYF97AMPd2A9OkkbPB13NvP0/YOkvtYH3oTOcO4e0RTqcPKuw3rTPAucGX0FSmGAi+scLq6VmCx61Bzf3lc6P5WUhaaUJAArK27baf5YU48XWgCE3JYCKKjiBLSEiNpbDI18Sgq+RyS0n0ccIzAJF6uiX/6cFcHz56IQHrQ+eYrU7b1ClzR1SfvRtZ500ciErW1aICwqHYCSQOXRDxWFernIxjPVQgj/OCw14ZDlykqDN6a0j8/ODm+X80Nb70W14AHflSMOvC0e9/MKo+nnD24ccuK39xVoP2sWzu35fup5yK03kBkFqlIb/oaue+VQNQZ3bZ74F3/UpxxDhOZes9P4tjV/yxLR/75dvCOSP5BkYqjGGP/nzshQYP7ZGxuutdK7fDEV8qSTVpF93X8Its7+rWOqDmaTGtsugWI4Ntm+D2JzJTIRNkFg/ZhY3wY5z/t1hxI6VkzOY+hRkN2QfLMvW/b4Iww3/XcJecyEDRehY36sYPvdfs2ouaod97/3hY16O+oeI8sS0YPtmnAZpB4dMJi362apsR1tgQfJIIMlfX+DOZZIW8gpeYSPHly0jggBrMxoQCm6bhwEUEEaiMPAWW9xsgYLdQuMGMq0GELqFlTU5O+oBLC6NP7RbY+pnLba04qZ+29ZRnY0kNLuGLwqXrPgXVX00wls6WybMyjs1bWNp9d/V5TaZP9/NgeNKm1QcSAFWJjDABlU4WSM9WmKBXg58Pk5R78H1JuCpWUNkbuZPsRY2xkpopuKVLj6ADsXwXenievUZTUyxqOuNSi8Zwkd/5SgQnVl//iaHH5Y3Vdbk7wHyWMHUm2E+KN/OFU9hWSxkBumwZAXSzA7Xd10bBAoK3h42ybTxWoZqHdFaP1+9D5Yk4O9igJ0kOqG6zyAGZAIKlZrZ14Jl4jr3RRqHpmNlUDjN6R7J5/HIoyShtARtBWYa+mR1B849iFiXG7lUeJiJhLb4UYkxYFef+lRslh8Scn4B4rwoGR6KLXDzu++qL7192oQFibn1Gf5ukld2THIt1IuCCLSJXUZU/yB4ws5uvMCGMHetl1R00WkDkBGCI6q3MLNuIbZ1lYCyctXWxHGI3oIWnctWgfuBXwrub5UbzCllviahI0Nijvm4zgaTW7as0sLIRY4pM5htka7q5Eo0GMWVtbOIBdafUsDm2CEX3uaGQ3MUaIiYf211ZCM+do6fS9Z7gx5mEK6uPjH/LnXY/HpOrpJiGuM/ZXWV9RJVnCfripF3+wXhMty39sXlypSM8WukEiNwRCM7d8ITQAiJRAc2aLu1O0QQ5mdQF1boXO+vz4lOoiHu/NJLdgwaOyWNDwTmr0s+SOSLZ8u0VqES3Re8Ech/y4sDMw8M+n2wOtjmtG7ctI4mbQR0P+huBr85No5IvMEyNtqwpdhPjlX72nU1RxZ7lyyvCXShEvqluuGQMksqujt2W40zVTz8r3tMi6MNsA81O5ndkHsbtNxhlVk3F2OGnw8wkIkfubyYtpVUBe+lluiIgYI37yvMPiOjyDrNTN70cB/IxO8Alv+ZE6yUmfIxXpOKkAvcYQTvK+er5KmnjG/yCqJLxSbtkgrhBU3HEBUSgHEfhpYamaj9c6u2teq76Hdcwe8wB7O78Ah6Q/LNvgubA1/yULiMzzakkRXYX6RneDHPHzf0Xrr5OcrKqXYJPRbv+RuVVNdwvdkYCS5wVilIbbCGrJzu105a6hCZejb/rkHzA/rFGCAE8K3DyWIQLuP3Z+aooTAgfziFhlyY8rsABZKxM5/ol+5q+FhVFm2MuNYRE6d+uYgq7gb401nx8o1ySZzK4gAirVUvlOUrRjX+1yckWcB9K0gVYdfLB3un8ztzT5fQBsTgT8VMjY4qQnwrbGIz20W9Y7wWxZ2LZfLsvKTFxuVEGkEJ2K8viZV1cQBm+niCfEO7U2ZXy2OMKz1CY4d7ZbyTpXOVXRtmOFHWquJied4YwPIfgOyHz3tselVn/7e791p23EjTRZ+mI865UAW8uYS3JDwB8EYBDxDeA3z6jWSV1JKq1N0zPZqZvZdUa5EJl8j88nf5G9gXOgG4NNmggElPcbtnIbst+mc66PaUMC4rYU4glzp+QElB8DlHBvyltKJR69xA0hUWXyljw+LjU/3pvlqeeY9Shr9oMiAEDIjYv3T3EUOW5xFpL1YrqY3kbA+fVXwT0FoabxI9e9ggjZhtOAXPKcClLFVc7FDCtia1d9bFDNdytfXVQAgD9UVuRr8dbcc5Nkq/+OSNIvE+f2BLNKzTThcVuIlMxGP6eOOuSsWHxWG1I8sIvctvoCnkrOIVN/KiYcmGoYhgdMPiqbJ8E84AmFVTNZoG6BpWO6WEWs36c1w6cCGdKfXozuhgiVhyeyGo2RxRbIbPzfU3BYfsFnYUPmzNl3xeek1NQz4riuZzMKp8j84KbECiOPQKOmYFO6lM4QNzXbxR99d0rwiF+2zOm49ejL1xkGwGM50e1qV3hV2kkkH78XHJK+obNhdo7wewm7Wi456Gxj0qWf4aAM/Pmp7cYttmWekau3me4wlQ51hbiqEQVk5IMW5lTM+AQvUw+xbM3ouTs6LL2iWQ37z+QOuUhRzo8UDMwwXLaaF14Vb7iOZuTCy7b6+vxmx3gyG3GqLjivgTlmuSK8RoNb4AinCD+cegPceNXWoleHhuMipBeAtVtCCAU+NppanCU+QN2jyPd19cNTIhWfLAZuWlKYYHD3eZhqXciwo3dcwzDmXiFeBrnEz7VPeseZuAPQl/Mvgrf+AvT9gtRcW9/JZR1PqacPXS0C7JV3Xg9G72YAZFcewdHXqeG/kmKTd2se3l8Gwq4y45drhg5bdV9wn6Uhz4l2cRtU3d5A4np0bx72uLluIBP+pD2pFdAzOndwpWb7Zl39ol0hIDArP6VgA1OrPM5F3KqoXCUNX0Ws8pNXj2OB8wG8k02U5ZroonVdTKgwpbldoYNrUMy6k4V+fS6JFkr7Srdw0fDAHmRZ297rboJnyfaAHENn6SV6TFAzlV1UASheWV+k46XQQ4Uv2w+HiXs03L4GyUbuckPbtZrFU1csahs2Muo3yUe7ETP/F7fr9hJym/ZndeOlvzIOGifyWKm6FBEvNAUgHK0jlBkyMTE9xJVC4qcW7dF/VMAhZ2H5nobAL2jBdXez4kJCnG5cbFMMY7VUqSr0P15c5wjInOcomM3n70bCEyl+v07o01T9OUy5Z7I11a6uIWsyoW4XRYMTbvcX4tAhUCdLndkpUuP/GqKDSaAJf2+x02LYhkO6W1RjOtn2GRCh4rpXOwZslkYrtL0Hum1TxRvY2xzS6Xp3sOSEtRF7d7JIp69YP2to2RMO0SV1hi5izAkSntxj7g7RIcSNW7wTo/uugnd7yQhWvll4xZocAa1KSgnrP4yecZxfYyWvw2mRxvHitQrsUX8D8X23zTk8F5iq4jYlHHX6I9IFPqdo6lG7OKek9HF19n5o57KpXny8MNc8U9g6dsp7fyUwQoeXq8JxLRKO034yJij4KHKK1u+T4qb72XOZIuJ8rew1RSmn3l6gzAsXuoNiEVXjO7J9raenXiXHJxNMQFcZ6avltOesyaoAioKTHStjRDGzqGfLvkxo4NmiJ7K8XViXv3PB83r53R9/TZajSDG7QccFkwPabpd8Dr2GxARcaXuuL5yDqxIpcb0PcUaPEw0z/02WDu7GGlktfXaxgUJq6OGGMCJvrJ1MXnnh97wUMX8MUldFGUgYdEZ1wczX0Fp1cWKvDSYUUDOXspbeQmaS/Z2PaC9AEXnoy0gUPohWtr5mOZl0aenM0l88RB7v3RyI+GMgMUGnb8RLfhk62l9Qp2D/aW6xyfWiBbc2H3Vh+TiB9FnTrIAuswYujKCN/Ii95U/TCs29kih27fOB9zOb/yqsKK6AUg1D4en/y5HfrUu4kLxy00S1mYn4LlUU/sk9gcRyM1+2wp0/4r7JXHJfk87rW9eabAI4pljnmGHNDc4KHDnAisR2l/D/BysRh0Hs5y4+wNqGk+adYMduuryZ2ivETXTLDUEDDKRHZd7gGiMTuLe3PVx5gY48ZzYR4s/OgZctQOKsCHFh9MxT5Z4oFP/eb3i9a+pfSxcuWylGZ+M6rghd+fc5ol0ORdmvp1o0NmOi4iK4OjiSh2XylNYg1OjPi+FfU0lMpjZYatKO4a8Bw9zykjTo0aR+sGtHlYSh7OK+UeyYa0lw6rirZu50/h/pHvAA8R2OISXo3HeIxDC/b2Pj6+rMdG4dPwaQ8IHufgbw5/kTX0teosRSFyUnRwwk5ZFYdhaDGUDakj4A3jg0Ge9imx6ivdwCbTnUBOZg8X6mxhgX06zIpU2cqU/I5UQcP2o9O7uE1qPakBUk7xgJgim4Sq3id4+f2C/PNaJkJqM9ajerDOntgeoMQFNwfBbqT6WzOTLCjShi7AC7CwmqTgXv4R+DZd1Cr1Si6CSrncJfmQHf1gVK/j2qGThjs75onS4ugSWTDHdAxaNoKQyhaPHoKLSCt5nMjtE1uP9WJQrwpvpAiidQMbDSju31W0zN9Dc4iXHhAv4rmt70d/yclL0ElwcVKyq3qRxdBU4Seyl7krlObNOEbrXTNz8yGKYE/O9Ee5LnyFyA37kctvJiP11+sc+2sYNv55Sq7AMyzs5dxMvc5hbz9bjBb9JgqP4SfaqsD6ttPgRHuLfG5bludGlldNOrH9LZFtA98BSxRtOyxneorlR9xWdUE4QLyr2FBQFou3DPLuw6PkrxIuUsq5fBIlCW8EJZzC8Q8Nh55u/gJb/vb+QObJCPVVAkaHitQcx0lDXlshZLwL5ja7Nz0xVNwY4mwHLnXuvYKC2PVTgqIPrdB1UUnVB5jGGuZvgL65ryema/UjwprDjWNHyPkESrzj2OzkcLYDkpj4ogVqIh8S7l9Uxdi+yqjCvt8Wcr+pQAgeUKk7KUNh3nQtBc21PIAFlU0aeOYCkUx1bH26uJndWNcT0/IS6ayWarXpUNdPZVcTy2zaL6z90iorI9AHQ3Xvc+tCYz/gbufsjGAozrlzwKxZSMqdJZpA6XXNv3M345OOyLmXhr2XC/JSupKJ9xSxqQUbCeLlzd77AcEMF5UyXjf9clqPmjhGp5wW19S7pV7HR7ne01pH3g1t7lwjk1a4OIZdAlK2bowJecHqjuoNI9Q4F3oe21KZSksO159um+MhQtesdhu0mLQiI7eY2/YeH10EyLH6QgTSUWBNkwKsuSdq6DiPCFg5w/NF3asHGuk7k/pxyQ42hwg5LMwDfEFizoFpVt1yW6789GCyMl7Zm8ZMT3QUOVFgvBBKj5umyHiqhcPWIJ5ZFE7/3Pt+ScX2UlC8aCabfccDTyxSf4LcfiNWyWE9qh/b3izBtnWY7wJBJlytQRWDUN5A+Gnw4Eafwo3eF93yXc0OEEKyUVd5QLxysFlSakpXjaPNPjYzsN29PXZxDWPOq9XTNVRMkvfGB/E/j3KEXDe2J/NoVSQ5R41Hpe2SDFIHJ/e18eBd7+hB5Kuuz9Edzp8SpdEBFNxn++nQwLvlznMRbd4xwCVrY6+9lWNCO8bahYjx5wnTD36hHoSzv/z1OUb5g8IOZoVAiR52q/uIEnXHtS8JRsOfdPNSYMV7cDYqIGh7FuhOP4lLR+ZD6DbhrPlkb1nEoftdaWYueznDMZ364gk0xbjyq1LeAim4M7Omx5ImLrPWMitmXeO22nYfuNbpg0pt5imI8gsHVHFYr55dgWXMiuJcgdI3Ry9wsJeKbPhrESYo5PuS36Vxjq11VbOnsSHKQxHtCUM11WK1WaMxFcSDdfS5apXoZKqwqgMvmRqcr2J5Me168A75cctkSc9OWG9OnFb6vl9DmpVe+6UKPHD9zNoUXUe7SunEvAMqI/LjqbP97r+YrJ/QrPvYod8OQtUlAyWAgLwSZ42128lUaNpdpNMmubjL4nTIOfaec03DTpgNzMo3xxKmoGEQaIZJNkH5MuAfFpEMCBvFeKnq1EizxlupW5rrC5TcnsVFAq1hIGLHebM3LOdOKPJxSky8vCd0zDWn4SxMbUoJ69Spx2qbl3zBl6FO3ohDg8vdY/RcB8YW/v01Dbfwxj7Z5QISK8NXNkxpYZ8Zrj0il+eeyr3uH5TwDLVHZjuK/hG5iqhFWKIj0na0DvvuZLIFcmjnXqXiOQlezLofVHGfBllrED/xocI6Tq3SUvQOxem6IWGSyFKWQv7GKsJNwJUM5VghSZ2tsU+FaetLcim1MoiThFAIrRfwDdraecmGm1MmHfl4rwoLIfX5SqHjU9r26+6ymz8hqWETWFME2GVUq5fGYw4NIIQ/Emba0XohlDITX/Nade2noDBUMs9WQfH2tk7UJXkeCc+pX2+XATsF+9J8HmT4Ft8WqonjrdrDS6KZtVTR6+22hNw18LNCy4X5yUinBPX2FLvErj2xebobvCXui3m+RMx9EEzDMone1Q1nJ7AoKjD2iZPkAE1BbpfWBD20swh22/YanlTvY/uEuNsjSLMpV6rH244pGnH6IqTJGUIiING1Pgesnltfh3QMN47D6Ct/KPfEP0JqeCF5pYmnfPfwXKzrI1aLFVhxlfX5EZ7wWg44/zTwWBv1S5P1RsZKk1p8nqFLeQboV7l6dfksJpBtXXKfoUuTsaF0t7t5F5+cG+wO2KMCblo4Evaax3pmExeAjHy84IG55xmx3RkOTMQJ5CefjOWOY6+hNMFR0upnSUB9ZZBTbyBm1THjQnU4GHMN2ejSKT5Jc2huuy5i/P2huHgDM1grRe07aTy3h+bJl+imwH07ioCLlnjv9DQzSaKus+PYD6GmTelQn/pOuZKd5dgEX3LAcMOBJzJyrBTLi6Orxf6cGywcvu7lpd+2VkAAo24kyK71WoOGC9Hn+BrDquDq3e3mSZa98x3cul7KiIp4W+L+EPDok5Z/ZDuL4WHomdqPS3p+8TGuv9E8xVoRCW1MXNHa5O/vQFeWt/1q4emJ3CTyJkjPnNi64abjN5bJPcaJeqOBuLz2MXoByufT/BQ/H4K4yVDDEu7Cy/pkdMjh0waGzbQNotua5tI04xkWcE+kbUWhVMptU1m51f1Z487TzaGCf6jheve1142DgVVWAXjPxPMBwgXBUjf18e21zb3DCoL8VAA5Kq0TR6+g9708YEnKxySFILeetyWFndHOyPeYcF1rnzVX9hmDPgxKZJDbU0rn5RPd+uzdSbDP3itqkVAZvSKZCtqtM6VHqSsNWY0c53Tpbg/1qHgaVTw+lEv14fz56TRSmzfdzIsetzKJGJSNdSYBSNHLOvRMEXA6xgx8w6Pp1syJKZnDEqDPownSKdRQYeunx7Qd1aCrWkuZSPgq3w0hMqYa3ci40OBVO9Tb0x2qkKeFjpvOaMzw10vBs/1S3Tlc0F4OUXPjhKqZKGw7Ucujzo22Nluit0SxftHAQRdmxRINbKn7F8Ua58bjJN/WTaT0RXU0Vo4pMTD0v0elMB4TdufO9wW7N9S3yglzR0daMSomG1CKRDnlmnHL76eDenLEMnVeBmqsDYRtw5fqbriveNMWWrPrybR2Zn0BO6JTmYwY8AJc6rC5+mSNuaQeB4AMMs5Y4yD7Npo424Ox5MwaCWbo1jqGZDMQJyfTs5f62udcwxp7VfbW4UNfeY/AKJ4st+YBNMzN6s7A8/siLguc3uc1hYFnI2YXMK7eGOCtt93NcjjPk72U8UIt4ceNgOt9okXlld7g0D3TjELmFQe+s90Egs9FQX6wdx5r9+Dj6b9ADqAL7JoTlYvJhKEzbP9cHCR6PSBXjNqAiiLrUnI/JiBPf86U80FpWsY3JqngmfmkkPc95p6PbcXDKAEVl5o8zOOjN9utMp+BCqmxlVMiURyg12u2iNHOvVNl9uBsLqJ0MelUd1TBXVZcvZvM7bUxRVY/H3ywA9UU8wS2y1jdzdh3rswi0qi3QgsvMRCYhe9vHZtCEU7jO8dpnpdublHHG5GzgMnsT3otI6ni53Xi35D7qqyG0KO2UKVUFF1zKy2iQwWY4Z+owdrz3u1Y6EoswaYPItjj5NawtH/PVF5Z34P00HqnMAlNZAdMgJCBlTRSlepLjuCBbVQ4er2rfOEFsefNJCuFf/uYSFvvpxhnbWKSrQaIcMvV4nvfCyvKRPEInNui3wPDMCvN3i7sKcpCKTGrxlu6ohRFrkZ9rdsBqokHUGVnOHomIcuB4PoxwExNFzn24rXEA5gIptedmGpAH5+M5XUbgCTXgPXL5b5FBGdAn5hSlCMhQsfGZzOwbmH9bJKpJ7+fW+kSqzzHqlh6/OPhM5Whs0H/lXlYsEJcJNW4VVlN8aXpLz0ccMPKTfEtMHc+zgaBGMOsuqmznR1nvz9VjHyjbi4Xrfe4xlb+sMa+lkV0eg9c4HaCH2lH6gpAq8iy59u6B2m+vitpuc2f0rCx4I/7FAPoGEmpHqJrOZIQQeG8vR9FZYT0UMm5QDYYPB8yJsZqeJ9vRq/rx4qmx6Nx7qdAiR6lB3ojoOMM2YZQQ8GGJ1u09VtA3WNFV0O4eO24+WxXKyRL+aia9o5pd0ZuEYjqRT4QucJDhvLgseF0QG+lW25lXrbOYQ+NLmEC5/5PrQaWNRkQSo4kOGcVweya7v3W7cqq06uG3kItb9xG/RRnaqIBGz1TXdsn/bjjQe7W0MFzet1QLH1DsvVQ3ZwDXsaLbBd+xk/9sHa7CuSgog6Hh9txsT1vIxfZpUIb1kgFTF7GUBOpj8ZQd6VYSg/Z/fQN3vRFbPeVbfxLAGuQS3LvcGpJo4xShxXAQIQJT8MtamaVw7CBSu3IqFnIBJD+8m3F5kDvWrWZTOLpG4XN3fV861+n+djEDsl9JiDbi+r7egyEkocY580lC9b9jQjbNCVfaHHNQCPFk/SeqkvzUodLEtucx80dO25GVaycEZpyoWF81f2TgZ/q21ts/+jCFtTiE/n1upX38thGJFHP88cKgrhZQoIyHPKAbu5y0gQXp1xJtiCrsqjvxJtaefiT5g51MEpge7xioJR5YW0bzHOoUW86WUgEun1iMxYhhuUmUd4x69t+9/A/qVqAbMv52PtAq5xX2/LuGpZsQKo3aAOFeTcbhCqzonRpXh4bfqJSHVz4CAyz4kC8yWcEc35Ka9xl7hFn3BOekv5eHyGZvCapU3smiEZoYXiSYtLuU2KQmijzsIYmdy23wS6moGqWnQAdVy3HMKUZ8ll50+5p1XCJxmTYrpLk87E6tjcyf/JdaaesKds+vwa00NcjYzMi3Smu642OLwH99cSyMRpRLM+e13pCvHS/loR7er7EVIWOpOSDIS1atpnKgkPLaryAxcjmZd+ja4SEFcZrks4bOfcg8SKvTxTnvRfnCamUP6OS9mSIS4GhYpcfxsGz/GblRoSOvOzAl75J5EkeofAWUXLXXCS920S6HaPWf1IaIpjOuiObkIGBIIy7vpYz3pdZSA7ahEONFtGnG0zAGE2RhMfbUB+MYiqhEd3PilGPj1JvdhkQyPPmB6SheESynNklFvqS6lyEHVcP1VggOM6Y++MTudIqEWCFU2brzyS2UityhhGtTiM/7Rpw5uVDA9lJ5A/KrAGpbXYS8m6P5KXO8aki4ljf5rCMdveJh9AZYwLXlr2ybynbzzd9Zcw9ybq64mrb620qPB2hf+Pu/gCP34ese2sIWDfdbMakGzsm6Zsr7fYtiq2tk4l5RqtA94O8dprRVgoT0YRDv1WKS30f2A+bB9R+Y16pQqe2RB/t6o1tKT30QtjDYxk2JdVUhoLoDOC6l/IWWSR0vlN8oOQUEjuKEgGltSSN2IYfFEi1u6Y4sAwCXeFV6RTthCf5jomThIDlEf9wJDtSn9I0bsDqtqntNOFyKD+xptQNuykkUeBK+S3WL4Z9rD3xSTslXGqyf9qAYMpYCCQxugHsfEtGXupNy/zEo6m1MEbq2EmTX/jJ0YnOpWXPH5kFVrWPAeMunO83rT37VxmW6ww8xONrgTV20xTvTKzUKTvY0bUVLrh3a1bCuUCxMmyQIXePqazGljdUQ+csvey+SgVKwgelUB525puMyUuNUPgFAsEqtELeQwiJ22prj5rBqFvbIrYleJVz+quMQzcWUd0ntsjr4VnSoMviNMsRZ2SX+v68pMEU0Xuix5dpV91r/VbMu8dy22eneA1WDF7eRzGhvMfRddZ6DmB9bhF688JnwkuZ7j7UvN5GO5HtYpHtFE3nykd+fUlt2ywVypmx0/WwLQlJrV/k50LaX129UPoGKx+PEDgQ93W00zZ7fMXyMpfMMCGHPD7pieWiTsY7nWbxxSAl22P5S23Q3/6paMpI6A+KIwn6DGdmZLyeNWDkqD9Rjmjn2ZI0WXOvMPphY16nWvcCGDIEFrqBOUVqXr+09+LiVEgidTQL5y9LM4nhpRZmeFEwExqK1+YlZlQ/3Y9BW4sJPvyE4B9nqz2eZQWMKLVvdhQHX0tdBkpu9oRmZwliLqtefTlT66feq4DStGSERmqPycFP8VtFn0Qi8KBaGivRrj9ypQArmfjA0QDpmPgGeIvYz31VJDhQyPMuQuDFq/2qUHZjkKFUdROyZfFTGliR5ZdjZ8SXnmM3pvEe1uLQhMDC7ASK/4jQFpAlsupErtCKKr66Y+gD2Jth3bqeHvPppxBN+DKy1UN77n4Uc8r0sJSvn6IITGt22Rsw3AhLR/lel8Sbk17vFPgydbqCj3UwPuh3vaUS/aA1LhzJWOjslrlEAeN5Sc2NS+5olVZEmz6VtvHEmAo6wKFuJyf2XQW/h+uM98YgvpvU1ziztAGkq7cJsbgn9uSsmK6WMA/zhQaRu5bl/JwihXNNjTmDZ8jgMZWfS3BPoUDA2nJlmO1TTKDn4CF7i+rGpyt34Oe0AyWsfYM61VrBZOoqUBGhQVTJ4spRvrHRQGPovR7LuhqyFDAqRt8awQmUuUW4pvBSx7/fhriN0+R2u9/pXsBEOeynSbrv772fEEOT1GPfQR1s9iX2+UoGVR1pddzUxbnfLwZ6vxSf9nrMEk+govLKX48RMYiSKDhUYb1XpI5oIE9a67Swl08dSom8kY3gOuaZteGlbEUZu9UJtvEr/0s/7thdvJfkJgG6VUmjG+wPxJ1x41NPHsO8KFJSX4SABYjPleL+9pWHEcjbMVIyjloMiYVxPL1stuA2OTfqCN1vvLsbxIGuOV2wGwv6IkrSrlD3DxEG+SLZqKwcJxi/7kezNRxCjDHsEvqpBM69covPVlZ07jgiSFvhMPkJ7A1P2/hW2PnTO8iKXjO9o8LFf13BcBDmonPuAdxUXs3NzigDu8M7GFWkCArMTlepnWqskKhX/qJXhIVfpjmTXUSDSr7XqL7ZlW+kt/I0PjFy5P3SP5O+9pE5peUVqWMdVZG6wK7e8Z/q0Tfantpcv9XJ4Itwv2dVxtvKMAfl42MOxJlfzjTvDAtMnxRSjYDYO8YrrV1WyR6O3AcnU6e7voE+rMstUZA2KpX3dDvGSvzk1yTSJSSzrZ6+CoCF4wGCBfkJ9MEMz79SeJ4Dnv/lafaotXv9Vteb0t7pY9+/nce7aQsow/FxOzHVukyIksYY8ON4D8PWcC5UlL+h/N9Qdl6mvs78Kl3KqwG+WvK+W5zqnYGv1PW9ieKsYfspzSaub/oJXIeg0OfnOly1UZF9PXwd6fruuvB6nYsCQkM0Zd3yOR+5lRLfQs7qB+EO0c67m0znJ5T4euKWTUt2fD0RBk2o8DeUaw8p69tsAdnuoW9HURj9QlL033++3eH8djF2Hf7asn97pesbhX1tKrOqKL/1B8O+gCwLoDmavzYVvz4NkO+vfQA04eCypvmlS5/PCFSlX68Z/GdHbHn98/REq3allSAifkLhb68VNWv29by/IURzPZm95oUols8IfW0Ao32dmkfJ1zOJce1BO9e31SUkQU7UXYwFujl/P3R9nZez+f354D4/zZ9pu9YPBGOgmtDfr/j6UKErKlCAEXKG63nzL30AXOTTjd93DQgnf29DfvdYZOrXLs3Sb5jZy2rJPje9GvYpGq62cmmbb4eLKUqrCwq/wQ9NphBJgo5XTfOb9hTPqBT7ITC/tvxy7jek/RGtZT9V76st+uXZAFtVEjVMU118HuWXfvgrAQojXxCC/nOAQt+hk6Cg7+FJQH8VNvHvsPmIpqpfAcqYNP2p737FBSil9C9ADb+g9kHNH9D272Hm9/OP/BhFeZ7SBPEHGOB/OuvgLDFqqwbMxvcr7NttvnUY/gM4/2rSRv5D4CDYj5BDfI8cGPkvgM4PXwn7U6p23f9ieFBSRtOcLb8Dy7rkP1H/QdoF/SmgfiWiP7hL8nWewB2mIv7/rlG4XhP65c////Xhn0fl3yDAfIau2TIAld8c/3ZvcLjrpxaQEnCsyZYlm36aL8hWXfH98Wuel5+qC+EAGODgL+/x9cgyXUDLr/N/ufIbnKBLOEx/f9dfL4yjpC4+y+anP7weglFf3wzB6G8f8N+95H9gPP8qHvRnz7WzuV8nUCsWUua+iZaqvzQw6KPnQY6lg99L1KXRlF6XRC1Yul08D7/nTf+cZf03oeX7F/5/Akb/W9DCrMuHqf8BJks/XYInYFqc/m9JMmBU/zk7+i2Dgf4VVhJ94zzJNY9ANP4LRQ4a+kcyMQah33EOFP2BzIH+VYwD/p5z3K/Z3MDsCUeWrF9n9g/z8gsL14H+YfZz9TkL5eN+Wfr297PzY3b/3RT8WIL8vQAKQawgAnFjLqMBdKU9LsFjKL9E73XKviR9mv18XQKuHPoK3FjYrvvP33ryVVv6db39QFr95wLIN5ghfylo0EveIH8vYpD090ChfqA6wX8RTn6RXH6DE3HtEjDv83fw6NeluZQZru+6LAE9+2VV/mYur/9E8PzvRMcfTz1CkjBM/BlO0mguP8Ir9BvA/Sk4/0VE/gmFQf4gi4JHRvPw9UXz6gD9+CH8vgPtPmNfmqiN0+jn/NtY/vso/ctQif8IlfgXHPr7D/wdRrEfYfS/QLf/MUb/XLe/eG/3L3E65E843VfRh6+iBkz033nX1xv/Cfv630km57H5ecqG/noekEb+ryGLGEF+If8IQOyfAfAHzBT/q5gpuP4PAPyDhPQHgPyRyFEMJfxLJOw7Zfx3ijf4+dch9aFVn6HA2et/6AuM4Ze8+evfv+E8ED/Bka/ff99G4T8+G/60/vEOv5z97e8f7g3/oe2Xnnx39u/vjfP/IslNqjnpf56jHAgL7QAGNlOSD9mNi68f+Gueq2EG0J+y+Vvbj66Phiiummo5v1RAH7g+/Bz9brL/71lZKEn/IykVIb+32+L/resK/W5dcVFSZt+tp+/48G/Wye9hzv9oJXzs5T8Y+G/tfNWCjdCmAhb8dk4isOn86cjPdpZW88/m1KdrsnyZt39OV79btr9a7b+3rv3A1PU/JwcgMHyR4d+A5Z9Lqj8CC4b8VWD5XqORq/lSScE7XJLahQsEMoalaq8Znr6D0C8k4xcs/FNS/I+n+U9o8B9x+nka80sr9BvMlcsyzB/hBHjO7fv+5VKuk+ZDwP72tbTZkiXAn+MrOpOYhn4a12w6fxqyCRgjoi7JvgwfVv8fITnUX0px8H9sUaW+14sx6Avy36rxfG+N1y+l5mPY+CeY+X42v5vw/6SS8j9LU/6MLqbREl0Q/foVEQHWEK56sIa9Q5pU9GB38+54peAV16cb+MUrHBNef7nF14gMnMDKd855WFd7oeRMWVefTdFmd8TmfX3Qhf06+7ixjDom0tUgyIdqC6KXqe+l85E9eDzwA6XQmnklrKC4k/6wPE2rODkKoTJnhYmDxHFF7g7SAO/8wR/pF+u2bXO3Km8XbTF09nwSozLThY05hmjhbstr0SAxnInFjthHOe2PmyEqlXU9hWMz1CSwuOkcgUT8Nl+2NI8ynAZegp/tcjrzk6vtPcmokYOcK+gTEyyrKMqSZTmOkwRBUBQltKxw1cJoGVlZwKT3lgK/JCgoYmQ67fDlU1HKoDioMIAOx2S9XNPMC3EueMqTzIxLhcL+hLMcJ0/7zc2o5qZy0pvBm/K7SLO0TS1deUzaK3Qfyp1ezloow4k7Dx7WCyFRBajIM6FWJdpkoFJHOVu68wnn7kbAKJYcyHuUAPqzL1im9wyacNu+Ub7C3TWH4Z9lvjicUwlutJM4jWkoAzwX89ti7lvC8sf95gu34Y0y5sePg7HPELjmpB7GLjwNFbXSV6GAeXHBUMS2zUTCyHtMscNurqx36pG8McyjM2kxYDxKkgv4QIFXUwnv942dmRdNuYyxMUP8iPlMeGLsFjoQIzzlsp2Nx1bCWJA+oE/OWG+XHzB82hurh4EmJG5kb7tD1Jl1m5XoiQdqYyWxB8NV5uK7hklnk7SkZfsDWt8mX0hndsF8qH9ppDwaN9+8MxTFdruZSc5x00CMmSDlQv5i8E5FQdTP4Qzu0zg8ihlJBTi5iTLbH/nbDz10MpdIdp3R0bDXu6B2rqN44JzA0cCJdCdR0XzueSljgsyiqr7k1RYCz03pE2OrQdPTuXpeMdMsSAGBceQJVFIFsURKjAvNUNHdAc6L9OGKiuN55yuE/SaHyrjIDabA+jCfa4yJER1l+7ZIDU2SUF5sVPlMVUWjmGlMEw+1poQBb2bEBZWkVlUDF8bTMHCN3tlJoUGXxKmCS+Adklef/G2ldgo5fqtq8RHGsWDtGb2A6WeB58O+BSqGfwLpuYp6SSKf+ZK4Kq/BqMMND/aFogxMKQSzL0GCBUbfc8oCodssykDhjFizW7ajphzjzHd34v5+jh1+wcPaQSKLQH2F/FuK0DM1pkzUTIrN2h7RwwsZXXtD4VlaiyN/kUeyMEoe4Mqm4G/d4XzDMvv1nt3RdY1CJHnuyL50Vrsaj10IGD0BwxtO5otG1DgGrBcQkgoqlSOWGfSW5TQWEKDEkfLJtGhdsyophKzPtse9wprHRIp+d1iNmdh7Dp4vxxBy5k6yhCptd3Q7EQVbGtaFqZfhyag3KxjGHiS/af0zOnIhQm9kVpA0vxVqbMBBawU7qFN2Us4nL4o9a81D9XCHL3KomF41WuvwKOLQ85o5z45IuXqlhRAcRMfgcwqcd6E9YTd/dkj93u4Z7q8GCZxvGLiNm1cISBmPFva9iLJ2SY6g1yhhe8mn3GRVDc3AN1ltY0U90M4E/kUJl6u5XZr71WnfpdOZpjWq3ET8qSZwle/YY1uAdxp8hEPczu+XdSyuOHWURkol7TzHVyEFoYiw/quWF/reZRk2IcRxfhzDpM0TJzEj4EB+3MU2BHhC4LufuWeAvywYZg9eA903HgTAGfdmN/u+i92nnkL9ls1H4OI2ngcBcFQrdf/u4Tec1BP4+ZR1GdkWVOaI1tcrgrnlFQz8MES3dnzyfirv0MZQPaGR0JAjaGwH8mvWOfDueQ2XOcEjtY+vD/YZlbrT6r4ZBY3plhDFop/RkSvDrDpzaXv8MdcXVaiUHM2GPXXHD3k/Zx0XhXZLlpLGPJEaXqJYA0Qb2ftgVVHU1wTjMidVs0+USLkIWSXjSlVfcy0Q+uwenhVK3cehWc35iAGjBtwVzf1+UeThLFfW/FQ5ydn4UyJPpwoQccKy7OyYDEoquYYV5mnPj3Xfw9RKCz7IpHf9Th/UNDJ3+cRivcNAChXD5+61/uosBBNflYnDuH+RbPBACeEZaV5fx7is5Qv71JBHVUSm4ep184Q3TZEvMYU2dlPGNo4reUIRtw3aAUfr1kwSus1MYmLC+OmUo4LeE/mc71GPl+wrUN/hkRHGOBlWPMyliDOGADuieHUiRqtDf8oyg9F1lID8Z906Jw5if5KkvNEbfLy5N6K4ULqd7H0hZADFMBQuPuawTyJV8249fDVW4jDTFAkhFGqiAs6KG6y8y1UG4qmKKbzzet03ApjRnUiNaymSdw8jzeIgtEg5BoxZQpSgejuxWHFVe1WrhaR8rfnM+zYfyqYSwIBGKr1wqA/4NkPLa4UqodKq4ja/DVjxtgfbtPIbJKz+BCdlRDwoKGCfFZpExObbuv0GSebqlFQSHe9uFjmi8jRMmHGjaNupCSfvd3FuT05aGfmEQSiiI4WOSJjZgeYFr18PK1hIplwEVuOnmQURanpIoeKN0AbGul9Cdt3dVWo5RMjpPG/9lMLxbOdNZtBEzAwI7ACe2lxVbx3ixG7yZD2EyYvAN9Dik4aWf71OPzRn3NvOniPLi7FgO7ZgeCy/6SCtjONe1moyuWIoU08GpJwKOG1QelJ20pRA+izAdtJP9LzbugQjxzYyVwQANpBl013BMsjtZi+whxKYYYxsmjI8ac593nzIq4ev7oqP4HgctCKbNveOnTu5JJlVH29IvnqGhhtRJHRZeyWpzwpOoh0Sn6VDsGrH581X6vSAc+h5cs646n1IzEu03hVUQknp/UblcLmjtwC75QkDDAN6SC2PcIyMmb8VlDlZB6At1wFEnamp0ty3fIGQKIvoJt/fa5LZ4KpmuahjSYfKw0qA67kb+kyckOQLTRIF0IawYfGNKyen0SnmXGNIwt81bE2DjDEvMzdWPcNIUQwRTzMwIIXjxSVLiOtwmmWZHbeh40uYuJura/dv4lp5/KhT1khEm2Dat65l7sdGBKRvMquMpUWLIxllOEcKT4yxOqs5Z4yAXqINwO0cedEUSJZhoj1w9xxYGcQe9AKzBhx8eyrrXibYgGkBJaKCHYVYwM6t+fDplfZMrKUZB2vim/xytvPio5ipwSF065N8e060pD3lzVJYnxTjBeJHop0JPwRxxcNt4UV5ebkfVZgajX2HTy3VdoPdSBGj5qzrt9gvs+eOp9n7mhIIFFJgC3sHl+tAu5ogtLcbLnv2dkmTC3AUFWlVCKebtb1q8eweSFjN79Im6NbE4sGLaWZnnub91cwHNJAFTyuBghdxpd8Ityseh4knHEjO81yCa5VunkAcpWXEew9nLx7toEgIi1BDuBOq3V3uYyELybBGriM6yBwp1nss5iFvsrJvcPJY7WDaSk4PSpkY3pSUkXNtHJckxSZ7VotZtC332cpjJEhiUL2VVeF4xNjnxuQv1skHPww3+n0KpptWZ48Qo7MMaVmDEres4Np5VuhxDmOy79V5Cw3Pqs+BS21Bhs4loutOc1Zoc1qDlIKxed3hcIq1DPIMtEQUnX/TRqd2mAt7Q85IJ6fjiH9pAcP01ULBeslTfz67WYLkZhc2kDvncROj6KijLYw/gbKzXBEvRPwQX+N4b0lEaq7bqjc6998DRdLymzdMOmFzdQptIF0GTPDOF9aMyO2wyet8pm0fb1/8JGwluSBClmf1WiosrmryzRFwP7HnWSEyEaiLwogX/m2N05zmli5NLLrNJZcFIrHLAtKYEAkzt+juC8hjSIudJChsA0lX2NLXcLlYuPSUHzxmmuEuTkdzSnoMSkpyW/IxMawX2Ex7hzkD05KAdIkLxHNJfqqSvgz0uPQQdYS6dnYqeSeuV5pdRACBkFFjV5E850bOltkFmJvwYdTM1BLRjpWFDItU+5BYTM2YbU/ulQ4rt4dI2c38SlCI4xc5vEkQjguYfbvNbIL1CeAX75nx1d5S8dSqY/VUj4qXW3kv3ON4DzTO6B6QCvz9EKlbzhEIKRirzaqjE0COJa4cua8PoUxx93XCMEO7Nwcwq1FLLzF3HkLMQtFne8rUeqMQHBbQCJ4dDoo/SRc9y5DOVqjIEJ4T3qhMJn3n7+da7nbfQwCC6VEJo3ogy0sQRgV2OJbLVJYpTaYorFdpWYq91/eYDRRW7PHWuKOX3ExXVtu9Prkvw3yQXsmb3ODsvF6Lj/z+pkBWXwucQP3wbv1NUHpL5nyLSqyA+rh/M2eFvYpwN1lE7u4oxhs7db4uMv6psGym9XMVVTl0TCIjKPS+plYfU1rV2ok6mYC6IrxwvdF9thmFBX700tY/hhAeFxsilvel8HCMuyUqFM/Yuo3ptF/CE+DAwSu0SNzpFuTeGcpozKqsTrVoMCrWuCt0OCAiE4x38VrfoxANKky1ZhkLz5B7QLEBBNK7ohjoyzDYS6IGscmmO0STzTiOOSum/5Xl8aLJIFb3Jo/9al8+onKMp3uvDjJC5bf3dcT6HOncT8Gl/FaVVqLaTGEK9YvMeBvw1hetdEDq6WqBjw4SfnXdr9/A8j3eJO06FxQxIhKyPHFxmsJVlUcSoKHupXcJ28dF7qrX5lYau6gmGvbzA6TUU26JKNcmv5rsXTXIQlhfw7uzPzqWneDu/WZLagdqJbMYawptBpg5lX8by+cAKHtaOkBF0aGPtMGoe34cqC2+okpq7e1dKFkvVksC8loOzYzo5dIqLK/fbiu77JcuKW9a/t71Xcy4BWHCAXoIiNLLWMvJX6lZskM4rKCeTQscQzkVy2RIN0i9X5jmLlFFS1CumTDpbua8Dl/AE9amYNCLZcZpZbE3umJsoGZ55QPGLska6oQbeliCX3NUbyihoDTnHKcje6i7nQzC9f2AchtrCLlQOM/6pIgca+7IzLDNbqnqF1t+9UNmD8zXoujuCsKs8SynCeze/NrHzxLQbVg05oi66JQpIm8ZN29wwhHDACsnF3L22kXawOGH40Th+7Gn97uhclZfTUTkCyr2WVDctaAM8bqbzFm4CEBU9HMtNsECKC9BMMY1fA9mgpvAn+t0l32b2oYFUx0pZixbG5jHrl4KExeJ6W5sTIaPyCD5c/jAsDDwayBCdlNylwRNgPeIb1vOMtgI75Gis3VN4FrXPni+KiMrFWbM0i4ZgOZLSr4bCBOpTyACZzdd8OxPymVocmK414nWfCrkXarO2rUFhHHnF4j/BKYQ2PfspTlXWXegPbxRFu5riSmLpnX0709+yluFjNfiS8aDR4aKy76+fbFbVcl8XR99dA9HUrSrunld4ki0sDf8YN7jvaQOGTADH52VBVOwdiiMRs3c9tnHk1OeqQMCSNaX1JSdAxBm+lm5jGHR/Z5SgXk/NEVgifGYfaLNrZJaEhYEtXwt5sPuUXspKZ/Kk0JGY/cmncfXMLOi+FD4a5Ud7iiB0Mz8hZQkeglS97mGiCx5XGqVXapDaHXV9Uh3VxRBwbxrqhOVH9fXOpIySJbn+dirVeMMTtyYQoyl9SXhbfT+gXrCq1o1sIAOh3q+TZHl1YusRL07n0Xc+XtT9tyx10ftXST4t09xxIeKQl0imGxGht4mY4E4MDPbrj2kSvFNUqQ0WnmnMrzRck/NqPPwEo4kW1U0LdmthNZcZy9NoSh2UOFAjKzEEYAmxXKeg7USGhWJTQF1yapAy8o+sU2qO+ElyY1P1Q93G05UlMmeECMoDwtbXB70AMONSFwTf38gTBcHQXnTBJG8vVVnr2bL0559Xhdx0ScqMsi/9OD78dN5g3GMcb+Jr9LAX4JZrPxgYXxlV/iPxxv2SGIMj+DFWQFX7l0y1mMt3YL64FJbgGbFPa8FYJsesIFFJ6S7fF+2nB1cBKmlI45G7xnCgPqhrM+AEFuQC9OCb2kUL7spHPJwrTEi1AJ9TrgzyJsMCfRtCtP5mKp4X7z9e0s0sE4f7Atoi5AAFky1dTEy6x9jvNAAY8hqtRz3l+4w0vgXCv7jFvQXHP1+v+i3+0P0F+wHPve/af6v3yIif+Tv0S9r979ia9rt+2b+f3szmoDxL/hvN6P/6J/2PUzI/0bHhX8QbZZW27/skPan/t7/Ibe2/+Rd9m8D9RtfeAT62w997H/4lNvaLBcBg5asizpglYP+1H3uv+Klfvp3H/Dvvu8/jqAoowl43f31g/A/OALXgV+jGZsqz5IzuR7zj3v0582fhfJfFh/wi/fuvxxqhv6IGv/Bb+9PPe7aKk2b7K8kgDCCfCF+yxh/TwF/EJmGYX/34/0tEfzFefHfIYKP0OL0XiVO+sGfcBbq4VL+9CMa+Bc53aRZHq0f4PwFfjfRAFy9vvTTh9P2lyIENvo/W0RpP1W//v25vAb1av/mc/OXTT32SyDqrwHW9BcU/o037PcyEgz9yBML/kKif9Hc/+Kx898x+f+C6+t/dvrXoemj9Mte1VWbpVX0DQTg+wC+A7+rvm0/sRDiUq6fGP/PP/CL+QDnZ7nasp8Bbj4iGSLCnyiwn3509K+GDk7RX2gax8gfEg34QgSE/QZI1PcefZfY9QNh+79CkKqMsI4XXi0xRLd/Uhu1Tb3/J2jIcnH+/kv1Mf3Oc7bMv7T91P/lE/5HfzvqDxP+PZuA4R+RCvoL/u/P8M8S2pi6tUKS6zaLj6X3YPvpe9f5//tmOG+qrv49n/iml13r+m/fHOPETyjr13N/nse1mqas+flq/PkT//k7JHw3wf8yOP5xRCL9m8WN/FMo4NiXXxSZ3y32vzf/B9AAeGYPxMVfj0nAtf7Wpxk44/8A \ No newline at end of file +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 \ No newline at end of file diff --git a/docs/imgs/kyuubi_ecosystem.drawio.png b/docs/imgs/kyuubi_ecosystem.drawio.png index 19de7adb52ec79e639fc2c6faaf5bc114aa9f760..72d221d1040b94f4b51558d47397423e7ce17b06 100644 GIT binary patch literal 352492 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

uvorb-lEklGz?3VcEZf^;cPqd9wKLYX@I^Q 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

    Q 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
+cnyAU z_P)E{{~a-IsLGu~x;b-+%^3a1jW} zeYdNdmBcmDkz7SqHe&MX4I3Hn`3#_%Vqkt1$K5%|8k;!Uq2W6k|Kb)61T3?lZ0;=Z zJY}r!8srA%rIz`5q)Xp-8XxjfbToRp<4eU3K_l%GM}AuoZLmlHOQoW5pBg{mpE2^qz;@4pI3c070?XcJqhU(clc!r)ioI(#{P2d5=CU(AB&7i!Sv8zcx5p z+-Z5@k?#BHCX+SBx5q8iGyF--YOCe-<2mj^tN?iG7v6(CDIN?AYvy21d@>bo->m1B zpP2zjgpWC%57ToYTX*+w4QRNWqbFvXR^^bxfYkws#D-n|kXgbh{4NpEAzac9Z|(}F zUgnNtC|W(mm`9fQv87i}q*u;YxDhvd^pbjGMR6h?S6F3Z(;HZs=F3dA+YZ696boY9 z;L8Ek^)BjFU?Fv>p-XJwRd0ZfQ?>idjlI0w0Oq)dcO>pp?~6F)`|I=>L9Z8{HJN!) zv3k)8lJv8~U4DkOjM4WCMO18%nlqg{%V8Znx8Z4q^f-Y>lo0BEl;?Bo@DixaT z@K--Rj4VA5fIRU=KF$zuVp3ZWZJEnM^d-NSGS2Fs`16qaNhB?>dX{b?`XbN{90uVH zvy+LOZ_pXUb2+qM~$N^B@K$mA@(y`p0CLXYAq!kSFmp_bBfP8k&TZ|Q5TCc zZ{XhlQob_?p9hD&ArAP6U9CnlV)3w5!IA~+WoY(E^>*?+FruO;^$`djML}f$p;)< zqu*d4q}G&LRrhs#?_AK>99$h~G?XFV%qi2(6?=GFz{wZ>A1;7YvUj}O<(K5IC`#dV zP(y&XEm<{UL*$^5ex;sU+6eQ~q50!|gq8e4`ZjQZQuNhTQJU;|B@UNwJ98jIc0;<2 z7Sq0vjL)H$SzU%z76BY4k}FKJe9Ie@H#~BJZby7CtoM!qV!s%D&wEpE7tWYhXPIe# z1eMYEVyK?n_=a_p0TC>92z=~Dj--Yk0naIIx>hy z4vVpwI{_kttzzAfAm+r1rH;^@Hh#^H-Q&Fwy8OlEDys9;y6vbMaO6Y1+MBpmF8;tw zsz0BVp>g~KC8ABhU;^r`i?`zsX~<>B(r>yU{Bk5-3iuieV>$rMPs;YtesZC;+cJ9OZE%r9Sf zlvarI1KjD~DVxV}my%-zC?YA+)u|RpXRVoASb6@(D*<#d-^>J#I zvUQ@6sO!MXF5vf?ec^dCRZoJm`b9HU%4boofm27N`l&-fY$adir_)QTMb3NNsvg9@rmLgIOD=khUg_mV56!Ku8c9mDa z942CXe8-)lWTj$LCF(!^+Yw?2g$J?k)e<<=V`*QSBH6x?-ZAcsZ7`Uz=cF<$;NrwK zgR!p_3qezil>j5A%Fl&Ziu#+e>M!F%KeUj1Jy^+XPDMyf-jd6;WQ6E*PxPoWRvnB( zgPH0=%X@3l|BP5hbqDkdfzNF-lC=uS?O<`5L?0n;yfxhg(ua1@wQoM3Y}+xxRCP$M zu<0+GLYI^dIHT>Qnf#0%!#^49y1fyY4t|VKg=O%*@+e5hay(9>FOD9CKTUkZIa- zBBhv`<8NNZT#M2jH7(ASRb(wO@Pvv(61HZPKl(K>v=sZ*+BvrsWpk@kC!6gV_gcEE zJs(ozlE3}{)N(TEv76@#V6fbET4nS?C`5nQX7T&DkgL$xM?Tl*VdsnEWIR?=g`Rhp zIi9LytVdb^!!^&gAq!B#^LE4rDM(SHK=ah=c1c`}6QK%_9>~R=J$9u(3c+LQXC+HD z{ASX>KBuRl>38fg`lJ_l^fXC2$@A@EpME{q%N@A6$~!9rI|5m0pKb5<@UMuBh*Qm< z=7o}8Uo2`(iG6)?jUnUhTYuy2tRavpRq(Za_gz@VU7SRx`xTM7`T3L%%Vibk*?!CT zhYy{oY@}|Ow<|6bI9@kU-%q&v;Zp}lQ^F!wJu%o5&h_E+pD!BbADlOpLCq+QaGYy# zVcg>@p)a&GEFK4V4C*X!WC&Zyc4D6t4;5a)$fxE`9vu2A)pcFhz!r)?#(qcoyzw>n zmro}ycPh&sk6aUL4jws}))42I&Alq))xIfGuby(*%PO;Hl-8FM&PK;>o6@@*mDa1% z*XnUWW)I}s5!W&w0m0BOLv6_=je(i2a60Za5y+-@Q5kpVhhNdKC0QNr9y;gXObafr zqt{T|)loyPSO<%7P~n$p1OyREe-bNRP$aJ5l;m5k&Qb^* zf8RUWTc-UEjR9A;C?fh`RnyP_Ix=5zpnAnqyl;&O`MOULM%k353S?xcqHi7lHbN_3 zoxB?%wZDxT6BAP?H65!VAS{kuJc3Bb%8Kc6xj>fiYHzYfW;e>M*1F?4p|7@SxqK23 zvQ%`u5ZKziMLbrMwTcub|1B~as8+7&_qdanRP_f7xok$IpK7EAL-eW} zsj_;dNqY$dz|`dZ24fKWrwviLp8$&DYfPn-Ml$E#YF2D`2XS*_gV>)Yv6eMPbL$X{vK4+shvA6%tYZIeSH^Bh3_ed z7fvjmyD&bu!H>w&?4<1h$JSGI5eY&urf&itt}qhlKMYW)p$wXD#S(;3m99FW&+@#Z zun1e^(F1PD0{H=Q86x+$pRzqWwA&((cUpiO7uZbYYHQL;CcF2ezqeNnojNbdVKxY_ z+Mx=3zT8R{N`sSX`9w%=my+59o}VCzGd~QoY>$qf28JcO+%=T!GVwn9j|rH3UDh~}clbDYjLc2X5WL#g5gaw2+)q=m>jxRtRj zBK9E|<9`QafN8~qBHxNGoheuCXHOwktE4B?T|MbfigBeH4?Np;=j{76_5wXEq|7~w zhSs0z5Snlg6F-IK%eH-ld3D}ILE2lw=1C`!%7@a1UG7sMctZ7CZfFQNUf`K|3YBG zur+|x{Xn{^XZYvu|Ic(ml@|nIJINAv&X8411Z@ni9iR}@#= zR}i4x4Oa#$?$0Z&NO(kRWkmllGL4RCGK~!)A8wbRloOFS!vlr8Nv{GMr}m}`QTMKD zzf}zUF4MHVJA>HU90C;4zYE9OT(5s*W;Paz=E3*Xu;P`jeB`i_Kb*4y#1~$e(Y>SG zP~yM7ueUx>zE>O{`_golp+n?7KIdc@4|jKX9=U{~8NcM%iv7~S=`$<-{c_!2!b}Iz zf5c?WsYuJet_34X8*QW>9O%$NgMnLjjGIt&wyQ1RNpQcwnq_t04_xKV*V$>b* zt3JHAmOv-`TZBhpPnB)Oo<`WR(+cQ`L1|jyUI{m-5?%XCAoTC`{ULGfesOKafykw$ zEsOR&qoY;U!U=(b@@J1i@=8y6D2n2rCqqN(GH(rn1!?1ht|pa3A4{Gw-?Qcnh(Rmy zB`qH2O~2>T!3)crT4J3F#*ziDvkBJvZ(Wm%J)>??t;9$ zybQYl<=|B{V0l;BAbtq*u6ap$c|E!9#pNZw?P(D$R){Ml3aza~l}pnIH-rsA=^M%Z z1&@9UNq7i(H&}Us!vMLzGP%J>HRa7nI8vmLa!F$|W?9$=s-o0jM+r&?MmetLq?hR@ zWVMGEI?5Zf$Qif%H~i#}d?s3?G;x%f*R7rhnHoyL)fb!Z@^?PUk0*0w!ogZ@?eV3mp_|rNwioOEVLGlx-lQj zAd}3Fk=go;1)9Xe!34-|vkzeBu~fBhC0x zk1#2*=7L8*XEoq+tfPCuZy%K$|6cY5{PXFV#nOnt^Umdcec3A->dUjG>T9JCKZ}C` zsw(qBE3Of_<1!k!Icm7ptHW8){@u+!u3$|gqk3S1(4^Fp6{RC0IV;rx&1A&tDR*?=ze3w}Y5yKVFMrfHR*4D1mL#U|p!#GefF}rE zpKtz9Ln%*_fV7a(`^H{i$Pu5`@pl}-$hjG%9yo=i-Xr=Kx~*vW@1M+h^U3k~{-PMs z7e;wvVZ=*A13(Mv#5>i$7)Pr#kuQzAdlLpa_+6^*3rscWuaPVX0NyUP=xWsQV%>XS z{ao{r)gPa;e;XXcJ<#FrHaeoMjVxzR{Q7nH>@wnpE1rH;Jh*Jw z##pLA<(VoMNfSj(|1nzavM=0PrBAEj%Ls{Gu&UyIVAv50+P!GPOg%IvYG`8p~;xv`9!|O z7|)g0NLd$E2;Qo3ECgrnzsf?G&_VWKF0EIh(eW=b5dBxzw#EYZ;TshqnlFdOdK;bZ z*p0dXdPmsLmJN@$K?PEw>J;DJv~e0Rk9Z+HE0$l1DM2sh&}x%5tx?CHOA(Q;(?Kxl zn8DN?A|!Bzdg*7M_VAc>O6FtBX$E4)2Y3?ezg|6aJnqP!uTl%nXqLEAId&%0k!Jm= zgu^R3-_Vq2!SGXNd{!g@8|BRcecfZ-LD`pn6&FRfXJJ1Ij3IDHa_ptWg9x=>2yxu*c zr1y4w(=p(n>ejzZxjo;cf9)eYW$qg4Xz!|yPgTc{uH5&v^djmWoS|0l$aFW5>hXBl zRk2vC#~p9IDpGkCWRUBz$x?n(Zr6tRW8?HAU%yd4RLU$?tq={iBBRv z3}oDaEv22^SOJl<@|@?T)X@{a@c@B`9)zJMdzCcUs@R@uVX2E{)l%^~7f+q?el0T> zOmP?1@d|+FEh=7HCKj|d_Q%ja^o;-uR|3Y;d22?D$20mKt`8Te^78!y0`!0(Pn4X~ z=~@SA$J538)`cvGRo*|`L5&}C)l`AvkBE7gC~$bn_M0p~7)I#`szm7galN9>AzHxa zw$$aiGhFg?rtb=;^)q}<*u|XRGC7`8QILw-n7l|9!L2{~LCA0P$x)ARY(z)$S4_v6Pg?SbmIgh})I_X`$aI{k~+y;2%C|V$cf{&6WTiz{%P=7>*aswjH2oM-4_C zpzmUkEKW-!iYgvdqUe(qVYP)P)ky^yA=`<8Ep;RsW2ci}oVWlA+dwQ42}yg8ySyg7 zh2Cfk`~FyTo^ggJ2yLmDGf7@Yg82906~Mo}g17+#>1)5bubCx&vU%jVmQQldM_c1c zYu>GCyn;pU&)7qUTbC84yP053-%M3&F(Xw0CLAobdxmJ_hBY2U57#Nwy@zK2ABIX_ z4Qj+&{E2B(^b-pXB@+VeOVd%7+VL?zp39bpP@AVtXqe@}mhMAG7JT2w_`Ows_h-bB zzMd4Fm)>xF*v~ts<8o*jTL(dYjqlSx^q2$uJBd3k+C)+KN78`%pz8`?gVQ(FJrun@$r)vPEOGsO+7AroDnBse2cV(w#(7l} zVsQb}Zv-9bLV;M2~sx0X3P%avpHw|kkKLq$O(<4HErSm67ZpPTjq;jLO6#!eI zGWo{)b%4Q6)#OI$8rHj@Am-xChs(!$kG=LI@lsm4KFZSgi_l^qRWv&gH`N83Nls_=i2I!2G7>k-dmtnnPa1qe^9e9;zD~Bv%+TfWOCi z0SH1S%QQJ}+>+%KD%7f=YMTu4GyiVTlaQ4av#D$>O3lcK0G0)7fA+=nRrV-& z!pmJqSr4&2NiFt-m-aiy3~f3vn0NrGHYl52DNU`!&BYS zhr?gZWh-fM!&2gWX2M-SEqWe#LMvTa_^m#@d$pt_N5Lc0^UJ(=XTGu!+5RgS?5BLF zx`~(nV2YX`5f^50ZWAnQ)q6}KpNmba$Pk3*E7_Sn0#7uCBrc@xy&y&qPiBbT_p(o< z6Oz4hxsa6iz*s@5U16xno1NFqif`kQsg`#Y;VaDand}XRE2bx(ns-jlC#O>3jef{YesMwglY@@Oe z^$)iul|X%t_KAfXBj+K5@}v{|Nngk1cEty(c*10RaUm?v{0@5Yj{V&gZ-uyrJ7+f*DSgifTd}iP>K}vjLWF7S4J26%s(>k|=mvhWVj4VV z1Tnm}6`Y?T>{&6;+uyog{`K3L{7`iJ9K^b(9ytMMpfM9jSFuIr9290ZRgf4QE)1L=<))x0)M>D<7N2Wh@SxWvgWXhT4$nX$5M?LI#mpEm+(xwRc^G zzVh+~ktX5iZkl^p)yHhQjz7yTaopMv(ES4IF}~WNWec!4jeNf>nTF#`$c*$pC8KP2Q7JCdb6euApEX0qo_+gils9>PjKE(xU z%!OWqOzU(aW9<}}o9jI#P8Q?rNJgxg;V8;31f?T>cz&>!A6u@%H;$0$*QybJB~LW- zrSfd^5c*|ejy@`_6(BOC#Lk!;(syNwQ7kORhuHGrrruRrL~kJkOC+70F;5_H;~90b z%m6CR4no8aMYm!xWO~yM3mV!G0-d$*9y{^V>Lu{D;^|s*rXIDBAGo&k*rE%VHX^q2q+Hs0Tyyel8|+6V;A4t~?7~6gEHDWO#PBaF2sNo)*ZC zGq`wqCX_9H<0QCim`i-ux3Avugy`=N<)+QsF+R4%ae35P(NlNxNF=*X(A0b*jaW_NboRcDKHS#ScwA~|HZVMg zzawYf6v^AUjmYsxC|@X~&z2C~I&+2r=&CX(6^y<8{hL6xoq&f2SD1kyZu0bj9kG^) zK{$wzat%@4$qD2I1ZltXQ<$N=X!m^lb{Hok^j#wD=Pn+*9Td52x&`xQpgxUM(ie!K zp&^}(4NGtn9WJdMp%2J{tTxN}%_zIlWsTt1l_W`KD^*rVp$CtSJf`t&jt?rQr{tn$ z66C!??|@`&b*YuxXaAI zM_P?9c7d>##3=hrtOKx<&hRL#=p+%a=p5+3BE%1IyLBHpiHI`0sI-YKbZ(x2VXIGeX#% zJA!T(Uf|!S(a2&fGBTHYe|%G3d2U43qULn=YddbuFP1e*EG@7%m*<0I9~Bdds6Qg> z-;DLfP2|GzdGOLOElp%(hJx>j_VQ+amEW%kts2`cfX&VZsY(Xf^l&pc<7l(&3P4Z+ zg$IlPH=S}GmyDaPztXm|Q-TVoSW2ls`dfaa_ zC)jRj7~nk~w-g8GOqm?UFy&^R_frI7#xPxlhKD;yE^SD+EC{G8Fr$RnZ*)V8Y;N!E zxwBY2e8_qG4%JuiH22OSpt5P_^8~klL`eIJX8>ZvRZzI>G!#E*;pCa93w+vM{ar^n ztKajj(bkmeY5#QtA`o+W)#vFxy>+*8bgMtR{mD_UMZIHool5%i@Nn2|VtgVOlkuhp z0oH8E!BOK`XBNq{9^;sF8VK#m&d_#Ps+Wop*O7l$wAJ&dJ9=yA++vha&co~czHd); zi#zk|F>GD;>Ba8&Wq8J!kB-~|=iD?4rP#@@GAm*Yh(oP>ED)lBH@RJHKFvaI|GCce!**#Ft5PL6ZwdKn5U9xXzX@mk81XsY%fvbg#&;rqOl&rW~r*F+{Bri-! zg;dlyrOlyQPo&+ttOjy&a+aEf%y_KB=Xu511sx@Z1*{LZzpjenqxpbF7yOn#>1f#?(ml$r zV(bCxu>rqN(A1guKHp33fx7B?FYV6b#5w7*4{e{lG!>?`=(-+Y@p+(s+?z@M;c8D# zCw93ja9$mWViQ#PshUg%jMCFDC{VtJ2-b``M(mj>vqp}Lx<(@A;k|pR{JF4m3)8_% zLs|FTDK>=#5)VIhSyvVYTf zv)vdf2+bUTh|c-Eq0csA0`|TbK1Pk_!^qyELMZf29-Q~~3OSsvrS<6&C7RR@Id1m8 z-yIK^5m+fUFcG|Le>^cL_a)640}aatxtd;n-&~JIF%>)sRw>syxF*A!11{G3D)cCg zV}X@ZXy8kmwi0k$gFl(YM>9xK7uP%w#V3H~^P^-KxeoIU!9TeGL+lr+pJ9JLoK2|G z696Yum0V|jFWWB&B99EFo5X(d0H3mqpLG6M-LMeH;L$isTS@)wfBxt|mMs z1L+Pzll}I5D+*D3zR>3O_V!(E&G-CO{~`N8*VdPah^B?CZlnKU<&64A+`V1Ahxq$};?U|7MLPlrUR5oEU{dmAm>-%*qYwurL zp#P8t6-M!;my_6~A%l(*=poyPU@x6st0D;BnZH8|K?6n73#3Wb8rf9Q31T)%*5XtemNjnIX7*kjOI!eR z9s%o%Po5&zdxa&7<=UYm-+z!uSP8p#Aq6g8jhrnwl&uRjUl2uHa~HO5^C3kR3z=GZ z^gJUj232XSlyP`}^P&Z;VpkQ;v#U%jrs`7ha>p*~2(0&d2nCv@&=~G|a_d|e4cAoH z=EppWSgFE5wg(3YiXq^$!`AR5Z=ZW|(=Eey=U1VF9Eb)oXAA}cJ=xr8QmcyJ;~Oy0 z)bT1oc=0sZHg)G8SdresrZNTBDJ?Q_MaL4hh!QfhT1_9qBFG?8aG>i6B|0~1FcJkY$UECL7A%zyXuFi#`_Ac)hA2-MqmxM>4fhzb( zTe)0hJlP|#r908uz;(&n~#!1fvld`7Ag^b+Q-&24hLc!a?B`nhWXrk#iR zX(YHJ%ENQ}h(wogRhJNW{eCZaT}lDE6hvs1f;-&pBQ7?!q&Ab9lr?d7K6<-Tp_9y6 zGZu{&*o$ZIyLWd}t&Pm^0Qw{lPMtYtR#sf# zZn|7+8m{*_$At|@UoTqdsV22(e|a$P?dxN3oY-M*;=%j`J<*7Mw2&#~!*TU#|Cxi; zHBnaicVE>-THPZ{HZ`+d2-!1T#Uo8K?AC!QA&*GT`#fa`HqTE8>;YMY(^T?h-+N5r zsxmU|x}fr=-^43K-Kk*gA3zIJ57H-i#~o@R}!2kNCdMwe!>xU~Nu4#R>+# zw~b=c>cJ)b_ifS_f}bf)!W)RVy8Pf)Rv{tFYfQi%6!mgn7cW`+l2hJ8ThpH%nwDG^LC8x0*g3JHfrNS|x%Eu9KU(gN zCTAw0)EHJp%8}QpdTJI8FVO-D5WQeQtmCp9P(ldBw6%~wVN>9JO8d= z8!Y7aZ?mDfv-JvL=q#laGbl}33RFY^x1SFR(hL2@GQa|FuSG9vi~n$S;0HvdGc(y9+=eU5&wsbGv$I%dj!9x8W=e+LM2^Sf(U@GxZnq5!bhII@j2M&yLC1BMtq2;V z_&g4kGL0QKnB#4Wbbo0c0lKzdH;dsHawZy5q7i19D_^-O4@02G9SxhRvN07L z9vnb#J(&H($Q64*Z1wsG_48)Y<~Ls{X0^{2elQYPLLT^>hdm zg?@_wvK$p81t#I`OI{W+H1kA-3cZcWfZ8CJxZ~(sfN1}4=IB%Lc43es+I;`F64?G? z-^)U~0T0GGFKW4oDUI-*{i%G{ZIkh__*?&`i73o;edopVewEt>AgV4lavJ{}lC^px z3UoODZ)Rfk*)}WqMDi5@pqmr&gagFTnCCe-|flj znCoD~uV%+1teAr`c}~M^v$A^%iwhJ5=7FuOgQsR71(81d0ygT%rcTdoLjqkT!-x7GC+-3 zFu8>Lj|t$9IM^e=r?AqN0ABAjq0Z&5JOuGn;-Q$Sm0$tc#1J?Qu{c!oQcw*3%&?%S znnn*t_+B^MU-zLU{m3Z`G6$+o*|*L8mT|Kmf91;t!_;S-Fhr_8mlmfr68MnH`yu>6 z7adF#B1Ze4jkZ#*MkO3Is?afC0KcBmHtcwE=!mkIW)X#rXa&(>!LLk=jA}oyA663Q zEnsnbg#FR;mU;o#OqfwpiY!El0FVvBn}xUCEn&v>9B?(y@4CpmDi3hj7kjne zX&@u=G@c5^p)~S5(5WWsxc?E;>U*>!pQ<%r5{|yaI!5X*m=xEtH9w*g>vx~6r6i(Y z;r&X*B?MaBA;KBx4`N-qiN3i_PMdNm!Y@2`cG0fXY0Gu*v3VIH*?RQR#EbXr(RUr` z$txPUv3bEyOOW^ixIrFo@y#UMMpxR_eFq${goT7w#jkV7cs&g5%C~Lnyo~Py;ckK%JBK z6(XUZ6F;|>b_PBQC{~jBoL}1K&G)w%lI8iv1l&;_Aj0kwU8HK6HJ1f31j*x|&dfI% zv$`#iHi;(XG7$8aO+cnaKm-PO>uXOd+yB4KY%E19+t_b&jz#BY{sd_0aEDV0S zJ2zRWFIz&?0E0hyJsz{>XU3p^*(Q$o`92b#Goi1cirLP+-tkB!dW5$c*a*V$*in_7 zSINplP?&RR9*-(IfH6Y*I9u}`kR>w!a?RcRW4n}}az4?qte%U|5Oc+!CZVK##0%}Y z_e$OYGGwsv6o=Q@S_8}}dpe#h!A7Tu@%~$W$(rU0jr)fF;K7xj>Y-(T!RbH25ril! zTpS#of=UK&_htS`KZi6Kq&x+1U*&RI*2z55Om>kr;U0s<&ty1)ZG0{^_PpE7?J*LC zf=>)ZQ9U?f+Gr(}N{cR&MD*SNJF1_dK)OB%MGyBU+a{WfBb#+!iI7@o2-Wp#e%|I`fUycJrk*P-pRTkE#KE8aJP()LneZw4ky5?c8wknqdwUG&RD*)-sK8>YMTDq>LRj$41~VF2}Hzw7YY{4S1GA7o1@BqA!Z5B zi%H1`0_(Auj1a=?NtP0Iy+YtI>3q;x4TVmk;hF6cMd%;2R8#_ibZJIRQw5#{w|yPD z&>SZ7d74`|76GfKEaXpl){m`5Ii=9dzTf4x)<^anPwAKZM~4M}cRHpSQ=I!WI7GJq zfRD=d!9ONUaLWfR+#^4!Xak2U2Y*VERrRQ{_qbH|g8k&C$X^!HG_=Mv%gVYY;Jy1Q z)~!X=<)H;gH1vMs_t+i@Zv84i_3m{uH~RxeNZtT&1kc}ageD(;93h*75RO!SQ_+mk zH=Rft8QWND0AbJo%=iD>JmIGxpVA@#FLBS(*;}!~re0k+NTo1`Kx1Vv7zEBl8k;I5 zd9Kq2BOuibYeKPmfuIed#QR{AFT#X(6KUDEYGalWK24!yV#csv5ge2E2xX!A`9GL_ zLz+JcqB2e_QQq=qguoUz^hu^+7 z*R&_&P{@A8DHi-vv{A8oP%05M<2XDn2eS&9GwP;&+1S1O?I2NwSl9kW12MjDz|%5N zY-=G+*Evey?T1h?VF5kN#KhH}CYHOyiqL_Mp9)P-VUJ(LsU8Eh63*pQ-hk;m!PA&| z$q-jvdr0Z`sGReseU+&XS>_#)Y*z!ezn2_fhtM9jhMbKaa@!>PF!|}xzF431LZi1u zXp(&T^#P1V&Fjl{A5x;V?Nk?XbLZlLrWCd{XuB={-je`5Dy;wh*0ct->C*8&pFuzq7zCXDIS82N?!h@1)BYt<>N`4lN5z29zxSudgz;15$OT(LJO5tv z9ZO|hP~5(&FSs$+Y;|FfB!ygvtwfP>^N&}+!#;of3B@j<`7)vvW7%A@URS@fWQ1$J z6+d$EI0gkxl381v<@2^KlXIcVgmE)Ul9xm_Rb5kRc_`j&Bcm@(J2EKvO$L$&k5=k! zPv_aW32xl>_(f<4yj>NEBK)9K&KK`D?l*dpLrBNk&X@UyI5=cPN_0~k3Yvdu9X1s9Rk1#=PCbvMS@?|F{C z-lv`XZu(Ym`K;2;W3LlY@zw*sad#eq*gUw_*6_c91N&o!EO~ntGH?wW`A2->r}H)1 z<1dXZ2Ga%x?Ks}Ms(f!D?v>{nmDCYEEw5DV?u2xyuM|4{&-5^|{hmXqhc$VQfa=7auWEt=3g`1->DdgDOzP zWvkQL%6_dIc+qDtj`c7a1D)c%XV;T@rL?mXqQ!7n zswK#htt1C!8u5an@GUtXeblY`F^q)wKvAeCPf3*OKLC^RcTnFj!21h!c-cypKM5j% zDD?Dh5kMicE>8_nto*AnDcz9^W@7b$Oeg6T_96Utmtj@zFLrMZq#E+}K+IjWAxVqLS|tP_^^2EjAU}eJt-p} zj-6Ew)U+JkR+&g2a=Ix3k28KDQPEWG>@wXB0{7dKHVe8bJXFwEM_a=r0kSLhOJQTf zFyZE!B|Xjbix$nG8W5K(rrhxYF3uc95|sBuNfkwM!BFqi1+Sb|nz3Sr18Tnx6W1z7 zVFRrU9${qEvmgGo-}vRdK}6?Hi+8g>LZ@R^4}X<8R~4D+;(oECi{3sjgQ15m*TbRq zE)U_7E99k-M=;X-EBrZrD=%~}1}I$$W@7lSr-KW;kAckeegEAFf0+a0gmzPR|3W5+ z-&0z6QSHOdO)ej)Gc149Ja~V;sBA*S^+bSY99>C&02)ojs3clb7!{vHkNHb=pcpP7 zf7b?Y)r_BqvO#`?1)!-nJ%NaVCMFb*6&hvz#mXf%z;5dnik&PKxYp)wFq*^w3tMPU zf7gep`wLigfB&Mx#&6D3V=dFD6EHL+eQe3ig#zsVCJ!7CQ90MN&?UQffhj{oOAAk> zLWee%a))qMnRZZqdeiiB&+TBG1FV=HF$cKqIWpTd%YTH5c%_)7Xn82jgHKUpQ*}gW zN@4mEyB1BISi!!-cw5-ReW+D(>sgqMp#kH>|M)8%Hm>2!OxrTLGP&g<#_9ar*4|I4 z3ji5-D{wNqOWN-c$#0DXHVS;CkZlYX#N#lO^(GtF&Q$NK=zlSZR>veN`%E;n1t!F7 zCU&s)V}RB&_jH#2GDtuK3=$asGDu*fkusG2LF4BYFOEw4GVYOL$Hf)8+-JLWi`3zL z_t5pPrA(*%ZBxG$(R61 ziZ@wZzGt_J#C(*Z5ncXfX#^O`8cHU$6y>E|O?GJGi3hfT?_3bow@jAl1Ih1V(~~Lv z!8r2j1}ck#VXL9w8T1982HWFL$QT&V3QK_T=X_}rB@In4K)Db6{o7*ydK+1e6x{4^ z@U6;VfS8oD7p{;*G*SuKv^?^Nb+^vMg6nn z9Hv|r^|$%Lz%J5pM616*nO|o7d_HgTMhW>t<@_mgP;$>!ah8d|Ox9zi%n(T{R9rYQ zDCn~NoDv=r`F?i>JziLd?`|Fyi)G8B`N`DEYc()m6O)eUO5t?tJ5@1%gz?V3$yh>(#)mJaU#Q$0R z*=#>8z3p>%e`)2XM3a-gN?Ro9^i+~x9FTw+=Rl|LTNFI0M^JVQYTyaILLajF_vmP! zyE_*ObU1qgjza3=M#Rm9e2A^0Mb$W?+F2iC-%`%G={fX9Frtr#w@vk6yVL=E-mOe(5xXD44cF6)QY zW=Fq_408f?F;_u2JUe%h9oi7QqEn%L`K?!D=~^sSY5=Ywwa7ikGTe}RjPlXh^(=(6E=Qvu z`MYR}eLzB5|FoeeyeK3xtDD<~xMc7JE5^GV*>w+2uB@EjRs1uYU8v0`Cc^K8?GV}z zjt+x+EmJ(?7AYo9Ez*l>1dEkznNKy>z8@oD2K?6P9jUQ92=AUdPhq@x#bD2_eJWsh zG>`z2i9vT@{^}a7`T3bP+I`g)nOnQf;^+=wrc7ypUMCcuH=X}!CnBOc&^4*>vo#)- zMXwr&D^S`#$)yzu6%xJ2Xtq#(|7R0Zn5V54y+m84c6-fKvSgQ#cmSSY;mHM`N6g^6DAi`DNkQslA%8dHCjn#K{Jzf z-J9Qi+cL>Bim2sk89$wu2cX&0YSDsCe<#eT#T!_6l$+5YMmsgBJY8+T0$nAg%Wp2smxL zGZ_dNbK=Rk`-2yp?qA}2qCm70TwF(21eKQt3C(X~w{Sj>js7K^(0+#m#0_#J<;jC> zWlFb7uhBtHS~*KpRwjz6!_zf%_E}_<7G~O6047|nMVwxcOvHCG`UeOEE-&9;@gAgx zu^1JGCre68!~3$>fnijgTkwFAl9Fh_EH;dp*z6AqxQY8craADi{k$JnouD}EP_51^ zURG2$s&dN1ygY-_5b}h<+vpH0%)uDHidOc1wX8=SHzLgn9TLk_k;Hm^QTqJh??ot+ z2l563R3-*C*ho+^q)mUI{tL%IA_ zB|fn*Uz00&LDqR5ZRBFJLjCs0W?n=w*F4TME1om*dLI4xm*qdIsW}3KdG~Jo9MYH^ z)$@!-fM^{e;`;`+tOJ_DluL*2?~_=bgrVwTJ0Hz;zlEoD+USg<^#NES^B6P1f9?1o zkh4py`IRyF*V}fWdFG94(D?Cy?M~yx<4=2E3ZT0__nZ=Xy`DK{zF+XwGC=+FUl{nE zYBc@uM~ukk4~WPH@kF$$a0{LSmF1LWAxF2vYjkc2FctOrPACEpBv9;HDisvbm-2~3ASf7L7pF-pmBsvHNo)+|9IyI7<$1?gN@bN;#gS-_ zt{TATmTxbQAUt-#t11&U>srIq-zva`#$di!D~xG4|NOg^?w=JH5qL0x!!#pzf8A+I zTcfzmegkmZE2}$7VEAbmHoXl0h)NdEtyCRt7XAyT{4?~0ivt*vxg2f_&%al2%ZKqKdCaJ89n7?5p-W{1T>el~ zk~bd=7O~#s#0*rL{$}qxy2XKThQ%G`bl8^zqe=e*=MVHw=k;-YypVGj0esS}UV|*Z`#s6;t znF0Zs-Ay(Gmi@nu?ade%7<2f5@fD#iGAIi|LW*$uoh1pRZMj&4V-S3_wgMt-Y)U>| zpmb_5U+#@bK>Y?)oN2_})dX?e+@y@9?gSqcYgX>hZeUo z{`V35L%}$IKYR@#pF4zjtsp*E#O-fbiDPJ~jYOE7jCDSv%y5@5mBvW@@qo~S(j*-7 zbmYYvu@(pa=?PG}1^TOk`o;Tp}zI)oZ9JpL(dV1OT zGf3mSz6>$lya7&17ONM+nV3-EwG-a@^Bz4b9(rbxn|$D`>2CN%!M!gfC`LQCzp}w8 zvvgT+<3%cRlFz%;eVgMd9KLu&Q^OCOkzVxKk`%D0Y@vq3eY;F4@rC3{&wy{}XdBig z{7nqgpF<$fS(}|tBY~ZmaSov`mECp|%0A5LVEXgE14jvnVtmZ=c6C61O-w~gYxDva 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

`z$_?8P``9$IejI}A6nxs1|wVXkZQIpCG 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%0DDuM;yu-0U8Pp?6aRMf$uj7Eoy1z*rzbM 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 EEI6yu52 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 grlJW@@et)vn-bJeBEFu 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%0Dr zT_A%nkDmhh?`*B+nCDIPi#@V|okUG?cgp!9PSP~O`3V;YYN)>phb!j$ec5;AR=(r-IrCA6Ly20_#V<}-_ubZX4UHO zuP_e@`ec6(5Uv~_h4UK0-FuynrwiRK7o)4d>A6;yGcnLl{=cEvM7}OhL9ss{dwdMC zz-(#JDf|zYa$(8D)OQV@9 zHNOMYP=X4A$ika%!bMc33LdG%%Oa6Mi^;H)9)`c2_waa2<5f(9>C4!cQneP+4g9z= z+8Yb;__~teT91ioH5aaItjoxJxv3O;%UWodY^Q?|C{o>#6d;vwm`#zC7h{R48^J>6 zS=Zt|P1Y(eVSd;ci?d-knJ4lFWb-yZot;&}W)1tOnS^5{#T)qWPUpZ9N87q<#v)Zgm*WcUW;*HS!YC#KOJxztR0={L!Z$(~ zY=B68OYH}JtTDb7v<4&$czAf79#=S3W>$#T}R>5CY))4ri2Ct-qw@<+5q*GVTMkx$z z(+(x=XFBPcaJC2?)zDLv!eK8~ebET&MczQ4#JQnf@wbedMy9Y*Q$E5f?N)ey3+&im zir?93H}b?TYr2DP;jOw`F*zwnG!~r3w1cA^N6^hu>8chPt;NM|B=;{iG6qV{69z=G zLnXDjBb1cNXlY2o%E{o^OMH}~uo9r#i$-y$S*O+EmMBb$Pezf`Bw9zOVFQ9BK(Mcz zyCgKps*>%ZQbr_X9`Z|djH}s+p$yZ%Jn+=G<|v^hVulZ$wKbhI--tM(%d@ciizBWP zBuBUK+pnlRlEiy_KkUX|MS=E^*+ysp8;!rY*M6c{^JV#pkGpM&ksTC8tlmoS-cO14O@W+Fnj@Ee zb8g3hU&#KB2U!3ww4rI8LwX`u8{>0U!&J-`yDebVRd2pdButr{6t^oo`vXUvA$d0+ z3`E@f?Bt~4dWrhk;#XbyKHzuqbI{W_q~4jSY?6lXhw*HG00H4NX(xy1o5L`3YR#zW zs?`M^J8g`1Yu=C?g2(Q?{M#oKw$lsglXa}6LlLC2U>5+J$rGKPV}D5CS)<9LIC28T zmq>}142$PAhx>QR=@v?nI#{a2p)=T0jnu__!YK(OE^hNWo1bNO)j`o zy0*QpxBXoHwK{u-=~Z=A?}t7{_}z&qlgAlhr)rKE`_Rua9Lfqqky{k%tbr+un4eoI z3nuvNC8(;PoifCo@ zH$r6N5^8u&S^)V$EGdAoP>xf@ykHp(+twr(oo7J2j1N{)ip<-@Nu2lZAm@VK0%`S|3`hf80#CT2=m-8Clz zxER(U(J~kkEf?YLLm%|ZhClK{Zsaf@4k{+MF(%@?>8BEUrea3QR%T?IPBHmiY$2J* zWiR>Tkb)dY;BGBuUypePcz3{Mm$aWr@}pjjEh01dTk`=9P73>bNSsci$7kJ@i~Nzk znrz3T?~3hz#L*M?kco6p(%u&f{NXktFJK?njVnpu>M8K^o-dR(op+Kz+i6z!RIs^G zGeSe;9(*k}Au9_&2M;1ZDByod3y8w8LjYwiB&_QW5<%sIEY&#vr(mH&0)OV?mRT~} z(>bR!`Tyn&&eZ#v@6Jh;9ZbqJy>B41Y)6b(YI{B~%9)N`z-#wWtr+kbw%gbiYvZI6 z5?@`8UY~CWgyS~z`f^%_S0al^1H>!HY3q^yTFxxA-;$7F`#rdCs<<^~@qSGM)u|yT zjDlxTon2h$MrX{QBUAKz?|EA6c7`XFgp6kUju7!@p~EKnv)d~pqL>e>3JfBhwGs@K zr6*Fac{Cz?J;~S`eeW((jfO(6p*gV`ng29%x6Uzc_51o?$%$qsDQw6+J%ss!wysQ{ z#;SQAqtz)JlZT=vJf6m;d_EQoy!3feFVhTzsBxp;7;jRi_xAQ*U4}~1$bWu4moS^) zlUGK?0xAR_t01nCQ`66XimcPSp|U;vf{yClCyVg)4zC6#pF~+Rx}NvUj#G+{BN zTFz_kDltd~OL%S2jIMGrS`k4R)y6lFsDP$u-8QS7%!*H+$&X#6Lpar0f8u4oNrm=BKVnp(@;@)( zS2ESM82Ibdl3jc&o=zaUeytUBaYB49*(0yc6&jO0NSXwCDb$^Y=d5wntJS9Pv^get zn=PR87f#f+C}f3ODOS8p%PDkZSD^HZT?=U3O3b$P8r=C~7q~2&m(3S=>$X&HjB%4p zmr8UVy}j;B{gv!Q;c4P(`YX+zfA*x&Hha(jtec?H+N1JPwW z#76ht;TEDyiE;Jf8DZ(eY!2&(;+-Bf(;GSSXc8Q$$C{;!)d$OVTwi_a*4Xa&3CX+0 zN3qYfVQ;E5w_MG@f>50vkgjI+cAw{QjQMfw)ZJwUA6Tv3EMhwNf?m@`3k{c(U)pG{OLxxoENydG;;x0l92RBA{~0v3I5x&W zK__`!wOdYG+Mjx%NbCpTAo1x*NgCdg>1FdDE`uJ^X^d}+R*1&)V}JL4Y_Oo?fpO~A z%(jeBpo(Z?UG_&SEZg(Kev32G3Z~Mfgwu$QKl=S8>K(Mz5mTIM1stENdUHjCo_I{W zU??71(?4}N`ezS|d*=Pz${_oNPX{_g@1|Bu`jQ(_!41tYduX6{Yv5;IRt++DVrjt` zyozQv;S_#YKH*S84K>`nR4ZuyAU>3_a#`A3>}gSAkvCD3S&|)6(~ss#yOQg`qoz<- z&B{CChX3Xc>_bC>3>2@Kj?k`;U=bmwLr$<_6MQNpX z)T`|3ur?#XioUNNj|iuq2rEvtZ|zMj8MUCC*dfyPE4@$a;eNUAyt-xvFY0?n>ged) zb2L5#MHzp+jebn3IDnfN?-*KsGVcy;Qrl=d_uAVRPWaAs*qAEk-aZQORmTYwbx^x&1xfiJNqD?!@%pCTCJT1{=+{4jIy!*`(xt*@S!5ns6M;BM18 zDcD>pN{1Tf@n&0(f~p-X2oO*9Dd|M`f7_Fi-j3@U_dLyyeHgUoK51{@6_V-Jj$T<@ zmL6eZ+Yaq+j#kyO#}mo8+Fl(lXDNO+AF>V zOk^~XbVC}0fk)QU{tHI3276C#z&BI;!Q!8paH&JHRk&QTDUV;($HdtYCzutv5HTfa zO5{fzM={}-VvHqCPGDd8)s#&vC?(@0>VCzBPJEiCgpo{^O(u_BhLTofbHkHy(*6>s zOgPpMQ8E5AF=9_WLPnmvS;`^u4wdR$YracDAh$seo}GJNF+M`}iv&{94|@WUV@GHYg$bX@ZV1Zk8 z17htWqY{8g+1DXO=p&eI584k2eLrLyJ{(sfo3)y)_sJ<3@mhjZha-nHg}vIinByur zrTQOLa^7L8ci|ncE`W8QB8DL$fk6zT;mU0)myS}e&ql)-UjsqQzoPxqAnB}jg#)Ar z^lBB~ipZ(<2)d>JFli3lTK>jjOk;M;O)VrmxicWO&~zNz(XYv!+rrPG*aSmcFu z?Z@tee>mksNLYjUKKc>(j?blML##Eoy8XPy#VV%-rb)1B%k1x9oFdq3<68NFqv5nw z8fGmLuE=;C8Jl^=@*-0Y?CORhzu{GfcJqZ}MEVYtaUJ;^z+$$!3T3{!-K(d*ZPX1#V?6 z9{Z+n&roKd^@&SRcQPIsB4?%?u}@^N;z)rEys&^X3}cQ|;Y5(wO)`Vow@MNb+Obm7 zjNl_`XWUZUuMNb-I##HI#_>#1xcv5JWxp%650Y_9$Qh5uu_Ooh6w!LD6^(bXhtcS% z%&eDr%!kYqv{M6R&<_$K$SYIVr35(iw{cB=G)b~BNN)86%)RfQ=Ai>;KaVQHt=avF z{9HB>V1Z@^Pc{b4M&$I!mz7^eU~R%bkZFzfGf-XaUcdy(aP@H~5@VX$t7pdefbpm608jwdsJmC12h3Vt zvcwFj$2QcX|wqsCF_wn6dgYPIpe)w>u=8|ftN%7FUtF0r1y`}m;V!O zu^`><*zM;x6{*tGuUW0;7|Rt&-y+GAG6|HYO6v4D zSPxJ`85@1{!xaeL^mrS16D7vPL33u1?YZm}81;7Ylxod-C9a&&M%IkbvE_evl-Sen zR_Pb*DR{syy}OFmlCUv_Te@@`jRa$?Od3(l39z!Wg(FTLrjUgy=3ws)q~y@IJ3-4Y%npT1MdWzFUn0z6R3teu<)DlBT`AQD zLk1e$4T^q_NM>(&u^t4@;VQ;S{E*4~`N@?IQM>?wySXlOFd3GX8ZI#l+TkP1x6|Em z%{NZDil@qhKh#3o12+*vf}3qIKBSFVe&fp;#`2gBPZrn4(cin0YDa76(F%;UMbdE~ z9!-I#VDd14qW%~bDwc~!jjw1Q`1@}Uh7Dw1ky>xJMOqC%lp8g`Pvm8B84_HHQy+yk5?}wXF|plzole747A!Ul0@wWhc9+6!R*7EeOxyQ*VkQ{Qd{Rfdpge>K9)i z>EMWQr$1k|PQF{Cou}EgWa0wAsLUM?dL4I{v+>j@6%ZB#AA0x)LH++fR{p0|tPk=S z{2-JSmz;>4I%UwCYugd%ZRekW@5=kN7{bQmuTkl|f;lcTRZTkHcc)O)f(GFR62Ca^ zn=xx}@$~nlwQA-(&pc^dj#2{?W}3J&U2fpeY^v+16B16%)+$tczN7$eKz7Tl82Em5wpdlp=#}Sqq$l-!c3f$%I8#aDcbLt1-Wd5*lVWaBCKBu{?yrS zBI=`(DyyFTS;J|$KA5mMaT!t|6uwjx?TBQ?#VY8i=Mmo`mle+vb)tyEuOTwH0UrF$e9+-Bc{ny&~_rvpk`hGvYanZEJk9h z%!adR-zv)!;+wtQ@#gjZCAZwZNBQx{kLGBMOHLgZU?KZc5vlAla#9RI$Bi>hUY#}) zGPCje0w>RDi-Sdt{Dz7s3|nR4qya$q~}A{eh_UT5gjuj`H4VFn>8mX1NKx^Q0=025>VRM4>T5J^az`tHa3##Zg-Fcdp4y zGIrX0@g&>2n}}X1o{M04nXncl9kx*|75r9irzf>OLedQ`mZxMwD85`-^%p9 zo^Xp2d7gLLAr`H75f>9YqQ~)M8Hw*|o_-C-Wx7}0XLE%s+6Pi{gI)+ zi((PtQd&Tfav_F1LzC+Y;$SiQO0D-%ox$$AlTz(#*#_tRbfH_5OaDJ)d`*gbZ@Yq{ zEZbtLjZmqKE4aSfI?kKdfB(n5J7pN znl{PD|o12B(-VkoY*q>ALqb#9B`e5j_K_6ig;Ao7DJtL z8boRMHMR`C>CNYsa7 z?4M-d4{rKTjD4K!4AF~y29RN7J3om(3a7Z5-on$xuvNU(VUbvD z-7>HN-z7oTYB`1~Cl39M!CVt?I*4OLDbdKp%UO3`ZCyPApoVr|y1V1T?(BI~qt$kM znk8+WsvR;{YxF>H>>W}2t81?c%M*IdeN%Yv5I+c!X!i%GlcU*(hV_G`&Q!T(58JAUIi%&?^S#1ITu$pjsnS$tC5wj33+X1bJyQb2xmO_q4O{DJeO zR6O6#fPjV&2KiEL)MqVpYm7VeEM4_<8~56_1@K<(BzGQ8tp`j&(?aJ6qEnV=qNh^=XBAG+hm;$+u}K97mr^Mv|&x*OGD!#+4i5gk!D%HRd( zX!=4N4;jiiom|ekSQ!+1ytr{WkQHyQlQrdkDDySX?UQWOLwbb_KHrD$nFxewUUbB5 zMK)JK4=q4adfWijjVL|IdnOeldc3Gf-uv z51Mqramudr9mL|>b;(#LapvQzyRP``xaD?A1G(P_tuE#`;nc8$)4zrq4KPN`i4?~| z7f677EOz`+2wk|g-y6^FdH3Hfe7*^WBxsp&Sj#J7br}!eS)>%tVCgJ-*=g^azyEM( zc20P}xAYmgr!CT*VW%HS8T`$*7PAcgWV|XeG7`f9E-HCfYN4)Ye^fqKZ#8`}nHnoh zY~u5+tHh~axUktH?5ExkmO=o-#1&mXXsO(tKm?7)=}FUbw|P1T zPtMPqqAo=5CI6#i$U<}s;Y9u&b#97buSX-Tf15?>(4Jz@>L3mgk59;R?@v@o-!nZP zPvOFA*AqFqcp=Eq>2I+=sS$ZM>#xEXh}OW7i7s%S8MD9&0WZy0_$b5< z3i#vP-J`=Xy5drGbO9{OL=75z#EiFZJ2UG@WXpE2y1*G1C2C!4heD!SsiWO8*ZKXg ztl;teIKJrEmQQd)iV+f^(JxTO%T*aOsm%-Ntiu7t$vI0QhMp+s(p#TF4)j zfZKlfilIpAQN*Kg;}+cH@Z`6Q(uC_u|GB0+C)r*wA*jUmeW%k^6)M|h8}Go({08i; z_Iy1;V&y+f`(o(H0av|FdTRc5H?Uvz{DuK(WK$IQ;^-0MRD|D{!pm_%OiKx_1!4&@ zXB@%HpJDc66v!kKo*tgdtoY4fnO=$G7$@HT9{ApEwguJnYX=fPxn7yHyxhiTxmy5( zKFCXK914}6?nqjl=Q+7i7wTb**QQD{91{QToZcY?=mJ#H_YdwbCdS}GS%0|iWm+22 zaIxjosU01+z|+*7!`d|4_*jg|Kr zx&}$e@J~#y_Gj(y#Opiigj3D8ugg}y@5uqNuj87aMYyF*1T4=HLWoPR8KQx)(a~9n z*ljIA29vas36@--A#PVftjv#b-azb5Uyd#r9t(lkij4s+Dv2`IZR8 zde8^*ayQoc>Gj3n*@H9Br#-f|SKrPJu$^JQ?O(0KS=l^7gX8!?5rV7HMkMmK`BJ8x zo1PrL;5kYW^En75Oo6@8jjV^1X2xjcl9gh*{H_^?J}*HF>-w)Di>1+(BwyH1uM@vU z;%`(bCNmPh+0D!?uY0Yo5|W;wX9S!DLUVPP|2U!|N`uxJINVi`5soh@yPKmh6s0Ma zisswpzNx6dHxvhnm9(kv6|p>Ak4<)GJ3I4ZhK+&Nbii>X@)Rl|4 zm`y2OTn5ro=WTN2HnI;Qsw`;NnsacP;&noK-gWyxZ87(vNvU$EVgq~B;hi|&iIDu)FA6iDD_vv$>N_6i%{sN>2+60KhlFV9j2-O-k`IB}}102(z= z>mV-V_w(SfJ}!FRA9^|CJ&P9z|IK(Jd(KwK0oIKsmAFO<%T`Ob!amPx+b=6HFcyao z9oNHcPVM?_f-1j}p_!^&?upXajkGmL`a7Ea>%LAVmaYM((d=6WHU0x`keMC-+h+P! z^Pg~C>e#d$6U<9F;RE$_=2qJpz{VWf&7a*CDCF*8$#u{G*Uh1cW~7o|46HZ%R~ zt_zRo?Q7Gk$T$>xYyyt}{|r8lTWfpp=5u4m^UDkaOqo;a0M}I5sGvED<5SA&Stx#u z*u`(_kxes^F`y@E+foe_Bx9C$J??Zf{=gG^B<_BCnK=DjDpTuc$qLx4Zn|29Q_~Zs z?g9^=3-DNxXYLx!t_sgtZpJ))3n}ERyuLITI~-Ip`uppX!29HazvW_8Urv zz7Lj&KDql4RCJ&~qBr7q1;}m?QA(dUGo4zz!~hc`h$}#dY5{W)m`nu9xyPGYZyKfm zEhJvux!SuFL=@waK{{kV-;X;L+Ot+~4f2rK zt**DcbxckI3*l*`dQ$ttd@xK<=hNxE4|xCXR{V;XD94e`d;eK6$B{-vjY0+P?@LM9 zt>%t;tXj~U_THZsl2ny5$GO`Z0v@Y@oM16_S=zXntImjzo+ri}J^*jwt3Oq}>5e}E zc1o9h83~n2rRlIAD@T1Zs{1&5Rh+BfMiS9v!vM&{ zHmom&>=GPfYvKQQ`tXGGf075(H~fwooccz%+K;fCUKD?{uX}06WT3pt5g`W?0io>H zuml{Ene*E2(Jd6lqTi!+qdEKzF^92P`&Oxi+RGP8Z=Lb4#PmFMc**$%2D{aA7Z%fx7D+TDqrFEMk4xc?-3XdIa=vn;py6%#<0$5O*!UQZ(uEm} zqlc^-36Z_}5t8E)9K+$1*=&rFkDTdagK!Jks;56hs#zuT4~s5;@%U7n$lY%> zqyBQ3eb&)TkPrG*F>gGpoTv)`ikmOHpv+w*Cs{2Yi$hT#FlQ)SR@SoB0YxG=rbb@V z8pqRQw4Kq+U~uCvOnF7W3Ca$)VJL0`nYyk z779#EvN6mHSTEq`qi=5%CNJ=`NsjeZ#tOpZ?OTjeJTA9jI>A)FW?=tlbzo{73^{RT z^v`|a1t$YWkwZYWmVabq$?88?zj26LlZG8L^CWQ35Qs(rsy$tXxVE#q|DmIgbRqW1 zP2Qhr*!xTCxsd^GJ=bpi$y~9ZqA0NPtXio#OAZ(pYPjlMel}YW^tX`5IZ(i-s2w$1 z#0u;x2VE(9iq<#!*)8B*5v~+L@~;q+vhg-aOK`~%vObFdj9 z>(}b(3$Ih`ukKtd`(&iHo+y(V`yVF91vB=9$IhPATuSx}T)fQ9Dm$d>9riHpah}0x zyz6rB^S^r<`egC{H;Un>_4%U+rKJa`Xr#$Y-itW@ zi`+<&mn||aSEf|K zCIx~S86TV1Il1t#!^1K2?F=T5(NR*5!ge;dmOb;F-~G(w=BQrfb(7I`|Gjzp9j_Mb zLMQ{5JK7%B4Q{^tfH$#xe_86fU#84`u|&CfF#o&dj)y2rkpN)Yq)oKaX*k4&9vcnb z?*ROxKwbMgI&of)uSJ73=AXb9xUkJheQjVUlleD?wM4;JZ>Rfa?jObRg|N^mPv~Q$ z92`+&Ni?@FHnmB!B`ChN&j{5@xS`hgw&1@^p(T<|=S&!*(^E(FBgDtut=m+mZJ!X^ zb^$-`NusN=s=$BsO)^X7c9U;sHIft%dQ9H$T4}c%mwtG!EwWMJL8k=ge#YZ@&{XBS zACO4D%Jqy_q{)$w3K$d@C&u>w3onrej>9eu=H|-0_ijlzWS|5|9~6$UBrN-~rb8Ma z02UfUC+JIn;NJ=QaqxxDb6l2{R=ivnBSn)4LnQmyqC)f{v)*mqiep&|DdYLu-RNgT z0{71rQ7y2MRBbMwIUKqJ$W(uMs1=B7XlLh1)YziSUJWzVPJefo!cbK|5_&-iHFO~= za5ZM5RbziuI}!k(lp&qKs}QGFZB1TI3N*D*Qg^+=S=j2jGKqt`YOk5HhVHn_!p+4h zygYQ$5GrNyG-fD=wIB=YRF;o z?d?!$;m;ayKYQ=eM<;0r+In&VR^;)r=n|{2kh-IM->ho7_zmMo)y4m+QQRm z%D1%`kYzvoR=JSTXsXRIH?_PLk`o>yvI1@2{V~IvrXG)=hkD1m@vg^m<*DSebDYiX zcXgXh*e3Lc<^_0rO{3jousWu#H+6&0AHCKumDMedwjFI+xJ%_cb$K=Vmo_~;y>C&k zKTZuWA!qaPJKZr@vg;Zl!h_$qW8?Pl{aF(3uinLR*3~- zZ-7Z*n}+N1#Dzd^D4c?tF%Q{VQHBNI9yrigHOFpGLVgvEZ60MWc?Z~hFzQjrm&BKl zl-{|>>1g&bL+$)vh~7j7w>5z47xh6M6N9Q7Dn-8oGoZjvC@`^sF)*kHLZ8R;o+*M= zmsd15622a*9f1?L5pAKRU4}slKz+w=V7GXfbVJMbZC~&7`Ec#~Om=s+5*bm!pEv&d zYaA{UlC~T*6uQ0S3>NI>QWIIFVOxx$`NTktxU|x1*?RG+8+D9+>E^$^0H$|9BfPS{ z)A-PA2Rf)^BGjDt2&IsA|GOos!-OAk2%Za+(J|qKP`fexfPFnIELHIBN!_XX{F_Q8 z97u^JNEHf5E{n2Z6X&9Z5%8u?eP#7ZpZ-W&A*RbD0*uX}ol9VZR0nduX1!Ij7>0*B zd{UR%8cTGZgzXAJBO+~#f#t%&L0U{=LdX96IYp)%K&c8CDc(BdG~-jMX2VgT|L$1h z?tBN+SFDMV0-qoOS3rYiu7Ow0piiQ~VmJBqV|CI z#>Yf(=MBRM8uo?v%*)G2GuH#;+zPrS0E6VnLlT8#@Zk+Irn&Vub#QQkqDB|0+{|MaS6WB531TI2YA|P&fyv^j0m3}ZsG1l= z|5^$EUKST6nB)N3bJD5mYBpA7oKNi&s>P|*&eZ3wyPxc|H!k@xaVmOWw1LW<^}>Gn z51TniGv9z)7neK|Qb$Hs4B2jrM1%L|sES_| za^<6CELKWCCRJ!fyuUUgVE(AF;PZxJ@DaqQ;S;&KVmDatf&^L<;(V`w_(2$_;Gb=l zvjw4C7NC#|B#&on0eYPcN$|piqg`~x4&I-4=Xknk;kv?ocRFjJ!c`Zz`9+5?i3nv_ z7U}^f%Ccw8jjK9Ty|sc^Jimh7h&z1U5Tr%G7fnOTM}jS%{}d~MltF+F?1}Q?wHNk`3rm`|ClAysP@l|461j$1H^z|ct(7hdv_noqS4SqCQ)wVSN2 zee(Wb1CtgYBG} zD=P>zB!yUCMgfD>pPt}xYb;~*v)befFK;4k4KuI0P-41Jzz78_+rCpY3ML@~NRHK! zc#nDAT#MA~fh_=maRNoqRX_u7TTX?4q4!f7SXh97B&3DgnK|xw8FY?H0N~F_R09Ls+~QtDvcP zF^A_&qWyiW*>bz^)R~BER~J+{oTt#c@UIm~o4)>pa50`C<73t)({UkjBUrsYQ^25VSEwgRNW^IJE#@Hos%gnSiYz3*=! zOTy}ba?ePPH98W5X**d|TZ?Yw5V(xI+3uLIXM$ltJG%tK*3}rMUq*C|L)MRD>p=Zualm1?W5+= zD2wFbnhCz?L4YCo9*Bq4nT4i9j~xzEO>W&P{ki78DQj}8jSQgE3boCJ#X@HZgSv}F zNUcMUAIF)C7gs~n5En^=Gkdi8$pwq_QGeiYt}KWCIE1GHLRAzBH{Mc01CAd73MM?M zij4XT3|&1DPQn1-bup)Savr)s=llNFEGHCpKnfc*P-jw%3L!3BWynS37d5F|VQmo6 zpeD13i!7|UyF4%)F|s;dA8>$Mp4&9@GJ?3f`qI;Ge70Jvz-7mv7O_hzU}^ga#AMyB z_tbbO7(T7YJ1A{j(A`@^6`SJ3+j518AE<5JD2py4MMKsjh74lH;0uN+bb41)T zk!-I4V(d9i2d8!v4T!*=?nIWaeTN!PPCLBc>)Y1UX_3~$tIX)@i8KzKMUW_SSO0zF zV76m=>r!QsfNO0uTQAg?#XlIBX8$g;|3V*wFyK$y;904u`*bma>kUs8r)+?HqKjWK zp#<=$rl=+3*qwsY6?0dgA-jv$?O=^}W~qvr~908H> zct}A1FgnD_H5H?y;U@oOJTJ}rV6j5d8Qk zG(3K>@y6!yp2II5L!6+2QL!%;7%`;lD2$Jm-`}Po?bi<-HuH(>#Ut_n@kvSyy^tAr z=6J3S%*tr}5xhld?kUX|yMI)fw5k!(KidZO%;LnD4fj?`&_g%{3;4jr)8XnNwTaQp zZT`7#LvVk>z*1GkVbMfv?3ZzhP5X>)Pjz!CzQ}_Yq=s#?QWlxAerT3PH3_x2bBotaxrKn zGNBela8V?tIZ{y|PwV+XMQT{L#{ zpf1x3I~=PK?q_glx`}Z@NVtj)YLB6ibm)K!yVW82A}^1~fMA>oa$c$+6&u9_gE@aR zE8qL1K)Uq!r?)8$cLGZwEjylj3Fn}50dE2k;V$eiBL?MOx!izMwP+_iQ3n-?-*Zej zcIMr2Y@g+8P4bo;D-rqm_LuE;q45ccfSE2Kc^WgNaevI3eb2ZX4RwJR*JKjnmd@H&*RGNA6oEJCLm&O_i$|uYb-q_#}hX`-@!_z04+48-}ug(nN(2Sc+Lj> zyOlm3tAV8P5PI7M4JScmC0qz+sHS#Ke>{bKLwbOo9?9N3%mQszTGC6YcLm*zEX9 zW1KLxN<+;-h|iJI6e$5A!PY$5+d^8BzO$T~nE?(;;?fVrb3beoik~{Z|cOiak|G*IJTZ9%J-zHG6v$=uH zdagKyD8s?dCzinR{95*K%N8l z|E#?o3_dLp@hc|@sziUellxLMMJ}Wez4$=THy|lmG2OkMgHc{_L(0IANTf(A*cWY` zto*whQoVFG#6(oOQz1HT-S`XQFtjdq2+-JMgNYrJ&Qcyyxd*>2NyaShkCqk}Gba&h zz0ND2+#jTY=XeC+WjQ)b2^a&kzDxN*rA`H~DtLqSktk8Bpb$4(z4Y?oMqF>*3C(dr zOu4)qIYPrp%)u1FK-e>6VZu-}O1)Q%wnuNaN2N3bh4Mc5$FbH-id!@u2G=A4U$Hhh zm#BZ*ttf?LfLM&fcNQew1>-o}y3o;ar1=;iIin4HN~$<5F1d0w)`riTaE=@OBD-U~ z)R>O@^&P5GHc#i;8!M$5-izgW&QTDXA4qAoN;5RnUe>s`BXUtk*k7!2Gj}$fj)Zbf z1n_~d9LGktm66BP-EJD0Ux@RaX32P1$&_=$d-Ph-_m^MzLeVE*%5u1a^6F0}8yNPG z7Yy5*fw^cbQqEj50{Au9gT3jHN>YQlR<98Pc!vY9{0{G}!sai$h${PN8S;ube|PyE zm&l07X>4mI9<7lmX6`h(C`VWian;x_4rXc3ya|Lq_c!RfNRcn6oXNe&y}s}&#|i#5 z+Wn66uVFJ-RdisN#L-rQMn`2F%7+4bRfol4I6i{t-v5X*7Zv7HJ6wxm$VImP9#;V3 zGA0`jo;e8kREoOu=w9e&?K(lSjH{%iAcUcO?(LxJZE@>??Me;{SMBYnM3!S(&z_0v z5O@cb;8t5*LkoE3ie|9@m46SwMJ}vM1ElGn+}wIrKPqH3MJ@`6E^WZXw!K3+04G6e;78-S%k$g!xK}25p_=Ws9B_SIq zVMNL$_tPjal1L{5&8i`(Dm8y|T!kq+MMzPK-mxNCjS?dkrLT|FhnVxuP=}hMYbB@ce)M*DhYoRSq)+kpYy4jG-(`Z4qF60r; zvhK5`(!_a=u1h36iGY!}of8og$j1$(6OF{zpg?9A?Qx7S8uBqNeKE*G8W5z^;@-lqY66Js957sa+^mMu1PMgixNX(u~H$Ev6^G?yaxhN zRA!j_kkTZK*6Jy;odhf`&UZaCEV!bvl4Nk>bJfH&? zc9UFvfd`F7$2F zsZ65v(ilo3;uFrlRB`ip8i<%CpuOAJ#Vpw8ZSuZh$utkue3M6ZcPHKIQ86yn(1y&C z6jYv{`*XVOrwVIVo&H5 z>`?XdyP%iK0Pe`DUVm`>we-YQu?4QRW}qabvyDzCqFo|GwnDFtXUUhzNK(fMlWMnl zjrM3nsiHq?TPg*nfuocnlSg?=)RC-+C(6{^q1FQ4jwFpg(ImbOH}m|lov&fDTV0J` zXnKs3#@^`>QYpbn=eF(XL)^ir!oHNu`cciK4#`Jv#E4(xu^;Pxdq|4zd`|&SOj;2A zIpgcsC0)bNTd_jKVD(W$&Uf?ceiuGJf<`z3M@ogqz4lUk*K!U4KzQ!rD3rrj zY4PjT;-BhL^l$E$54eSQ&$t_`q(QanvfT%q#G=)ySPE!{3Y)zcvZR^aR z9QwK1*`cZnZBvD)K?E_Q7QCs4@fv_V_@Y-gaEeI&-xMMHcZw+gCq?-0jOvli80em1 zI2b{6=+pvQrsx^inS9sqwm1B&7fP1(i_SC%r~ECV0b>{#y}iZ#W&Xu<%%noo zvz29Cld*6h8@sA$n}Tm3E;thOSguh}@koC!|NjCPyRcBHSgv;6VjgU)^=Zo&kWjIP z5=LjoWyN}Hq+E;}(&5`yi}-O4r_5$oZJ4pFZSCFuRaRTcU(e$Hq;w%<&kB6Saz(Q# ziK4GN+ZwQS2^>_Yt#;l&S2z^fMo=(B80^CwNARu~Na`}ovwqIi`rJjx(71|Jpc2}p z`B~)lN}-yq0s4EA|By+j&)rul6H@-HnCjl0Z1yVr&UtiXej147U|icpdu4y}FU|O< z*I%Z;LTD=y+v#`04Kfq}4C~2ZI?~6bWtn~vd1>pE%{iTu>=S-OQ0b(>#P-$dN=e4J zKK^r>Kn>b;?N=lF3$l}=V~16b%L4{;+-q4ib9+)Aqyl{#T5N0=EEk_){MAYgZ&+H| z`;6-hggijI0LH>X%>UuU|AY5y#mMV=6r$`kta5yuen5DYJ1htj#G8|aZghw=C*o~t z$RK^A z=JKIyGzA=-O;v$KaYWGKI~H@g4y9B{@8gJ!9Km*ohcKr|xKujEgHEJXA`@G&COgVw zsVa-g(6hQDV6fDF-M+Y++4KbR2uO&Cj$ZWcHauw`{%%eg9urPOiSe|}EN9CMFt@zK ze090W*tG^(H0U*dMHwsH_&v`(eRYb}n1rc|L6;maca5c?obJ@3Q5y4?91z!pD6tqd zq#c`)b%%Dx$8*LqATO##crpIC%KcBV6XKmO$43hKc0rwk_;xghh{IFaz zlP6X%xv88r76F<(ow3|rqD9x-1A8N_9=bIqdv9tBrsjBsbek5)s?Ms#J9A+ukz(Gz zkUlLainf#%)CxvXCSoE)yYJu=VRHU0tUMla1Jh*w5>oz?G=KkbggF#djqQhx$BuZP z8$NM>JyNjobU^A66)IB!JimlsdOslwq%zquB12A9LseBpyk9R-oq?Ixz>>Q|+q1r8 z!<44MB#(zq!oyN4vj84c89SU*5_Rz52jpd-hRC=4Sy)8mxkTj#T?QRI$RZ-ILDL{1 zqS@L7&@||8ExR)tbj{^Du0b7cj3N3g*N;1UUoBte-ce&@KT<1>)0$@exqIA}7wFW# zLC`#tycAAyW*=;4FHl;aBkt%p+wQ6O@^D^Wwh;hI1@WRAka;ug@bh5Uag`HzmR zW$lgtX`GID+{!*mtOC+rzd?!q?d8-JBkNN(eC7S_I@=|KMVx9WV44|NED9z?W^}GU zog>z0)SzEZe|%a*!6CT8sS3=hJ((pmAZ6@0sto(T`qY1>RR|Rd0002D1op7A|;+yRTYV!Jt8l!Q^y=OX^IiG}fU^Ap!8);2I7&kGM(U2Xf?t zpp=GSLA->4mx38=wYthTzg+Q?&m_9v&-tWMyM+69F!4x3slH`V8Wc!oDd1YX$(X#w zgsYou)JPxB7c%9Kb|ekB5$${pMEF1h((=84nl~{cABX9K#Ht}oT2NR3?Dh62fQzuP z{1GqtH?!wv76Uqv8b&;Rw9IiB`v^LUdC&MKu-?}?lXLnHx$j@o8Q?ATTL}|rXHzjTsqvFeK#v;d$`vd+s^8SH)+D&njB~Z4#v>tnwS`A_J>A_GbPfS1wKnZT^%S^%Fk~}W`{&dnioP1CnuV$ zH)U+<)UyE#bChg2V|TATs98hJhmYc7{Xp(OSicU5XDQffBfYPkcPbL~aPxQ^2!Ual zGiN)~YjogDL`~EQb#FjmbVkg&PjJ*pN&7+pK$iPB3T=f-o?Ju7d2;P(M;7aUutmVL zhe4UQ>t2nCK(A4@AIU5KdMC<2VAr?Zp#U@# zPq7|_5OXoY|6MK~V@1Av32P!pFd%Hu%Z&_BpXK{f4m5@YuVIinuy;v6 zmc)wsuky6152ce4M)X>FDNJUlLkL?Qe=y$HE?BhIThKOjv7wFJyZ;abv|V}ciU^Bd z$T@vjd8o}Kp}BVsJWo}6X?=q=2rX91EvAQaVG6F&3RoQw{B`Y0$IV@}6T~g!BNAAW2~SGfz4E%K3d6v;3w7a<0QMZY1RbF6#A>$NO$$ zus3hx_wvt*bkkpbFd3vKKB~CpW;r6ex2EvXZq|IhZ@g*uOEJKiRrdM15J#e`@Wfn| z1nDTVWE8JNAHczu6f5S?WP@ODQhq$(RF8~!(FteeMZ>9JaWNbSpo%3iQI+p%_q-)) zIq@$%-;aZ!oyZb?%^V@vTZpF=@b7ZG`{3U+H4+R-=%0+=68;Ozj7fEfZAn{xnyYj)6CP)3f>+<>m zZndm$U^*C?l9r`iq*%=zFU)z?QdW07h43En|8lDPXX9E3P1l8LL@{0d11SW)&a zl~rw8*oR;#aEC={bdL`Nhr*~2lmGxUepO$hHQ-a#Sl17~Q&w&u_uRl9Y<@>5QgTaTQ_rQ|HiDG}b&-u6hQU-~PH}VngCcg^?s-o<4=UmSb28^x%h<{l+Q1 zRtmgipo4RrrzzH(r?Bk0`0|R3)i_V{@;rG|^kDp(rE_AlijjgrXY4tZqejTu<&Tfa zN^LKV*%HN;qdN%>bh7b$HK$)zNzNcM?KM5K4%K?SL5RAk?BeA**XZ0_OMP-QFuF+3 zd^kET;Pd`{&Du9Mzip>--WmwtFXAYJPTyz}Fg=%3KtSI+SraeW75Qz0vBEP0u2{Kf zD>d76{NG*xFTXC&OB6a`5$~Hq(Yx#ekvoyQIHy`_FvCLbm3;!wC%oT%oku>}?0%;f z)A3@SFMBPAMtQf9ky<}`a#EJgIv{PNl3`J2O9ZpyO=KU6%VU90&yd}3%jZA zyzd1RAZ^^4fKI1k_xULZneS{#=uFB-NpdhKCX}7bdmqzl`TGTP`@46*n=P{OYe4pW zCFa{x3(8y5o;rA2KutJv!o6kG1>|X3uKMAHx?-OhI}D0dH~j$Ig(O?(tP#<^QhM;J z!t($`+5#Vk?e0iWMM87DY96HBqK-AAvse`jJ%9s(jolrU*!b>RL{n`@1E|FTq$cQ_ z$EQtSSAQYtZjX1pW-Fhtx$b`j3B4eg7jld~R#Jdz>OTKJq3%3wan)Kc&l{?2tjXXl zA#z+C!~Xm0@oe?H zZ2_APvMav!k#@Fg)83n1XXkD0WTa!V#>+!|)ioTVg!mu6q)a9lEeOB3!gaB9^^F*> z&W~9}^T&#LcoEGrMSRkPdgPv;XmHjM&ztJ8o`PjscZj}kaZMC^c$IdWtyDv(b?Pdb zF#p0l`c{cqIQu`fCzmt&=G_+UqQ5qt7q?q1yFaf9uBqNQJgR zwBF3k!c6j0yT(M)e+WM%Vo*GG8Hr%aJ8|CfyFZy`lyt^E{aRgAou@$p2NVF){zZ&3 zkVfuaEAxy}obYEp3LNG|=5r|B(AfHjZlN((vwHd4`UctRU2pG-r{-ir_|=Ie2ASaB z1uc!t;w}F%ZofC-8e$20sdun86u#-@;$oc3HRwxEjHnwtZx;Qho*43h9!e|z-DCe> z_5^c(3v=M@f*c)FtBw_+tK2W#Y$I`VounT*eY3R-YerMxfK-6rmp&+ShHQI!jo{(D zI7CX^-Wl`T#A+>K&$uAUdnv}5K#V-rch3e_k@rS4x;@jhP){o@)kZS!urm`hM@B2z|r~2>U41geG-pL{KqRS!X z#KaXx4du<1Qaz|E5?NHv9bmvEm%UVz&)ga_?Uh(4M-R-rDC>P760)n{zF^AKAKv*L zUi4x=wGe8uuqCZ^qfvs#b%@Hhz;$W2NoG@R!&L%-6scW_MNHn%?-O>21n}86-#wvU0_4T$K!@l z;54rO&ty-I>=#a07rcELjQM)zxo_E`eRQgg2<40IiseV^Lb_&?_M5gPa{BWDqn)Oy9{QQ!C(dP)QRNLVdxq6pF8qIh{_g{k&7%|d zdIA@8t`Y4`MmPRdZye;TUW)b;-ET?ze&@Vv!lH{M5miNxJHs6WqeT0CzzkIQKz0zL zbQqse|5?-F`8Zp|}fEI1e}rql=@l#^$em6m8y+RNvjuzE!pdKCORw zsReP|o3_-p!E>Lsgu+@^0meW{1d{~<42?@s&F;1;9ZVaFvMk%jnWTD3fUk6Q1gO?{ z-IiIE#Y=zS(X%8r`;Q=vw&i}nm9gi#nXOmxGFJW0^FRdYPmva++4~m&i5Ko~Oxw&w#YV?1@-Y|5bppLE-eX-yaa_ZN{aMg+&OOOVXnsyT}0_37_L^t|^ zKmh~LW6+%F-pkj&<9HPvLA65OM6v?q%LufbWq`4XP@Cu814o|Dd}0Ot$iB~YnfZKE z=to)@RBb%lgTl}?2Wnc=+%!lPDIh3a**gbezbGoQ4S4X<#L#%3Cf@jN))Ny}0sk^( zvf7pMxrKrTtvp0jHP+{sUfYM@FI_q9>r0H4+s}^&4g*EVM6*Uv z0TA7`h-nLMf#<__Y-R1l4UGKT+v8^lh_OKNj5z|~Y=-S^7p=xUnsNK{kXCh)tyNP& z94ACl5;HON;~hNws_p^7`>E3rmFfm_QthWxMsGX7Vk|9Np&{-|=N6o2J!4Q8p7#$c zzxlU2@jH`nP1PunKBDaBjh-Pw+8Q?x{_h0)Bc~x1pEz6X)lp8gG0|Mjl}fnSN}2@% z#xxH(dp-w@IoRCZN^g7(-Kx7lX2Ga&aV8}>9T8t+&-CF->L2i6&Y6n1SbB#LX6+d5 zi1H=XM4yqG9sRW6m1_i5VCGdUS88?)wd~%5<`(`nRytQ!o^%xrApgv_IeB$!|=CR`HU{db;Z$o9;Q8e-# zAbA-Q*np|w)Fsnl1kBs&l*Feu>-byBIs)F9@U+sfXF@TOk67k(##M$<1{Ihs2x@(z zX#8cS_R+siMek%6EL^~w4*dJeJPQ;bFo?~!&Vmj9t z8j>RM<@|YDJE=nC_+!*p&d4vKXh{WP{JpaDA8uEk99(L*b7z9>OTm|F(6Z#WyOTsO ze1p%93*y~>JqkVF&+Na9&e$|)B4E{Zc&7TwW_;T2C=BCFGk0(TSL4e;VL2AlrgeWW zXkhpw&@<ac;xFy)S+0(qZ^cgzgZ)y3|8vO`w zFK&E!P;MSKQm}X#D=c+e(sNyFYyGXs|3Gu^;ywrBJE04PYrl`<*k(B+=|e7&G@*C=^i^b>EDCN#JdF7E(Z&B>9#G3rF;AL$7FLwSj(+#*{9@&Ht!u=_^#Zi00T~G$IS^>iN)7wv-XC=qRX0ufW1Z~N_;pYcggCJbr=9(`4 za%NlymyKY|4hFz{FyEoj9R^yKNB4b0ZQS*h<{0?RSCxD0ey1*SzkKkT&~HS5(@#qE zuOERy&|H#%+cl=c_cpk5bTE==Krip_KSZQ7D4{%`>(} zg8N&eFOaosDfv6`V&g{9=bppPMB%4LZQ2^iUmmJ`>GOZ4CBG*D|HD3mHbGYQr$cy% zuqo~78=YY1O|2Pm@0ht%2JF*o0OzP?+ty0>4>`Dmh4%YXTF!bH}oM0ylR`Dm6f zT(PnY=(b;su+HIApOnQ8!~5Pko=wAji+r(U1;V6@D)?yYRbh=0inQNOEIrOPggu5}jQ6pnD$LFqPnCg!pp#3OuoQ ze`iRnIUwbolHr2Nl+KpXhBpw>^*dN?Sg=supjY=(Y5Z(Z(B1z*G;V~tSg#R+X*9)6 zu0-Dfza-2Ju5m`jKS8W;GAp zE>tw_vS5)SXojSk{y&jbn+8P$=NlEF6q6Pbv&C9$>aS$h z-lcM-B4#?+Rvt%8h9cjz7Xe&PLaT=##csyQFtIjbaqtVGL@fGryC%c35oZLG>O_VU zoWevh;N;sWA`DJj(IK9dDF0cp!ORHJU#6=`%(2Gmyz@v2d`OQMk|Fi`$55=lSgn5? zWk7;dW(!)*C!USCQlcM*7MmBxT}M#^yaL0Nt>0Ab23n7|jv8q7X+<`qKXgcB9A+fO z>;ioZV1=Qd!+xW%#wI>Cl6eG}N!fz^-&A7t8UG}q$%V1q9ahkbyqmU`fiq*{S;F$H z(R%?wgm`%SA0r0Z3=^#k#7ljk!&$Hb36h`2y8GT}Dl6o>ZehYg*A@O!%7s!d;|vW? z3tedyhr+Qx`S=;tJlx=g7j@BQEdKFDX&_U^AV=Dt{A!z%h!>zxdfz&1ccn!b_6gF_ zU&1D2uVJR+{)^#XD(zR-kF#VNJFj>ReC>!RQh;xRI19i5`mAuis;`TGDoF_I!PYf6 zK(5?B=7t>VF~R@5vB8K8p(iTxo8pwLiN_`nk?!IjX#HH8|0<6dkfw%kvrVX$#^>D$jI%4p(}jJBb6( z(eOm*;V>3L*^lJtqwtim{FVL+oS|W(Kq>IN-k&j}RM7uz_k>{;zg88Z6#1um5=Vvr z{_h^mQ?l^-F`dx^!5L=jAF7XDjPpLtd4U9qlx(DEpM3)cuw3)S5Ei8aAM#}mc`*dx zhoL6|hDfmyCf)NdzwG?@PBd+K1O5r4@7FnK+>^&hV(_1WDwDo0vLmQgnp4JR==IAd z{+Hahixpsl__w*Ml`r|Gmb>XTVMN}Usb{^w#8>O4*UCnyi~0y|N$UqYG(Cya#smlja`{grha1)++78z%_FF`B5JV$M)3#`BFT;E7AQDI~*0{~LsZbZ*i+!bT*Gc=_6h9bKQ^AmN z#lZ>+i`#S(LlDy0_D|v&nurN7?fPrfOThot>Afnl}a$KfsZVKN%m{54Qzz- zeJ3SW!lj2TBR~P*LGh?2x&v-O|6K^##=XHj*080)58>w1P}R;Ahtr-T^Mi@3!sLOg z-#)ThS}DnN5#O_t;lZc8{?4~JJjjOPK&e>~#$&9^R}9*}isbJiNfCb-6;Y%X@`H^5 z8B#`QM;dapxUjM6cGuQ>QbGj&f%`>7^l-5a&ph_oGS=mLOGpzhn2$;K42Qf1p>fqF zOhHddyxN$q5)xvwYn!zS#xhZ33V-iV26t3{5!aU@*N|hOoMh9F{^Ox7j)LR}^Kl63 z)9s+!wMIM;v}lUsC5Rpi+i8b}Du@pS*ELhn3yzE|i&NIw$w{orpksX{XctTCQx19%=fbSK9DGiQD#F0xy?!mV0mHuk41L@k@WD7V@A8S4v z|C7AH5b4h%01-MTsfEC#d-ip~8|2?lLF6Zd1D^L%aH6i;N;Fy_AtGMl!30RAJ{a-& zJaO`UKa4Za1RH`)&Q@E_ZQC4o;Aj*xU)KM!OCtVlmwey^<=CyY63SE6uR%avw(>|= zQsrRHq0Mxjxy;$QuGH6-VFr9`2>ESq@F^>b60{p^-3GQ_|17a~ zyg8p?t7DUlzq=*>fiQU_@wR500#6))Gvpn+u@v-|N|j+_!ddLkgvf6U!@14%8kE}C z2b+N-W;}f!j8dQR5a&aUyu~RL#pk!$@d)t)@WcIfo6A7JBRr*;AVoJNM-Dh`FJrfugEj-4#yZHYY1pDb?cEAk$@s+FJ1r&*;^%dE4*pmThMaWhr-K z7j1hHdW)Akl`Uf1sO{vF_S>R&B0CgE@-M4tWSmx9M9DT8XEzI_;?6HOgEveQiXgrF z5x47y>tQnAXEdz49x1eOH|4D#JdfvuFaqvS*1C*p* z?k2se_;^zIyU*Mvua*%H;XKvcneMInFc|8K43c38>hM$w_#=k?#$E9n@vNTMG=|kfn5};-19iYROq~b=H-djxW;5?{c(ev8 z4&Qa4qJ&5={LYDk&dtlkd;oFDXwo58NQG8!D&a8cDfvyM8w<^b^9c!eKHp1_p5MJ6 zcJ4el?lPt5rtOqI&^r;y6C8YUEB`CiDM#8cHMuItEGTskv8w9 zvRWLlL>Hx%JLX+VN{>UM;1@i@-zgVnLIwt=X@WBwq=krkTMH_NlE+n9(|)f(jo4(h z=tN{k9e7Wc6X-VF%=vUsE!k}D+qjZ7IUOCZOX#N;0Knin;;8lD;?c#Os+NEYGiy>s z9rg$8!LDa{P=srTE!kZs` zEwRa;$)DM5ysI%3z>eNs^P7b~4MwB_TNIdj5P%-ZwZaAmhXcUqqa7qsV>wvOdd0k9 zNI5WUabq9WblJ&~LY_s#oBn zDn6}kV_nl8rF8Ne37h$VgB-~fwgMU7U{hNXeGDyn)}^1~lMBi(89o4LVIxN-L*~Kc z*&e)-Sa~|%)l{_ilg#xH9xa#DW-axg1=G=X&G4DFA&_OA4e&;sHwP5H6#V>tFviU2 zYP7`aS9lD?xHH1S{8*G?&EA}B0d{S^uXYg>3@5{2hgKT4_g}LqA9OSOy`77@xr#C* z1B_OFvvB|~FdFWzXAy1v{?TV=&1~?uS7h zn^8_uq2X7DC*7s3f-$c;BjXA{6AGM|ii-_Xo`{14=Zg32qDQ=x-{P@G5 zXK~+zl5cmF_+){Bp+belWsd_L2*PU+s=BU8lzk!haC-@of8n%$n~>2x^T!Nao@;#C zg^eHz7m=Fef$u51&mt3B&A0WCEagID`q9j02zWQ*f&3*Cz_~>z{f2v)f7a>IIHq`+QjE8y`A_UroR zBa#hke7o)W#I}nrZ2yZ*ht;k}47p~GCbr%I6UM2gM~pYG2lOEKHhd4}p!0b@_GN22 z^yq=*u`8C1Is~Vdgh(zq;$=rW=sP=J|3_ga(LEj9>Y;Be`5cJrFmT=7H4u$eORwFo zpK2^X3-KOV+QR&|x0`#PVsq=Um#T8^WM8H!MZnrcJXb7hi4nZ2cE|TBd2oH2jt*jv zpa2XK;FO?HUMyiFprA-R-y;5SIwZZ@kwmLDdj8DOZ^@7LNt$6% zF@LywzIdZ5Rw{ssXu*-yN}zFmu3Is{Q_(Gq6Q@Wna=#v^emY-o4gHX8zkLF^R;SnX zHpbt3EbE&WeRYCQG5m5#vuNBlB7)38VMrPN`$@o&obnq#Bs1GV~ArKUBSSP+NV} zEnEu4-Q5cX+Tt3dI214LUZA)`u;T6%*HT=AyQTzpcXurWg5Nyvz3(^kP5w;I%*mX+ ze|zn<*4cZ0k@My>JJuI^%;`v#}4 z$4uz9`x9Tj^~=K9>l;01%bLkfRm<8` zvmR^I-)?$nq0;r5aBJSmKeFo1ogWh!d^c!3Rq1E$#hwu~wbtDq8{IqwQUNHY3%-TN zTmQxS$dtJ?3DP^{t-~%0M{2$T53)&wTytRW@Hp#C?kC07qd7NR64$OgF@%7ZroiyT z6<;$VlB#h^(fG390GW}va}G|;9?uiH8}fA`NoJg$746*5`@tLOYYr`y4I zUY{~XrAtyg?ynC8$(npl^4e?NzSqf4fsJMR3lGxVsPnJC6rW}8d;hZ-E4%(JRU4d9 zDGKM#495a?72z7eF5;ItfoJVIDk_oHZp-v~p?z#k>ib%{N6h90S_ zq4?5X2jJM5na2svRtUSVeNIYQFG(YZ&DJGOSSDwKh}y7Rh%EAbs0@-y`Z*|@SYT|@ zpf0ySIhbi3SGvYy4dtgFro7^!TKNQN(yxF2!}__T^o*3$OhWun?`RsU8v+ikEIIZSM=U0SB? z*VZ*k3}&e=lCUOx4mYH@-I*LWPZxHiG+2vb-fQc%8l8iJgMv??>YBNyz=z6Xg=;F$El)(`oMQ8cbtebuTl*iT?Pexw`MCw_X;y5Ko~@G=z#m4^GiTUTQh0dc`^V2xrXQ$c^ zDT4h4tMW(=U0s>9rwM*VaUAd=$hE0UfuRPYeGeFTh4W7(t1<3#{WBw3=i7Pk z2)e^BxE?Rlwwb#kUv2&OMq7FPrxdGr^my#1L2>Dnk+?6_4P}j_QSJT&d{|0`i$@8| z-$A50rqd7?Q;2Fo+`qj_$lI&;U3u3OmtQR@mo-nvtp;d_6*nb9l`EtTveAo+(Oa4w zV~WIHBdKg}%^L1--Oe|t)lAh6bqe`6tfsT4()qnP?au5c%aZ}Ve%Q`*p+z|)3p=-S zJ}~FS8tds|gzq17-cHOjT-|KBRxWnqIuI#rk0wT^^La6q^OyF$OEqTxxcS(0asQ4H zAFVpKGms=6K9xMKwN#_tAoF^ERX+N1BCb$4MsGyfv& zBIEHutU@g+`)irV)Y3MRJ6u;zvF9Xy$Tx3k=qhL6HtXkQE$YXm4qVw=n*M#w0J_ay z^BbBel0qh&a1ACU&6W>QPr{$%t8(*YKy9Qvw-hljhYu;kIv)znC)ePW93Ri1 z&`|Ghy7CKSzticZcqa>bwM*ynnTlWeer;&~N%P8|_q;WHeiVA*v?8nSnyo z0{T92@lqFp!bRX<#pZ#RWS?>>bJpl-x~3_!-m~$8=f6lSxr;Y59wG?a$+raJbvSkL zigEEQx!g`iDaM)V8v_I*&bNcoAG2DK&A(%cL;UT%MdQDUv#%J>RXr zgg?Iy^y|lS{GMTSJ6^ypw89){5U+PSKYU+#V;J#`$l&Ux*VXs>uXRe92Kva|7-XKP z-O1#{w8M)sUBig#j{H*ZAC=;#Z58v4*M$ih}z1=3UkOzeY1{66p|&tzTxGh zg?_1`l%c}AG}6aw)!Hng8q<336?_5$Zyg#D$ek4YpuHS{y%X@JU0O-9&ucr>pS4xP z&`-PQ3$(Xsegp?{r{Aq@Ye3tYZ6hv7lz%F+Dv89|JWa#Kc410Im`TqG&ldgue2nfTijC9r;nnGB)A{8B zsB~=d6mPE9=F8I6S*-4dnZtqV7Y3CvZ~lJ4TWXGj^8{Pbux`|YRW60)P`X_vj*~grAN?E^nIvxQ`=-P?tzsg7_ z50)@Zj0KZ>#|o#tJ?|Hn4M!skko%d($MptWtksi$ZT=a;C}|Uh(g-Mkt_v_3fr-8C zJ4+2r;^%)T9(FK5*_x6b@AkUP!-=}z^U#F-zgI`A75x8#bMeox-wu?>x*=0#UR0FV zXQJ!{HRz3YJ&x>W&Y;?4ZfXGk_}bqFe^*Y&6`qu;ai)LSXrEQ60ZZvNT&=cRAyl!X zQKZnaY@sI&hjC1{?XL!6x)V`{m)4XkcQorAm=Zd*>m$Fa1l7y5yc-T@Q)=FM-Yp|) zl%5Ze>{~1xjWWoP6-lk_H^MF;IJtBmIAE`*hA3Vda0tOK5{UHe@KeL2)E6Y1Z%Ayk zAEMq#fB730s{$TKGl4wl1|UhFM!ZA7`zs-vNr?C7E`$;H8!C(b>p{+#5?|Ju-4K&q zpWsBYRlU!_c#ikM=&H{I;DE9*`6LoFqgG@3asT+gy(+qp zhof=KLDXN{$p%gHhO^mnuiPR^B}oq%zD#Bw$tt}qhq3Ob`uj!Dk+G&@1@={hEw1`; z2cX7F4iCVXzL-l~mGxU%;`-apFo$y)WLmA;nR409I#MjnQ3g_tR!X2R`)lW_@G*{| zSe!@5-8Q^uGymHGz?MHitnT82Tict4_FCTKfw?Ns10yx*uUoH!d12hAcL9#Z?3G~U zfN+&QiBm6Fkm|d?++18qZbF8?D-@Q1rvQ{8p`wZUeCd`zD|TUf&G%k?{ze(EqGJI`_9KlitITUS3};x&;NQAHEZN#Ll(IFVMr zzjZ}I-LJGG6SyvVF#&tSzs`xy;7Ar1Ys=5@Nf0Yuguiv5>wu?*0UUS7jS2&}b7Out zkDbHu#&LZWsr1&NKsx^F!?%2#iW89&PcTNz*t+rOpK$?cc)-PN@Z#%)>8PKjsDrl? zQf89_X7GBx3py#53{mc0Qm>&}yYiWf0p6;QrUFrXPE;i?b7tyR3^aN4qdzke#5?rkUBCV&|-VGRWBd~YeeI)01 z4DmF65_^Ik5G(p!ajtUQ0@GudZlbygaYUZqICRIGR#)A2Q7b)eo)3ANZSMj!*B(92 zAXVBif8EVxHp|G==Nu82ae5D(`8NZZ&o%cTO61Wfxh*BuY~SURyD}cBjJ)^VgT9O+q zbKY7rO$1p{E~f&=UeE=c3brKf#FwS*8Tt;aAUAR4sd8LUa>M32j96)#fWdZwiiDEz z2UK`k7`gN5=@fc#IW|gTsJw(?oc(jev7@1H0pR>i0@RAMBKr;qJ?5_+dGD8oLkc9o z7jk#4&?*!}6huZ-IVaZnCWniQ^h6%$`SM6d9Pr|r!Piy4Y1wcxJy5eOYI-Tj3SOzq z+xX%u;oAKpi$@0jW28XW*f)_mr3r!9S#;EZ1EAZmk#PjoD+Pf~BCRSFZtZ)IhF6t5 zQA4Us|M3;;_a2o~Sk!IyTrRFIoK{nn-_Dl1AKy5TaS^k3@KZfVbpGp_l1u(9@)?|i zvr&Lb^i|v+;j=w}R6RESP4_f zd@ib5t`n~gryAJL?<^l*jh!uP6p_=Nj;>fmaJUt#Kxw7K-5f#35lQvM^4hOgFT!!%+^G1__F zh$pEv+8ayVuYM1#KV)6%fn$Vp9;|%TT4oSc(AV8l^1v_AmrcK9PZf=YIpt9p`6BG2 z!km>x5ENbOT(th|dO6(cQm*9%?FKVnp3k^m^;~=A+jftmVxvjP$#J|5Ml_wJfam0= ztl`y~jjngC-hzX}f{jqO+p?jVun9usf~2{ZV0IGSHXPFPTg{k#^}%^|&-M;NLL?*v z@g8Hm#qMj|F5f%AUSt2IeHEY)JgNU#)>neN+xA~2iP+yTv6sH~=g|D4L|CE66h1zhArCKS;^8dGj7()VG$*T-pr|&+VVS2+e&gfc==km+NUl#q$e%h-)l!5FeW@AqlEk~O zZn=4_Q1Adge3#>4@Uw671t0pknQw+UvmD8(n~ZRy0OEDiuEW?cw_?v}Zc#GJO7`BQ z>{kX4xnrIP`+b6Vs@9nUz8}9dD^Va$ zmyX?$s8jmN0!O+Eo$PD4OT!#EcOdy<{s5^L67};i_%L`BMHk0HBn7H^j^ZL#IeHLi zB7JJGf9dUUFsKagj{J>jhGkR9?@|9J0{rh_l>J}{bLWZk@C?&6py$r9X(ARv~4F^)-6;uv*LT12yT)wSp z*AFxZ?@EIXBy$&kQ0S>XYoCS6tP4@zDYjBTu&Mvf`V<$aC5L%k&u9~=ai2k%r)N+1e%LCnmsm}_&A1?g!L6_E2bkH~9UUlmJOMK4Z zB%N|z$gAEJ!#NqZ!6=bY2OI2(kOmJR_H>EZWWN#y*X;5gH@d8s8#=6s_U`D##Avp* zwx~USh4lrzK9evjXa7_#k?aq{nyNMgG!hs|!B25k5(GLA;eP%q4l+2Xa-U8CjEm{UW$7wUF9c@<-QXN z?4L#bHJkBTZubjX0?L00|L)+hL?#~7|Mt`=e?1^4ni-^$IyyilDgP&h7>JaZK;}QA zD(rO4uvl;I=p6VEd(1+Ad{Bi!UJP&rTwH^Dry!Udzh|8}m=JeiU(D}mFP*hE(1b$IL-oLj#z-cB)p z#_dJ;H)w$~o0Zy_?8H~#3eipIYO(A$%cda66+fdWf@+NRecsEsQ*-3GfwbQHG`#-~ z;4lAGQAC8!vNy7>w<~sXi3dah0Y+EFem9Ge*qy6o(`cX_nrpSjJ_R#K7fN2hi{S0W z!D`dj66G8YuS=775ie{?fu9qikC^JkisDszO$C2PARoE83G5_gWRN8!C5bXzZ%=hG zX+$a4+CA?WdWu+rzKltIiJ+PoXbf2d_1HC;%w&!&Hhjn6;^xw{u2WtU%HP=RLd8V? zu#@Pt?M1GfH~J+D+g=0q<4iQif1B`_KZ;HBrN&(!cY}`_1U*dDt-F5h$qkkh1il^9 z=asI9eb<4Wkl&EaVu<-Ya^GHvMx+C$N;gVHGn?(oU9OPA6KEHDO(xAR#2ynf5<`^@ z0kj_&Q3D(wQ2bEtB+fI04>2(C2xKRePOjIE)Jx`ol$!9BG0i%o0=16`b=pmK1o3b7 zl735y`*%Hv?7`5$%?^QDXjiBje|*RMl-yb4e~fZy=* zVM!TSzlojIYG)^C%_}i`?*TT4WF6dO^3{|Rf$FVR5QIe^-U;8oFd7JNO2iy%^tG*w z?_KJdrnjFfg@o#7_|Xhzm$*6Sj<5YU=ne+h^5 z5HsN68Mr1Rv`xDUe*Ujo{-kw!p}U+I!V<_dw0MHfJl1!MiD`u2zC)RnR9qxdCNhYi z63cuPrT6H1`lqnFx3|@_;?Q4h*nWAC7qGoMlsxPcj->MCO>A;eXc&urp@z74;P4u^ z_{J=C!TS7s-2&+w7e*n24>#J5{F-1kDCgMwpB@*g_kF2ne;ZEU@qq9>(x+%zNJ8U zXBtpVMkd(WLf_>8{pL&4bX$|+o@6fDe=Te*rYG%IDjc6MxplK;pf4pnT3^4e-!Zm%ysS!lOTBt{+dpRT7NLya|68pFX_f{ zkUSC=hnAQp*m$43KZSsm4}U5D();g0T>SSS9*)^|cXu~)ea_W#7+C7;mbj!Z4S+{Q zQL6>iw`w|=_ z!?87`&?Rg9E_+di6(ZaPuWtn5z9awpJrYe6R|G@%IZf-S-|t*pU9ZLg7n@-e4qxDd znKhp=KgpU|&z7pe9xu9`;fFbo?L20Mb`5f8Ffu0TPw`;nvbvEpb_m?LH1~={&hutv zl*0*ZGM34^*kF^~S1g~*bU0HI|ATpNvo~0$&LZ+3*R;hpIMRA`(b<|>Y&sL2SGs0_ zK}3X#aYoKdGZj9=w5bUnA3tbuQK!grT&`vKhl)~78skT(HdqAd8%8_>#H$VYu`q%r zuwwMXr<2t89$p*i?vo+fcUg%>I(lhAZaw^INWVQ5-vFyPXuCLw#0=JMI@U11m_bbV z7GVW*JYs}L@$E0bUtdq1$Um>%In#ekWyJV6IR&jn7ae1YcCoqn3mxCJWZ-_Bov+M} z`47ORAeEf2m2|Ss)vLTR>U@lpl#NGuUB&AZq#}DKRzZStu9!S=9NfPNI|*HMmK`dJ z`JV`0d!T;fi*#I6k<$eHI~r#w30xc#n?dU7 zR6YrZeUwBiv-N3`K^3S_i_^zipQFl4okFwLX=k(o1`Y+40b*b=Babt|tm0G-$(S(xmjGN{ECSd59PHge;M8rz14WhnN2wZ>D*^8Py6f~JY8xoHE z$YYn)BsOGNb=S}VfI56N+Tno2jmBqkiV0_I*#B^6T`;$jjiS!88_eaga$hG&U-?wg z(MCl*6G7!kI5Wc)P_-OrZ|g~a--DL^iFh|>NcI?%JUBks`#cf%_ZL6TO~WwPp1KN^ ztloh0=j<7R@QJVGPNCld)sZ^hdnglq{Iq;u9!fhW?77?Sju8{E?X3IsVHQ80N*C|{ zRpfC7q!;B^0$G<^LVNM+iAUF=c`=*3IfB*egQ0|<4-pvc0|nnN)>?@wQb!?8ShmPj z#@#aZ4)`Mf>jmJ8z3+XR7#&q?(E0dz<9e9EgH$xuh(8k&H~)$JNz&CJAkN-BDu zOkL>04vi~xvR1{#3LJqqTo{(iYiC?26FUo9s<*1RVASt|ZDUB!PVR*Lo7Zg% zmr=H{U+(m2F3;PYwXNwa?g*EOqtyMucyx9>%vMwCKW!e#h_j+J9d3cwDen~v@8Qb1 z7|H12b17RhKf`73>(XfM@1v^g%6n*#-TQ+)_RkWI9Qd*0jKabOp`8R}q;Fjh#x;o& z2#L%f+#l63;^{{=4P-?sl)h@qBH2Y!ExYF3Lw73c(tE40t%^*}df(FaaG`Sh2>9JOWrE5Z zAaVO&i@eXhUY~oa8PbJa+R)qQi`6kKyT41+4<0U;ZU|tfZMds+yIPR17*I*)P_qoMczi~mpp&yG%<7=2Wn zxeNk2#AbF3bMshlpQOx6j9{waQOy=FkWCu5wj+v#Di?VsSp4n$YAP&f@&4(00*-zc z47CcAm~3opVBN2eVnq8f)Pg>@r@iL!BE@|soE^*XGfjV3U53HOp%bdb$jDgw!w7iv zeox+Y~_WR7sB)P# z{z(HGzZ*5qqy8Poj;k)8!A~h+I)#-9tm^Z=(cLeXw7i*zzJ_&-HVFFke@u(H!s< zE%jL=fPRtu)WBj!a=TSbI6*BLHYuH$}{F+hM3gu}04m@SAKGKNB z#`rOZxKz}SD?cxi#&mdTNyq5l)^4`odD^j{nUekB+#YLT@bZ~AOCKd3&EP{e#LwI= z;eFj~gM9y`HoTsrzx}wtOwUxFYK3mA{?HfzwZ_M;9Gl$=`hby|*N`N{K!LJZq5)@7 zG0uOGh2UUk;F7;_E5+*YByIVK9f6GulsT#WF!y#^;lhwGMyOYL>}apaS^eJ+(ous0 z!*`v@JG_25Hu@()E+QVjGN)c5w%OTojyYBrK!VrjOEtHPjirS-k_BdJz%OL`jHpfq zkLd)XS}AJO@-4)v1lSaB*dj;;b>tG?x4?th1M1lQTx=Ak-|S%Mo?Mk_6e;g+!((cV z>zRz8JyQ)_vomY3o}!N-;~qE>V%?D zyU)9+{NvnW|6HGGDK3<~(fmQ&D z+FAaMBHE-uBMN(8X!TAqku!v+lhw!%swutLV7s84Q|w(ALoGs11@O@524U7ns*s?c z^yejpY`r$sIc-T0xtwKKzkZWej zK}v|HoeO80S<2%^FhDyZam?uP0$6aTZI#P+cZr_VMY86lpBL&MBhc$M@BGEBNi3n| zO9`gm{>a~@f00f%=$wy;&EN|>9Q}^Oe*boCytvE<0u654%mCELIQY{sjluNPsDZ&~ z6rR)rQ?rViR=$O#mFdtAh)hxpOWwQ-Zx2y+Kn3YVXgxx$z7n#K@ zLsC*Mip&)sFsQW~I?x(}3dfAxOb#zTN z=#xDbw&iO@`E}YOXrA9D{vtO`0+Ad(?xGQUP9}oNc#LUdZYLHJCj#oP{Z0s6zFdmC zTe4qiD)V8@n>vh@h(}+afEmb6hk*x!-eG8za*eSuu(4shK5o25^*4gX*}I?c4YK6l zOtpt05xAamgiyE^Iqe`_tk@CoVR z8Lj8F+U)P88Oz+Aug!6P7i0NC3iu_aSO` zdZHy$C*c$Pj1I8Tk)}G#8U;#NrKEwEqwjH92`)bVc6%?hJ`-M^uX*p@x|j3iscwP% z1fX*jQL6-Sb%!nd6X^P=B(MB_(3@$SJTN7wUm6_ewXG3FH?x4e__ee~(+!0u%C3C+ z2aj>NY@AxS&6!*J^ye0xKpyn)iP8Y8OsAtC>MSdHf+svN7^2byhN8z(A@pBL`jeW^ zC4dOJ;TBP3s{Kx|ND9{wYbE89L+*Qyq&|F~D<4qp-Yd?7r zgRTa(5SuKsF5Tg2LiV$$jESh%(|6>{M@2qUcjQay3EDpgS_+|%Q2$ymzv<>l? zUo3EO2h;7O`^I|jL$&Id7zI$p0})uY6Q(S*E(Yz=m?+|F2;+0UPL3Q4(5%d#$C%_? zTa}=DpE7=G;qTH-hJj1<+y~yFgs1G#ey3p_^A9jy+PU`*g@)emlYg-35K&361!oFQ9vWAoL(E+L)NeH-jXEE&PF>`?_YlM4WF$%)95fZo zm_G#+=vx*g1kgE2k7QNAr>|4x*Z%dWgy287OOS6s{)F7lse zTUrw*k;UYxbkc9_luepQ=D0|+3Qbd-pZ~dvK+DZASkmrGf0@p{eY3&&7{}FBld-~9Yd!F4+!f{#*u!%7eo}M#85WT z=IyvyOzyK4#t58Wjw1dRo8_WYV}Ho$U}TzuDGxa?(J$ctkZ1a%F6F()v9^sTM*JBZ z&4x#;-~*Ol_xAYyZ537cX=<4rr0n?1-QHc(Kl)T0-dYnbVFp-bOgjFO-^jGH%aZCvs2h=arqM8j^cEbZB?Fo{1ZaGPt)e6bU@9}o#3GIP5& znZ=naQ?-j$ALV%4Qs@6>-XcBG7D-R)jW^TDm3xVzHE}^d`w_7v4b580t}+^^-hG1e zhWakE?yN)LNbl*OiLPA9P9Ktr1%g(TL_Kz0!O$Vp0Iq=X=zDVj&hmU$?Z1eeqiANP zqn}`8kkTY=Qu@@>SmV$BGp(>aZ%n1PW`+KLVZXM^lRu*~Ca`nU zQ!te-0}LC>FUVzgilI|Rd>?8_8W#y5B)iUrz|2NCsC)&#mtqe{9gGMOj83EWtdZTe zlVs$lvHvsngA!>~=@m&>U%tymb7rx_!9w5}?!(l5o%twmLcba+7_%jz(G;1|Pb4^u8ml$8NK(E7idfn4kSf+IMi^R=6o%B6_l_IVrHqMqUWWb>zv zB+R$~1w(S7<9Zxm8JE0TumT)- z6xV$0BtcWC{XR^uUNu|SLa%#B`B~z0wh|A{lRRE2vIvAI zTER8Vx#5gC)WG>+^Aa+DIX<&f0oWupeLER*hD`12`+OeJV=+Yat^ZNFC}O%0U`aN<|iUIq0-us8W)} z{rfBgz~)N($ARrFjc@DVv|o=?^A;`0mEJLMWv?jzmDWkKf^#l&aSMYlcGNfHJxCn! zUo?AHsf6w&KT~oJhw0nzInD2!`@#GQH}Zi*p)R!Nv>cS4&bd*b>e2>Rd^mIMn9jrm#Bz~K#jmWIM}a* zgmCDa*=Vi6X;!^|LiXk=@a6s>I#!7Y3sLr4w5fZ`PKL#YSL+1xAp53?r4Gi(tBuze z?IW5{pmI=Bndwt|HbD zyUqiFW`Q&wbE*Zg7eRfB78yf0Nf4Fr$J%|VDcb(ZLe#siNyA+iAAnNtp#`q zW7CNE9fbQmaQ1CtfUk8Ew+@pzM`{rizsRuhsTSy^b62r|DZKo_tVD31hJUO26O+_C z1X~p3LTM?RYpyd=hF2bw)|}up=e-Gz3pVd!>?YDFAud(>;=`!|K~b*71na1a@UAmFF81tzt#;VrfUJF}U-uN8I>zxRE%WP!R z*zs04|M@4;aB$puWew+p>dZ|$>*G$;V!^b|$J8zm2y`;6UuH-E04crMN8dcXy0UUm z7LB8kgDkcy`LK(YCWDjv$%-mq zu|33Hb4^Qjp>XSUh&fNg0=W~-AGU?kxRM}ax<8tqm^&~Ge_6L;l@`+!j7!ebQg4?h z>PN^4_Bw%!CEt)e$t{i=#Thy|m@dbf z=(MSZ7R_MOpMG$+?o;!F0S&z?Az_o&Mv{TEGt9chQq0DwBry#~OAl3W zz{*OD6g&26i|jE6z{PukPM&?#D7Dkjd5I8#9B`ZE_uhG1(OQb>>hH;=FmR2dk_ZcW zvuxUDc6(PpA*v3rT*AH$T$kqwtIuPcK8*Zwf!6d(3NMl+$&r!cP)*t_;!jrUb%fMg zP(ffO-8Z>x5N4-~K+`I8c()JJzK@a-v!rkLALB_&^nzNJ*Jxrw3~i*iNC7SdyrB9q z9{u%I07sguh%mDBaD19oI;P@u{=+}39j3G8U>GOzI4BeW;H2f0loW-`CMpXY6HJtF z=I&-xq5QJfJtlY@y_%Nei(`SNf;0CE`rt5uOJrHjXtH*IFDc*DJBiVbO`a{aFbx%w zy(4K?qLfvf1GERlPin)=3{fKL0rT8?(mpC?2^MoCk%e&gk`gai32l#0&Uw7zenWf2rYIUAQgc<4`RCUb%6j4Xy>k8*LF z#sH~9)3xV_LKm+vk9MDe1?&aNl#?g}x<;kb7X&zrPAUy~`O;>y$Wd+LDEk#Q-ou?GZV_b_Hx6e#b8&%hBJ@kIW zWXu~K_%ro<1;nE(!~<0Gc(2wfT;A^R(*gKtuKJ4uTfM708@Ri?8{Mdd=DD}0Ui&8k zUZVH0%xjP8%OX%Q1`ydN`mMz5HHxqPfi*ugiN!w`xSS3?35p%W7Xv6?JsR8T1oEGr zLb~WYwucAiJb6tGsKhQO@SZNNaGdP|zB*4)r@8l*MlR$(OWPadoZmXLt4u#Oy(*GM2OfZK3CaFc%{Yl@(Lqvt?P9%GYIOqVLb zjzuo-T_xzc|Dm5iGtkZ-v|Mo;aHyKB=i~Sk$2_=br#80A+y@2s)EB3Y^oB zb;$?qLy?`yotCKanRtX|`g=r;F+@n@nikPPrXRgz|E;=C{Gty(Y!t7H2y+CIS{HP{ z(~zb2pIuh!N{G~(ohI%|lA$+A+&m_(9dfKw0*ZanVDi{e9_wV)TBTLxLLs!zT%ZIdIgcEkKW3m= z4SxqUG&lsIlT%JANsci?d^^;h0VEkid4$wCcPibEERUS@>l{@iXAvikK=KLC3ZG3+ z>@X0_Nn0RO7wN}1B~C@lDQCFTqRKC~sRk*lp=1EYg3cJb1e)R=#JNKJcA23e>n3pV z#6_;Mt)kzKt$#X!b_@8$kz++QbbS9kBr*iO|7{ZLbVrR`c2f5AK$L4b61F6l%XpI0 zZnYZlSU?}+6e2N00X$neMi{Zlb?(Sboe8wZMwYQ{tapkQ}HM;5!d_8?Kx0m2;H7414f9C$^J@fT6#-;oR+}Apy zYwc;z9!Vv39LQ5dPPd5xC;40Eti8E5GEG(p5-9<>`Fpy$|5CsJeiqx%aCRRDV<-u;$wNt#90y%V2XjCrHg^rJ`lYW5&!QfLbFe{8ot~ z1$*!IR+|lEHsW7YE%tKZZ?M=D2?37}ci)=b5YUvJ7|t{^oKsHe&y@`I?qVu>+ZN~x zdb|kVw$po9ya+hc3dYZe-!gi*X^NkIfLqo)RT0-8FPI9^nxS5(@b$&%mtZC9scM$E zQ^B~J4gHvuejAw6U$zKaO7-0e9neW6B+iQeC@SSPY*EnvmST*#@w8-~l->0s(Vg`8 zolV@w2V5L`SJ}q*9*1Z>PXau|$x``rnG!g}l#PUS!8`#VugjqPTnU!$|;6Bl8|3;hXo0jNQw?Y?GKsStiiL^1LVOq-914wN3h2n z)1&`4N4Xz~yY*ueJMt^?r+Bi*c=CAUue{R<1D@N`eL?Uvdx{0m{ipE_zS*ugqI3PC zCQCXTw;RNeQ#%nWsi=!`fEp=o)wlS-NMJABA5`$E>*c{#n#Xzc!>r*km3wZO|0lcb zVoyfG!&b?EA-O_LeS%;T2mDXqel;-xSvjexlc+wyuQ`*|j{14+LX~)2rxhZC`s*FL zz9wG6z!Rq&lW}ICTjDM}zfKT`l;$NR(g*0rJ%M7**Z?^2gaoZ~X0AJ7FU^nDVJ^re z>CI5Rz!R!2g&J`4XZoL7;^6z+S@C&Gw()snpsuq4+=r%9-eC{VuT*|W4r_WI^H(SwzxoZiSZ#{QlA zRG4VRVSZGg=-WJzi7{k|X9h9+Bg_uDV->Bi@zxZn*?JP%@p?hX<|D)d9B3~THA|y5 z#2&$|Bg2PK1=7VRwfO7ebQLA+Pdbcq$B-zfz{ zBcf0x<(zO+yWDAh7yjtAYuYRfwdH(SUO#?~9_Wbilbvzayuj9h?faEeYYTcEcr(@Hf20V{}%mD%K)T9fR8sY?oL9UMtt>yMq`h4K|nq1cN z>o)Ul-AJm$YwAGk#&+#o$asD9IW-A6d9IFFv9qsR4p8(RfPt65^ELppSyULmkmkeC zP@U_Hk+8^jWNL5dXH3ee73KxPc&faf!@8vZAt-@xg7P{1KY}s|>1;1Y$K-1uy@yme z)musZ_=;38w(;O$j-=1H3A8SNk! zj&DC>S-n#J{c|^viKlMbHt0YQKUcw=6RU;rQ#X1iqMX@fGBio`ck`Q-o&;H*)EQcZ z-yva|0|yctPU#^u+iyMm~?Fj8bk?hi?92k(&$NB_Y-*%nCt_#QM+>&-~= zVc8R(`eu>tNP<4<-brr0_TrS;dA?)4;lvdi&oQnF-Ue3Yizd4t^;oe8y0eC$WOe+^_Ag-MfQq_EIr!whEzqS~akwJ|`lzcacMBYw+LiDaBEyp3&` zUN)=XZvyu4#z}np)mqu$6h%%ZO`q->u_N<4VO{G6Mz=N4#g#_#C|egU1xKNQC+L!N zToyD73Q2O5Y%3nl{w>^j9aM$oqK>Y|G)0j)!?H^F&<{xB#Ls zRaPe&1jopn7o|duCUd`pRS*&I%(v-;k*{RGZ4Q;rV>M`~Lc;g1ZliQtQ~F>?9$qe) z5e=33?IUxfS!7n5mcGueP8o!IwjmczTJ&n#-oV&azC+^Paa08pz^fGw4Yn z2e!HhH9CZ-;5%ybC11(sZ9nd?>!dtLmx%IHl%-&ALkqO zer#Vd>rFq$`K`M$kS=P))%PQ`3=3!*upXmLlN&iyqayWcDiWO(E$DgZ+cL20pX zVK+8BlrC%!Q~=$MR@&t1CJDr^Jqi}k&?u7d34tRV!sMNla&D88Dg_1E zXD0qMsU>vTTe+k!k-Inn%-($o3OseTyXf+9$~i)&g9&th?^We$FFk?QwUqHQi^{*j zB){=2c^K80v4AB5n6F$Okx*)?dBIHfzLSYHMh!$mIBsjW?W0YEi!!9&e1Jl*AePtr2 zDcNFMXOc}}1nbY-$i|Uo0U7iR8rH(0jfI9vPU&Tjr4n}YJ23|dPo6+lNZfBSBv<@~ z*vya-F9*$27>@y8CsQ^2P-LJf!GwOFalAoL>TP1k+XHk+PTN=0SD4rVwfx-dtj3&{ zzG<^heZy{c}E-Co#ein6u{@|^mhISPFd6WH4%UP?)P9=l+sOvvx5_2kjr z|HIZ|iyCfus2I(F^BqgO(a%iNxOKJ!~x`yt)ujf18x#ygF zA08M6X5fvz*IIk6_5bZG*+fCVHixfRt$>&Nl!+mBWA5hrNveFc!Q_J6!%obXn|-GS zR)W)E6+E{}r*hJBUn`YJP|^3xv@9waMWp)`Nuj~vm+T)|KL3m;_LjAITuE3X*)Xa6uN{7RPJqSzF#*ub7mIKpR9v!Wi!betpp9apZH|pB0 zr3&rrl=iYdzFUH#Ow#`PxtDzP+rM#_i^Cv_g9E*{x1hw3@SApzKI)~9Y^#f^L z=0KJ=i<5wPF$$b4I(!LTNd7v&KvKgnW{`9JdK)z24iYUL5(flj5}%eNzR%(*w>-j_J(fB!#=(w z*^ow3{w*hIHvW1-q-M~3HyzholBF>bE?Tc$nRk*Ak%kp6#DUEoW+UlRRvg~#Nxf4+ zG^Y|Dh_IDVhc;2xP&gsK?!L|{9HT4+)v8^n=a+`ypT+hPA-y3!QxI6#zO#6iCr>)5 ze$0UX4(!=It<(zB!!XrH*cfX!4gO&~YryFWT zE)@=hy*@_VF4gPjNT2Yi>=Ia=iRvNz&MoeY5%HFUv8}MULKxeD@i)L`o z(`d(5bKkZ@hk$17nAqo75wja9pi$nz^xih=N4@usZn+^h#S&jRQC-j@@xoi(RGi77 zdTyW@o((^4rJt$FD359!g z(ucB9z)$FQU#EuouTme{i19$42q-p&ER%xQJqY)h*25}5c&?qW>*YBAN7TI3(0T-v zk1s|jM`3Fhzk6OotQ0g4>S_u2?r{aKwuS}UgLkl;Oxwv0(w#L@j`q3InbgR}!sLFV zGph%`m*xHiw8=KR-hKV(-K)wC-Zb?19Ied(FI(4KW{533i?a2@v0~dmiiXp>HWtuv zBE9@X8*s8iF@zOKS=fjqI{(8^9-q6Hcu1)sgj@)rnDW5`Xo{MrQPi}bGo(Un9NYHAAK+QIbKHPLyU0m__uI^eD4$i6fDaQ*P{2CpjCsf5yp$r7PZQMBoQae1-)3% z4yf@FEorTP=bSZSe9GJ{rNq#>`f!YO@#Q3PagcwbAlYYgZsZT^z<8FGxbP`7`BnpG zJIaHOL%2PM+Fut9vf;OVTN2*qBr%-)wwEtjR$uy-edNekLj<%eF~o1v$^dHj{CTe9 zxF9&3V}u%S+`$SFt{M9cEMjc;Z)Dg|E&3*vQ61^25w-c@A6 z$_A9wg?eI;jlec{+e?W~B1p=Iy8MGl@FV(6TxCVOf0otGTp7FUBUK^|zgps)%0@M4JXoEd`)gAm z6GN@rsWZohrz$yi*8IRWM{ehJuv1S{ubNI~BSj0K3o$sqw?DhH>sZ^nCMc`wXcb0! z#i4o<3Uwo)X403!{Dy5g;8f!1zXh+uNt7S}qQtvzpy;g}yRTkrtYD^H8f>ZTCDU># zcU6z7i{Fr+kWW^3(#byo8o_+~tx3hso5N_iuh1GWkU0jFXw)xn;+_04sLL$Cz`7H8 z_6!^Kcb_XDvZ?)HBUd!FLkEYNDLa83scj9FUg8YaK|_?9=8`58^(5Rc{~7mpUbspP zb3h%XV4qb&873HZ?rwVt<`WU$hu#b>k*b;j7U_e|dB2t1Um5Jc{v@?`G}#pmwZIGh zx`aL*wnEYUIq!S!>Wids%X7p!WZVWiP!m>EK_LN~p>_!IM4ndS_MuGVlXP;bIsytS z9afO1kmK%}B}*b{Rm@FY++k>mhK(pl;A)RS?R(PBXtI>0?fr_ivah#7GmfU-giCifues3T{$Ta`yFtqDH82wqwv-%DX3 zc5-2zYuBkVy^atQ_Pxe{&dDkH-I=iplpUf(7Rv#l7TdCejQ|tpuO=jcT$qjKgZH-vU?#3Q0d?Q!{-TnGZz7hZ2G)#~?+pk> z5^b%5bwffwHiM21e_@hY)-QK~lW+NH%zXE0!b z3Lhr|%Bd@{1hN_tN>RTLbABsURaXWF#*zt|TAex&%)eI23Ae1B&*yKyA&b*HX1rVQ zMPEOATct9s|FD|UXt~|Ua@x5oQ4H%5`LH(J;kFuQMkT^ZDBi(?Qmt%OlZ3No(MTQ4r8wW zT#>X(1Z`upRU;{Me^fsTb|4SZhmai_&J3IxB;pg8dBGS6%^)B=(V&Su;(yp<6qkUBs8(%25~8zv<9prx>a=f4z&Gdh;i)FYV}6RI>>}Oiq}c z7#o@7l-Oo|z_n69r5Ytdx$P$t3aTgcy=*z1`NwHVpcX$I6hq+$X3l91a~^}E10p!J)-K29r8091l0PBAVG zDo_aQXtHpQKh(!?Tk9&m^I=~7n7K4v%|fI0NRE&D5zMvrjildXzmr0)Rw2pg}0rRZQym*OM5R<95!)?3pcH{Vr9PBlObYL!-=N5IT zGIoQ$IjMoCw<$yMt#H_MLek+;l%1fRV2w2%-`D#KysU2tyCg#Tbav5XarY11pLah_ zCYgsrKToT28nF&?O!hdjkHIE$P0FGtr-t!yiOo5)-hGNb{AIm0MaMj#sxq6HRr%Zw zTYat3hK2n{1aC|$j)(Ju($QSGNDutzkpVW&vJ19Akuoo!f^@=}NpxKQ?BXWd1AWd- zaeK4+u$l<1xy!Ru%K=nyO`iH{7wt8|LT10vL%JmJ+QE-rFGri%*D8hEI-G|xkEs7J zLA1;j2cEJQboETOYRV38l8&Fia*c0ZS+`1?#ep&g11_&`nGaL$grXJW2Of5qKb$s}q_l-uzQlSIhX;z&kWiF}iP7W^IAc{jjBtX#YC@1=;TDbd9P* z574KaT9!!b!!%k?M=qRe5lH*Nn+q66nVLUKDE}Zqw6yMEBSGaNcZEQ@3qE9ABM@k& zh)(LW+#?|&X{i*c1D@^=@Vyx#GgFj(PtR9%L*kh3(ht6D(V3<71=2xSk;{`9WE?IB zEhBrLz<5I+b9X%Z3}`Il$x6-OS#3$=*m7p|RL&Pzo@Ojx47+v7T}W+5Jql4Cgn3zI zmnYVU4c{>y6qY9z&%c~F#<=YEE7a)4ES+c#uDHRvjwd=YVNGWCqZj|cPSZQ?%QN#d zsi77_){t3*j+B)nV=9O`0h+Uy%an1E9>=mY7GE7W9KM6e%&MfqRU>7X zzIWO*r_C(GExS+jUs>|yyE&<5ior+qT!c;E{2#dphB7PQLGzt8nQ%ZSOMv3dzCj1s zP@UD3#jKX=B(an$3tL;r;EBq^-JWtF!ZRcVjbSYU38fr#WXJ~5Tr6~|RAAE!=1YEi z#Hiq_FrKgT1_jgM`-8G2$-4iX|YYM!}C3*GT7^f0gOu8&F%( z>e-W&98G~1e;wzXn$f{+*05zdZ%3dl7={2GVTN}??OKMZ>BjtH9R81V!iha zu(M7#g73AFAT%U$%v~QHYWo1i(;=pJGLSIL2-dZXx~5AAzDzZ3NhXV0|!4%zV zzXz&4rp1cLgbe}?Z$K(jKRrZbSH)2;zx1ckA1!|~!-F&jEq=EyvMZ{-{fBJs%x`We zh8^o0BBW;+ZJU%-=$vhw5WT524Hw|K{EvG5pAP{M@1_m)3MHBjFh%}a@E?E$cm9_J zZzukUJd=`{nQ0HvH*Vj_sp=gcK`_kkBvpSIUUvPxg8GcBA*OIFEcaRoxF%fZJ3QOt zIb~ULU-f{eva!9YKXWZ!=lJ700txQiM)&3SP34t0p7AaEz^_YGzW)j)y?yDYB~ecn zoY~R-rK2)BXRTJV`pWzzra4h~rf7D%zW|!}fl?xw9g6Palok}?#*hBs0e`VCShFK= z6nMpVK><>Tka91!L4;_9)b|%^u_(gq36w_6qAni17~k69K3aDa>b9q^Sg`mYk~av0 zWhkV94@DPKZVKyvFcz_XQO!yF{${)D#Q-^&BzL7aq z5>N77bk5ltj-_054zfx$ae%w#CoiO(QowGZU3Aqx|FK>>UC%y#BH>hqGttq_uTrH2 z?blr@yA^qI!3C2(kw=tBBK7LB!C~kg3H_=#>I4Ku%&IV`A542LRz2o2c0hHuBDs0Ap06-L80YiZUZE%pTNDDkRg70ih_LIojB-%Uur zN)BWAUoYDF3BCZ#nie~5vmXv$$#xpEmX8yMYTzX^+^bhy=AAFhBz;Zi=zwskN7a&{ z?WE}%Z!mL~{G}hP4%2er!0qAjsDm;vZ?yXjm2L<$c5qAF|)l#eU8v9ZP zu{~xgE!D<@6!PY)ZOD6mP$ww}0r#vnQ-k7dQP6D&)}fg-Z^VI1F@-vB1JoYW#2;~i z&fk8Zz+{}qNay+ohF(NKF1+fIn>YV>tX+j**?lnLbb=j%#?&G4fwkON57Vu<4F3jH zJpX-$;<5m{;hBUKU=E}ZbfR!g+ajuT5#f~8g4y8t!?U-*h}AhBG5 zmXmZQ2*3)F3KtF(z~GXW-Dm-`1A2t~6SH)<6gM-u7#ovAyePhx_Ky%+C*oZ(w(vB8 zn)0ugF_%^(VZpN+*x9;gltCw3)JoScdsuFnHQWC2)_(@! zz=QfFA~1(8Gg|&XJfH?@b0GaxY(Y0LX3GPBEAoRl%{(~NOCNskk5NnM4A%Rtb@inR zP=jia6QExVUPPL)r~%$HxO|z3sTVBFD9Bzbe*FQs_-sPEtc{_dN>XpwBF&9H7Um=7 zqwA)j=hJ9nTp>dG0pxsU9d&f+QwuN41n0|Ie;1DicMtkhEPu;VY^^nImr+l$ZR2## zQ`O@SM(!mohyf>kmP*$lJYPId;-KC@TG_;xgzv9tS zR!P*ptE&tbd!4E@fU|PcG-cf%-8gSqPZ4410srf@8y4YAIT(4uRtbhnI0j8O_?WT< zuoUJPdchxblHfTani~Kf1&`Bp-f!?ls8wzbZSm{>v9QCGu5K4e?lEM!<`Dh6{{H?- z77Q`|=p5P9v`SunVT=|&fLhgM!ot8-t;)aS2Sj|c6 z^6ovfl>;t&U_}Q^rh9U-;IPAsKa>K|sPpYAS; z=U~8~yfx*94;s@D1!A$>H-BTXhI@}jnu-&~goMdG&od?g+`SNVArtdtu}MN_+{dI_ zs)Mk&%eD305iDX%-6Lukjb-VXB&6cWjix=wj%(#{OK{?d-X(1gX#e;kuhW_VIR63l z_y6f)PR!uLp{IqA(@(%xa&12?phcRw4QuBF?g<#zaBzT#B;@Oc5bzq*rNHJTZAQMK zF;h18TD^6FJSkZSIk(A|R0#CB6JQ;P_sssp9>I3q0}v$MI1C z`KK*&L`X^^L`BnNeN*+Nng^^&tCYb8I5K#)iQ$r>B6Dc9#qNAno*hGOY4P=zi;Q6i zrtl621TQ~2JHHbpUn?B!2+?=9cIMd?F*Xvy&);tgLBaLHa-y?d;Zn`DCcZGmdxk+do!Td^55~%R0NJ)V&uN& zzeyQDC)D^qP+)&aH2)kf54Zs1=fNIWXu1g!O8!OgX(|-G=|o-v)c2d4;twaIFofC1&h*P2z(DD$_RqnxNB_x-FfIFWfa8C) zmj@q5=eE0h`X}#ysD%G!qo)7*iA33%SC)1FFQe&d(NX8?h1*8|tp)>gBhv>0PdU$w z@9rGyrS+wFk@;BT=E}PrVh#m9{}#8|9A~o0vx&e}HGa$MsAQkz1AUP}fJZR>n4i!0 zLQ+I4gS%LTGgGzGzCpm{UwZ^B!=WJmkWQ3sXsHc#W)JjJSI69+EsJVu61W_A24KHI z&HDk!u@t(X?NOp^A+H}d#{m6>k&*GlL8+?40lr_%yJFriKw(s8%MS*VpV~-4TS(b1 zAHRq5Xt5D=Y}Ly0>iX*}^*@!f&GZ^vNIeVZ<|fBq#N#+UUi7ZjehLus(==d$|B(en zLYkyu&3{29BpT|eJZyXFM?LgPVagW8xAR8>hM~^U2&;svOYu=`o^OC`=Y1Uc(_HJ9PGP&y7M%>ZNkCQrgrG@15H7d@mv; zCFS;T<5pVHyq1>SEuFJSi&KcgfZx>5$L-t9GW z7$Qd^1A&6+Gt}Rmd z`biq*-Ft9akb{p40myzmew+wuHp5u<(9`fDZ3{kcVNh&}&udE&^hmN-&4~_H*Iq zbsyjSS`8=Mku&x-aQG$sn>Uu7A74K46O|dn0k$@%QxQvem`cWDZI(}V=z9HuXO=WC zKOc;UzHh9&pVaR_ffKvf6@qv%XfhDW^M+^i#jgosw;z-G z&)0k8N-5ERNXHN>&%uMau|A=$XpI;f^wx>zmyE)~E!vE{*g|-x<6b zK~TiRXflp%|Edf{29TKi+gGDG>r0#bI@)GGflCtHcy%&5ObJ>Kot_e)ecdI5Erfk8 zWBhsCZ8Uwq!OLKgB%ep(&-*|oJR>-_MoKj{Lz7+LRB%sF27{EmVi|A>^a3~Xn zYALz!;(b%?wBz8ZpF`A?|0TcJl;D!O)0WPlJ3w#R!WPGKNCF&|MAfFTk>w6Rc8~#( zHxK|2u&Qm^JM_{N8bm#G zzt~N9iHKZHDKZnE|Bh$Zeq)&ROB!AToR#=)hI!Q2SB7&{2{#$hn#APaUP-=E?B`SZ zz@9o97BL1y;^Y&MD-8VXIT8zM^Zgbii>~0ppd8%1ZR5tn)o)kV0y(K$Si<9~H4Ogp zeA=I35R47k?zMoF^73+SLASrYg!3dw!T3;t)K9aS1t@M=eX0Zr=4FpHu0VR2C#)D8 zy4p_1vfiqc<7wC#ySmDMZ*$>$aF~*2-7b9GZvpzf%YNW-%>P#@p+XYx%>k-CIf&5f z6;H$jmYC8F>sqZfSrRbIU;ed}NnZJq2k+9Dh~-e$Evf}p5_XQl%79J_Y8|hwzcR||HgDgYt0HMC+>p`jOiC7w_ZlnWT? zLD?>O1y!t8Ri8!LZMNQ6tws6`R}x}7^|L){<+z9skD_%~EmH}C#pMv$PThgD9T<2!#lo@*$nHs@hKP78s`OS#?NIucWMa8B;s(x(2b6A25a zZ$?CC^?xiZ?XC{+k2jiUK;DbTK~cQYwPK@-R{!%k*k z?*156CHi$x7+d`6PV0^fdcIM(c$)@wd#eN#`Qv(;l6Nf#8y_A5=5dAj zgpvJDzk*YGF5%swP(7xGFe#M@gD={+p3K8i<3|#E3msb1l#t#$zJu{r2 z2O~OOgKQaAmT|>&RD|uF^f#o5<+=`KUu=N`ket=t_F0?L#=*`SxN8UyNVS>1*9Bf^-4@C6 z9nZG=hH79^K-O&eDZSkxf#lw9e?k#y6k(y0Zc@bI#PwNr!o6{4oCk7Vd8#Om%T>J$lkFD^6XndQ!0FN>%lhVx~u z+VTmIboqZ&mUnuadU|)&vap#%=Wstr0t*ase!*5{ex3Hp#o}R3O~HQdr-s`CvM=Hu zC(wN2f8=@4t@%w7QTsDA26qbXaiZW-Bd7Rs>$E+G8V^W%S4Wso$Q?0iedaGf7xd05WQIByu6AGRcw+`_G`qpAk(5u~x;E z9ryYG{Br-U+~dp(0gK4OIXG7g^ISI#Qy9k6)8QbAKv>AsnKh31PBNvo$yuEn?iEpR zWv|zsx*z;@)%);l5f+KQB=5`pT`ZFCa;6rPs@do|rs`R!hsj{mL!JWIn#}z^nk-qn z$r7S4db(3M5)yi^^L(9LN`~tU8eFIoiCtLPlv7vL^TdkRJ2{<--=e%Jxk<8;lr!p* zD8G;}h2SKLm?)9)Pj1WgHMk@K)lLmoJ`mtdF?`FXR?(Igf{SI$dGQjCmWrxvlr|;v zcHZ0Ns7^UgA=7?p0zs) z(3KnP=_TRd`e-sEZ~VS^sv5Zm%!M_!1*^mU1o^}=o1y?*1=8d}s^TA|CECmhl{4S+ zsAFi{82VF6Q(w78Kr@rM`hA`WrN9eGGYiCdVbsx3H|1So)JO>|EG&OguO2E0&MYgw z^wCZC4_@AHtXhS#T4AMjt=+M${!Y%uJ z>^w$z5_!G2>aRH6XSFpR;!MIBteZ7q%d^Xe#qh`bAKDkhE zBP~u_bGo&yovkcdKBe@-P~BjjQF*tVI6a|44)}D7aN!(T@>PA30&4ut;4oV6UEr96 zn^kmmh6pFZ@1@TMn8F`sXQx=3-e|qqmfW(V4jM(m{66#Xh0(0VN4kGB(Ho?QARZ>>9-9cQMf007s>{0ZJXVZSXK?S5bhu>CG=8%gDhr z4)40o%;k4!d3X2}R5NOa^kRF`Aj}5R*iciB-r7uVP6W}VRN{LxL#lz&jyi5;ufd9i zati`1JWaIJJo5T_Y;D~U$hkcHlK{@}J`%<+%l@Re=(riektuCp2g}8)Z!;N)`R{^X zALY%FaTWc7NbRfp3WdECH}Nco76@41+R%NQWXVs-z{Rh_7oej<5H(D{U4DIDH~w5F zJV>Gm%(xsKU0vNo*!S{sPhS5v&kO$#6)*?Hnkacv554!&n?6eG(afYC*aKFS0ZCJM zfMvYUa2bo*?zK_eR7rRTOJd=Zh24xdU#BipkE)qO%b9kNFm2ou$^@4_4R>ozr&B(r zL`?>o6Xd~*rpeP9LpH*?V2W52<;HeR7WP>z3T(gBm2*^|qMGk{8NiV zSN-WO9vS3dl)dU3`S#-%<=eN2qCXqtI#v`^;HqB*=f1O-$6Rbo@P?A_hbhy`Nb{_9 zEiHQriHaFOUNSxU=WAJp%01o*n{R0h_|Su2$bF>w10O;hMxm=>Y@MY^FUTk1f=zsogJKyy9ZyC0DwiVoR|BRGxqw02 zNac|gf+MU-RU_}(>Xercb?cFbXeC0wkEk(cs0F(?(DHVNIi9>*G{FDRxBe){(S^|= zXLUSe8}GD-YX)A5GEoc~P_Lvy?7{dVuTsMQ1w)z9{kavkAy*WK(B z7rv{=5qSmfJ{uu7F2uZNYE0pRmxdx@mvv=2JbF&RJ8}vq3Y|SjE|24T3Th=E$qcps zfUyXmt8Wh;{91{EL@>uJv9TbYtKqe*x!`(-_eIbPe^mE4ZKq6c`*{|5MMi#l;Z&Sx z;FxtdRx`Ssrz&*>s6W4}HinCkwk%-&ih31IW>SH^6Er5>{1Z6>ulOT1A$9~)P@?|A z$8j0L`JI}G0mgI*&m>pF(h?OI^u17@BahzY9(j+1bz*%@6xf9K$Lew_@uNzI ze{NP@US5PSFLiF#S0lmn7&43ydr|fP&Bmw2N!rxKDfLIj&hyXekwdR@q z$zdk32$fLf(sulzp-~{KU1V1YkcHLtwPObxHaH)**KYPx2uK@>t&WY7XTGzwKR&Ep z{vs3k9AhfAy$&Ygx4iYA5IRh$fN$>{)@_bT%;^%R2t-36SO$~GU0#247Qq2VR410b zJ-njln5*pc(BXT)M)FysrT-ln5Ij6#RUFeoq_6CG*9P%qp?`M~1|@%$5r{1{ANg8X zcnY;jEC6;taN<8d`rUH+ONm3GaH=VxRk66cGvw9p@c|yF+(*P_BpiB5I=YCSO*puL zXsCu(#%q%HNSG7i1k2TcFo|+(PQ^1juKWklde#Xe<2nVAM z6${75W@#-ZPkH)2MH36%x*?cUmOBupO!f~+dU_+Lv*SKA&0ag-&h4kO z@lV+fRy{;MC39Ot#%7v6JQ(N(*Xu@BdDnNw#C0i;@wEDjMS@tqX}$kB7Yv~gCyVbN zr@16}FzSmmzxdNY+2};Oi1hj8OHE2`CSGhV>88g{Tw(gqmQ6*ZvzqfHfA%OWacsI| zp(j2$!=1FP4DMa(2i(uAZ6IMq@wW3MhL`Im?adJJAl}mX z!BJ+R_Fbu*Om7`J%4u70d(;4jtV%sT%1RT7^Upd-w;-k!qYLqe=5sggB1d!8HH-+bCzk8r`MdD9n1 zh=z&e;&GLaA8?{`wm1Y@IcP1yIBlf?$ot^|FoFequ-mkbdq*7#A?t;niANmC(B^}y zx2Q0qsM%&?TwMH(se8M8FUfMbt;)wB2Kuo8VZPyl4DPNc!?>M9 zJzWOkm>i=h({};khOT`j2<&)w8w28&>YCrJ-&2W4kgKVRzI=^^>9v*Dgy}>J3Vo<= zy6&$9<$V}@ZB+eZ&L>yCq6(Cut>g_Bi{j%-y*#U0p2HsC2(K0cIcb2GWUXuOSl+q^ zJXkpzGo4%AsFDO=9a^!7__f~!D6wR>TE|w=pkoJ%3(A(($;54!cw7ic(5ltt!Be?+ zDIJ!ez(EQt)i*l9uO@XJ6ONi`W8cX?gu=kWnhL+UH{n7R95%Nvy+1zztsqV><=B^^ zeYo_leWb2@-ZEv_e7&7n&LK_~w6JJjp>C_W#^Sw5=l3$2`7qBw1oWYgT}fg=cgB+gcq#7^U0!xbQGIu~`|uP%H}J^7 zV)lgX@gVrb4ladg;r|(6yLY?$lWspBX`EvqhV}mK?9E1%GO*SCYfYwGG`@UAlY|Q4 zr_RaG*4d(YVJ%0&L>-TAq-ALxA2*K?nSEjU|z2XA|1CqWymxO<-Fu3+QK?h=ze208Vw+C zH~I#LSl+eFovMGgC&+}~9Ix=+|D7S|jH?@vaa0|6mAgBj>WeDwvJf?m&>`8v=XSYm zp?7C(<6?1*o zTG{Lzb%BisW9sKf9&B=P!Jj@9r)PmT^g{!VDNm2Th(8M$&qlTFHN6KyQoQf7@;h%J zwT|=b%GmWGum^GV#EN(*A=3a_p7fg8qYVzEypyiC<=OI_8J5g{mrjnCJh(XUdcX5nwkP%mR z`NU4GfEbzazOs)x8hN{*mXBhY<8yHPkv%O4h9@~~d+y4+9WR8Cs%e+}yeUHsGWJOR zO)Z%KsHX|f9pB4Crm+p0;r6%+KA>CD%y{i|;R^1sBUoNTz{{)#zvZ@>b_sc+0t?O@?+ zA6#7$DB)I>-rh9Tk4S+A`c@Sn8^8X%(J$vuhGBIuffK97R##UgBc#Gr?d=2k&)O>+ zxkfx9pv_y&SpD=1#KGY0s;=ae0{48VV76A6+I8wmhm+kbGOJnx8%m`S6(k0Q24p_S zxuVf8+{c{mGtlIlo3RuooJhUnB=!LJd3TW?i%qyU)0UWjgolc$j-w+9M82bWPEcCP zHfBE>7u+_xg#?*4cEe)(ymJs_DBaupS~!T6vhx&^9$t+6#>e$A2veIe%I#)eZLRb| z%Jd0O7PU?x+NO7;W`dt{U6xiiT7WR{94>7qZHVS%w$W%`WmQ`@9b9qxz>>G#mN;L1 zBO2)EC3~0R!4)+6LOH7$)3IYUVxbY-_1ylDycI0CVJdQa;>^fa*4Y|m+Vz9CL;rJ& zYnRi*ee0}mLwmcBn6J6)azy|OyLYLQm-w&3_ihOVPKgx(Me!dpS!?+31k9zSOxY?1| z?km=$Jx%OuIKVa0yTpFJD*=NxAqO8V@j4x^m;_Agv0t@e+gr zotxPf&F@{@t#gVu!TrU=)*6y8Cf;5b#Ws#cwsM|$yl(N zjuoPXgl6XXvsJxeK{*)s6g_-hVOffVdP~0m9~+nS`Ez)9ID7-omuPImW$Ny@Ppzm-B~u5=}NyiIh7$FHxs&jG)O=qwqyrNjO%ie;>$i8S2TEfIBp zD#Nrhqsd-my(em4To{&z-LQBi4Jl6u&hl!`>=*T?CKrWkDw74g3RC#y%a@nNe|uxJ7|mp}fM85s z+0V}x1?PieUP#U@b&?0;ryyL3@exP`E%w)i)RC@ zI9CZr`Tq#}W3TO)L{z^Tx(k*EJ}A(|rit%}p?g!Q$tazab9irfDt;{cS$$YneWOPM_BgF*>!o-6Pv4iq zYDK8uKMkTEnu1*voSoO_A&IZbYxt?0`?Wy#m)(HXXbuh@-Oq0~9mN8D&xJkk!1?}D0^hL#c-oHL@(gBYu&^PEVeT`~ zFa2lfxovO?`d@MMEh-NXUc9Dglw$f2^#)HNMEJ}oy9;G zG6tE?uFLwgrj>igyLi^1)#Bv!LP&-(dh6p;UW)kqUU$;4))t9xVaw%cFwGa@%`4j* zlzlr2?@iuIjJDpX*wH|E{BI0;<#ejhqCWjE7og@GccQdNGsHx$&~06k(UDfl=;;)x8NDaMwOamMtg^msDvk!=|{eHGCCgQHBf{yu({Ze)`nWh3# z@Az5X(t3FcNSQb@x`|LJ-AP079aXCNScr)BW?GkeVJ)G4C8jW2Av0H?E1x7>fD&wU z_pJK2#FMukEZWU`)auw(&*^B)0j$~b6;OcKXAD&3NPR8ggM&!Ll2I7VO*x5(86dvN z@jHJZa&kr{Rw=;wby&sRRojLY`9fDnBJ2bX-Stg|9?`w-j3Ll_#ObH0+n- zxi*nTOscVU&wBTnRKydxEppkW*Xkz8^u}eY!+^Y|<;X15ChtRUCB=#bau##_n-ECh zLNkY$s+}|{Q^26gBF1WK>=eKFFcA8fqdPq#@*Eq+=X8)unr<7uRcnoT6hEk{!X(V^ zR;mgHm*$zpHa?#tMxv?8YCbO+G)vC`0V${XjJWGO?DS^0f`JN&q+7m7^vkN1Z#uhK z)GbCwY}7LsTH|kSVqj_2HR?bES?}42clOyRM#u<_)aJ3)=3sJl%H%1BUB)dh*Q>Rs zuGK%4rE__ztHo#Ba0V@$BB(xG37Y!fvjp7i`8hp5vB4I+;ZX>JJAbU!9Cbi152%Vd z>QI;X+GB#-?!+U%RPa4}ZN7EMy+mDX0QY=RBDd5}iIr=Wo$oW>&e;SmATe49PZm4l zlwcy$FaYCgp=H&(kR-Rx+X?hMD9l(lJ0vdE>S&L zQvi$a=gEl)3}AB`nTcFc33gv;nshJC@1L8^uw%$IkCWV8o}V5k-`A7R?u;mBLr+P^ z3T^ydH3G8&GLYD(rRNjS!9#dU{$52WSi)+m;oj-un&7*@wu7b-~N}|&HbKp zTGjN3x*^l6*K1VsPh+9={dY|L<1HjYHDH-22Tr>gBKRMk?DdHcNo`m4G0;rmZB*>9 z%=?$egUzl1*N#fEM|I%uoorIIYgI-1qS)E3n4@+s^^Tqqnx!0=ZhEMzE<_5xG6`KVX4e9VZ22#qrN9dJRKl*%s$ z>9BXv-js{F673HY&*1A}vDmT>;8~VR84J>vW;gmjnvi`Mt;!}6Su- za^NGh@@WhARg~Dao10U!yH**SPV>bLEG-ZDa_~VY9{GH0*|lymc6k z@NqE->WLsECHNHih@=@2qn(>TivG&Ennl3{+GJVU^{1W@YGTPCkr^ZJYidG7es{*x z@NtKrVJ^1+^pB5m$A$#9*yrDfk?V(`9Rl~UIp;}Z*%!$s0(ZPxk%+dPuhd$H2EU9s znhp%km7}ixCIPEyt}s34{Psi}T6%ns(!qOVmLmbiEUz92+?Fn;ZdMW0YA?8AQV4VDcKFxZ z_1K=<{!CDKos)Ish2L%}I(uYSVS#RixK&{SQr}J|JS+#ew!d1+UEH#llx!FzBNuYp zN!sGM*MAh;qb{MVAB$x%Z203%sQ`sPmuSaVLJ9A?JEcIGL}Bu(sNcFWhC zh^F+|N^IWWi7f1{jmyY=qQ4v~CKi?4)GYi|Drsl#H7Z{IX*NtQvxuleo9|mkzBI?hkNnAVWK6#k;`X&l=9;P>-t~^(lf3>F5uL+OLdLaS z^*wq05G_aDlEtpjpwc~-Q$(bhIzBba1h$8V^N!#ax6hj&Zvv!zZ zp&CtR)!f#=&U}XgH#2A(EY>$8H9C5iZiM@BqOr~f;$+!9+hj8+7>=M*JMeupuE4fe z6^%U8xG{O*b-)ZSclpW!vE4a5-Y6{Dj?k_5s7&$o%5yu7N3AU1w-KB8-Bd?E8g9fN zU&jzOs;`?)RF@ij1#QtMLY3ctn-6X?4XudEvbvrkTo+6FD%A)Cx>G`OQ-YKN8<0k7)7_iyzOVmt&Ntur?!Ci|!;H=#?|y&3 z=Xusz&sw_-$iKy%@nM;##S#|tq6u$^u%5ZxOcVHK6N8BPy)?h|t_cY_Q0WMKY-+GJ zZMGON@Nn6?B0JuZL1Oe+cMo-i?xp#xeaa8-B@sP@zIv2Z_94i*-$v?N_X52X=N>Wj zJE;b$GA@6OQ}RqlkcvZGAjjMA*xcCeBCC=~@aYo9*Uv>~^GcGJ35V45*PVH5Ydat4 zHJ;Um%*Rd9xh!A=e|(M*_;6ZVl4}>acMkExa+Q zVe6B|)aoKy_1X|CzQ8#aA&3OS_EnF6mY4@tM4K&COfTRI(kmYaT4>$p$@>Sky`wnb z-zMA^n@`niDq4j1ks5Zpri9Hl93>b6kAdMLkta11Ja4onvyn&{si0q(;*);UfJZna z8-368XXhi^1AY`;2g-<)1i9Y@{jWU!OX`6PuaT%|_{*AoIYq0shO@U}huzfLM|WHU z2@ikRuFl#*o7U)`8#cT|L;pnQ-hA{&cK?1qZp~*C7FXlDcj)zb`jbt^^Si)~#Y6AK zpod%g41(57d4bkslWxqr+Eaic zXW`;|-(qZYFxq{3%^C69!NGwFWI<0g6x^f}r5@kjq)(x83UQH9^ zVN!>e4~H$h+?CAQ99BN|&b5S${gqW?mP5H{rB#zMW`%P5$=G@o!|$XM6bB?PqEoVZ zFWMpPw7+5BT>>;l5GSPzA2NadwCKC?DnuTyjHV5Gb>;Ik83!hH+Muhi|2AI2*VmU} z+BGLbHowMBFhPQ(D~2Rbf54axY@CZf)a(6;2oyCvG7$e{M{_8HnX|Kzn%pJ${^ApT z%}GpyGGYvU&6(aN36<#M1VB)+Cv{q_$LN0JdSj&LQ&V4I^{b%%(l=P!<)2ga0#P}_s!&!L;AO@1@J?0mfrjKm9~C7y89s ze?RNnR##r;m=E}wBgPGFM`zg#mzIZtttn;U!?ZU>R7Y_ZG z!JEsG-iN5v@h$Vqc4qM0eEaLC6*tb*H!R-r>ZXhWrl!Ld1lf1{f@XN+rey!{a(qm# z1`G7^N1{Mce$j@s#tD_ z9ELH{q^5@UgEU}9I><>L05~mcY%wsRZ_3?HCQh09#-^%yH=2yRL?btL`JGHejmwgx z<4opSD+b>>Jbsq4yUo%bu~p8WvGn^BE4arOJ=Ytiz36 zP`Fo7Gjx$GA-RG^su@r06A3G+3e5R&F_iu zS~vTCDIebvOlinU2fvjbYTw0n$v(9Gi{L7YwXUgm!~R1?#Y7aORgRM zmNM#0I3x0maj0AnvCL{2;m%mCwJMPINcoB|(i_|mt?cJvjn5%E(BCiajOWxY zMWVBKK1@U(lFc!K&HE?;H6Qyfs)G=3YbP*!&~&RQQt@MxMBJ9Ni46v1P>fSVWJ5L~ z4rE-CE{6VZB}6E5OAzAvlq34Oc6=^9WWO;aoV_)Ch;PwB(!21dOshNJDe!_{+dy=heis6(TZ56S|@ z0<14iBvt%p-xqCL-qo;)Ye*!HWfAI3Cyah|xxd1Hx2n*qF)-8oyF#&&o!FQD4c=VXO4n&o$!F(f?a!<_QNn-5GzkgVMt z76eV5u#&-j!yYq^s$aKrhcfJJzC#Mi{^bb%;{jLv{!YnfCx*1|?mp;h$iC|S-G`XSvy)ey~UBCSR^?MG{kY)Ow zF*Lduhjv`-Q#E=Y^0wO#PLuyyIN7{UK~nycOGAlwBU-oj1>U@aWJh{|HPD;EMQJGK z2Hwn_FD7Zm@KR={Yii zv(Ec8WK@#t?3M}!(;m-V)VgEq?s%9u-R2 zQi)}hmKr@+(ec~cwSn}XTMh)E^)eL}w?PxYn9DMnghzfg{#KRW2AAX6Bc4s{FIW`0 zrk2*KTV2GU0HE`pl=B%4Lov%#?H(>*q>Gz2*Nlyz-HrS84oGk#g!XDK(GY{wpmK>J zE&;Q9Ug#4O!Pc<7G^RnC2nlQa}ZhlGndm)ru<2_fC7P3<%p4(99&l!De)#I&% zaSF*M^1h&&X42H8we_8++wgYQ&aTfUYQEPB?3}zxsj(r?Eh6#dcGIEEw*qMRWnt_Z z3SX&AK;J(anZ($vFvkBzeZ`-kS~9Yk_gdDY-kvln_dEU1w3PsgUxhlH@tOJ%PA?&% zx!$@Hc~FioofDA-&x5{6CCp4|q^3P>6@7*dZMepL>UrnYU!_2Dl3?21Qf!#_T+rOyEOghE`^{&Mw=pLo5g z%O%a>bERb?ndR0-=BkOFm;3)w>8&N>m3%!ym6QR1T6J?PelW>JkdzB%lDR$g$1B5( zYFMisS{n9dQ_a&rw^n+Ox(kJTGC`)#CNpt1@1-N@Jp3w?vC9|*iW{-MsKA#jeu%Pi z_#mg9*!nfcCa0E-S@zfuNDl&}N^w6U@t)wfPl$TfH7z0LCMD^OFPl zELy_%#LDwvK3&2^TO0x;A(GW&+El@JCd!DPsii$}!?*^f*%|=VEw6&}A2P6kvrL_% zP{UQcS1pq5C3=$y*h9Z^R=_#CYqdGv^9LE5+))k?2oFlYH>Fk+-cmpc+(|too*Ct- zL~Zj4g1xO_yVZN>6oN*-FA{W0EW!0M9giZ?S#R|17wQ|$EjX3## zYUGD^y6(+g}q8?MhtPbLpJ_j}udvsnowt`Qg8oa;~b)$DvnwIau4`JI%+ z-93Fiw!QrO$(=tRU2iQwEnuhPeaDgbWWz`mHL0y7MLQh%Grx{dZH48BO`*6zI$grA z$!!9&kdG!O(x|DGd){AlLaQ6SrQMp>E~^uMLn&qj@!Zo<-Lc+izyCQMv{6`7Lvl^- zuK6`V`WuN(Mr`z~+ZKF{+xZLE+V6$Q)tOhat#99MK84|x=|Gwon3QzNNBNse&n6%> zj6@~Ar<;mIg-{*NoW_|2e+!y?z~=E{ZY2h!rWG4$l}b?^j!8d;TiW78Jl-OUPzw}> z8ML;ndHbB<01__YUZ2;92@Y^{WxcyOPuQZ{xnT3&p>`uv(GM{0(w&2S5 zfv~KSVvFv2nF?i%0g*wk>{xnvrN@fZZ+>d@NNQo#V#1QSyOTrB_H2T7Dd{2$Nm{vQU9}_jY7bZ$hR6g zAyUBg;==*S>iiYuh~}2FsF7iHYx)q0&kMbXL@EhL9-(m&7HF(23pWBu&53zRrW?1+ zS8e1{_Q!rU_nbveY#$R-{9uf6ckO>sX4BeKJ2;^o3{WR}_kDDCB1#^dOv zVzWWsA!Iw-Y4bI>^pZ4yi@gPuJL2g2WZ=2m&6z!TF-6m`=zy^nVj(nISBT+74Z5begQ6n5zhv^2%;(Qs`(IuKkLEd)4nlCXNE1MUZ?CP1P?E4EknbGw z`7s)VW;d7nS0Ch@jC5iricEd5)7!JIYGlmH7N6@uH9aVrn-YhqF9U zuW_h_i@L`~;8RS@cP#40#x%@qkoYFk%G%m5D}L7})`}Mf<m8H%a|2x zobK&7^fpEgVZaU+clu^6`N=eH?pEAkoHR2i5Btz1fbb*ZI`Jsmxf?9Dg?Xv#tcnN( zJ*k6eXPr-;Z){v`O0I~y(*)2JjZVwp`}(%yGVO5Wm{rcizCHZ#Yc(~*0yCgvm-RCR z>37NN`^{6L+|;Yq^r;(xHzb3AbeTZN6dAw`U?0Z2Z)ty6 z6e7#(#u}tG0|#*qvZY)mw^cc0D7GhNlcOg{)Hh4>)slBfEE60!r*&_T+6$-(!y)=u zz)~K+^Sme!`x*k9VZ3x$b@pO%w@na3V}(&VcMwUtI-V7vZ385W&~bzDbB9z~<@ zR<}BpbKoB%_&pv zSuzv5I6-=lmHC)c^-2uxNWOKr!P1c!AWNPM_7?JZeBdaPxc%x#D-b9~l=y|#hFObE zFK5GHOq}+gxpEov1tV&o*Q`D;;0`v^u#>uPhB(ic1|FT(?f=Nb63P|{1yrHSm~aJ# zl13i)hAjB?TU#U&A|zR|sCz z$p7O*-7l%k62Twlf~uDaAnmuN1LeQ^xs1a1NPz#NC&VW?xGxiki5HNSkIt7v`*O=@ zv@GCFEx(%y1TWVbh_5&H&Hn)JJ8*Y>U=vDK_?8Wn#*gz&zQnnmrnb7Aq?<#TTcl*| zeca=w(#pd$dP(Xt=(c~jL<&m%AFDZ^D7ncwm>SrF4kLke&LxkZhe^^VvpkGj6M z%rcgYk|^~~y~DA)Pp{6puoM~K1;Vi6s|qOPMuf=IxSY?G-@?3BOsCaDgW1TYxWb8&I;tg^52Fp_}8 zFa4wzwtwop=z4c)2Aez{;~+VcuHATyCqut4|KOs_g;ifJ=q2fzt1tB)#gV&oLCR2K zZZ#to+8Xvynhuxh`2A0wR9}|q{R6~!{x2Y=I;?qZ@V^9a34~?Dwkwe_E`1x+?$9TZ@zyo);+B7d418E>WIXT)lPiE-1`>;Yn<@wt_LQI zl3gDY%L)r23VI({Cgr*j;J5WPee-h=@f&+06~AFG4|xBELGy_Uu_tsm|HA zH#sv-sLC^PRyYX~MLlOd+ujjt|5uU43!TPmA^9ea zVCWM0Lpi|n@-gEQ5AQ%U(w9<@&u$(USYv_ix0oHv+>jfb9iwdXL%`2`zSkPONs9_8 z5a+aZ`!M?l$Ay=DWRC*L8>C|{vlv(TNbxc-rz;Oz2j zT>QXARa9kbxt1|vVlPJA#{Xymh{wLFl8+*@!TR!HHHmB*0wNCU1w%$oa=f#DW7Rjm zD8HCx)Y!2X5$1{4@~0DE^0xcs3^*5H%wv0uVLJ3#ruoD#tw#<;iQ)hth z5-i5Y!I1}=v#}O$7jO=EGSR_yAce=&n}b;{t#hYkhX9mH@WnfKq22!ei{>OEcXXhj z^`Jup?ar{4bSBxW8}G6W>~wAF;VQv3p>mf_8gt)ksafe&(E}A}*XcL%eAN^}Ew;`m zptE4tez)oAH z()y8)7;)NW4?kQ3bM1wr7^K2-E8R+@-Gp1K=Q1p}qq%$J^yDe_DZuk49K8JdN?%iU za}FP!XLam6{d0a}(RaJgIzHU%r)|`+e)^m~&R^W!G-d=v?^HUrk(LciwbS-1b<#%` zG1@7(Kh;4uU+ZKMNxSu{JizyLhL+(x>Dx6kTx@xDd7K2+a{X$XUFyh0%K`R%U{N9c zN4Vogf3hfGDUg6#AjA~u!RL432q@X4tEZVkAnM$}o3d%z_Zi*lc(S}w+chre5kwiw zJEN%MvIW6!E$3GLWQ)G_h3ixrs!?8O1Ug1TJt|l7MU?ZV`k<$-sx*?+F-F;YmNj0d zL^v9fai#H`%GNdqJ5=VaS0QkF#BKES<)w5mA?UEkSDZf9DUdBmD|un~<}-XOv3_Be zqxxKJA9mee%UL}8YMTUKH~4DQ-pkhSuj0lSIxZ^m3Dm7#arXi;|Nn9Pu`$;t8i6K5 zyNSTLF`t6~M8c4BS2Nz};p)n#Y%PuC{V60Qjp(D~x?be`8(m%9kH{|)Lq1xBC8eg; z{X9B*46Au<|JXVlk?mLow}*V($WsG8nM~&&SP~!3_c*#R_WcC!UddrPwVP4gGjAi|d0Y zOKz=>W`)xOT#jOxqeM=xLa|FcI^7thNRp{o9UQ)n?tFE;17t(&8)EUYY`nEJ5j>Fu zw5Gn3Vy@<9JG$kW`u_XOqqSzGO4&&&R?1N=)8?ktknCuo%f?t2!d$v-`WH(1t~4wI ziF8)9Lp_-qfetH?m6tWnO}>j1o@!vario-RpdOMY`6m_!Jtj@i-J?6vian44iw_{~ z7wUmj>p+O^zZ)mle*ipS6DyB{;b%m4u`B7kOV=a=n4Mi90N98bYY*_dq;_J%XwENC z*aI#(=I77j2&EUPn88V$+I-+bts!Y%w4sORbeYXW&FI4(`i_gS4AZB?3F-0i!IA+t zc?wZv+^$z+g-ULKS4GxKkFaLVx<+c3)`T)Yc^Dp{uqS>RbwUwfK!OIVXYh6=`M%YK zFXh>04%W)}%b$@fZ8>CA2$M6SX*8|W(v{*-;Cc1&@F^oxRqizJawDGKEEp{05{7(Bn&I5RUm}Nraalmu8nhP$fjDjU z;Ac4Ha_a87C#8_mbMtT+*0Apf&@I=04BC}4XdZU$dLszkgpjgI0N#bKrDUhQ$vqPt4%9rza z#Z!=1K79*p&5`-!Naj%KBV1vzC6>LD{v_T^FJGp^Cb4jFt=-s_VTy{JLN`e%DHecC zD0(~Ja;%SM6|f|U=^h4@2{krDRGaQG(S3}Zeh4^}8+YTYz*LpVp2I1k`0wRGCtJns z=|`*f9QUJ`6`6xhKwA$i8Ts3}gJb`F-_@OsXN~rycvl?SqOtL>4IfwWE1%`dPnFks zCaxNuPiYaZvjHCh7~Ag(SKqx#zEJmvUuyV84E@%&q)LECI84zb5Do?O*O>n1`qfsZ zu{MRl_HH*|EjoGqzJZIXAqI&RiH1p;fl90;%7f{a=?LahxL;$XKYGgpCpF|i7mn>kneBK<$7ajc_O78j=JbdU8nM-2|p`1x~L(c z%*sX`d~v|}Qkriuk{OavE{si~@eRW}h8P6w9(WHnCFVe~%)7$!ffC1%YEMGko09M+ zj06PO3G_Vgxa(X!EGjBu;NXb3=b?hFGOi|L(#_(`7r&s9&U- zlvCtg%!sy70z!zH;#!K&Rb_RL9Jj3r=~7o5dQ9}E^73wwSr|gmIRvgmdOyaY1kxY* z{>=7eU_G&szi#jp+y9@fV^&XPguHMRQUWM?=6KAnM8@SOZ2+Czzy;I?Jad19{|0&z zv~Y%D8nkXHezt2|q_(}Ec*8>U_etjhzm%9xtHv@Xl$Z+?DtgUv$hFS;KNrd0HtjD% z;vX!n?u4di78}3zo-u?thEREMyK@vv1dO#Uh6fap}Cn$o$`ca4`6Z(meN}S ziNJ6MO<7`5yo_MYC&u`WhrBskTybmC#~KGVYdbIha==fNo=8KOkeMx=J+PkA6M)*i zVTv6(Ege$9kGuvwVlq&#iR4MqA5z4GLtRHVH@1WXYLc>TwqID5fy@eK7)AtuaX=;* z-NVzBDI(8LEzRK`;#XJ8ml(P9SN?rlau0q#gYd6Ur3)qS`7BCOh(VG*tahzT;w)fe zr{#M4aX50lUC|%k0fJ2jaP>HDLMgwtmJQ$pIFz}db>K+#h_8q+NTUGtnm(=0KLtn; zg#X@o!!p{0Qm}F9`TiU^$y=otOjgQRa{L8-MvaaUe>ryKa0oSjF*`vUkA(DpnG z1wn_SvtOcqdUhtv>Er)%FWyD93hriK>XX~23-p{Rzez!hT|=sW-<$u{?H>^$zifyA zCTwPI>dG{97BB#@xB*GghMpT{H<-rK0iiN-7niwTh&&V1LWajzg<)9klfp4A-+_z- zht2m4w9Md(ie~f=f9ih_(9*^LRfiA@3rq3xJsKJs!|HKo=u<@n+-4hQk+4FgI7L_2 z8U?n#h1rY!x%6_G+U{<799l6FokqL$&KL-d*0=kI+rv@27r4l%u(}K&YTEd5#+*=E~%dB;mh! z^$GSiUe~v8YvTm@pA-AD0rCW9PDLm9)I5?swFSuUlM||tyjB;(>C4L|(s?O}^)ohI zehvX2Iu)8UsxbLMDHYH_{55m~GX}F~`z^ZVpVqNASHIhMb7%E|&hg91>L+V!)~94& z+eT2beuz8HYrwsi<8df(cGgNp&;n>EwGn;lRK3LB5;kEeF21i1`XkqVn;5$3>lI^# zE#cnA)igF^mv}HP3H;r*HJsXwylapid(r#j{$pC35zeXo#Tzd`=}IX zU-=#_%M`aWGWDbontNO>cr*W<{*Pwr?TNKl42XptvwsiT?U&^bWxzGdzNhJOg!Tlb zDG+myfZm(j-z9bFQECN{-hB)5(#z}JYKX)u#crkiQ08Kya=?9so+`ApJ8OL(Y`$O| zxOjr}dCfB72|c~Pzi7tk|pZfB5p73~qe0;hOlh<91T)Lzrn=2|~)~mQ= zfA_guS@AX2DF5cC$nSRd3rdtjw*G3`|JQA@jCnQ9>pinw`x`E#7RcsPt-ix9gGT)Fc zxzT6Oa8NQZEcHC3P`Rwn`;CwHUbZ$clY7Xbi2x_9z1_ZvX55uu*x0^nQf>TrUD9)K zK1e9j_4Sm5bw7#czC$GL(OR5TNFQxvy5-2T*BPISidZaLvlm*tErH?poJ2AP4gPn# zNLT(0=!A*ncsf-~`Gn+{l-%Gx#a`8; z2BeCjjN5e-NZ)curyfD8nZH(kSTMkvCUr&`oh?M-&6DA*^CaM5*W2KB%%&B(V%YsX zPUvB2`~yG|P5B&iLjR6!h$3LePq$KB@c)h%KmAbMJ&WKw+ckOrjTrp8)jKB2VjPVJ zb|IpmcH}GJ)6W`Y9(l3wGEcNkNdt$t!l{VXb@kR%6oed*;uAT>E<;h#$?{7KuY3%Li$tpnC3%XEpGr);?>)z(J z;IBk(Rd}1Qk6C)6_G-fsVKTB6AcG*7`QbFzSgwe$vcl9B4VY#ItC$J2w8|s6U(g9^ z^{lqmCA>?|)oL;O`@E&96BP>kgTx&+jt^`b6W87yKNmGQ4o?rK-@ERaMIn%KdHLMK zpIbuy`0vxc_83^Ye8ANDT&RD=-d#!~r8=8o6ae^hI2KoPZ6l7K`mAoILXSQLl21y0c|uqjmyKtInVTy5@I_QV^KZ7_q?{aZb) zzhTPg#8P#iwfEDSd3!`+9LC=r&gBkzCS-rf+p0}otDONrVc5(;`4OgdN@$^dy?Ie6fSs83eUFunc=~?u9L8?q_4C)`hC#} zre0`@V>;vYNjg_DfX{A};d5A6tt3!0R$30*7Q1+gU}cu*7ctS%@&M+}ZuaUG*2&39-lT&Qk@VsdXLZD1?>R_RRM!u$yWpl9J2oQf%l8!fD-BT@s{CpaSxVvKwiWw5>F6F#pO`z{%dHcr=T-*+ zodVKA>ly=qGn5~%>23aFuyCe2Bj$Pc*I)rOs9J=V0Q#4l@?X&Hzk0jZNJxP8FSL9) zkQ#XMZlll2aN_$%8`AM!@pINky7mo(1!fW18k?YIc=u}UfW;4Lu&4Ii%7J_`#A zASW0()!V!QjKVCY#=H$EDdHm|LZKQuLVk0ZyA{KIKuG!@gscl2eP$0V0T>b$y*5^!NaV zPV9J!0vavt34*b;ryj}(omN{tG(ylLpP$4u;~;PePZ?~I)7j7SbQ#jNgESI;zUXfs zWw)=ZDkRu!FHcPUmpD%pxslN4zrlsntkc;SnRN|lP+~$$;jh|oC2S-PqeLlF;mUET z!_`kkD}=7e@Q6g(?^*~~GlBV3oJ&^AA9B}!?Xm);j&5b-gGVs_UL!J=Lm0r`LAnyY z#chx7)%&F?bHB@j)yy8Mhx1N1+|$5;CoDS}u{an3S?Pxnv@c%~Jt=FI%$Iu<`698& zf`7lX+eOyQ5tyEA6i)jT>g5wnYpb7{>*9!tjEtN{R02HgH=N15x*95UT4(VsLj5l4 zR}eIUW4RsWO1Y|;?iXhrYrS!n`?C$_^KH#K&ALoXd423tdJ-`84W;6Vq9&}SImdI= zaK6*iVb1UU8ow!lG#AR@UAy2@F?&IsYh0PiiQCtUxTE5xEdSK#CT9XZ3Y(l}%sE(VZ}`O=y!@ zdzvBBuUh-;0|1p}#_#Noo99{(J66C%gTPObcI8$2gp){Sy|Cuy?TXk8H2Pkc81Q^ zk1YYU-P&GxOioWz^CK>`@G@ZO?A$ovc5>4_%odU{{4Q9AN$%B8V@o7t4ok7;#g`3kuar&~Ak_NnsZ&F;mKI=T{{7;7|EC!(85&X_+ zR#N&l;!!2N^t=y5nH}@GheQpBDZ$MnY6{^wPVko+1LUjl=<5rU0>48vjAjZ_YD@U^oJo? zWqIoms1d7IH-erNfUON4wUDJaI%tVspYtrF)3Q)f0IHpTt#9x~Vi6&0pVUV`;+ zs(1MtF~}>Dmd>)|xawtf*>5HS3JM6Yt$(&Nj&Xc^9G%2iunGJoef|9w$DS4YJ=8Ap zhl_0hv!N!bhe7Y7eFIhYUj02v(Wkm6rll#Mr6*$)PAgvj3>hEy(2#)G>aFPoL8UUCuS&vE z&a^}pOSVuiZteH)oU%*k*L^uOm>J;oj|*4aECP%^IXtID{-^E$Kjaqn;lC9Q?4vmW zQHgYN+}^M$QSz|q&i|%Cmq~yc3hcP-ur}>>$Kt|b6V@56)farD3_!fy@Q>}hRlsX7 zeuhr#k|2l*^dP_bsNA8AAI=@DY-|L+J2WDSGo39|=&?L3(YXz4QK@2jn94djq>43i zhb>m(36JGa99^x02s6fuaKh#=GWOg!_{wnZ}sin&)e%6qeN3|Kr8JcfRh-yS29Q|*0 zN+kDP0xWDRk5gVK7qM%q3rw*Q0Il0<<@>`qF{wTBWX-y%)L4h;eXl9H@59>BwD#1c z8z$z#olb-AaRnx}H%51@M=6U&sag1%CZAl2qX-2^`vy_j%C65RN9>t@mV#rhgu>OgZal0$bz{O6U@7ByX(@y+u*7yqJb{hZLmQ9;J_NnVv z_S`(1=9Z>e^aN(P za+?Q=B_SfLAszOy?c>Eo*(5zx@i=G~CY{7TB{L(p!wB;Y4ozgWpd*|Q;FGiO+uYU^ zvqasHlarGbYkOJ)_UFFaI?#m5GT`!>{P^s(Hf&o%>OE))&+}_AFOTa;c7B531kW2z8=P;V+kJDOC@ZWiBNb+v-0#kN&J^@w zm&%S)sLA%Y5)&KiD8rVE{YSK{I>JW)JU_gK2jK7$7R7M47S5cH|3=^cQG->|5b&#YjVM=!iMN4v0w z{MtV1J>PblAbX`SraZyEk!?R+M0{#`=>${{k(LCiv{pzX21DY|W2F;igO#&NOW9eT zJu6U6<;BFux8vJ(MU0jFLc~n!lL8?qi)rjJtdC+U&DAq=kyI+K#J7LFs$w_ z#&7Wn<%>Ufqsns=32&>IP2R^B$d=1CnD%(Re4WvFDumlN;GuYUaymU9$d`JJogwpS zeG)T%x=AuUHvL2oCUYX*8g=c3^4RX#l<1uJ&^l=4KO1+XwUeGOut|54@N)NUJr3vfeRam zsu)BIe?QL6>KZw#?cAI6dS zi8qY=>0k^W7yTxSUOxg8?q9SPN^cM}$Fq|;-+7UJ0}qY4-S0#L9aGCPc?EnwNIG9N ztz6>fVqA%jl58P;gzxMg{Z8cq8~gLybz9+TAkzGGlG;ROY({?AI4Xf(z0O8fiYy$# zlOAyG_+Z}~gO5OG=#xmqJej!%wF4Ya-08|y)E~N zl~eaaP(SWlrxeJGO&EMVzy2ZLb2imGb-g3bCD*Yqf$Nq*_&DCz`++?_#Zx+lWtQ1) zvH?3uKr5VSTRH7VWEDti)92qqR@$bVR2kmT-8{2*D-&2?YJN_vO5yZO`f>RscJoYJ zIet>!$ovVF`n?w(ew#4Hz2apzaiVDtOwhN7K0)JxN9LV!uq#3VP@MpNsC4(jE!k!N zm((A8&JMVp@Ke||{qOS?d#4JOvlXG~?;W%b;6HIBST~qy$8b((jXGb zB*Y3DYT%GDf){P20CS(_G~;J7-Mb^V8Cb z=nS*(stN1kIFyGH-<)e&yo%;tqe(VDy*$LmuPI$>yG`e$TSSp?2zqn-NsLa-h>a2b zZF0%15g}-tY!Rdz!eO3|i-}J<7_eZ7c6WX%b$!tB^lGWLj0GV%JcmEL{Bhc)P-g8- z@%)YCh(DNq0Ucc0psVsekQboip@uVtaZ*y7#FM}5lUvk>m;!GWkehp#`IDcK6{*)3 z5rLACzbd{V{O_+)a#SK~8H8t54Yw{-`}y|*qfnh>IAJtS1m+k>KzZh4+58IA(=?54u^=lLBY;eE_n! zbLhNg079qSuQXwV5BU>1Zh`mVA~2<|9Q#D-2z8!iT=DvE;ZO^!9VatSNUW}309}=+ z>vFJrTLr@jknS5@^5rL41h%!$Oc4+_l>D~h$InB z@{|hG=YDr(S3ZPqZo_A#`ag`8ugTHU@80DN$L*fV1W4+vT`%h*bkq=t1K4vlrU0^jf*P^5OlrL+`vRG;j4c?9Sh8ST3L?<;)`a9@eI_?OL22o6eXS2r?<#j zlo&v@qbNI~i(Di`KfNAk{T35d-K5Mv&_U<`z;V=7 z<6F4wl96n0SI~?Z&_`ebR!KiE;P;D5r z`tY-9o9l-t=$Zdg4# zD@k1$lKb?u(6jc+tAcM&?4)@u38kk0HG!m;19cW1E?b37t-v@IHeRJBIEXt<>76S( zIX;zX;h6&yDH8DY(SsBzYEhwd%=GK-bOxL-l2+z`gjbk!OP|$r)Zu0!!*cEL`aPkP zQZ%)+s}F%-@!(3X!rLn!SXA2mQ2%OU@iqCBKC`_JOg;IB%09C$fN!O&LjH75wo40P z{PPq3{*?cGeH4@asfzF{d%>0rFC!_qiFyNU<8YFy^@$vBKufhWRd3*g-RgSnQPb6X zIyT@aaPjGw6q|vk%Wc6PDHpgEyu~F9+mr#0!^T0G+b#;yipbXN`|7U%@)9{y54a|f zaU0~_ahJgfH{s4rE9d~;mE=%q){z6Cg4td$^;gmAK(;eVM{WP81>2jJj^U`wJmw|h z2sylOb*+N?iduEFXZF;c)Q`7e3Hpl$`}Zw#J$NhyGAxJw!qq*uF?6Y!9VvI62TcYq zjBxkGz?P#&Tzphfm&B(h?n#G6*^N`TQ4I^ZI^)wrf$k=U8s*o%_^GA$b%D3G^|_># zI$DXk7bGU{Ew|O4=0LBBuXJARAKuDpBndvj4 zLdgZJFPkJ5Tb}eFCbxMk3`61Sm$7LKw68x!Wv7)(g8lOg%e>g}KMwG=L<2U|mM^H> znvE-ro4)`9O=5O-B?}e~P7#2XdhXAX<{$k(T>Vv0TwSy-3@1Q>2MF$N!Ce}6f&~xm z?(Xi|xH|-QcM_m+cX#*3ov+`s&#wQ}U$d$&`exmXxz-rtF*M;gK?eti)^*$5&zvkQ ziY(Y%`hWc!G^wlhNPf{BM6@HiDlk9gz!|f^5(DHq(glPjVE(-YUhymf%{4dx2 zPIe2W1RU{^{*d!GU5KQg!uwpsHl=Z(eeL$_UKZ(C3ZXu8jh^zwUfHgIR5jgWL6;c@HdhYzh4npM0sTt8ZwKG!)yQZC?Hj+BfCrYD(#Fs?$8@g;)3 z_Vyw|Itz$?@f(aHnaFIh-0@l)8&#t8Q%-#Rzq92AX$1wQKPNnhs!XaykmlfF!>hR> zK+W-cDL~VZxU%-=d<*CxCFb|~cB?Vdy$ddZE%LA$+i=7n&T>QhZg}*5!#`>SQ6pGtc5?^<&(6k{&ax^8bBS*vPPQ6nE|hNIx|oRAF*R~nPeK7` zxsf7CjmVHU8|-{X-N||@-bPH6tZ+{I6n_(O*8=c+-pM1c4?*co5U*hAP2saQ z&6~GuQix%R)t@MXc?_*TsPlTk5EXN;!Ar8bZW;Ga?0115h@j%7k%#|kpbO?H{rAhQ zP)0C9gWv}9Ng}rz@tErCc@ihcHGCfa$=lI|e88n5Lz;rAuJC!O?98`paq=6KNEsUh z#()5Eq08EQUxPmg^7|pvYh*ennl&f>f0S!TgqMIxsNCFmNjRa;&ar^2d{ZFMew{STm#|vPWikP8uOBZvE$FkIO@GD3ne8 z%WCfS+oRDrEVO2mTN!d_t9IfT;-FwDlzN;SW(b^3i z3Vw95n<}?irsAJmLRMi@gY>Ug@G#DJ=>wmiFc(a`3W91D%=o#Tnp`;bTqo(79NQ)Y zdp6m85Qsc1by^-D1JL;do-uWw-r+S}B4|h6`={B7h*)B1^#oW@eQ8 zn-3akjRY!gT<6*Y4;8=8le%JnVjJgIJ?o}!h_-+ARJfPEn^dVRjKtF?Y{>!5T&j#kKpu!zL5BmZta2byl}^l}(S(=pB zUbPhl)L~x@_OxAIY8+a>Y>#lN<8m7Q@B0^p%J&zy?hhaIs#TZXb64;g`L0I0xvyd} zQ_l%ib?5sx&pX0ki3-(lYT=Qw$1PRLiMNmRpJGAgAw?P=lbdU9rES)#Y-tXFcxg|uc;?m@B75;hp`_GY8(v><-)QQAxI%SJZT$`Y?^be!UYMaWn zl*%|>b-fu4McA0!TjJZV&^7!I%!M@_$tA_zqi-!SN+(V){e#SzZ|;Z5B{;56^PP9o zS>J21iH7Jjr?9p)YX2(t(7~#!de6P*(LYW;fl<-Z4pmW8&E#>6G$&Q` z*L+`NHR=<}azNzS(U7`3~YiwwHPz$4e|*WpYfqElLyVnRhYawY89Vjk3) zZ_xL{N@p}1od3w>XQ#5e;6s$Y2$swsxm)Y*Qv3VpzVl4sDjy_L7}5(=x#}Qs=--U& z&V6=6j-qD2$+4pIOl0yUB*sx*v7Yhip98o4GxN630&Ze9c|u|gA$A+i zXG`&EX*hvFL6`+%DJJ57AYt^wkoJ11*#=*&V%|))RxbM_yBDOeN$R==gEw2g?!#9f z>aG6iTT9)Fg;l~<(Y#Qz=wME^R-1{eo+2i6 zfV&TCOa`ZVH!MsDhAM!LJf?N&?rg0+zNzBfSbdEuWX)qcMj@Ho(ADQG$7*;)_ArV# z1=|LC(`lDEIK%wkh`TJ7psC+GQEvAK0$mH$&EXC)=CwqeB08~0Y@y^g*8PZ%$D>tH z^wDzCR;|6SZnG#UFvsIaEtXhrD^qXb>KPFT(R1e38^0O!lHvW>2e!BVDOhQHE{0!o zRrS;L`LHU;WH)=xoc!^l*i84?-^#A7rBrv{Mmg8gFCW#voTrz2-8Up-Bg#KgZ~57$ z$!|{Y!yGRSsHU7yNzYHWn6tzK@q|?zU$*;*ylR-eyuQ$Ov=NLpg{Je>o!q?n*#C1$ zR4DgQzDmS+X;1{|b+6N^H8M_60fBS0N@sJG+#Jb~e?21nYg;;;_vJ4M- zi1$G~F0-f?J=Koxir33s|`p(;aMxq3%Wfy7cbY+AQF`w>J18{;5i*E;x)@8#FoURu8ak5IL*d zh!0)fIUgm1BRg8z)^UGrJq<^< z-{yOrqYDn^&|gV>n97$?2=P8yDI|8|e?-DYAyVYovPAlCM!muMEDV{Vt@lG#&;cDK z3h`iBhDC8vk81q#E4PxqVJ^GZ-WeXNpYwi4E}C% zlq3_AzsZM*oVfSm`AbS*_hgF=vNU>-YTw@)#{N0TpO>jMLu5xJ_Gx?Gb8(qIxn74!;6iea}nG9H-T$QhHBMr@F zd+1s}7Dm(WW-WPL&OI^=59>GNj;GyFYkaS&3Zj35524=eW@28Oc;f$Vp-Md1i6@#e zc$f_+bb@>P=-;0JAPlXV_f+O08R#$jFGRV$rB-&7BqUP9g8X+d!ubaD8q7da@`0^G z>|f<;sku4*fyCX0(BoiI;;O1I{E(Z(D`p#$vVKW zWXo60U|VUqx461yRogTx<)Zam1Ex-fCs>``AF|QeVC9L=C48Qs28MRZqoVmws;>A5QRg%P9&Q3k=+`gcA4!k$;TLZsVsn zT2R4y{L!gS@JyJ%5iY8^7ma&wqlS*h$I*Ryjjp6g!S1ZmBUci5$sw$`{`S|~+P%qh z*$;c#82n)SmnQ7lCL?qx%r6SZcPZlszh0XuON#7S`SCRtrkTm2O=J4PW07d>9~`!0 z4#l4M$XuIs>1FHSK@+sX&uAt0q-BREMbBO`+zpi3km3(~PPK?U$C$M;`QZ*Tt-Pb$ zI~Yr1mt+=pJ*%uASoK&^zQoh;CO!VvD-5Z6OC`p?iFU#JBnvL6|3LAzS z>crspF}-6h_h6BY=q?97puBJ5>nq@ z$RK{sYB#LLc)l#9*tPa@bi-~ntw#HDDd@Xha%pP6xvSiE6HpRq@9lC8U8&O+y!pO- z`Id>K{LU7z>6c!(S2Jzy{*X+K>$&_p$M+aIh4D4ukO;G1{)cIl-!$upK?_TPy0T`Y z?POAmDUAj^nZ@dXpGGrf#Gjt=-56}5ZIGL3e0~Nnd-~U?=dLZChEJ%ND44(mD3DX9AUhzjf1)wu zK|Wdk=WlZu1Jd4Oqiit4NI#8n!b>xiz5XCtuyu&Ia4AJ)Fv)7M!Qvs1cIaUymbJlI2OduY(@bHkg#Vf9IZO@lLHHyzELowSIuhMaD7c-qLK z|8hl6UX4-I3uHgLpUv_W)_~gsDrOrM=X%RnW{nE{!vB-$#Kym9aeSQlpOV(G%+vRI zE5K9t?$)VikBwp%V@9pW)nl>3MI(#FoWH_-Qz39#iyZF1Djq~Za_3wnYv_W%klmHmkY3H0t0hN#Z7^dINUqjs6E4mkBYVt6^#Mwwy{L)7j<=`cz3w; z#QctF7#4{_oy{iTy7P77yTHFujTl6L;N*0JpLix(uEiPc#mqv77msB1ga<#1{}@7w^<8vP9};+swE zT<)r~4r1Sw4&tn63b>mDJ2u&Y8uJ4n4%=j$zUljn6|ZMtK3CM1FnCkrH?p;D>E*Vi?!^cFfwf za#hQc%yzkrR>6laK9BjMolYUabYtmayJO-ZB!a22ro2B^y{-Xd_JHWP9s{;>)<7%r zNS6dRFjT4fe9_pu#$hq9qPd(pc{i)N;>*2St)&deBz_D;MkMO{j)%m{uc=Ju{EnSJ ztr=%hQlC#Pt+{q@qS_BIjZ04!5JMQR-z_fx<9(E^PLA>jp)4z_tNxgE=xZKE`6Yn4dw@jh_1#6a;ELTg41p zJuh&2=+2T72<6~Pz0YPMqkL*LOpqgBrPa10066-GD#}frBL9?um@|P_jUvLMsaV@B zXthkpCWE2D1OlzVxGOl#reiUoU$t(8nm82P-ufu69Wk8e9VtJVR7AA6sv)6~KZ>Vc zU!jsuZBwIkRyn7i3c5|IgOn0to%A7##XymxsV3@G&UevWQ-%ohttP`b2Ug6)W!cw> zsx__)C9Enrl1kyef;)$qy1t@Z*?iSid7N?6`ILn=TN#o~*VJO^`sI=0s79=%7KaL4T2Uqmg@5{h4=YKGn#>$rj zIc@pJ%o-M%oqu_0HpLdpwD^wf%$!So1;*{jNa3L;aDI};4b*_16Dy=;Ij?wANM==6 z96955Oq=s?vR}QH>0-de=OEjgiO0d?Ds%l%KLoCf6aVl%Z9q5ZnTj$M6pu-jSN6i5&02GmyM`f84Ekii$T-tNOx8cpQjO*C(D_oyJx3 z7!%PFT_(shpS*B%3wIqmYD$Vo48WwgxAle}WVqapFP73tJ($lcZOAOr4bQfGTvEi~ zPJUTZgd=jW)F$;qfFt=be2oDJ{11`OTAwIR@f9H70`$j<<@p^$6S{_ld3Q*n#XSC$ znt-$%Z!h=Yfc}ITP zI#hSRl^96o#?MxnakZVlxlt2}fdt&Kzr8=mf1@;Gdp}`3n*APU-8Q7jU(bWeLxQx7 z<4^Cpi@^2G7$t!tv&rvuoIzO~eaB9J@Fg+M+4G7mF`Mq@;I_qLS!!q6!{hb@udm(% zC3h8&Z}}l;^}xjBN`5SpN&@H_{DMf2OJdOobAKTt^zqrezpOzC5OL!DXGc-|;0nsS z+r;z`Jk_wtY}k)BpmTWX!Ubb|=q|;*v*TVM^R#Xrhr_cLJzT{7T0s(y6eu+NNn`eV zBcaf>`q&5&PsRY`S#|o`-$PMG40@{q*JwMrzDp)(vKTG+>bjScTJWTTb4xz90<^wP z_o!RtYS&~(+)a6ipPdnKAO-`o%YRmLhf>HT@X~9l9E)tElFh{}mHkBjel6$iL0`U8 znt^};2!TZGqAAC;j{PbCrr_ z%*iCKtg42G?7=HP&2|c$#+)jMUg&$Cmd7Blg>Lg|f zK7wX2RF8C4d5hqIT`TNQ4e@Gjzu!RTM6w&Q(VAa1Jt+^pyr9F@jW&BLG`b$H`oY}{ zCk!2vp6c109(E1H5CCKfDG6ggT5mA@3N0T<&5&Pb(hYVC2`_}@-#GXJ5k7qZ`J-fI z_vbTF!-Qrx!zk-XYQiRllb9eBRB*#?AOqZT5CClDj8~%2+@p=3eHYxL+1;;xvw-Go z_@!{gED5L6&`1JGnfsAT*a*wG1Bcw6h6l*uKrPMP8Rq8O06rI0chXo?GcOR~$%vak z5$pm=(m65f>;#jBeqp1|*#OSJoKnh8Ucc_1?wc`b{ah%H8Dhj9sQu-PhCCDnRDUj; zFeE+8IA!6C0p|!Y^7M>ujZE}YtkI)eV9 z{lV#W-5(iX)UgNn z*Vk|O%K8k2<**EM?&Wf3F50SZ`ZH~Ux|(6x!l3^L;|=CQM8a_G=*s6znbz$%G-|Gj zFdJA=hld0Iz>y!nEUU9a#l!7Y&UGhDm@np|eDf$;isx#+q_|t@+Lh(YvdT2ugn&iP zrvUw@JUZJvxxvWuM9GG98Ve_zC_eJz8|{2lPo6e68?wt6m{&U+;iF1l?(c_&(^?hU zrq$#yVIRLlMEdNBn|8t{`&kRnsFh<;4>SWQp5jv4N2-*_7qFqYHlK(ZE?9TL`2GWl z2-VJ4aIC+efYE)lPOSX7{DTSbcL3p9aVm0!oeK@kHAmFh>W(Cq)VrOfZWXNeH@*9; zIo8I)MC=a)lf&!7@=n^{dgrR6U>}(Nh>zRLLw){QV%!LSTb(-RBeCZ|J^KR;ZZVtH zl(NSDtzD61BR_M9L(|NT8IN65~H_)3Jw)CZc#WsmUFFV zl%>8UMH8Gkrb5|@`NC*Y!Kkme{89l@kQ6BFTIl`k6Am(~m7r!qbIth7!@XtN8y;_S z{@G2WVhf{er3`wG-E81iah+tits~`)U(@bbJ&UdNx(@N1SDAZWvq1k@|C{pG{%ws zbJ;NjzI{yUs;aq1Y4Xbg?iQ-15&B506Pk>@ad3jDuAhA$UX+bBax}3+Rlc3`jqII% zjAFVWZ8w(;q*#=KaxS-Gi4<6GSGr}rkBbWq<{G`Ne2I8?Aj2xp^8caCL55Y$Wj3vi zH3O1_14QHQM9;G{O^w$Ehpq=uP%+u4vc$SQ^^US)xFLGr8WmU` zKs29rl?%K5Gb2bteWnItkkZn@P-MYG`r`-Qse+_bml;D!d<@*nf#Egq-M@!)6+5$f z;h3I0F5U!kDZ_P3O!lgX!N*e*V(i*LV^>nEzIYDB$V4lY_#q|sgbgS>!g>z$8Lq_b z-$Unmd-ZqWYQwwj@7NA{i#eU(Lo;Xdvpi*G>_pR-#MUR1#$KalJQbMo0RL6X{k<%^ zI%;aV;zf~AsP<4xW5B;Cq_Z!on0wt6jiZtq(R`z z)gHX2UxnpxS$c4{U5>1q-lmXzTpwgSD$G)Qq#-55sR92(8uxF3SpmTn(R|25E1&Z^ ze)EtpKRsO@Z#dAkY;39Cgp+um6Y$qZ3GmYw9cT|mPd0=9GC;$_*U#m}9;zjXdFDX8 zXJT{>OI%Y>xdO}r=ibB41-z&$gwHB98bax6I+Yxf1bkl~e`Xrrn7h9T$%3@3A&vd$ zoM!sL;?J}h{_(-s->H}cW-6S{h;sGtfs&64wD)yv{odiEDSgQcdNKcjSif9gyhE1C zJYyzp$NH@aaeFZ`f6%wa1%EktQ+w%H*_`wjDwS(36frdW|A&$7?wJI9usltna_*%v_owc5DqmGGZ33ZxADs(g0E za=16?3g5{v`4jeACrDeHF8x)m?nxCA+wJa#T~)%CB23YH2{M^N3EbF2w`6#T9=nwI zZ-&f8{BVPlu65vv0sD(<GOtDeW3>vxWVag2IEGiq4km5$Ep2 zf5oc;?MSv{e^Q*M<~kIWkaG~RnnXVU!cX(8hB5TR)X8Ii0o38*Rxb|p)0Vl;EWBU;MqT%Ht*awg{w1fp}) z@5>iE3Q+o-&lMMUXEy)npV-K*tJGu%R-G;)>9p9!OcLQGKCH1; zJig^)rOC9$??2R9Ksx81;Rx_YQ`nX~X$wW6?EvZ^IVkcA1xcR&dm zPj7DQfXs7J*%ps@Qz4D(4{+VC13JA%Iz}g2q6(IS7B1A`{T;-=)e#KBevu`Id83Lj zYoag`bVhX&gT6u$`fl37u{Bl~-{wGgpLlFOy<2x=!F**n77!psWN*d3w|YIi)By~4 z9iPU1o%*5Rg-o;@`)}KDGC_4{SuNwX1J35gR7#U;rINsUfe#6p4D-&(z#eo7n(5?G zD~#z!0R-!g4%v6`P} zDm7dLlMC_bHT*G)v}59`S*4MQ_4hsV?}}HY>X=q0p8lP`UL-TaBO|g|aw)GTh4Ibz z^1Wv&*4%)QzrIIiuShXG*vaT0XZa<0{h9uGUL~! z>}uvxi{rJxJ*285heZEC^VyM0Z-a#349%4PFfq=1(J#uKL10B)&_uD}PW2SFsE4G$ z3D~o_0=_tJn*foK$~DmM49K#p#hySyKI$%(T&h=INO*9iixb`Wwv#uW}9wr@XVN;$583hBuIq4^dnS1KXWw`J5Bd!IE8Xq2h#pXVc~Z! z+V{Y@^`x@N%V48dyn$x<;C^{a_u?l{zJGL-fRlG75wPU z&J0@6&)e>oK7tC0%l_tQ)B*`dBwq7S+c(j&W;ch1p8f(`i1JO`wPQ%o84l7FaQc|j z_w&vu!WCe5FGbP;8hKZDO5oNBA84Y!tR`DMKB(>`2Z})9}lkOAh#6%Sch`{FaX2 zFGr$B&~L}iA4WKt68U%LLkiqty{~%A9I+y&#Np(V1M#`Od4pEU@a7v>6x0?@ew6pA zPq%(%yaxmSwq^>a&7~jjykhA!B_6}yVbY+bG<(o&5(k{S?_2{P2)2qi2$gfAgta>L zcGBKI3!_sT&Zec!9vXfXd_dw1`dm_4io~hWCtk=zNsL>F>K~Cveuo1CPi3_>6zCuS zh4hbbf{8I`qfqimM@6~@oCoHPw^??tENi_{a^SfUW-Va*l6Qw9evSA!=u69>|7?|T zVH!7fn|Yi`%wz6&CKp(NvVUT(Y=c(&RRuy0bqU?o;GMZqMMRw!m5ZtRA2gg7>YOa= zDEZ;FUUVH5;8L{2gP&35r1;-mvWSs*`NDj48zWULt+V*NCu0oZk<=KP841_$&qvLa zWw-g(n6+i-f5RK7+w%A+POr{Ms_%;OTHixwmWH{=G>R76XU9w`?-$$rgv&Ge=kW;s zrGU6?SO#u}kd_?yY_oZc`D^qcw&7ajZHs&^a&hgBXR+D&YU`}h}drLA$QZ1ebpjg{Z_6F&W|-7!bZ?rD0+e$NPO_|{ur*;yjOo+jxUY8{Bu{r zqjD{EGAVcV7<(=F0if3#Y~#oLlG+gthe)$j3g|c&Tz#zAPmS?EbKd?5h#;qhxANv| zCJ)1$k0GT94o$pJmaUHf?~aU-nB7@w5G>ows@d>bhvIia8JQL zqO-p$(Vqj{71&<>KHen1CigJUwRHBMp!k zzrhGR8A5Z#>dKi*ML?^50fHK{NXN30$BXb^(7ZEfn{R49C}&PzNnP(Z*$_2|{Jw7V z`1aHCQ@A|R53B1={Qo!#<)+IC=U?BYkjY6uFutoPhXS)jL?laT<;mTG14|Gf;J_yD z22i?rcF{hZv>3V@>Pe$a(k3F?QH@tuNw`#^yebCMl8|cw8s4}clz97MxFX<6{LcnJ zV;u6fC9Gt$Ywq%7l7=t#djrt%@$k+E#IP`&7Xv<%Ch$Y6sf)eT* z1$Z&~W7_Ll83#JZ0p2A_!gSQ{LnalLn8f{cMqWIB~A zpK4t5;%mIUsWJjc{+Xv>Dc;SMc?-ektMm<0y*2$TRWrR@V*B&K@!$VLbcskojL2qN zg)Ps7FnU>@+|}%vJgC`Bbqn-W2b$Gm>vr z0&2hY=WGQUBOgrgNBr2CcaEPIONH9mUd>>~3^+s%2G99*{(*`hI|$AK(_ffwdm}gO zREVg4v@d6j-FE2_QA@WKuJFnudlkwjC}4<3?YW!(ORA{p#JY{UWhJO#%#T^d(>)dk?@mamEITaFLF5_g4F9BIB^_0JPB*5$X1*C&vhj)Q_C*;pDI2a zS-~lv%}<`3-OiayHBQ8&Q2|{)WVH!Fv7L4^s6yTdeF$YPb(52<)#_?srDO5E>zlZ0 zk%SJ_6oZ)R(Y=EB+(b>^XWy8aMTdfC5?6$ZtmC;lMz**|Z9!I@z&Re!U~Iu(TA4cK zggx3v9cy{)c)k*dNr{EhaWOyyunsSq&=9K#c&M*Ht|KRy?pWV2E2+gHQ{nv<1`Qkx zpzoMAcQ$YOdh00~Ks+)sPcK42)<_E2@JYQls{WUq7{eflESj2w^BUcX{w)rpBPuPP zoJCyn2SwcJ73qPTPkO!rpS1pnbiD}xS+i*`)q;uT>e#H7-MYUWV4MHob(x2VEUY$1 zBQm`&vFora4E(5ecT`fox;@`N7mT!Eg%^0m>5)b;0$%D`uqj96b3Zel81ANm6cJyvT2%=ek%%EtVk z*voD<10l-+rddh|vYbqI{od%c0>5=##Q){KPX^MPvI~!u8Kk zN87+>_Dn7=@Wn_wc{HLTl{~$PeZv7$tw$7wd$C~J{rqc1e~0I%l3L=<=YP3l&j!#I zxE?S#F7!Um%*T>j*LJDk$8gRy!Z~QGNFpu&Z%!?!fp5)*RO zL)vxP!!k3L@a?U{@BChcb!qdyZX-8a_TBMe_Z&t#YC>&V45Yl+Zeo9~{p8SD!gbQw zwmKEFQBK?h>^u0pUv4}TW>T+n!`=`cj!xWBXi(Y5M z1VdAG-+~4&C;K;R8a0w9yKTi~-SHWT$uL|GKWh?YHfjMDo7S%Y!4-zw3a?urTOWzbNygS#=yjr;FQf6 z4ivt4o&AcFbKBE5vX!QPE?aQJ_zSaV2?jXiA{gk#gDLdXlPRKmlzoqfPiAd<>DXYa zl{5|oeyPlti@voyctLHhSjAkwU>-hMaNBOeIsz@3G%Mq>Kl|B^=>Kv7HYaQdo;^q> zQl`y=C*_>s6sQkOPR)7W7_J$pd`~>lx^n%nGD#dL;9H0?VsIkVE>)quxOO>4{xNk0 z+ZXNoyn1yWoEU=+V~XMgd4VMAd_%m#6Xx-2@&*@K*-NY;{;$BY zMpDJ=uP|-9QU6Z9QBQiq$5wC|eo!a-1GEf0{>!U2!Bl%lv*JeEW-0pih&~vL)GK%j ztDb%}suisUt|$ye#0_vWt1QwQH0N!O*_oylYK8o*oYWq&@r-?O>^J_l!4W_n}trs95F`F6IM zlg@s;21PKmJNh-)8n>T~M6DzwiE2;Rei>ClVI%jUw(|1`jw87V@s^ou#wH=@;NP>` zl(=tq;cjq>14S%+zdrzx{>ttVJUAFkNu4p2=G;PW8~^(o1C;-R$7n;5r2R(5Y*cDRj{^Wexjg@gM@u=DMN z`G~vYV`nLykgLbsEfh_8b#Nez`uIJnntHCpI2?Sn|6o&l$gqEK3I&I7n*cDxYrU3W zAntMmu*8T=x};U8e294_Qp{7<-8`xkeTT$=ZnSupz|=X7+>Kaz{Pf%xAS^Zg?_R&1 zihKnVVVz^pUY~cKP}lfGLa6HnMGzC=6h@t&A|fJ9vURFWDd~*R>9*vY8j|%gH2W|S zh1PP|AB@sz6kPEu*87*^jnowxKH2@~k;sM9rQZFQB5j5(s}8(AT9_*6hLWhC3SuN< z3B(iDAg20t*2`p`&d9qbEXeRE3gXv$`SI*UCVwk6!kA%^O_%l;QI~(AQtP?^)%lh1 ze1$!;UM)TwY@^|1fJmf?|!Qr$q_gy|J)T(S{BCvoCjwro~rbiG>bt zQ6GlYojad@&~Kv+mBB9c*djC7wMT_KtU)q61;y}}QLBDcz)iB$2P+r$9XS@P;wntM zzx{r`6Qje$P!fGisvFM=*hB~JOuewQND|wAykTqimedug4q>aC*K+48&%xCsNTN#i zuB8W0oZA#iMv&p4Gr5vQXeC{`<`1_RO->H(J8xlQMS8PluIaqg+AfLQKgSps6ATp1 z7jl_TaUy^|R!%MK9rUgj-X-ogd%kZq8Zdjh5r)jghxC}{AH{zGgv_Fb%sqLIWe;3m7Q4Scx!HsmN&MF_ z{~;L0h|K;TCOi>y{%_}N#T`F(i`)E*dbt0J-(2@kZl=z9fml;vnXnsO^+qeYbIEf4%VHZ{MoF{&+dgo`upN zs9WZ2Oqb>XhqZmCS*#&|285A$DgFCe=iL}rvLH7{YN28kzO{qnVDS~)Qp4EN{jDXy zxzC)wXW#KUrzwrePHYH;pT&4gm|QJeU}*wz=co#}<;%_zWM%u26n^xJ6GZq&rts4R z9_lbt-S8Rr#de`XA^hwU`a=zbe&TC-?}SnySVs9bF2FAZ@mzpf<4IooomXT9)?h}; z-BG)<*_g*EarfzPc$MW?RBEEd9^|K2(nK4r(4ieEUbFvFw7%D#t&P1NTJ!C4??W1_W$&%{#$hiE~jyS>>3 zMv&LR;z7JG#%|tAcFGt@)a!NN4v6k~@AW17)4#@$|InguI}N40%88TF?>?OMgJ{|9 z&!5ES^A_%#>!figr>Fy%h?rP7gXhVIgSYbLQ`rHn6ttneAQpnk;3YJ!;Q|LIg{Xw9PiIkw2c0~Hw)BJ<_nGhBgxb0Naay8dYHK6O<2J`JZH^JrJtIn7!q$_rAR`n z?l98S(MqjHV&|Ve6Cb`QGD~KSp=Qv{Sji&QO#s<3~-%e%{~n{D0X!+_U}iCi{zvcmDpL zL_|g5zkQ=(W<~|CZ*J;6X^9J`(aA_llTuTML0psS>+6SxhDaC~#w!xIqw?D!W(_Mp z8;5OMz^lD_L$ZRssz!jB_H8OH?Kw^y&PX)|MVB|HucLC9xSu{(7YH&q;SQo z$9R7B4(6}=g9g_Tr#;^FBN6hr>7Crv80G7!{Qy|zc4JmUE;QF+)aZZPt(3x3=BQT9 zw_Kas;T%!y+0nYYVH00sWBg{l1{RCF*%Cv^CG}6{(6TH0Oy>+RzNc4OYX|rSTd&JF z`ce&acTHJ_7)q+iR#*snWA_!kI7pdxpE#hk*!_+gHOGKTW4+9ltPnnR^cp7Gwv$W& zyRU^KAYewv?Ze%)6UBQ-a5g`})TdVIUvBu^KK8r}uQ|mfvh_&*lmn4@Z<+HQIWrpb zw}LY2%-k|`Sp17Z8d&}iN|ek(SPoM^Y>PX^VhwkcA79XVtaL`)K<)XEE+ALgSxgfp z^b@>tEm7*gzA6*^y|j0dr6tVWA8&R>uIjB6cgFUpDq5_C*M{m|BD(*fQbE|$UH>2f z@$3eXRrYyhf?cvjkwK9`D=2dgzs5cCl=~v(>S#|_TXhQ=<~@Kl9zW4|r>R}((75t4avw%y(|GXz2fX>I)Si?e7#%Tv#fAvo*SG_-{t<*bIk1zZ$}l1nN_R99EPFx~CQ14a z$Le`w<13+w4xSCs%#Ef8zz;edO|ilhDpoxHl%H&N*s{k8 z`200P;;Vk$QPPVGJ5SiHEI?-^gsx>`|2{4uWfF|u1_O&{vCJ+N;YPs|_9^OfqN0pv zXv`EUy00n!u!UMI!RSBpnHef7oCb|B24581(NwyF#mF2HPr}dqp8CgApLK*jB`ohz zLa6OEnfGtn*WZZLnuVN&g@cg@qubs>3`WX|-`w4D(!=hXjJ2w-!jwEvO_y`*TvZe= zRqnb=!QPqbW5W&L$YEnoba#!7Dr#5HL zeQ|{((y=a82Va33a1n7pSdnQBl`4XLqIv|>n+-YoOUWg33RHH=mNioy9}1H15_l&cz*Ygl$3WH)%8 z)MP+T3xQwo+hyZyK7r}J@Y_terQh_66)O@IDA$(C-=BTy=WS8^3457+1;IC z1$|or%11;Hirm11;C4g=MWu}l7XQbOH{y1b4~gjwX;MMpT+PAMQ4V|>WbjW-jw$i; z139N_(Nu)QH=+oL2p$t%H%XX2oQ+qFe)N}Ns~ca|rZ$?7QC$tW_Xl6vlP;g{t=>#) z5&oW?jdQe)i;tpPjYNmtPQCq?o+7+5rpH}xiuqX%fA(Mjf9TN-ky`C7Ncvh|QANCd z2UfCzZHIm2D3gQK*qA9njge$2F-Wa&uqt_)7A6*7`c!R9m@bmA3f7qj1e5ugsYc@- zY$!>-ixrj^Nh1h=?5avt|1T7Wc^=TaVDqId(o`>sxj~7S0ccCQU0t$x;`X zHsv89I#+cW=3m|KgzRi~7xwp<0LW?(*?eu%{}zcPR$!4tYPRV9X41ouT2ogciNw{L zouK7`jJ2Iw66$cvPf~`y1_$hvA{X0k!XO<@>W&Zbu*%GP@G9tWLP#!% zA9VD0a*t%q_50$4HS0~b@w1otGDT9-8sFEk?BO*pqGAUrI3~vTbiro3LN6)N_AeZH z3g#3aMU-`{Z`@sY90xOLH1cs1EnetLjYoBC<|icr(|6QX;TDL zWq4>Nv)b*$And8A;k-b=>2%Y3^jp05rp&;8rdqd2%tykylEF|G2jSiB6I*iSh5yaS zCO4xy|Cu-ngT9hCj#}6D>}OEt#LnRtPFL*2yjV=^oPNDB5T;2b(u>G)_4BB#jJmj< zjVwP)%u64N*T%ZYu-rUqNbH=^I@MF=CX#TP3#nQui`cd zg^t5vC*PXKjy*eAxqLqTdK&26I|SSrgjlSY4KBqmjoqWcZj&$>WON1@tzcPX375u9 zYLjS4eXMW?vZvd}ij9pgIi7b$L{TwfAc!amB0-Ymys#``a}JZI`~OtUE}H`iy6V>R z3_C-0g|5`q^?vXBVIMpILvZl%rt`Z?`T8F-8TH`b33I@=PdrMWkt6ZPhLf{&1x0P! z(tqmJ1f``=TvCR)Zx2+}D49m$UbH#yVZZ!Rip?gznmC@3cibW6|8$>N;|-`j9ZSFN z_zGNch&wi*3LiYeFOa4O06IbB5#Qqnu-@CB zANDp~9kK=3Ik8zRfSp_k?6icI7HDaKmKJDffnU-BJ>z}y8Qf6##lTJrW9OH2Q6R0p z_tKH*%m!WT{C<8A+S=Vd_}v3LlCh(&`}yr~ic=Q*;!vZ`Be(q`Y?G4-den^FQ#Y{V z)oqm8E~ISq52ztVZ1W-rQ=6y%pi|szE!kIT0fBulTeXkB-1rWUoIjae8R<;w+l%Dz z5K3HSys~016C*CfWHZubSSS&_+mpR&2hoGOQg%3t{acEOw0jA%_-W-z!M~>%r`ONh z8N0b<$N)atyNlOTcJulbcQJk5vmD6D#BL74W-=2I97g|Etr-%Zh}q!BX4mP`(Zn5} ztmMX9N3&yZCVTg1^W3aSL`5`>9njLbjkkc%_f9)j!hyrZWaT<=xO|w5Dsj=loYpad zr1+3pfLcmg5%U)A<5-r1%O-T>jKPUGoIXDL<{&%w=hG@Wm@z{V>DeWUZ`U5>^KVko z{5tnu--m=&cKKTz+)g^P?QsA}q`&uxHT3HqLt(L-uuwBo&h8*VqDTI>iD0Xd;r-(n z+`Coz14_r8mPYB8Ew~OJ!S8Sc*j!bDQ2>*jIuSg05M@R?5#bFVIBPC9xgUIhD1!|L zmCh-&4|0_Y?44qg$DgEce(T%Zu{VR~-+GOgo(`=Ckg29xH6N$`yxXA=$Y)cR%1^=Izty)b95y%iPpHO;-eCXK$8+LGpYbPHR2m9xkh^Vj#0CW#`5@8Eo{}T64W`F!Hz~x#Z7!Rl?rAds(}7EiP9;+VNC{{{8zi zT(DFWMScS7mhjag{wg>fF7gX<$;!+izaXCCqM`!Lr0 zxU+HzijJ0sFq*31KXe42$3bw1cK8M3Mt~XkFsmk9f_Wn)zXf4MC)nk{YYQW@ARFtk zV;D69Wq7eTd=%#uP_{RX;+5OTNj;2RF%WE0FndL%1>~fo>OSd*NY!;5k)b4CbQY;^ zd`b`+X3dXgGSRACdyX79h}G|vfQ|rm#C-|CN_-J^DQb`pV~mQ?Dx}o}6;uVI67Zd4 zpj>uMNUVw9;v)bYA-@)2>!`w5#VEiY9kb%b7_Os4nD8s2uMHR!lk|C{$+G=ues?+3 zN=1mbDS|l_-H&zd-mKl<^f4(|XcHz(V8ny8IciDZ9_uBS{OS&qnGt*K)~UE9GB zj;`9#6|oWbqi7C1ej_&1aE`W_Pw1jSgmztCF2SbY^o65Yl2CjF1aT~272Be@*=+m$ zWNPAmzwRKDroDwxHSoX7Zy_~5lhzTD^h-?UzR%`zar_;G+k@yhJOXow6-B)8s0v2A zg~A=_96qKI6Yj@q@L|bcMOeCq%jf4G`&aSg&@&ma;B6-L9mhwTmg4ey7~iuWe?R*? z3FN4%7ex^-XI>1d2UYd4H9echR&2oI@pD^PFZPudu-2EwcS~*#_*!Whm%nISy1g3T ztUAIE8#CCST10V~M-C@}kl1Y|x_69Z$~o=n-y`;TIWCuv`ClJo=e~Sg9zQ)gMKbN8 zZW4?U%o>wHWk9c3y0#C;<@WQ`>zfI&o5{>^aM8IP7~DIS`Cq5vbb1-qKc1vG8+MzB zFJsn!HgfT~9of7qmsU|hTr|F;JgB(s;Z+Rm8N+FvA{pAJRb{^mS+{}$JV%e>N;@n+ zzTy(Oj*G(uYwJWjNgep9L|9C=3oQ=8Z1e7wSk zfY9}?C`XhpoHrX)q)li&<%@a?3tf}26t3esF%`wy=ky4_0U8#wQL@<#h7-H5rr!U_ zxbCS}KW6;+RJwK+`;CT!)2TCmVJQp0a*~xL038n{rIjq4ng0mqp?mB1J>vH0+;~SG z7hMp+nWGF?s&L9(Zk5NLb7SzG#>~6MH#+Z6=5zF9wBw1^C&1V_iNMa0$7(ZnlKxQ% z?9`5Lth;9^Rnp6+%!%jGSQ#fbUXfIf=P?>*C~@~T-F&{OwfGYPJExz1x@7581i`@h zM7Q(3UdjGwYV-Y^>)PSvLba4mc#bD8EG%Tnk|ok7MkfgMfwfvMs{FgrVwo!hta z{fcE26=&1CZ#OctG8i@}f?nMWY}>qzHp$_1?;MP(IHg}g0VEtenoY3Xj@cMYR)&k1 z@E{^16m*lqcPozZ-xm*(neX7LX@js?e2f|z!{g8IAtNuE;NWP?q2ZW9?5LUnqaXa< zB5uCv7Lt=&qv!%IDVGfs&(dV%#F+AAtW?R%AGy%fA@IGWAcmVGID<>05r!4J95#`aV`hEOxMhkM|5yGcn& z;lP0d(t&7DP>^)AD0HloOtF-{eE`EX`Go3QWp(_z87*!a>(*`Lw9`(*P`ZIIb$64h zTITSOS6D`JLgP+g8_TfLDSKmAw#1*T7TCEji!qnJ#>{i?=E<*L;lG#uADN{&94$y^ zbf3$>p!2849lhl>yJ`La}mznr>!3yb$&A(bYW zHl5ps$zqOWm9ctLCUG&rlBJWG>tO1IT_`N}@YTx0j2sY8VqAbxwSH?hyAKpHpl1v_ z_T>>B5yY6G3G7NK;D?Qw@=$xq+3jf+9mM`aMXcJC$rY12vF4{td3Zgde;g%c9-f}P zjl|es2KQ-2pKejLZ&%mxmz3ho$-#9f4NpcU#<+OWEUj3!)4_&a`CKx-gDk7Dpp}(T zuy_e&2Le7kEOD)fIC~uCgcjD0d`4)nY>SQ8zY3F5Y<=6ixMWl*$+2FJxh>2()P{w5 zLJX&FaL1(RTojYdbDa)Q;#YX3$WDYo;}W}rutuodb@iRc>*t*>QYb0&Rz@peHmY(Q z4(b^r*=IG*p=xC%#__jn(@D>EQ0fTeWe+w8DIOuQoeQ+=IsxkXU>5tr8x2s*oF|+8KX#Frz2bc5Cw1|DyTp zr6Kj!aqR{_(NQ)!wXbd2tZ52gFW0&H#-sf7OyuHRH>H&59$HT0m&^^rE8rk*CUi>{J1C>rLNS{--jYqoCZV zw7z$7qYZo_7n}gFvuf2UCQX_o_wQ4uo-CWY6RGSkzg9@E)npXBphvJeBVYR442Aie77GZ<%#TWU!GJ7jiukOJ` z=OuyC6weB;_#KwW8?tJJTBTMv_>%mlQ?D)eI`u8>Qo3)%2@i(J9w$X zC?Y%_rR>%r001BWNklRag;xyG_x?;GuhyI+{0q0Ou&%TnQ-vm%pb{5vl{M^dtNm^EYKYrhI0q92&v z3Ee*%osoeis@Xs_ADSl=KTGNK#;( zVzYPq?YGyevH*6TefC*GLP8ildNidOD~V8d;3F6{;aaY{?mGVZ*S|90v`|94KQ!r< zue|+TK3%d6$NndqbbWmn9nCDF(}+j-a_)3S59|1g@@h%kcnhrGa)e&p;u&-4tHdhZ z`Nz2XnDY9?%)R;*GRv}9xo;(7yNu%SmLz)hjw3YIB#$tyk!HLlUMvx2d=4MGcI6No z7KBMH<>{sOQC6H!f!D>svLY<10k`JIY%p?e_Yqv${|sV7?2?@m92JZ$K7ydQNK9cN zc!~-sJ(9-jpUvmxZ@#CM#m1MzFJygD7KvsX;U)`ZelJ# z{AOFOINk!^tUAhTAMPTrFd$7LjTco_T9OLDrD_;AyfydV&_`N{3YLyw?bw1%a?>CV z!J{Lr@_SLKhftddqZAs4q7skHH`H#Xv`oB!>+-t*l?1~_Se%H1bHP{;zIX(~M@X=h zIfM*`f-T61&#%eDZ&9bP5HlgR1~x*X?k+dpG6!pR7V^P2M@UOA#plzR^|t|1%B>=Z z!*X$G9&28rg6Ldn9VAKd&M$oOfu(;N|M%7Rf8y})7c-(aW4H*Nyi+z9CIe4Lhr6oTs^rH zV}~WyDtXdPU$g5#AxmC6n@;7xsko@1*v;%u_VVdBX%v;XrB5kQte7uukDoiP?#^Z7 zJIZ=Cw{SG0h}j>1LTp@SH84wnP;t?b96A)kiq*w*=p4^O_Z48X=E%P4Ht;C>5BjRz z&f71?aOEZTdT-I`G7w~~?LVVxf~Q|{uxooVuReaUWIX**LsNjAeLFX^a`8uOS+{ER zk3X(z&}d9Q_FQeoPU4-Fz)tP>#+r7PQYF2(xQ;R!%j36=SCsj9^xKrSch*KBe`d`J z3JPZEy58vf(H{ZWnLK$iadDN;;B<^Utkq$t@iW`}t;uFcH z$=7!5(1E0+)|3|KvUu_5goed1ancl)EdGKH?af?v=@4|SjHS!=$mix6gA9aQ4pLB< ziNORR!6wQ2`0~44;-by;JuMVP*T^Yw^7XPTR&6NYj4_d%eR?vAYT~=^)7f*NjDma@ zSN^3fJ-S&XnDG5dl{+8$fgJ~&T=}052=6%vkH<;z&NX;;uH_%sO`~m_Hc~1~0yl!G zqn9&81(7wNVTM6cbi{gHo%J9wAW1@zPJ1M)-|zHLT2z8PDje0Lb8N>!ir1#IaKSgU z9^Z+JZodSp&%}H0y~i#0-hn}}FzvmCsPUaqR0FY&Y~GzTmM7Z{CBiR!-{^Q%m0-UY zUG+$ItdW4f9G_(VY8XVl{Tiylh~g8%b%MPUFhVtGf>#vS;|j?*RTr!xFj%~pqE!&j z6hi>W5<(ZkUycBM040$HLDvOUvItjmW&rbZws)~ zTD+Xu|qoQXwhYOk$*o zU`wTMFCoX8lKT=Yn=6NY7t@5ls1@pHtU}xfM>8w8J63@)hyfU!fcaM63oeIWIRT{B;iKs#=ZqC1d zZD2=Fx$z<3;S+l-p1{>Q-vhNfQdd7$I{$A=)tvPG)6WN`VNJAZ399E&D}5bhR84*& zz)rlrHo(}aNl1XiN zgOi$VdpFl{aH92^`^it_7dd!k_Ff*I_yP&`L@s%AEFVl;$d89tawvB{JzDicOBqhj z5w?J?v~W6V7IF+i)<8k?>KrvzfMD>6`sTI2dVHYuWIyfk4w$}4j5@PB?{$NqfzJ`-dyaQ>NX88a+_%p4~l zEjh@p{ROgp2lbBSZ&&poUN9CKnExj_?oQ?PB@Qp|eUZW^OAkswNF27m`tV3Pv<;Qx z!|nC6bmdXjY|iA;VG$%8+=#!R5QE)@wM{a{s7M@p_v1KtNLJGl9!k_-F2oem0Fku` z?f!ddzlQ4R_amj)UjM3+0U>ndyCizJYjQqec8wHg5YHS*;M-hrkJ-qhOUbgPZr&HTd<~%x**yx~Y?Fp9a@~C&a`|7|GhyuJT zn1c0%{6aqu%qn2_J}=k)HI%C^t?qxVs952O>pVPq_a*f0k=X3o>tyvyx-zs;#?Io8 z<{&;O%B=NkRyGJG=*Rvu6NM^1c9^9k-c|+JsS)4UD}i9{S4l7DY0WYk%cFV3XP_GU zC~-GspN#7KseqjeE+np1Y_(>4T8kuwh7U1B0>8&gSwSV}Qj(P?^D&rB1V@A(&oi`D z(}1FXsQI+1!q^cRJU{m{td6Xc(XF-e2n`LVbz&mAmd4htD<~|m;&hrAIlLp6UECdm z;Q&8v+r!R1Ci?a2LeKVj=VT-qgC88y^~uDH=u6FJ#VfC z2N_Ux4=$HRUV)L&$WS)_bcnWXjI@atl4}aQa&WoFVtFA?6Q4_>^id-X|f zJbO6jj2wnNC`i^{FoG&rIRdZ|zl^%@l2e88QVx&^={7m16#-nR@~_x?9XWUuZ-JZq zyrU#_?uv6~KA$}~j}X6^PM7y(;M8##eUP_#KNByVL?$jU266A29pt-Ie2Rj9)mLQx z``=89??H&}$LI5)nT%L8O{l*~zd9zdXApjS1lXc$7z}=boj!Df5sy(t*Ze5ruc%|W z6|*V~nA8B%R`~ldoBfz$Okhzk82u=CB^YA}{1X6s1gIkc9UXyfiScK)g`kE;P-IjI z3JW12IRVGv9h4l~PI!z7Q;-*oqX)M`*HqST z+}J3vGjnEOITn9gfSnVYwg>Z6vP%NTh_&pz%L$|vIX0JgdvOX;*=uUow|ti7#SWu1 zwBvCD{pMExE(&42!RTJn3ZWF&xKxaUirgV7mcpkOW zSC6k*xf20)67;n-7(2hViedfbx%6m!5tDiR@h8}|YnQyu#Kgqoz>cuQ4-XH=>2%6Z z1!G45J0gUJhVsyZ4-yfSL!|yAp6KiF<$X)A9*`pngof|;IIwf*=_XxYUw%HH#_El! z^zWI7-Bwl3rTMgXQ`)1k_G&KiH~-(~e7Srtm-qQ6Jrf2KZ%^XNxdVCks?~gZU_M9k z_7fY_j?>gDY1=*U!dLOKCj=LAN{@1+AcciH-XNnWm8jq-lERbd7Tbd)TRY6U5nWiQ z;gv62Q4vNW!i@w637v2+sv>;rh>x&h^YT+#3XgyB6?6KYO@g)3T6;rbCS7ci>@LY= zaAZQYsr>q(ZLE*TWx<xW0e0*w z;D7J!WJupwE}z&Dqj;&UWBv=LzrwOjuyy>JhDrDo5&lIgtUasAan(J411^063=83N zL_D;G=B$)Yug0*bC;f49QDuBizLVJ>?ctj>0gZCOE_&;UQAC9Y$uidbl*My%wn;|C z9hY^YZd3he8!5Rl# z79LA$!=k(>X;LF3*k0(gk4aJa6lpN8B$yvvR=ncGs5}@Ioncltw}h9-URs4ZTzPaX!-d0F+_v~9K8s+ zmy8@I7u>oGms{hRzYk>Gu+~-9Eu9HBe#4%FMFC((fR5$!<;gd;G53oUuAJ0?`>yR( zzNQ7r6Z1rT3oLWZRW-W){lbd1DV%x9%RF>nB>%j>@<4Xq0S_b3$sjkc@=UyRx#K3o@fVlk5(9bpJ|2Hg;qvikGi*RR(zA-m$t$Ht zmpE*}Rh;rPvcEZz_4T({A5J>3lkxIQP%42P^jS*$O_jh-t@vt>;K+l+2T$c!sJnXs&$eJh0kjE*l`pTQ=FA|{3Ae} z6Ac3Fu|}UUfXS#CtjwOfPZ|M? z9ub9K7t+P1fGnCW^v+BHMutf~N|5laXq3z$$wm^49l`Q37$m4< zwpxg8+m6Uiohdt#ieb-sx<~nlHM#I9E>s(sBfw-4EFuA5nxx|{UF2zt!X4WaHF+GC zgg$tD!YITbpF|rrZe;D6H4P5?7GUQ@SB1*!s{%VeKbK!GrPY~9|JBnmdQ0mq)Mv7@ zdh8d!UZq+}YH5L%7HF^qdL&y(imFul_-4-~c)WY1AL~;c+?tsI$@S4v&ZAHf@d{Nap3_keZT0sLjTUFTO}gNx<2-VEDZ7!t?mk z-$8A4F}_>@>>Oxv)h1jso24uE;!Qm5Y>mf$Nm?e?j^zAo@wt5;C~l? z&#FTU*?H_o+JyAx%=TAd4_7Jk6iG1Whl5|R?$B3sj_t$94rkFLp&zQ^mq3j{^-GB+ zgX#+aH^TBw{+}10&qZ#2F(oCXM1-q^hA5a#evBqBpZv6jS66(;qM;YkI;egLp1OKq z@{gbM!vD@^NWbJiRtKGeyIHjSFmpcLFMaEX&^{@I$L|>^CDkgH-uyzBWFG`Toys5% zmK`oXsYgoqVN)hQ?aY%bA90W_u0O3~IA;z?pl6o|$$SVLlGhI6`r=YATX*M4e>*~w zO8ger$HfFOuxAWs434K=>k#QMzGm=vH8OJ?Y~Gbe>d_J@QzabT3%NBR@AkjQ`FxSRUU&g^BC0ufDJ7(S*kR=pBNTYS1_&Mzo?R@gW=;Mr? zib5{Ab2&Rwinw=LccxxY-G*kujmy}RTEvo9$CU#+fmDL6^Y#}97&|nc=l(vZ-g-5q zznarEe!s@Ri7&8u$1z3@v#?@OB|u+Vrg76lnlOC& zFxLOLh|V2+oIlVp(g|RvGQE7xh%y9t)Z7j5 z5I%OeQHj6q#8wN1x6~wZ>^K20qSo@%h#?va_l)_kPL5 zFqlk4w~AyyawJ!GjHDC7$f$bAD=g-M_tF`rZ9Zkd&VT^}88&nT-+uiObKdw5d3ij4oI(V$UaV8zI4F5UODsnyZBLpH@O_Jh9z-V>H-g8Hy_Q_W~L*qU!!~WfUVO zzm5+bw@<_7EoJYjMW|j6N&QAZSd?VA82l=VrsCDyC?*q#{g;sCHJX`x$9?!T;jh!j z>r+Nz53&&5RWNRY17H_E$>tF)r$L8%zO%3SFcN6eOSi(%M@1#tBHO zQCvh~=`pVEqH#`V6@%tMHK-^?AErMq$5h=E+z|P#cb4f@@V8Ma~zht`nY0@6ZRmiH3u~XARzZv`v%il`h^>;Vg zKbr?VLs4Y!YJ2Nu?0F(E_nJZb9=8u{sZUFPoTTw6m^$5)DvY6F$*xJLdUXTRMkn+y zyuj)6a`tH}2r@;=oC-?)tld|7($|OnNg53)UH>A+&dkqlY#SHfYDRFdZK}yAHZyg- zF;uiveUzTxHvUvfvFWMzU5%nYjjl8UO&O*s?VhX-?4;gX&e&=4n_jg%^xElbl~Hw` z3>h-wjNXHXFF$?!C3NoIumYR_bp*Im=aZ=BO*Eu3FlushgTo}3w=%t9Q^#SK9#%wwSX_*zD`=)KKh?!%wMt{aZn^eSg4ih zR}AI0sUv6|Unyx*^VUV$H$Pm@tMC2D(Tqy}U2R)Oa_iLLTya4^;$lKhvWuxYI^b6v zA9Z$rmGavTd7LZzKT6NUVe$p5bx3C(`(_$f4|~L*dg4Np zxv=kzBt>*VRfSIrX}69ltlNb1P$4uR!5m+}(ohlLj`;WqGO7ey3QB{wjrDI`@hjG6@bbLfMwyEDaBSdup|1dbAp@OC*~6y?!%#} zl=+QBnS|gAd{|;*L0NUVteW=_XY_G(NGXG?0pPLr;Hcbr3(2$bH!iCV?9}KV0T}k1 zvVbma!&v<4SlNHV2hPn8ujRWRGgvk6T;igtIHjzkf5kYx;I`$QH7tQg?&wE&x#XJ5 z?W4z}FRBAO<>`e#sta%XmSb5?UV3maBL|c}8_VOz-}nuCQ;TZ>J74~Al&5Cz;AmzU zV+O}_|8+g(yc9sGV6**_{*!I_8;##}n-4Q|@{3Xiz+_U`x3M+JNtN?@{(>^5-I7CT zS>R017!aTI3?Fy>UjgTii|1H+F~>3-bnFm< z+byif9SrJkse9y{@c;hVfSp55EC0Q)4O519ZLqDDWIEWA z?__&cF(n=!Vn>*i9Xga9+qbZ0)mP-@A7jj@7{-nAf+lc7BdgYxl9?S$w=SWW)GVUI6=EXn zau2O5UNobL1&f`uZWY9UzQU`F2fwOHd_mEThlCJAEOB^J&)CI zJ&)7xXUhLQLa|`z2=K@kkfGKvqv+mpe>viJ;JM>1EMVU!ZxXlZV;-H5fQ@45<3~L3 zyk-NI#8&97d*E+9ija20@%V$J>{sQ`KM^Yc%54f-=CNXe~` zVr%TV)jZn{R!W-Yv7rUEk}Bch3n{Czspb~+uD zmKJX=E-t?7p^2|F=-{-e<3m62U?)`nY!qc=vsq|$eVWqlvFgB1+5>aY^{GuBaJm48 zD)Z25rmqoSS9`rWZ-L(b^ofl&WxTegSuu8mHI=4VaoNve(bt>!@WT)3(W3|VJn#Sx zxk+zYFsmwiHgD$n=bpo2u`qkKQ0D`0_7ZIR{zfyo_~v(6_QO6(%RF*J9uZcFP>aMu z@?_;FH_Lo_jD4D&`?8q(+@)M~=Ld2#92;fFZnN;=|E{Ee&(N^0ukH+h$1@Om001BW zNklNZPdh2*WS4Y=8Uc|8W$lfTDjU$1ba-c>Pui=Upq%R#Ircn7^NV1i<3wUYeTs9rrjnD6sn%TFt@5kjy z-3S&hLXCu6pPQ#k_tQIeD{D5y^FpuDl=?i(P&V?xOP4g3{7)Y*4vZgrb(@rdl1^@c z5Sxkf&rD|WxHbWBr$!JGX1h`fn78mCKkdvdmrV=2&o4g- zOk|~`qF`7oTa%9PZIY4WIIi_7{WhtJ%%h@CvR-x*(=X}5xucR~Swi+r_&j;_!@V3% zuP#eq4>t0zJNnDI2v$yZIdC=p^wx|Wp1>>b?`2sGRvE@A9Ud9z60XAtvApq=rD?!S-7&{edJD&Y1o!f@8;J;&}Z<>^}V(x$TC+P!c z(#<8TD5=5L!GkXoxC1v;HNr;kr()0XO1_2R%SCQ9XlIkAiyR=K|r zOIJ-opjaQi{oaYqrtrec#q8bh<;zdv2oFzl3& zBSRh5;QgEsV<+pK0Aoi6u|5i5=C4(Nof`3t*#Oo^FLKqh-kXV171*h$esi$lWh0tC zO^Lg5`>7lxO$ByD9rok&Z z!mMZk#>xqXt*M#3veCh3DS?B20WAC)z>b(pnHk4ezu{}zChTE&KL-XyV1@z|3Ff_z za@e)c#gvPZ=-tDB!RM4rm;iWUX2HU2;$v+L=pTk^bV?~P1qn%K%g>Z2UnjErAbdUp7UAbo7rSQX@8F20dl&E+VnRh@oOjG@mJ!A4|m<=Y}!Y@!rG=9GTE&Nzjg@q4IXWV5s zVC&SEGK&?naK7y*!sB#GSv66P6$@UEA2m$ao%+yCp+w~F9>h}<; zg4wU(60&nDs2)FxLB%2D(KH34s-l;b;n=m74d1Whth?{QsT-s$v{4wIX`z$F5qJoZ%6U@Gt{z@;3$Sy76Z!-%Psyod z?8s+PGZ(fE&)-VF4NYL|419)ya>mZ@PpG%D6PtT~wAAnSCm zQiZ$+Bx_h2IJ$daVICj+RJys);ho0ogSOOnb9L&wR0VdTB(QVx5UF1o5P9>m)9$?X z1fNy4F4uR?G-KEP0fTl&s*j2zs)nh%dkU}qA4n>JGBRRQdX?;`EzZ%pN!GrC9_HgV|X@%Xv*pP%u@ z`|D|&6iMo8G2AQbD=dYFUGS1T@SSznS=@KiXvtC#l6B($+#VmdJoqVdKH7-IY-Go> zd+69!99;gfw?>b?^gV|*mZAH5FtO)dxcyEHijkKK$tgt{i+Houm2^ zWC|&NS(H-M7=siFy_xLG*g{^}5z0J;QiD3wYA4(ljy=dqWJoAZcQI@Ct|293KiS1O zWEbZWqQkh@HjIc&q-R(R;ijf9PrJLf=8LTxxOX%R`S({W9ef#)W-FH;`ikYBUsrAX z{ko6x-^X|nWB#*m4l(!h1JW6LMTiWyF!{`6CX7xdJXE~62ku84pr)ml@bde6*|Z~9 z&KbcR=#U&rmv&)9M_8mJRqD}FcI+>Zl4>G|!{-|=?ZSoQ+DfSrA(ysdT?TJ|o+9Pj zM6d^&=+QZxHVJlutVT*oz3e$y$o{lqgxs14aWTO>aZi7e;_cGs$(;H7`SSZCvR>jK zy+d+{lvxY1nz-?@F6=*4%)ejRipw2fES^3nj{HKm^c|$A3Z2@7N>E40y@~ZH5UQ+i z0^}(x#a~n+9r79@BT$2_fkA&f{C4Yqt@_ri|0(?46ktabpA_w3>exbhw{ubGS6NsV z%ok-gDHhX|Fws`V)uC18*y?WY`hlJ5*9%~$V{!-UR{w>St+CG$-xAdWXk^puT1pBP_32a4oe9neDnJncDSV5}VP zzo+r|<>~|cxIL49{X3uoA04Ig*}JV6HnLM@*DQ?Ay7S?-zf> z)^)2!|M=so293t_oDT(vQ^m&)vy_-iDuJEa@r_OEYU$;Qd*XRCR>sMQFGf-RkEo0O ztB8lzsSG!4kWzH_kBvZl8Vi3UV5j0n2It7!E6oS>H zqWBe@PFS-kmzgs+v0-Zt_V&Fv_kqW-cRNkWet9;pM{^b97cypO_}v~%!beW4c39eU zKsTBZRb2AG4Rq<)9yP>)01fhquBD{BRJQ#50=90I0C1IVrVsFzOqsNb)eH72Zt>r$1gb|Y;T`{1? zwkEiJUo>+tMqe5E%Rfc^;yE6k*q89Q2z=3PFm)M0aQpsveFk(P%_jIl0!tLTA!(UV zn?l{djHDDm5mzox76o% zGR|6vUMCMW*tOyhL;2=Gf!89yoA-Z5c+4*6@=Gj|^-C*pd5vT^}A571f|WlSY9Z z!P@z~p35pIzZlrj4?iqU#b=|_safJ%=+l&B0mM`}nmGLM9CQM{f10!qR?kDPmA-Zv zHRnxJft`45H3{14K=R*6%A}(1ni2$ww{XJ^H*nEK7fCSYh5!5qe!)wT5WkxF_rJ^h z=FOWYW!Z-GiX_DSZSAF|j{Bc*%^a3~zYp)h$LejL9^;-p4(xpK#??$XvsLN@0MQF?uIA2%7m^qk%7Gv5mmp7_;WzI8lQI4@zs{J; z-b5`8VPKn{^h>-OztFALy|fPL#8c0vkTjrYG;z^EQXWl!F)3*! zeE4N5>$m4hhK_jm6>Np;F6u00)E<9h2YU||$(Q0Y2DOrT2sVTOePbi7e77!xXXorX zeqX}(Pf4jq0zpEiO)!84_K1?qjX&!A{-xuizVrbpHao8`t>3AAxXsT6Ly8#Ps}z?B z7MIys;;`Ws-iMnIgv`nXwo)#!*TdMUGJge#IR2*Z0*sx-=hyjs7UR3~d0$Az&Z7T~ zMl~qpiH-cYHJ9}to?D%zR>%4vBnT(myqsZuVtDxW-n5DgSV@&Sy!5$nv6Nz) z|HA2Vj6eF?cDC#;V9MBJ?wZ<-*r*y7Rpt4gJ6!-fB`kS$j078xhbrZZx{Gb^!Z81iz+K0d{J| zH}<|&ExqjPs%10}oP0hyw<$4GGMcbV{qjrxl)#RdbM?RAwGW4(Nj7>V1koWTnAmVT z(P1_sBb&ySEh;Lapu|CDp_3zpZg%GBtjjjAxmbX^LW(QE&N&X~luL67iLR+(TTh`B zTYrWP8zv3$vU9RnxpE=Nk((Ji+$Sg}=u*E-W$ijQg{3A2^bI05(vRC~VCSA<+`b@E z_GOcwQOxL3v2^Hcpu}Y%Ki^HOXfvucgj;U^k<}~Hxb?O%3>?}Kr!z-NwApQK_~?_} zeDc{3V?tz`R^Ag(-aERQrNj+Pkz*uC=CVfM&B~zgoA>EnwwvpxOrTYtAt;eacr^i+2{E^d zkuMlnV((e8|E!H4HwDpd+)7P(4OoMIhenSBZX_oDEaB6R3y}rNbB&Sbci~O zPSN8CwY064Z)$2PD}Pu;XuDAutkqSGerW{;wbdJB>;&HJP6n_O8QpYzps6^*uXf$f z2X^$d$09KBtOQ7!a}ZK&$q!Tqc8)$40Ct)hYUH8UOkYPCHM4Fiup>a7c)c9hk)yO; z7ImPAQy8x)!H{q>S6y`#S6p$0)Z)GOp@(qze9~9WJMYY8`0x>Q=@I}p>I%Yw?d`YU z;@or3qjl@n(()_k;6XNT-pr;=n-C6K&m7hkyK8CP8PvEx3GA%chd1@Hdhfr-*Z{C| z-k7c|o^wr|Ec8XossKCJR!Ffe;KesrbNb-+eD~oE<6Q<5#K(E$pSa_Sv zfA$9(xbD7B7(1dPOW(b&@*u2^{t-TV&iU(`e7ib@?>@MJGlsUWeN93Ph z=w#Hzv*}{*&s9UG@X@AqOzwR%4sS83*}Hgq{bS5H?;YAkoK{{PjU9PEuwvh5#M#<0 zAbAW1)g)h314W1Wvu$r`5%)i}NiuN+!{zJ|iCi(EJ#n$tT8`bb^IW{~@jkv=n<0JQ2xiZm z$A=RgVUR6-hS{?atw`ZT55J&8O--EX+KGZ&0Y4zL7U#>WY-Zvn%)_8?BMlJ zQkXQRHIMwQFG1#juO-2v=_`O8DaCfW^z$TGHvgNqm!4fBc>eDLq;H&BL5Fj0_?DEz z<-ktcD!@*S`iq}pP8>K=!jrRivSNJ}*G}okT~kk!^Rv#-4A-}>)*WWt_<^ijw~p&> ze2d+C1FXHt6M}erMiiYo1lnoaPB-H(<ad(t>U3<#uMj)bwYFkaYBUbu5IZh~X#cwUuQ{zB>B?|2)ZvXzuqFdLd0$92No9umEG6pvN?@l}d}Bpd zPcNJJg!5>ujGr5?A4TCtCGz~mKR1V;mF{N(J3p(eW{{|=6kDCuk>G|wICcp*xv%&x z|EhGgyNIw^2n{w7Vl_#Xf-;Xrkz1qC>1V$)1Q+H&dNDag9&(^n06OAt#rI04&acJT z89scdJj~BX&tUPwxwMPh&4uUL(bZCPMU?=_;bR(`wz=@S{n)HN?2&eMZ_6ew3Wkn| zCo~AmMjh2`U{6XJAAWI^*qCto^iJTl*<0AYEsH^e2GYKBFA9sxFq_N_88VWLj6$A$ z=4tlsOJ($3Gif_+GKL@<#@tM{&3=}D-E%8_`t)GQ!i7Be&_78#a+q`e`65X}#-aoT zV<}5#-krA*nUFw4&jHxu+hH>L5rk*4&lBtw>0eU=L9~wP(u<%7I~q1(JVku++RL0i z{YK2jQ0!)%7tiX?Q(ZVxz<4zcTLT2c)!KSy?V$JwA(zXTE^TXreeb zo83=8$W+ZsSBD>;%gb8aytsb@p0H@TTzC~{TzwaQRi`v{FKa*hnDcJE2bbAG!J+-6 zzx5=S`HwQHbr@^%Jj`6Sl?;Q0E~6)K*8MYZslgQN*@frFMNA!kI%kYNv&rn_y5>R) zuybk`ECK9f9tU>n1DY%9@Y}~n6l`HY6M&ta-R~tm;p}?czt_Bv=5v2_TXMJ5FVF(N z{rGF?pSqX^4Uf% zt|GFeoO8~R6ZZTo zuasxKJ9q5hp?^Ne*s7g#Yr7n154XD z5v2*xI=S6Dhdw4tjeI9aU}yU3V#dyxN)-|kW;0*hLQ+~0Pv6slTSk|H^-I>A=7qU? zq$JA6&-bBIWYuy=>vyDa-^*L^_SEsz-5nX;zsZ?#m0Mt^+t{VWjGY*Ky!51JTX}v? ztYp~CoERyem#V^uyME*7seG1wFt|KprMApKyUIUb%H*B*bI(Y!`TJ>D zvm}%b?FF|OcE;LpICXUHpkOqdlYk=ASZx~Knyb>(w;Q+K(4)%v>#u8#y1kQ|u~V18 z&XO`dcI2+?IVEsp8DOWv^>bCHLVEeG$f#u==ekGjuRBqB3%hof5;CGZMXdID)jmZ7 zuv4MUioqBG%$xyrgcFsL?`6TBvcHS#ZpKTckGw-!V5cHuM|^kt*}0HXY~6eGpnt#q zWM`$b{*RSxT>B%vd%3uNWMe#x`EC^fQD>K)%xs0_D^Ig;Um6{w%tZHUL}-8soxrSw z*nmcWDO$2}bw2ts1{`h`eP6I(f-I;L9Oxj)HHp&uU(o;`A&cX#>QD9RU z|HfQ=y7j^7FpwX+1?S$iJbcG(L`1Y^#y!*cY0)BVE`{4a{1!vY_R_~r{+_iQ+pvu; zk;t!41-Gq5X8au04N@&BIV$uI*=jj$-C zh*vi3#TFPs)D4sAb@SagR1L|o8?kR)!PC?4pl#dAY817ZAZiQjBqk;@YSbvAqus`k z4dLu95SIeUxiwoi)Q7eV$l6Vh?dN9fh_W7SbsI-p-C7@t-B5;x7Wmt?z=F3QCN;qg zrijqGb!&zW9ZH`*eI(d(A%oRwB{?~n113RbRDh76H zs>P-$mxo#@efjGZAEZ8gq6c;96TK?tn(;(-=z7-Ytch!#o3T?PiMR{fye^Gk?9Al5 z@4h1*=(`MMc9#lX%T{bhSU z@Z2KF*xB>zLv)M`uC_dx9wB7gvZbG%`Q5Ijhkx*SzqbmV zc09FbcV=Ey=07GKq4mrr43BAj+4fs)`?)6ZGjgo_`@j38%u@+-N1T()nABFvoIUXJ zR`wssmS-nd_6cSBq_+6`7^;1qe&3wTM+*;=l5UY{UVp4RzFr2VKetJ;bp)s~^X7I8 zk8a{Ny{KZou-RO^`q3WNY)O%2^7A(E^;P-Bk-rI_Kszf8djJ3+07*naRI_JB z(Xp-Hnf?}%TOIafOc^m+F?08+UaJCSTjCRFbg<^IJ{Y@j^VO_3ge+ zXNeSBd04bo-@}$Y89Xwj zJ$K&N27L+htRAdO&edNpzPFlb4~)j&=Ol{#XOdG28GBm_o3>izv#QrAJbJ$mFa9%t zv~(w(`z4T??)I}KLZ6;SzWSsw&BAmP6u9{4vplXD?oVXv(m2(fcweFZO#nwg}t&=wz)#_&LNI5gnEIR1}R8^fgUBb_f zkT_P|(r3a;i$lZVBU*k~mWWqi3>n!PEV(J@v_^(sb3Gk9cSWbuNuvd8k(Jvf-^TC1 zt-`x;E3SU_b-X)wLZj-~{=r+^Jg76*54#e*UeEQ{UB{NK+wo}Bj2qwh2#;0~s7?(V z=DbSu^%GHpo8!QRLE|DJ?jUNR4b`BP7R5S6k1Bu>y`HA6+o98Wplb9i`{!Ludf^SS zR1chriZ*Tw8yd(RU&t{Bg0&F!z$UVtdIGz~pjV5~o<7E^ zdnWR)uvUZ?LcD6_#gsURo85>j+559)yyRI^}407ki7hyoq2ir4?i>d z{j)kam7600>S#4PQ8CwPPhIF4*rndCgxx>C?C4WZzx!X{_8QkiEuTlF^cAjG9O=FK z#mq)kXN??xJK}S;;AZTsEsi2K3uOoCH?VW?ISK5v8}c90GV;mT^-LK8U-kWSEIyOg1KyA_ zE%u`?7SA8&uK&5(r@mcJOvz>7g!w!>_y-DXSp=BF$+2W|AY}_(n+_t}Kaw14I=R*i zf;^hCE&f-2h<%^lEqc+bc~^qG12O4z7{%t%sq)s+_t}#22jBG?O>@r*vO>k*2u z{2z`=$MS+DGyj#o#3vT;>^rd%ToL|s?!7sZe%%^X`+Qlg4qp3sA8WRz%C2nD)XbdO zz4(6lDL(n`sG#*qmcwIrw5N^mPg309iK!M|et$3fkGkv8rM*AH`!$h=^Cfj<0y{I-NIx`@ExhI2FZ?u#zRoLjNFcFYD<8i7Pjne}OKJXgn~OVV zZ)W|DbSc#r9B86N6SG`4o=7MlHPeFA1u;Du@$S<-Fnb85S84i!LI+VdETd&pGvB>E zKr(hpu7CZ@NuHmxmqz|Zetb6uf8Ub1&Ygbv-K#hz89RfBXjW|K05Z+bt4|XeWRmi6 zBG34w0%GH`$;`3Qwxtj6JSA9gUYFGS>7Dr-c z5o-bLtXsE^F=NI^N9j|hT$)UBw-j5kkDcl^;8KRID#lI~K#z=9{JR)?QogOiNA{g6 z895i%$4(FW_3y`mFFwI(U&Wo1oAcZ9!&t3$h7W0kmr3{%RB>q`Bh!jb>w&+wSu%6d zbBZt;?f94-sHz=B)nd1qSiCF?v!5?JVimgfzLhRfUD4^a=yYz0WKmzC&=4PgihF0y zWZlM1ghmY{`raq;j)=g7ixp2lz?)Cbq<^m-B&H;D+by@UduJ?-x(s0G6(g z%93XvW$28DF@&_lp|g>;cLO^Ye1R=D(>+5d!pDvtol8X**^wc)KZMT9UjiNlA{b1~T!bcd;0~$WKioan(}V4jd|3Mil0f{_D3K z{rp|V_32E3-pH~Y``LHmG);Q+W$5Ftpl{R)r67mIRX-7upUA&ndJfk)wUW<1sWbpP z7i!XV=X)?2l7tl7g+Ax1gZzWjH_$cKZBr4OR7gTjsYJ1dUc=zl?)~gXKm zHq;fMjtF{3ewB?Wu5a!3FLJ#b3?VOtaZuxP(FP>1UOd-dz4HjEr%M?-HJQ;uLPBWV zxG_ONLHPOk;p^**pPw59ve|7|g>Sf`A}kgQ1%-ta6cora+u8)r<^#|ged`oq?AMC3 z`qWI{qdz=jA$PWp zQ&ZoMz^N89c52hb7d7rwC3ISO#!k}v;(Kj&E#>l1E2pn?kFqtc4X`6X9RcptmyqR* zMD>Zm)6{Ok9LQ)xEC+<0n1|3)4^a-ms9V2J&EWFH>L4=AqZH-N!*XTL)=-ia8 zi%O;J>Px@ZRXLK=@`)KgkDJ;($(0dfS$61KhP9u}zgNxRj=s-JskJmq0`cjGSas+d z#&@4WWaCJ58V!oD9>j^wZsl}xCP}GDyt?&KKJPy4oW68~#6qs!Mq*JuOHz)JP?*O{ z9R}c22H@G0n#hKnBpz(jO@aw$3#ryZK1$ugsHyGg)h~#D&nlYB}Jy~QE3 zWG@s4hr`9vKN5Lw!6EDpw*~x=KEd36Lu)2Ku~GW?5M41Mx(PFGim2Le2#}s-<>UVy zkk_3G4{=&CHu48PX@DS~;ZbTpO={vY$=6(q$aswEiD= zcVOQ4N2S!8$h$|!0Pee`9pS|r8ByjFrwe%Ejh&LgQBsHLH%0Q(ZwY+0_yj`EO61q8 zQy?=YMG_t=EGrvAOm}-nLgEBXPaGR`z|Y=ewoi_U**g1L4jHZ4=~cJ zyOGbnbkU#!7x z`W;&CQ_L}?x}$ZN{rO~-m6cVzUp;&3%Lmx0dfhK;e-0lzZh5w1#!i)t9&xRNsZ$K} zh7IB8i9O)cPyLf! zvAgLq@g6#in~E{8F{Z*?w!QltFFrDj?p?aFY1?KVyze279y&~qsSncjhFdUt`{Pk$ z;kS?9rppbJ@eXZ{s&le`)lXQqZ>GqRk4~UbiW7%RgB~sZy#~=H`YKeTfk140_RM2k z^W-cPQxGan4lMr~-Tqy8o4wE&jhs$S!me>*)dw@=ACI9H88B*8=8hl9gA2bW8zXkn zttSq%b^aV0JFTc%6{n(*ZnNSrn(^zVBOD{6^#n&h_cwlfk$v(V~ zxP_k)s1~3>OFjx&HVYbGe|$Sf6E)>v`oTCr_~V`>z>%&rAx;W*pf$q^({==H4y% z2l`NCbC8*lgF|beQB-$2jGBN`@gz6l82Rg#adqcbOd3BH=Q&(_o@=hC39vJNzS}NV z0KE;2opZHl`Rfwc$*Eu$Q)1syon6sIxbB^QlwLa?li>KIaN4LH*bnCG4Ba*yq)w`j@jCtb_J1u&NY_^`%);&aTJq6w-VS!u)jQ z?3nBN{^i=a1XrwBugkdo|4#M)`aFQ0s-9O*Pcsv5y6Iexa*eMGU`Jyt!`P{V@pmp@ zXY_}OtUp!+8g(%5_pEmBaSmXoGGnKrflz(@?gn-;Y6a|+^r)Kj$rR~hN1WOk9hyHpMN zN?Ra?-~pSY&I)iUfu)*1eEXKyj=2ga@o6oKheD+ z@V75POPK?b8q{yp?Vq`Uoue<5BEc2f$IaMDC428P_Rm-qYJSk#UH$!UT?H@8+%+ZMeHd=UPu3`|>jQEPfxe z+x5dk>-Kw+l%LO`{493orP0#Mmp%baXk_$|i_gOaSuDvqMvHy{e6nOWm|)_Vj!eH< zI4Lg%?k>ar`m^IpNSmEbDP;TpEGbhqZg@C-qJkt#L5y#wLuJMKB;Nhv5Jfh(<@~K< zTF^PtpU2m+t&@gTp)dIh=l5WLkFjl*hQ%Qx)`RXvc4`#`-+;~+pCXH;toAVB^ zey3Y9Zdm^i?i|~OMuA4RF|M%0JC?)4ukJ(~xQj6Us%G3griHxt%sYFCjdSNGjVH^;|Y?@oLPJ--*Lyb{=n_2J{?jqvkwa9!^#hD7C|=v3AenfY(7zhv#yGz4gz zOz_H4&K%^S4mz5e&1Fi3KF3TQKM} zG!6D3w2>#C9>vmY)r^l5Nd@Q>MS_@O%$L1iVUdH>EGq`BhL9jrx$C0b;&~7Zva+C& zkk7GLoD!@OhdDF`EKd8Yp?O--G2oV(pZO3UlI>> za+-@TzRBam`FUh!xyUb28QixuKYes7CZmqezu(D%#Y@oX6*g?PNoMVoo15_6kLg52 zn0WfpAlgUT@bpkAvT8VX(vzQ-=Cf!~0l$2D2O*8TYBU}$*7H@{@m~_(4eXRX*sc5o zE?GDa@j+3Z+p^)08X1k%^f|7}cxvv(+8TJB>-Gt#u4#&I|0U;&_*Y+V0Cvi@xI*V< z{#+9kTFksrh|jdLH@T45M2psx;cZ?IKTac4$p)l_lLkIaVd`KYSO^m1xJ4F@~zplzdtGCm&qZj}E zB9o}T_c375&`QHstlQ?iH~^*xW>HHG$(k-Yrc zE6o4obF#B@nDExecy;UxIt_;S<80f$mWY^Rfiq@^*u{$TyDXNouz5FL8lbQfKvuC@faPhYR z*r{D(A4rBoX*XU5(J1LLxt}RRwJA{~_=~UW9R);hosLIQ3MqMZwx2FQ-|G(SJ?=SE zK7IUJJXStVbW3l^&{L1%pOI((&W`zH%k^+O>SeXL*ttKG?^c|W5@iB_>2`%bcaCdKWJ@0$4wVDPa#*-5 zo=v;c<(k&ptYg;9PV$iZ&Ho-eb7P}HLrjmxTsJg~*3G=-!MW(8l=LDt>_}tr>O^@M zEkZ1ka;*RIaH}W_E+Kg^dEPo}`R#aL3XWouIV}T|>sB5omDj6Gl z4rTM=+`STX5XeJdsIt1y%oq87 z6baAHH4Kfl4#s%qGRU(459v&%Azbb)aR02ueE8KiDM2Kj`S7M%CQtHaKwlGmdKfXA z-Q&k*axnSf|8XMm1kIXxVzVnW4GiPay9e>h>O*YX z6A#(~j2?FEb_JWoL~uY5cTDIZjY7^r0KH_J|FQyh^0&=HRi~8E&Ml*vQ_@8>QDnf0 zs=P&^ewGpv<$m$hvv5AZ&Y(eq5Z3yank<`JimhtKj<1KFFQ@fkKxBE}Z6)0`{hLGb zFn;d%HdXduPLY%MHf6K)Pyt^}XhOKJ+ek=+#c_p9UzA3U#ZkuB&R^?er+bec^zYYC zvPz4rR(9>&%+jSlqR}4brfa?E+0lf0;5ZI+R z4i!!;`-Xmv&D=C{g!IQc?#Aocuwg5DuK>pX-#k29bjGE1aQyoPoH}uc{(M`vTv8c>{iY#Puj3aaA7 zCG9SRWs!=i2*6E2t5NYzI?mhE?&P6Gzmw^5qID`bHG-w%lD>Ep!4gshIHRI!94M-a zPIaO!C}h{y^VqXtBX@uBG1)qA>1RlXBIVt*S{>qmS5XxS>^Ma_jTVDT#hH=Fp%p*l z@DHTZRX51`xrDDHDZPptv_e6vx~=^kitsDsf}AwsS1+Tr%fch~-Gh&xZ}B)g|H-BS z*g5~Ut4YdIV5cUrYZphbYrON1;gf%~N*OeZDTG2(FnM0B&d8-zbq4|Jh&+VvoXzq1 z&|(1g5pPtAs{8IXx|=j;GtWs)Hv(+88d|b>bz^Q0CsLI5A38aD3xHS(fu`3mBy3K|UReDn zvT;i+n>KA?%(d6jv{frKdc9n@3hSYg$3e)qh>x&t&PhvS{hBqB(R1(2X;_nf#7}qf zyxgok{>m@>>w|TC`0BOXHL+*03;@_2P7HdnxKQah{9QM&v*(}qdKYVLR{#v%jGZrd z>)C6H89OQuJh!wAV@F5;RSoc9xEcbCmuS4V=Wpe}A|rQo7>hyYNrBVG zwAC-*Z*E4nW`k(w=}WK9{W*Cmi<3uB(?YX|4vjmI+-NLzoiD1QB|YyH->kZgcRCl# z@t^1G;*Ydb^au#WvzWCbvavax6geDnV`k85@z9mZ996u{3kNqc`td8cdQj`jzTav) zE{m)#mi>{)+;5IbnJEzj_!`yLkG8_n%b{}cST2W7-7q59Xc;Z6p}9V3C*; zN`uLuk${k3@A!G^u~?lPKb6nkBROQ}x}_U~1!!_ZD+cv!gkG|?h?#p|*e*dLA(eI4 zjjgz0SeOK2#d9tIs>N$g^UmjoW&S#?!ee)|lag=3I7|5HSoq6H=KpX))>Gswm`5`w zMH126d`6ePw(a|Oi=R`dBQZlyi(ngiy^G&0Ui`1Xmn_%WH<4w>P_Wj{XwO_?Oa+)V zVm#Ep_v+uRU;m2w9#6>Pv*kzm;iv8Fjy+X87eRo(mdMsR`t&q$%LFevbuh@ox(>aw zdGxM9eEHLE`gCo^6Eg-#cNuZV(n!lJz{8{`s$B?y0iJRQ2{uqhRv`rz8(NJ9KOYl; z{$>Oq{a4iQVvXZOO$WyU$zPJ}?+5Hu zz3%^NS|3IVrcM=M$=-ApY)j$GDILz)Cu>hwn7$~Luf~VcDY(=yD>m82r2i!mpJl6t zvEyWl?`~S?OMMKU^JcY((4$8W`t|E8`5pqu5Fp0yzyHp@{r}^d!O*3h5o=*SOP6J1 z)eUFBkO^$u_y-v&%NRDmLX$uzNog83?au`X;s5|307*naRD`1^{P8gRA@BpeP9Zbf zgXn>yY1^({xe|$epYX49-@Om8Zo>w={DX+P;bwfoo1?+V-lab|+RWDF zKQU~^qiFpa;}p=0M!}`pCDfvkpbj{2p@_Yl%YkCI<2-VZ_1}FB9!C1zG8sc~E9oOo zFlwEOiq@sz(zxWFSlA!BTpBm=t0<4`L$RDXxSwH&9&}Zum0-PCVElY-9XEt{ESB=YisYVp%!3%d4 zvF>P*+}{q0FfsB9BVy~gKiSUv>x<-jp>4398@reY_Nlpn+NIhi4duSDEwJ$a9wn`o zK6a`qQC$N&#_(t<#a5SLKpO>9^~ZDkDaOw1AEvbk2@Rg*;ps6&f0jeQx~x!L=k;f# z*eaGqRTmmaSDymq2~f|^oW8CrSdyQRNozc@Jg}3#u-HNxZzg5R1HogakTw?o79C-3Sj4 zm%rO=Hd0elIdI?r>({TBPPZmanj~e~x_9e_Bjr1Mb?(EK+J=ogP7yusBWX21?dG1e z4EN{96$j*j?)JsgWp<^&&eLb{u@hGY*by5c$^bj}(IK*NwMDB8?6?ma#UlRp$9~}R z?{~@ai9GI@(47`xe#9r{Fz53v96FXRWnHFC>dCv$kE(6;FUk1%1vdH)`;vu2pGD}G z3kRhF+_`%3L-Y%e;pJfuQlKd8+;W_eLA!AnLr4!8Dy`U@iWZy0!R~}jw6p#}XCJrE zjq?p@MTLBtu%AcU^f<39-a4|K_LD=HGB)bG(qGPr#G*FtL@w`qc9?_5g~w2L5Wt3z zMH8EA;bguf2(aUx@vSAe^3n$fSiL31%}lQl#PcGUE)J&);e@^vp8e zI0F0;<-BJ?Ysvf(3?WYs9eS;T$*7f|1~yOiPsb^Tp1HX}9I_j0%n}XKIJn-NL$pT$ zUg8v}A^dGxz-n`_;XoEW`nP5I(%1Oc+sPa~?vS!;C3B2$qY=_r%N6bPY})E#-oI{O z+%+B8z9*4G$I}_ms|6v!Dzo4IncWA{xw3yF6s?Vwf8-E#g*W3zx8a|!?Zn$l;e{t6 zc<7l!xKw!h{wN~DJM;QSKe6vnF2iF4=&Z2hx70K3FKkdAyrUN}-TGfreUhjhc=<@N z73}>pQf#>>%F}pU@tz`1mg{-=`vE(Z89Sq*LM2e6xZDAM374YgG z*(|*+RLZiIgtaFvjQsR;u`JtP%h+)PJ8qw$ZrxagxVSiW?OKh?9*33$G)@b9_E_jS z;7&$ecat>mT>A4DC`B9T&{jumoSi~vM+OWULttQ#bfPTT)jmG>`1qmIl{c&q`@3!1 zwzGKgQc_cr(CPJf1_Yro7*I5N3bQh46V?u2vkwkuAqNh{l9!(&{oZ){2cgjkxjV>B zO_cIyIu9>A{ro{IfGI5+A;eY$M|K{@KQM^Q)C`*UZi!LRvwzcWjCLa#DM{$VZmu+N zJbmIYjoWtM+Lu4TC!O?-OYlfKNl-w5=rU9RSe104WCB5xpg;yk_n}?;c2XMd z&q%P9jMdr#J0-GgnVFgOe8#IE#k&x|PP_H@)5acGs|pI>=AfnxCkM|tldfgL4|F*4 zzWl!KcpmvWUtBkSh07%Ta8(@)-%mcn+Bp?69EZ+KSn{t`GKR|L@V5YV#NIQ<$s4N* z3Gq@0G%KVPz_=(QEdsRsdvy_cRwv!Uv}}!cGNPS<0j&)}g|Z(_NlVIlbic{}rRb0274lHiE2OXD9u-Ua!Gc9<_d(}A$Paa{L4Z5; zF%+szF@)X5Lk~SfQc@Dlnl)qc8rH5|%aJ2T$ji%<0FJQg5nxa6-o5$itFQRu zk3ZzW=)eE{Z?v`qf|cdxRYhqLv1Bh3?_F3febf~p>{>cQ{?_~257~btm8{sOs+G*z zeIS{x*UaIocg8bebVr%)-yd(_kr#jA(CP<>Xi>eCT6S&`fn8p~+iWCb_mlF_x+IA5 zZQpa6t0sRgEqW`4;2ExEH2+}~IbCGrXG#xDc1s#@@ynRdGO`@g=6nW_VCOuwZ!NEP8hBn# z!B?lB!t+*7t{oP6KDWQ@(+Gfd<;D~~{qdw^##9x=^U}GkAGeHYPR9rz>GxyW?Cp}t zBd`+z(g^xhQK#xct(ZZYYSBKNHqd#Vk@&^q)DWE8rLvQ{!j#ZmeCCDIr zk~})KEuAAvty4r9cI?aItko{T5#RXItV= z+nF=(*qJsq8WiTgHIRwd47sG*JyA1|ByG%D%Y4cB*eQscha#ldY8}2qb={|!uSlwO z%8QhsrofIU-ob+hS+QaTCr+HWtfbhMK0JWvh^i&n_9W)>%JLI@dAoZrP}A_liVSkB zFn4tM{esW)hbu64gwGwPpl)1HA3NoN9d|99PA3`Z>BJq_Lt^rAoDN}4?m^e?{b}2- zy^M1xE{?t1S7W!Q;8X(W*?TAvZQB+fT2$W}!f;0TS4~Y#!(tH**NZXEGfX!P8m&TT zNEn_TMyysV$tlTLtriJR$mlX|S%;M+`zaNdqCx9&k`$Z5^8YSn=(sCc`NJBne`YK` zUY;DS;J2B(UL%ZWj;;h}L#R*V2@v=ZTW zB0Tn}stc!JTdPjFPjv{nHG>B40AIX)eK804q4;=6hK|nH2ZO&CngD+^!2y{4d?kG2 z4rS^g@m}}u0;V~1=nxOw_aGU$xrBC)=Ehg&V$ph%bLt3bD;6+i=m6TaYb(Jx5sLrF z_Awd^1O*0SG?~b?igr=(HY&JX`<*|;(*W$$^hrOzc>5CFj2!_pN(No=-x9`c#oudk z-Sop0l=L_PJazb(w9>zgXB869xmG8IHkSkwG725&8n>h9`uVaPf~C_X?oC;#*rY;E z7iv*Py@RcBr{eMC9=m1BXK)nIBZeuM-=h-l&uMyT^Ow^3_%Qo^Sp( z2Yk81%DyB!xAip1^M(KIvJ%xq%iu^oU+%CG=Bv=Jg@Lbk7D=FZY!?$veTCQ0x*H$o zTmFmPehv3OzxmXxu+`Lzl`R#1uHzwljjGd0QLQ|DANjBX`vG{nhlJr=U^aKvk+g9k1(~R-2yy zc1D*6b~1jh9k4_BJk(0*E8nAHacTP!&EiRV} zPft&LeSM{4MXgpVt>nc5zM!Ch?Cfm$Ju)(q2Of9;lX@CI+j5jYdDxbcmQO-*u9ST8 z@it0UjF3waA+NxO&E_D`-?Q3LA<86}H=)7aXTTqkS7K@&;h{cftn;hOK!7$zqpn*2 zIKpE1L_#*H=>_EE72)seL8CykET>!2>F*Uj`g$i@zsTetov+8Csw7$SFlmfTSoIc* zZ+??w$8%_GJwZ^TW@M^nvJ0~DSJUuP3pkmT#NOO=##&zi``)pggmlO!%i`q35e)e-k;KZ9h+2;gPH!yBc%ngDr*_6?SCmi>{;wtZP-<=SMv zg4Gh)+?zqY8_~Z@5Pm-TVyVN*tt>#9oIJY(c9yS8=E%u>=^S1xor3&L^z0PC)iJ>& zBwN_9GgHn{BEJW3i)8!0EPh^b2754iP^ffj zUp5Hl&+4tItlge|=Dfq(tmnR4+K`xPk)V-~rt>gqX%+58fbZq&YwOQG2dgb!EXemK zvj#G3dd0;IpLF^f3(?nqWBNbaG!5yiX#p{>EZY#zpsTuIHmpQ(?U9VZLW@dXo{JOl z4vrkNlbGTpF3!fnA1zEA6UCMNTG1rfn*lwAL{MIhfSp~-CvxBHMf|ihk4fXad3fqI zjJe}m5|f-P{-PgCSEey%{$Wf;F^?%23<`5z8OYdSgDlJFikPFUUDJIbHVbwd+TgaX8mKRKjL$)@OK1up7`-F z%>zA|KA`Ejny1|L_-qFq=bR?tk??Y?9iQiqAUvnwx|oceQmHQC>qP(-VqIjjSaR&U?;1b-(jfbxqa$n5laD+1Y`gR*llf)k34^$fE98Y!2y;=M>M|6>|Lrp8!pDa`hkGcdN5LNX{|{-ptQqtqYk7UCM}OvP~7IS}vBFU;JLZ zbMi-x`{Vqumt^J6-z^)+Es-;X$JwARaCh%7{)G=Kvn3=U1}*iR@%0*M5e9}0#A^8n zwC;xf*#NN2dHlBi2e4;FPjccbtA?+Sx~-00OK((Ixw6xVy?E{NwhbJ6a(@XnP@AO5 z&8jHmt@_hZgzM!&_2`yrFX}??)^vHUx+4~t^Kr?p7hMRkj(RJjVpWubqwd;L%Q{q$ zxz7E2y~ZhHOCXIZ&Ozu0K!dA0KD^m-56p?BT`aYBkkxzIN{3L~U)}&M;=@*CemZ_`Vsg zl#lI$l|y21m5=34lSZ4&<&X80y~k^#IAoPvZD1yvP~0HRYe?pp%|T0W0)n|IFw z-QYdxQ4sP{oMjaQQ@$7(+!HFtNVgU%qltY7`(7cp_0AMi-)xOM&@ zSKQ0kn;%3x%%`0N3hKHC1X5Z{e^RFHvcroF2bY;>(r6JG!#5BsqXq z-OCeajKE)*TNd zyta9?^Rjc#GJcIkdmdX4eY;;}bA)hG2d?FV&)y>_?Tt zHy=7TrC?A)z$!F#g7`4}Xntst7uV)&(~ImxGty`22v<64h~F7yq>Zb9w`2JG#qEGn zZF?ThV7{l#KdaDvpUeLg2l<3%A53E)v-U2=qN6C(h{DN#jXVaHv>irxVk8bPGLY{v zB06nG;xMo8lpSlFc%Q-h%dud9vno` zi)O6zQVVk(YQbv1=Jce)-4S7Jx1WZJXJ?dFYw~}O6>NBPUx4=fca<-TZx8Nd)T~#w zXLKb@=F83TqyfoqrnlAe8&i2LnNa<0qVuFEDg~s@}KW@*T0pQewr(EXa22CnUJVX>3O}Y$f?VO zg%-r8PZZ3DCpFgcuFk`Uaiql(qno(-NKI@xUY?#Xad1AgXp;E2dV?+I93uAG6i!GA z^SeC3CR=ZA2^a6~3Cn=k-{p7an^)B=a;r0T3T%yl4e1oIA<;4csdgDVP5(gV;9v7l zVav+Ts7ut}dn(ZEY(;T$W?Jw72NAfljX1wK_L)b_A$j}daN10`@0t6}Q_jqSeU%j!R? zt+>KG76Kqe-2`Avbw}bNoU9K(AgR#UuQ3N&;=>4f2_JtT6=fe0R*T}no1O!WMX3@q zHg*l__R!0D56xk# zLhD3OO3cZbF1W$ZOD;WK7ep7>+w|4gzsrmb{?o)sftZ@F} z2Y0w&Xp@^Hj<)7_%M&D*vhv!HuPUGvf9xPB3`GiP87$e-_Qyeoii6f)1;*r)DW8~IUnIy;=B*58+W7pklpop~w6h$-u<}%={FMG3?9bxVC6&{-okEI? zOBwfPj_d7x-rHl6OTIE630#MHSXUtS%}p4;IbW(@C6diZpFRM4GUJv9FV7*tq*AHz zjdfQrn@!}MGR;nfHI3zK9~hh1&b3b4JeZc?`>y*0m_x+dlOg9B1EP#tSQ0n|?a=G( z&E%cI^Y~&Hb$&L|=0FwIK7D3k^KhLdJHxU&o)t*JH9<=#W-dYu7*2NH0`*PmiOQME zgzrcQ>AsEarCWB=+asEY{ElPwB-1>#pvO-so#XRW!L;2hCLszYUGck6IiD^PnW})u z~H6*}yUK7mrC&Sl22?@;RHq%$z z1cT_ci@0nww0UAcy_+M`#X=qtMJ*H7BinXKc^wsV+YR7ZqFV+cm?Nb53iWxt#n1Jq zK_lQV2}=Qh{7S^qV%k6ZP`C2pva&MWW+m!nj)Hw(P};DV7f4Q6c&rih!7tV6$d7Pze|=`28k9<5k@*pxJG|8sp4$Z zz68R{Adjz5_Yq^lom$_`8FB~iJW6dzVUJC&=+;Ci{1U+JBaylYp{qh9=t5F@i=EET z!D?enG{-hoyD(olDMpMy00C?P8?t5>k<7QdPm8_W$GHqE$t}%O|_iZkHI)VNj35&rQ?+aIN6}H3O2z zc2wQn*JG^bz&=8N6+m-J4;zN4Z^Hq6L#O0UyT8#14N0*_c!X~_iI~{O zdshk$i$*ANa`d#XcikY>y(P*fY$8Klgte{GLI)AHtpE21jRTtfrbl*26dc zj(I-#D?>zDiz|>Wx=8v0l83*z-+N(!?Kb+ut}KYIt|eMcl9V#baAQDXhb%9<X_< zGb8nJ>k9oNr(JUkxS1B)1Z-kYUBeMfWq{Mkg{wFFqd+ZSiYa9O%`d8}#oMIhwe#G& z$I`x6@Jp3pCcD>R!+m%JJtD%c!ygVC2eLrAh-2Z6VNud8VLAV_I>+8e5#Y&HS(V-# z4w`-a{;#^wbknHHLFf3F1t~|0{gARt%A&Se{0{y)3v|{O59y1g_3oqmDovwuj&z2! zwdDljv!~OmJ!p-fKmL5fYb#)G?M9vPt7B+1T*y_L06EJk&6|H<{K`!moc%Ok_Gptl z1s%9^VI*zz)X_8!Yw4p-v=~lv&)-Rb(iy%&LIU%25uhZ-hnY+oQXL+5;v2SQ^z=LJ z?kw|mbMaK=g_+W~pq76pU%L54!HL@`P<55Ieu2R;=Sg^ZhP;2;_!|zQ@PKEPh`EhI z7l^p<(=8I8Y+63UNQpNf`a%B~&uP0O&Pm$aC{-m}0uZrZG^(aHC!cGbdV{}A%BF>b zT%Gc>QZOeaL{rP~kBPzEQ)-cy{FE#%sKhPs+W=1r^j)^6^x77Dk!cnz`&)HUevwf9_#Z96*v!mEMtb`DC0F6ryFwxykJlcK z=H^$*y6gwZ2ff3a&b zZZ49Ocxf}pBa4fFEHXqe?hIa>!{)#V0~D0`d?xlf*X?5_MVK8oY{i+b$Pt+1<}D3_ z?F6M88a%q46Rb-1st{~8U2IhS|J4+Lh=)yLN4C#c@(gB8IwH!% z>9w0#azsdJ{|(4c)BAaro^{EA;Ic+-(sAkHMcV=^o zJXr)oiYIi!TdrGlJq9!Z4<4)twa!Yy^!Sw!bora-668ce#w4K~bT`Zm7N~p=vR$yoyykcGnQ4^$iwV{L>Fb!|Fc7L{JlHkv7U4m1bHK3{4~8z3Ln zqO{~t4|loLs%He^mn|Ix3uZJ3v+pATAGS4uN*JL`O#n0^5amsFA&|tECTb zk90FnrDQkJ5UR=0ROu*s_YMIedTVMu4{rfjyD{aFST#dbr6|OMDZs(H+EQvdYB;A` z8&+Otj<9%^MmozzxT-LyWo;+&*tn9Ere7)Z!`VQy^QTQOLDL|`FLKsjxW*WMFllwQFd1m09Z%dRFsCg#yVYO^nc*cw4efYr+Y;L1dLq_h%=&;Hu3Vv4vTJzkKB3_uHye3-7%uQ~YEj7Biq5Kkd0NE-RFajElW8LXywH9Q4jP+z zAEr~$4o?pn=X+ZY`LK+!VT}*a{;1z`^!LezTc@q_zA`_+p}bWGyT<2efia;Xs9y&m z%bZF$KtoLT@lGX|Rbx*p%IAMB_`cE^I1Gpr)b$qYHf^3acjJDP5buKCoZY|2Zs9{e zFeH0cDrMaG(4;-*nU)j^aTME3;ierayE&M5c3<^|pv6JsAEW8LVvOpsNQ?&mqhcjYUy?%0X>yUIzDyPF~@pL*^rGo7blEf`!#nWq~Gl z6u~2`ULUXPF_7w>>Q8^ePx|{(zx_E!@j#ER@vQ$r>LWA$6Q^;dZM)9C^;=Fm6>D*n zps_$5?poN4nW?#fp^uM{Idxb)hBHneqfi*B6uwJU0RgRbSVJNmXBSUgDh|aX z3fdb8$b*9xdc#g}NWQ#W;ry80o81fJ{eF3S$1EX~#~~)JRp%iW+NT+OTFT~50lDqd zYJGG3QQ!C%=;4?i+>YAQcM~-8eGZ8R4XtmuI9|L^ZKqz(Wan%svcefxOb|KRgggRJ zLr$vtfF51P)zY_iSz#4T@kSxn*y6OgoxU}e`T{jBN5Xm*OS(ue${T)Zx!O<1Vo#Ma z8-WGh#i+@i>urH9K&W2k$N!o?CIK42my-?;@s|WG6d#(fHDN+b;G=xX%pa9O5!Xen!Bs18{J%{(sMf*Y#yi!V? zMn)`LRJD^xSNgN(@&tf*vG%%2*FuoK1|ywQE=u)W1Ac!V@<2sjwCg;3E8~SIdHrhj zHLPt=^q<-Sc|!7k`pBs?d&7eigE9szii3DVxSe<4fZ?c9zf+F3Lg{1uAI8I-qB=}7 zpTZSd?$y#>rMWJzTI92txcAav1aF<_adJzh@Vj9i?}N!?*fKE);h-VuGGbb7zGyz7 zCnSR1KOMw-JjW>=h8S61X(+K;8?+2v5@$T7wlZ>%98#w4_xS1-N(8ddoVBBoI$HHxikXr@ zvn8$>4)4(){Lv;G(%4(E^!n)Am)v#~$qB@uX8}?PkKdj25QQ(?Q|LK}IW^R2%$SeN z`8v^sTI@I9E&&d&Wvi7%U1HtVdIRRG0RQ+0yD$lXDq$_B|4S~a_7w@0K#D;%ie!D!}+CocR4Q>@RidYmS zDR=6TX5LYm|86n4o!E1(o_sQ)WwPiWPO3-7BB!pM91J%)7K;f~S68P1o+4%%@J2K9 zzTg4kjTb-Gt}pd&mQ6j~BjvQWI8@K%8vCp}#gICb{2&JncM-3e9)x^3DAyE=Kz0ut zul-p~9SD9A8CDKfl;PzHMp6aay_z!iPfOL~ahq0H1QbAQPkljqIT*l``y>DT582T~ z!8eKjTt)1^ew(L4JW~*AhYL_)Q8Pb{;%DmDrv;b^B9I#8;k-W~?fjgQ&UdK~x@&pt z92>zuoUkU$iqEJ!4>qn}AY4@X7^^B^N!TuB-IcEQqoy9q>AY0JJHds=`p0IH=bzwO zFJg{@N`yV%lRP!O$7~jErbELe=~&_yL`qGeE?|>dE8B>#fAFh1*XBlnOf9edty87; zVA6delOkbJ1kI#Ig`(Tw-=&+OFK>GbAR$u%rI|_vgmU? zQg>K@XHR&G(hIenGNZ&biptSCiJf1DpGk~^YM2>TMr@5pF(m#iB6nwVIbvJM!% zO^I$+ZcQqA;v&a50S&dERAFTC7lx5&Tk9b~T@R=|CV@E7h$itC2T}}NM0K?^=mrwg zrsrGIg(?ra=*^)+2_KN5~2BdijFSz!#!4hPUhCH?({(%4OuW!W%B1)cgiE9>?s zlY{I1a-`SjbRiG{gPJJLdNZOXlsSqoV644CUJ$mpie=D&N|eaGqAZ=&Mtt3PBgR2< z^eh#xX4s!R;J6Ld*|~T6wf$`P-q&?BB`z_$3gqa>8TR?CtJ+V(1);%=VV+UQ&A)0! z8wklAxu{xyhi73mA7SfQ!Dwjp*5T=;eG85X;$l>bWaCV`QMBetyI8V#_u-3>ucsSO4+4s_;hn?u(_yFd)YFrm1XUgC3C8&?J_|uYaKLXgQ zq=W4yg`sX-s8g%(3DZk>VI;_a($G~?FK<4NGNJfD+8Sh8X*o2ltes$Ge=qxWp%$;r z=6~UJd$Wl|nU?j*Z;J!SXREDpsAy=QidPN|98HJ$1` zx5MLO7RI7Z6BK})+!*7>$aq6h>)o@XRQ|R;RF2I@iPRxw>~EaH{?rZ)-x${R=HP1t z;v&Iw*F$_SoQI%lg3_~?g_eyuUIv^U`<;o!EA#U|q*HPw8&deQ`uL%1Ewx|oSN$-^ zDuy1BbN3Ev3TLJ9Lz#I4WBzR^Nt|k%)0Z7OX->J4j78pr-DEyE*L<9ECLLWwiY7$? z`sX6Q&soOHvfuq`)uC~xjebGnU zm^}8@X`bB^z)CNpA=K^O3TBnLYiseskcRe6zlf4CjASFIzr+vQRX#hP@RWz(tm!q~^o&{jCPPwSRM^;SYd^`@5-Rrmk zL?J3S`se~VBr^&DK{HzMg+p=k&R1{geOhKRP5xUw0gvaM7cRF)bJmAHm2PJe0WARh zv*9u_&Nc^I5;Eg^VrkF#eroD90n$mpyB`^GyjEUaf&3umRv)g~NY2n_3kW~__@_Xr z;C|)3NQ)OvvwDk~*6Kz5g2FRVG*a@`R#!ZLcrd1Tj8^!H4-CJSD)Nf1ab;KylCZBQ zfk%lt$yBAJWSxHfk{tu`L@xPrm-KJ6f>LS z78JuR9VClY{p`W3kNA_ijXLp3DA?mMOo}I!eV5ZbcY13!Ng?bF&}tfh?|wetz(GN% zupfo%?M(t+P)6aGAd4>MjN=;Pv}lg=aC+yhEjEIp{3mhEIxCVaK~evJH<`6BdGSuQ zy4z)+oi&V$|M-8!hZX1$p4^CJ&=%Yc*r2&C2i8PrCdJn!HU4DgrJUS%pmJeqLmSt+ zqb@{JD+P(5U!~$>m{&+fwX2WmR3Q%u7<~$lh?mu=)Z%cU-pJ}zel%G&K?20(z6*Em zQi*IBCuyl#w~=3bo&Yw{t+3`xgMUa>{?g{@!61D~xSgM&mDBgO+3Hnvc>Oll@LHLfxk0!mY_47{;^mD^84{X?1seV1c;jsLvIV?H-$oQL0m#2EOLJI|Z?l1yxahAIUCm4ISOw5Och|Nm#V;cpu`loTV`s0ucEZ8$F2`{WOP1BUxIMu>FtMQz_ zh4Uyc$rVyzC>>P};gUMUA;%rcLql~YcvqgHxRZ!D?w6|S=hvEE=^uoa4)CmZ1G6 z=m}}7B#t;kSP`RBN#P*uQ19Md2cF~cR~l^)`ohZ#be9myMgoL5)LR@kHC>)dzT*K| zqOIdTKQGOQ_s@SgQHYYKql7e}dYhGYyRwG24M_f19Q=1Qe1(1cN})p?4sun_YB(E7 z^**Z6e8`dKXH6v}lFE}p0W3x8-#>VWCa>bpD7IA*9a85)`jSj$V5nT0N zfQ(0_=O!db%yS#_KP0oEYLO+7vU4zX26Dw|nNG*!6Ihh0C8yFqalt@2g4pSnLg<`! z=Rv4OJ6Q}27293?Uwdq3TfEc#&|ngXGov8USS~CdB?QA6U0uTuVlfP_B|5a7Rna@z zh{f6s@j9--ja7bseY{8{^dh*A?N;O z0pF(2$u18Gre(Afj|b@)m%SU(h78HB@2ViI?G2hW=iE_ej&WVD(J1OC>va*;QfLF- zH_f9du**Vtl}5k4Oqj?wO?e?1ox_pDXlehZjbNv0O@d_&jf2r)x4;OwVR}6}NrbVs zQ#VX}S@@lnGxPEop9}<#sHN>5N~*Uwiyi?RrAO;tWSzl}g&oK`?)QhaQT2`1y)Y}K zia%|?G!+sFR?FMTxE9CY3LqFVe5AXb%PWnF*5Ju0PPMKASoY^fTN^GKh4q}#?Bk6+ zx~0S93nF9tIm|Ma(Djb7>`j1hf2L~Mn`3m0pfJ-TH|Pz|j40`P%?9_JEyy0%h%pZ> zW*)r!E{CP=5w#Q#%!pa&(Z7w-5^ES(S-z<}=kHX1PpVRaqC)y4`UsFK6YgXtQtNW- z_nHgje)AEX{x}Jh)uqz`HHHcWe?#04cz~8QM74`QEax!6A^TzNko2!*_S&uA(TSsX(WlVP{=F=C034vu74@o( z6}LJV+R0nUaBesG%(ZFVHHP#ckiNYBS%o@ry%0C&&0chK(_8+j%H5ISoP;RSruIBM zhI;7N<+)f@U>$x^C(1+D-8?n(bfJ3_ZBMw%P>Wj-bSV-$vUou^qJe>lT{g=9#9beL z|8ycMnjU1q^G&n$^?77(Sy@JfL~T76Ib=wWse)t2@du2|qLJ%)<^x9K4x_D#WB=v! zPShuD=H2Pl%~}f4rYul_C7cmBHgDb_Umz>?^Y;OQ+11_E(~+&8rW_OU0@H@|pxC3@ z1+&9;Bho{K=@%0~2l{Q4wxgQkfc|-+=F!~dIdTCF(O4M>N6`3gJsG#p> zfV3tNBtAwPZD=YJj;m*deHXjjW$i;We5`E06nMN_eDp)fr$1D(^RooYwJmxzanZRQ{qXB?hSoZRXhh%5~Dxy=_FNgmZX1*(@xw zVQNOKxv0?~o|oIGPhScpG>s~0kdZl=ZXFB1+g|^Gr3}q5<;a|^tVXCF(Bu&7{~`48 zvq=gkg>h{C#v;<9-ZTB`!wn}jFDa+#*Di#@4m-?>RLTh~U^egW8r1tSTFb&vg;hU7 z-xS$woC-23tF(}B;21lqtIlX~MoE32%S9~$d$caUK%3ttKD>h}jiV`#5=Ke7fCN0+ zobbAtcyn|$?kx=`<$UiL^qV$6WRlZ`NP6Es=kWiI?2l`VSPY6s{63`IUA(Ewjr&{l z!@{Jv9uhzeh8?2I0$dVIf_-W?sx6*km*otL=Xz{Ys2}nc*3G@VUaa9WFqY|x^A6ho zx4?B8JY;UuHJ{7f1a|#7>P5%^SukVOGpsIOAeiMu0H~xhr3n`L4)10-3{88Q?(LXQ z>Urru`QL@53*Z3h0zHU`;|)R+m>puU3`UG&NUz$FIk<-7F6cxYuDL_j*9{djxsk`l z#&Xy?^wJ(q)u`jLAF7yJz&sBUJuZ2fI+h7U`kn!Mx|BUQT1#*O>8mDj(R-WW`X*6F z&g7vbkD9Emhc6R<`7ylgzyB>Un#VBq!9!%%+)l{n$zva=lx_<#2cgo>tj5*Nb1RRyIrBeOX{MsR2Qb9(1ZoxOr!s-QJzXlOM^oL5mQV@m#Y%xjIKgzlowv$JpByqN6p>S&L#`2f` zL2?u}>)*Wjo$wh8VNh;78^3k+0-eaU28Uo?OT6@G)oJ4=lNCh3X83%*Ur?s)Iz3;z za#ln=w(p8b>J??FLwq^th6NW7z^T*f8f34oSav&@9_26qUu59eLm~J#T(4WRv%ysQrUa zRRZHWV(jN>0h=!$Ci^*)K}o;zh)S~r|j#qII{NV_BAxFJ@! zO9hD&6ZD8`kSxHYn!MMb^Y{84Rq0pe>LU|--EPbuYHLc|f}t8-pNb(QvSudfoe&=R z{gfqt<3lzhr-fdNvFqXeV-$rHgX@wBrJH7^Wf_H7X??DVEORQtjtCkOmbs}`{sA3f zk_)MhvtQAtSQ;?{t(>FUIhLvvmVdP%lkd{3jQ<(}*_^`6dybTX)tZ!O#Y)p~qRA*@ zR&v4j}Y43$l79>bFhJ}|`FnrUc-6cZN`X=(EI)h2Nxb(n?oloNK?^jnBm%c@uf%3T;h zM;)P4*xbm#a5ZOkfT089NLQdWrst zEsVHdSy~#wsci>=R`q$Iyq{Bin>L!crGfyUtyAequ7`|SS_4g_fhYc(2}8>J(K0N% z2}zbiRHegiBhujVh9yjGfJPyZLd<}`+jM0YPbC;iYHDn!Q$x*xO)Pl!Hdks6KKIn2 z+6Sn0PkxV5o|W+c>PFSA+ufZF%%&`~tUoopzx?!T={WZ4B>UR7X8HdXYyMCpb=6t`sv4yf&|Nt9Y3n# z;bepV-+*B87q^qya9a=;xl_=i5^J+c+^5CVXJinA`5*lXgw0tGJ5)8r-L6`(lfBS;^f(?XspWTxRx{x#jN zc9x1x5;x&>{h;sld@6Sa_*hv--B@J2#$rTLJyb#CtPGj*ovzLgHd{s`b3r-?aU& z%b%N%f2aKLAC>VT>U%ORrl<0%$RGFo!I-^=W@Aw_v_6)f8z^5e~8Ry(7f4u%?fQhN7NOG5&l((hs9cfI+!LE^bvUdF@A0@9Qd zlwTy#VlN9zUft5v49&zlU2tp8%B8cSDn9Ud?wx>dQcUNA#Mt(tkcs0nXPG41)UfGw zQ3MPe>k&G>h(*sCMWEI*ZJELJ$H(J$y?Y-tg0jpmWn)NZ^dT{Lcz_exc#)iLSC;`i zfD%5if)xlm?_lZP2zI>yp02G!B_3*j1CH{8YEKYs(Z)6TWU;ploCx*{*Y7HO`4bR* zTtqH?xBrDaAa0UB{ri2hh4}fTBwuzht3S>;NSwR9Wf%#-$jFG%dG~ROHERerE0P7M zX)h%B<#VlKR4pHZf*0Q2&H?_3E<~3V3f)V`lGETH;h`CGEctiV$V$WdrzZ<>qd8Z} zTAvSRp|J#i>U)TJ{owAiSx4qx<-cN^3pld0qRV547E~HbNS(gLJs>|ZlIPD~2s#YQ z4Y^wfT*=Ftf(*)M0ov-Hz7i8lmQ|?_$IDD|EMksU>FP&Ra1@1wn^Hc*`Kl7M^v>N3 zZoXcp)DXxM;&*BG|kRna~Yh5GfT$lG=gBTF~8_vo?jkn zIriVTQZ>vHD5*+U6m~Oev}wgGwopL^0gUs}z7PYEACc$c3*}$%+$s&Noi^b_w(c5b zpzn$a8ML=WtWhC}$bG#BNGBT)Q&Q^HU#ROMc z$FYYBjfR%C^b<)=SK#E}7?kD*+YS9=HV#$>57Y$x(`bkbdWn#!ZN-zFEk;(QQzO;?i>3 z7=*z$hrmswdJ6)RFX%rw(1b|;XSS-|XC@{bEbUlNn^ybds5Ri3lNExxEQ}Fq1VomP zDXhrY`&Op;c6RgV9j}d_@7MSuF3EcwbA>g~;^HH|?$waL;{x*r!R2zXr@3cOeCSvI zi%OOpSzs_+vR($2ygl|5{D({)`X4gcB3PrewV}l7SFdG+5RFxmLIll`s8p3Jvl5$K z!?9YjQsyP^re;Sy-I7pk$OUs(k52&KADXOX+*;<%8Gk>~&OE~J79D`>z)0wBj*~dU z)y4#g`VPGTiM|!^KpICTOq5UqM)^cE3D|_ewUzTmN(NUsKU!m&6rQ{{AoE3>59MuhiI4|r%fiOeel;tt-w)13*JA;~ z9ymA|jf<5oBd#^eI^=|L_qQ_+N}+9x9+9$cD-D@zw)?lvrxqpbsgF zFf9z#YOC);$saZ^*7&w)@v^BsiRlB~>%!kXDYtbkP8Z_qlW{ygEk}@fe9s+``(2BK z6rw|e=@5+vI7f@c0Dgv#V&rpTmJcXvUnOBERiXHarNE_y5T_UL{!iyeCJv>4h zHSz%cas1suPb^X10LA}Hlm7P-abZMaZ~}|}F|6@ji)X%?H7W-NiMAUearu7#ii!h- z<2S3n*&DL8#EW`F_YBwLGlyuRipYC7Qo^n1o-8zjIiqbqX-`1a7RpVU-?PNMD8&Z0 z+;cK!Sq{`O^Xm4v%f4iylnh zYYmkgLOaWww$@fI3S_B~%yYZSR42`jedGjEDIOes`y3*VeGU}OHbQJQ#*CTG`yQmt ztp~2}Fh||~mkm7+J5~4uTRZ!evv>SFII8SD0)!I(UtR>wkut@%Q-E00wj-T}a1o^G zYCfVC)Dff?e3N`UNFnrGq9`jvJ9wyMt>zGL;}Is5s&7$Fou%$jryo{!_GC+HicHKi z6T;ex;dZ#<{SV!Ox#>R}9A*=H4)Q>DA4%_^gZOV)@lH#B?h0qMT7=GV3tY>^M$A^f zzQhs&4@!)Fv3TkIG;0SjlTd!chw8;|dFoKBiD{z1HC1vX02HV5eiCw`-L9!C48{C6 zKA)c&jr_kKz+e71g_Nm|te_>>)!`tI76<4rD2b-C)E_}=z~3*mHg}ByVMZMxlwe@8 z{FchhYXCE{gl4cYbEbQWR7v{5RUKxx$p3aJTRQXvHk3AuTnUyp0xnaTf{zbBnNCUE z&CP8d)d7aqQLt>JkBbh_cUcAU+TXqVL*fj2XLjNjA=Yo+GrBrJ;Oen||MYWDnj2&P z;!jiW@R#H}1Ek00*WE|m5onL3MnjYDRn!1ief-bvvf&2IjusK5v5w-|`JcXF!Zz#c z%BUtaMcM9#an_a2RP-OB&1SBE(BRWm|@+`Dlo!|0?KW=mnT|knVWx z*0L8)o`;8L=>n^KR1!BS+S-Q(?qnBrj#*L$)RMv{J zEhllUfW3&LRG^0urTD+_^hHbowh$Rg??au2S!eQ%+LmhC zEI7vawaP}?G1&`goWifqX4B6aAAbG%&%_M8?mRA#M=J1$&g_GR6GPocldV!B1zHEy zDz%Pq4ux`$RO(iOb(3K*XDP&ghO(3ge0lGJZpU!}DO@`I*?4Q0|8Ce43|?XoRE;VV?>xc$2Q zT4%#JaT>OaxQtj6-z9qars^3qtH24yf3+c?*)i_B^Z4JGXr247pkVF63pM5f($-*A$Y(zRK= zna|0+e`Vdw*{U!8MUC1};vXoZjwJ3GCsn)v-wyU@gH;`~;B+n<*dqI^P-ay>pOR)= zkSZ+7jt&q1pG`}NkIhRXoK(%C?HI#Xs|y+V?=VlWP2&Ie-34RAep%qhc!iT+*x0ac zwqK!&5|#Z^Rh`AhkBhxt6HMQx1ck?Iqu#K{3BfK3$xeFxL(CDR4<&<31HKR;N){m7 zT$sqxgv>?Cw)a1tWKYwRGF4j^O0=^QI@JGcJQoPdN$z}5z5r67C~#yfK%V4CeXa{H z#cbHq>K{afjtVo4IE`qGZiwDS<|e%VFZlnk^%YQ6ecRV`cT0x|NK1EzNQ;1UcXvvM zGy;NjN_T^_ba!{drMtg_@4f%S-}?p*$91Utp0oFgx#nDZ?<*197F4)t#1?2ie_=S^u(0aA}ixsYpCaRq9*KB`)cB*H+xB=nB$LWlcP4#h@(>F(8 ztemEjUI@U2gOoJcZJl+!?GbwA6#ctCw+qsBx>=pF>((=e$-D5n5))K(&JD^86?KN| z?Slc0Kj;@0RNKQTHKyy@!hqZjIDWhl5!&H&1k_>>h9jzT^V(G32i@G!onKDm0Ys%4 zhHbA$ir{Aucu2lnldY|jIUahHztVnqoB5gPU-QlST~L^Stz;J$#HUO3 z2YCLjmI4}2k^ta|KR@)rjaVOajpC)KXxG=e+PmK^ogAk1)h|h@Jh*a$?x!w)dU78@(*Lw?;KIpVg|dJ7RgYvCSEJtg zkonmH>bcDd3dtwB$w3m7evw+nV%~Y~051tdNg)SmX-6>zH}2!mdu;4YY8sl}xAY7t z%q)(?kjZYYZo}FY!o=U+_3BqwFP5!&`o5$I)L_8CLkHlFkD5H40SZhtve0Ru0$mbSYGQ-P_KoN+aoE=~6LylD}_lZ^TSX zovND08FmWr&J7?^$VGsunk*F~SQigJ)#>1u5!TRdS4UW&b9?AU0`ddZ&dcAqmcrY%1-7cq?T8WW)L{i4W=w`C%1xF!}F zWZy36+1%>{yJc@0LC{@nRTRB+f}eekVrP01XXt8RVFB6-e8)itnkb*K%~$s7d#$_5 z2PEcqqTiNi%;6`{o1D#V7XfNz4yIC_giCWMSvT~ zfw!=1D8s~rT#cZY$2F6mu{F(fAs_!Ya^_Qr9e?L=D(0UcCt^J0r^XO~AfCI;1o>>6 z|H&(7q&k5xZ)lL*KGDV&&WSK~-e&6q@9#TXErm}5f-@)7{Un5liD;yjpvq~(Uo~(e zsM}q=Iio%OB=gVRYNcldL_(1i7l0N1G!b`L9VOf_r9FN!2-SVzcIJx*mY-XOA!UwdT!NJb# z_c2k)$sh?t4IS3aYcWh^3tWcT0!32jO&l2`clvhB?qOl?GoQ1z{%MiO8hi~N(%;3< z&7b8)%I>j>Z@TmkeuWu(4GCn z5qZ`&BJH+*_EscosaF)b$K=zR-VUFHgdoJm#u~)o#1AF>;En{BZMuHYnF$``>4a zAIZImBVyf8LT>ZlSB?F;tTd5i1i=UWq;dA^rewoZl336rES#@i@1(Xn|mj1 z-`l%!t?1N!JN?AUTpN3JkLAQ>e3FUt_U(kU_z@6J#ml*dJ*b>@h!H{v1 zq+UeP6?Pv;uFo@NX0Ix0|Ks4b@NSR(0GU}39-PXU^o&~oi>#9+1^hPrj@I_vcm2Bi zv5|gZOs`F!ff5xN)pW@Neei%iFh0vgOLK1ouy+fZph^7+B}$!u1_ zzxWK zW_l;8`iIAp816xYJw9ioV5N}ng`;4C5fn0Hvhh=ZZDC8wh`Pn&6C2>V#4BFm6lm$scD&sBoWPiMLtkc+MA`KB?XtHjs8m&7&Es$| zFy4L28f9%_i%*9F+7yY-v$Hp1eL5scNLWss0Bs4>81fnOKf&YAlwNy-1rU)4I#nVd z@K{0UJ3R|c|NiCUO*#S)HB%wGyBJY8L`30v2?={B>V97*J*$n5bJsVqv8n0*&VF-4 z+6@}S@7_%ISC_f`XPiF+{wGoti6ig~!JTlvcnCpK3>T+PF^`6(*M;c=Ko}wL1>d&~9ZPD{;2ZfzVmhX4$+co548!t(cw*2}L^fhX+gZI>(j zuG{@D+8uzHZ#k_ulO8{CljiWT@x!b|e*L>F#RUKSU81Y2%gQQG4jF+*VHEhruQgl( zWwAu>fhHjqAeu52(txhP-WC3LY5@3c09Er)hk?2nXLfZpPHC?6p%`$T4@9n)N&g*R z4ax_Ac*-gk5gJ7|58D{PDw-4k*&f|QS>%O-*}MmK1Ju8sIZ%<(?T&=;llj*!hkt$D zG3)af7xYfNi=s#0B|?dgPJxpt+ar!tE8o0)^<_+;+;`tXF$bzH3BR_V|4kBL@Ggmh zSpg}t{Upf+lcwXDnfe&*?H!~-zDP;|GtrRSe8Av*xuEaye+Seuxjh5QzXR$AzzaOy z&%f6MoWX=?2J7Vm$YGjPl~LfI3JoQ*_Sm^qXX;y~d|Ev@yTS(S*QCAm4x z#&THv-f`%$oBQ2+C3MgH7vdJQrvTDaq+)gkMb6%6o`LyNhd`53B2l9N+2N z*4m&W`ke6Y&$5!aOpSnw$>ePhqoA&k&q$6D5Y*sizCb~ILE!OWlCq$9X>$613?=YW ze^k?-<}?_Na5kK5*x~-?S_Ja?ssL7@jQYjf`51|kItnqc6P17|ovIEH>>bwziRk9e z=}`%0$j_WCKKmw-`P56VU2%sf{7&zC?e}_cuE70_!{k4TyCbzdDykq`v-sj}Zc8p%=4?77R6E#zvm0 zhlaZhzsT@Gw3}g(Zwl}RVv*f)-=g-R<>{??`m^2>W+wsTFNip&HY%qBX;mrp-29--5Q~VZlaAb_zA!g|DbHGM)i$e z_|9|um&3Fn2Pu69shSs8EaY4Hr?td;2V7lxMYKoc8i%Kx;!}W<`CXA1?_Ke0jEX)= zB|FRmn2Ewm?5ZEbb42eiYaLO9D^jh&LtOUw{B@Cfj3o7`@w90?rRv|!g^ev~3p7El zFPGr?h0^n(lArU5^02wj)mz=wrmt8|iQMlsxRy0_si^L7dD$M6leru<;4SW^ahHDg z*kWqrhq(SV73m64O9>CS9=uO%n+v^Hd=XUI_qF|-08)8{&GHhV0I}EQhqt|M_nR)+ zbed<^J_VK2ED`A%FP9v++z**6L^*@^uD{W{Od=tB$KB-|ZYc9I(9(Zz<^NJ`A4TqT6#7$Pl-L!n& zj|S?GP9r4+beFXM=`PVp5gw?23Pk7pvO!uRn-a!##((yn;4}P-=m-X{3U8s+0^@c4 z0M%0ww(BL^eg4W+b`^Nhkl83B&znC zrtd#1e||q3_9OocZ)GAB)9fV%Hm82L@KG;&Ok!lFb0u7Qv?#_^@67yb*;<)>NR&N& zsB)JJ46e5`OPOyI3Gjc5=>}E)ox~+uwc%KS{k)PY4k>}c{xn10^ndky;>#{*^up3rn3TonTk|;OqaxO27x7EveS8oe_j(zQJgYK&CgE zYaj>Vox4fnFG1rDrwH8=gfj2exOUEI-7AK@bj4b5d^!91qDaHr%H#H>OB3%?L*xHABwf`p;HJALtz%ANAPn&U{u4 zToZbqt@g0=cqVtfKB1)Hb*AaEg+1+SzyqP8tmZq*awuUj&v}0l!0`}rluFdH)>tag zsXzP+saSbhXP)0<`_r$RwHTLUIj;^8uOEgSN0=>X-h%=jezgJ*#2w<{SB1?L%+Hp- zLIa`BGPOE7-%atFj&Rv1MchQyr74pJdAM zzX@&C0%_ZaWGEWeH_AI)9{jK`i0inEz5iHeD>MWU99~%ud!l&4B6kKA)+ndxzQbqU z-1Fh|&_V5zYd|g?(B2X@ji?0OJF*H(<0Y)e?PF5i(^un@osW#OgO`m8@vGOXkk`Xw*3qn6_+C42=Kn}KT4cvw z&f1Y_)1_@`#!WsnsP`FKL|9J0d{YAc5hGZ?c>}TMndwsN7R!YW0#ali+jl%3@Sh;8 zt#NF1if#Y-T*^N>E5F@C!q?g?m9-VOZR^=%KH?-hWDK|SE-$aSeWx2JYiE-`L@ks6;C_(+SoLUb)Zv86j-fL-FU?l`yUz27?m$-5^Y#6sCr;f34tBWU5 z={uDi4)cI09ZNLv^}00g{q!s{j=;X$m2PK6r^|UhuI&@%@a&-bV5?Jz{P|%#0h7x$ zwH$lA;j`a(YolL(>3qj~c6_ko<%)q*d|j_K4_N=CP5GaN{+G%H{64YHA-#TGxW%e-{x}^iR@hJ~>CtODiQdj2w}jF<|gjU=ZYi z)75_Sw0xd-mDPCPd1Y^&)Ju6o#hz0AP&6A>y^>IbzL_oGzNPKUn1tp+N}%uK^kTG1qLkO~N~ zHw||ts;TCjL2IXZ@VFLN(s*hqUZ4w(^~tN|BIjl_(=YQa1m(r^x@`i+x(9v3CmSE0 z+BWhdXl9Prm)}r@shaOprZ^xj@Cg0;cqKlXI@qx!@BD z(72$x`Tk|v06uGxuPDu+uSH~nRAVOXsKAy!{DA@6KV82Pg;tY|M3tT0!HbkTcb5{+ z(#Y*R|L+krADVU8wUwn5sbh0=D0MhQj&P9A$8MBq#E^%M0>AE*U2^F%QsfiG_tbl2Ph*vf%EU*5hR+UFU0#f+t&2^S&?=D?L8kJawGP z;ZD(70~uAXoxY`eH>P{9x+C#sf3St7J%P4YXGDLg(5vItx>@n}k5QG}wHuXXu|oEP zw80lxn6QVKXjezjK0O`Ary}Yfh7&P)j#=(!ZWcaSPW$pt;oi zU?y^U^)x@LrrWnqxUQm04W@hRa_c9?TPq6~MM#WsOlXW(`kHo;||ucyU@SOwLj(BQ%7j>hA7N`rDLTr#Ec zLioLc5Bclj1vlw(W!vw~cefvT?{=^#=a;qINh+T#>JN&0`^5HZa1h@V))|!2k#TBr zU2-pc`Q#-QTpq6xPHDjY<2cCu&Du6eOYx^LoAoyf_3-9Is}Mt4eVj@mliD8s@o8?V zTy(!_ie+U^{Hdar$Vus5;r{xLvfq?$#W(*&1L$6na>UD;P(RSC|A*DT zmXG)T7@DG1GV^-%Cp4Otg;|rD$no{8Eq2+l76K3dkBbQZlm$!)TM`KsHome(h-oS(TohcJV*D&VsRxc-+67lhCswd1!Of`>A z_{X!foMm#;0XCqWnV_UfwdKC56b*He9p}!;vfGhk`Rn#$v!Lzzn>A?76z=-e`ML&> zsJhj!iX?KV#nZc>lh!n&wcCCk>e;1h8_I9pGD-`ri@alNeV_m@uQ1yftIpL2uDZJh zvOeP@I`!N-Z%p6NGO5JIm6i~(L6r|UIz!`;)UNC#45dyYDoRm7?Y_Kt$S`=}Fi_Az z=^uqrbBgzsA8_SlT4cDkBfaim$;q>@72dxlLwPt~v>QmZZ8_C~<2ZY2Bt~82Rz~mS zSFc4ZY`IrOZ}11ZI-)d>fq_7-Sx`|SQ zY`(o@B!JJ?h~QK>JkjzR<8bR(6Ty-r_x*}kos^Fhc%`;5n8-T{;Uw1m(*^fP~s~np0 zuk>B3cQ-7s>Xr5@SM|)}VOMzINdo`P!mazj<8)AhjTn_!0-DvVzdQS?tc61Y7 zwEZ-jANH!4Y7tyNHd%pmb3vM&$G$Dacdr^a#>Wex%_cNu;^gSS)Uf zAc5sbz?v;M(#mi!ltl!J9x>$jT|&vf!xd7RH`t0gcj9j~m9oDrck-VcNkM%?XBpF0 zsR-u(+WdJwU$HRyX59I?EZConDa2sn(nTSH_kF?KBitC#L)`O^o~-M%fJfozdruZh3c|#X^Y2w z`c~|`BnimLHcYD6e__NZK|d%E!hd?!e~O7qQ2GZ3OxBLr8vIx{PAgPWb+IM~h=eL8 zWa%z=)CEV~aEcX{XA{N|{yJtc69{ z9?wnV&n*XSuh{h*xDB{0*Ea4*A1z`kNqV#_a6*BO0Fw+Tf9U?@)N4=2qi(qdnR1-+ zH)M2l9%q|sJQZSL_~?aQX0!U$Gv!o(n_Vy1bU#Q*6BII?^u#dg%#ox@bTxdsUgwL6 z-4k8FBV)CETqO__#nAhPCSgT1F2U7&;Sc{dPU@xw&0pE`M8Od$F+kh8-J4dNld#ik zDA6YeQA?%B1Bd?UmE}5*aN(q`c^5aY+nvkiFOn;4ovTawNoDpaPLX!}?Wx%25U|&D zh7~myN^2JA5`7ycV z2Nz;ev&!Z)g#^6jL7fjG!NToxyI;4r)>0+XEHYRU7lCOw#s)WDRtFecv)8JB^7Vh? zjyDSg0Gpm`RhFbda9R*hB4*~L!@XmorFRr+z3i9;yX~+=;CW<1umDsx?>NEtwq%NK z5-O0ccjHIQ7O`SGXr^bkY%Xhu`*BZ*wdSkQ3X_KeMTgFSS)C401Rur9BOW%E4wr0( z92Bdhc4m4FE1aGav0}Mbcqu$GYP7&->(yq(y7H=c0L3y_xaZkP{;sIM%Meg8#$=c)*~2ZII637>(;Hyp z(CM4>D=796B?zc9uf2=KMGEmm5Qm=Y+fA<_EigoM6~0yU~id^clW zOs;$SLj~Km&jxFP;R7I*e@a5-Z=$z)|B;tvC`T;lod9yn(X8$Ze*n?+XJ#s3UajGH zpRP4k&n6W+Aq;n~WxY&>VO3Fs;xhsDYwKU!XYieG#Z|irx$1CPo{p?2Bcj;g zGu~J2k{o#j#O_-u{s!*%?i1Rm+`6&q{=E5A^14tghRu5ZlIWx~Qkh2oWb&Ky$ym&| z?LJ@R$}X2R?dQjb)A8#S`cSb5o5?o|8?$fF<9DjXv{{BCXpesc0VLFWD0~bjWqNg} z6WI5O)sAN{R`})^rhoCA&yZXR0$Cl@v*nbvF3m$be?4Agk>RN-9!{I%J;)_onBlvno#^Sc1~R%nyM z>5O&o(M0uJcp_c@O!a|rGWQoK0OW}teTf$-F8{rR4xnV(Oex(*vDfE2;gDQBI;=ak zgW=AC+Mj(O-JPg$*oxhOF%}!9Pg-pM-8g$`da^zu)2>2IIGY9_ts>Ms40nPvd8U7!e@_|wX`c$y; z;4jA=L4xDNb|TLr*PS-#o^yTm#a#meXKDJ$`dJH8_+KsT7$4mD`_>DHZjP+q_ScBO zgUSbaf7GRV%UuC(qP%SpQK7N7{#Fgp!}|VL4_m3-{v>@$z2-Yn6Z^ybac>+7DoT zWyfvsD(W@VoVlSZbe~jzWuH`^@qlznH(0J#vOLf>h;#P(e{=YY^@@k*yjub+*)aBB zqV|6$bj1(?&}O`o8F-7iq~f`KWkoJlJt_PG+2}ytffc^ON zf71zkaETKXX&-uVCSa?Kz7`QYj_eD0>Df%fyzNyihNOxRiFqt`lH&XtOXG}ub zAsZ1EvFdx}yvjv33cez@q-B}S!7BZf(KK}$ZY7xqgZ_4>g%;QQa z!DN@$vO1j;pl{)_u+Y$O8Lk{h1AS`0hyaCXx?t=xnwoZ1q~pJ*PcR*mn!W!ze_4zP1nLqz`fb#jATm z;fu7E{9jXBScIUcl7UPxQDH!YOc7C`SfpO>x4a1HZn}9xn{1r$+5FaF)ZVRnXn0=SdH-YKHA50ZT?k2Ny4>C~kCr@43v~C)av~F6F3SVnEw$8mdyQAwU@EPfxOZG9gFTP;yBCFbi=wxGKS5ucX zvHHohsep#0H$%7o^~pa{9R7#a{4&f}M0HkpuO$(m(wfe|A`7JFpFDrQi5bYI%0^c+ zfhQ*d7i+cdgg~hC#wO^jF3xLAD`7}4DRC^mqbX`EEGb#@8m;zeWJTZ22*Tht&5rLq zw|ksEla+lwJ(#lB!1ri9&OBj2{pjpX`%TK7>mmN-ox3k~(AAkXIEvH#bV%(6f(>*X zgn%>j6!x^fdjR?*4v$$;>c^n!DM?DqC@eAwwQPj(aYtOf9Wsfb5JeVtju~~y=z>^g zl|kz8^Y&Q5zT^&?2u@m$1z)^I>P!^o3d$=D+AN$bJDNqAKeSB0!m6l}rta2&nH;E> zk7%Zg{eVzdZ&pYf)nd(&S;dXiN8}?Zqoi7Vr1x1mXEx$WRw-jdM@&SYFChX%&nz6F zTOM6U#gjqN;cuvIw@b%wZ4qsC;Ff~Hq8E#_&H&bLB?E?*DTa)1<{j_-Sp~B#Biu=`Pp>P@4l1EQAz0c<7LQ7-kXBH|z~K*(nrxYBJx3`4A>E%hJ}PdHMbuSRq$^v!2zff#qxS5;lfZcX$=UhkklVzeamUgatF$3 z#ce>$A93~->7M4sn6SGylUIzC8?C7r^Nss7SRL3yFH-VDzVlR(r8(dF^Kj5uh3BNe z+oDy@dWXmA1H(5l;>8_n=1adv2H-hWmhRol_>eIje|)KTg**1BJ26$R4f0*!iCj2# zHlW-N+`|YkNNw3j6H2MJ)XW{S8@He>ZofqQWO@0Hg9q}*Xkrv-bo@0T$RX+6L+^k6 zkW>(w*EhwuS}WzC+r+aY%1kW#Z6ib0d!O0aOsC(SNX~F;2?CFZ zMFi7qIcoL-)V;vLCqqeI!Z*(QHeWWiXC&s5Ge%JQ!2uml%dHyuJQ}r6=(iB){5ppyeA1 z3wGK-6cS`h>0!I9s1Mt668*L%83*C3CHK%jza!Rh%gkpj@`NIU(<^2!66UsN^=8=j z(=o7CH1HI!H%ZIHXZ!4jogFVV1SOg6hT$_2eJF8_sW;KOvjabsEjcqsFl*UsgJ_vk zq>viDT$&3?a1XlWXS0@>X(uXB7qKGn@|$b%JkIXkVtz|`c#xj8WuOW9=-H4xcnztS zX)Dsg>J>jrdEoKT6ql>Fk1>0b{{Qg|Gz6EXtlP&7?0(Xaem^`Gx(3w&96aPM&z~^; zW4Xq^`Vo_zRzJPsSq<4l>olAmY(hNWByN86SqSJyurL_~ZybckorH^N#z(G>W7;#< zsZ}H8PKoNplFy?+&k2{X5Jpf^C`a3rqE<2!Jbp5IuM^sEttQ7cIO(vq+s`WcxYCQ+YG?$%)Pnm59e7DeDW8r*LEF_=pj%tM8fz`sQ5wCjUp(a- zC&sTpwvmYi2=`U1JfP)=7&)$YcPOoKD$CwQkA7-ZY)N4~^N)}=f+$ z;9T($VrJqvk7tv{lG5)x4?Yk}h${=Eo%w%&^yThHjm@0GZ*T}LzFOi?3ow?*4h($_ zUfeN$L@(txHCf$G4ZUE#i6h@cB!^z2(@nG|O;V3mE9Y5adNb_Fju@LdxU9HN`76ZusO-W@c51g-WFganDxtRtHW3Jf(0##Z^PLAK%9Xi zCvx$5$d#ui>)AkqdS&56#maoL6BierSNEDzm4|oBi*(bAIwOs!r%5u9oN=s>FiE_sb5h^@VG#x=5|LgmL>F&!UngSj^f!4jZ^{p z-=5l%=W*9dVACurk)?K(Z8MXSB99;*=*WXt(d#=Wa1YZrmIwg%68IDzh?cE&M(JS`i@_KMTFQO$EFK-- zUixCKz+FXKjW94m!>mYwYP7@dNzJ#VmF3r#Jm}`$X!@=L9pq7ULQ^!~7;?~$85`X? z^zp3Oq;?1;$0ke3DpvX6BETJD?7Lzm-`#FeSpd(4GJ*1Dnvm?TA?v!z{5%METC-n! zBhe0~n{N=6)2J;Vo#1#!@f&&OAbFr~?#edjCudsh_hX58YEFZ?8t(K?ZtDLD00iuz z8PvLEJ?UY2GPvjY&_Qr$y5@O;z4;G$#dO9TAK4TXm=Xq}`qdVhFg4|al6jO1iQd&G zPxhf5pisvm%vdV_&dDaHzf%-kyeK1Any#+g&Y0kjU6TkogyN|ROoemqNzn(ef<@zl z7sTQNcSEBSH4#CMA;qzXkazCJ_@pI+JH59mYLJfxFOW3J)~4J^KlFn@>tmGbTp%Yx zzSwTXaxpQ}lftJ5NR-Wk*1eV%zsyRi45fl77Cl0vMK{K(2F@Z6OD?yZt1dseNA#~W zrs+Wh+3Dw|>W^pGJ8#lUdv?Z2#!FKCnjwSO*(ejvw{g9K2KSr&7a4MBF`4!%dg~lV z6)(Y{eX{yXekB%XP(g7yeu+_mD6D*#a5y+Hv?=QERI#>S^GU`V*dqvstx?tyu{}GA z5UlD6R$PnZLppSUFuJqUHZhpXq+d_ zPpdFa)Yx-r&A(zS%`g4CnT*@lEiVrkXUI4!kBfbZywXdE_Cy}iu65qN-49jeW|!(0 z-<@qILm&uK^~?;W@6@h48!fjd1zV^04PZ)L;NF<{@hzGVM?UCso_OZQ>0((>8_gfP zvBq*0H9VjLeB1m5DE^nD--lrzug;*ySB`;)7bhs>2K_SD$^FjbBy3ybjz3fW<2-@alED%N2Cp-#)$)|yZp~!GO3UWL6?O&|6i5ME4caYO z+YR1TBvruZLt?$9#9*mFk55Ie-&=csG5Oh;dZ88NppdGS$Zi{Lr2O8UZD;r}BY2d| z^>#9NCk5Wp3MH1AJ7Ea7O-VI4F|8)cX#SEt-uiy)>0JG>q z1UKW=ew_XrK{k7a_Sw#l+Unuq%f)R|7t84@J3<2un_AMzZbVww%aoi|&JrHh6^Da(!PUQWk*CM+pRRs}VW>+SfcT2B_YD}vx}?Ue(L zF?w3sR1t)&UM*UKx8cnosqAb+;VAe7w*$f* zy|v>buVz%4Dx9;UFK|$S?R;>1MVv0+Y~O3DKYJ8w7RIgy*uvu_5rW@Vx_hX~M_!1% z*-@%UUmc191RLkCPo{Yu#kGY3SGXXD3vRVY5?P(x%n~%YFP!1v*ai8O=Uu40eXEQ! z8AGv(2i^C>Ot0<9Az6H2atyO>^0s96V{LpQCVw1Ic1N)OWUL$4@+2}1(`iKs{~${Q zkB2_;>IYU3c=@PMuH(LRN_IKt%r3&Gn}q*Qs#l?2884?0Hn4)GaqDwGoY@UoUvnKu zg>({~L7wFFG=MD<=cA2fi0L1pDr6iR8%HN1Lav^RV@XXNpu$DWlN1H(>)%V?{r0XRYQ~)!D|+>$%4$be_>LujVkQGSsDY`zWxAtb z*L`cK-0B=j=k{q!;oRNBw6SA$e=quJgo2+e;*NCNP_P^f4}XznlpJju&LI{|51%YD zS5$suL1B3Xf_HTbKy+UCj1PxG_f;o6$}L8tS^b|E6LXvw;XF0_V@0y4WZV4Y?$LR? zG=??UHa31Z?h6mJY?Bq;xKP^PY1m#MUvC#oD`tlaZN;ap72PGgCCu z^_#+e2Tu^2sDeK5PIMK)DNnhxZ{XNYb@%AOuyJ?FjZrIDX!s@#+GxM$WT11MegF(^ zCl(F@RQy=Fg5s3@I3Wtbo7!jmF_HZMfWvwd*JWJap%kY)PvAdzDmxrvZ7rWMiAOXN z_LJ0O=0yS=oIi86fD6qMXLIVQUnhog z1`0^hAa7aC!r^)K>nsWl)BXFesZfq40)(Jz`%kU}|DQ6C5bGGuN!g>xq#lfJyvrmb zJlts#*VN(gJXWQ07NTOCTx? zNM85IYicIS*ntb28avAUmztH3Pp1!S4iX}!5@?dIxumjO4Wn2;vbPN(@S{J)Og?ey z(xI44k^nB3^+RtdqIGF$cO*r(&jQbZn0ao*#1!Vt$>u^vGdd!P@0QQ&$mm z5AaFKOAufJl?VdfghKvg-IG^Z_kgRj5CJAG!dKr!KgF^KCu?{*l&Af@g)~gaCK;%l z&lEjpm)Wns2ZpB)!FKQe(Emorb3Qv`z!rk+)D%jXTctU^EhFJz-|f!U*Up3`caeE*+f)6a78+O_3++xqpL;#+}j+2 zD06&%J8oLX-c~F(eBb(QO)89|Bq@p$_d&w3&;N&uVo*-^EW<1eU=ZIgD5sd5+7&AG z^=?_>v$)~YEFvRGwW zmfO)gmOt4aXOqW8k!6|2mP66s>Hj5dobbfhgqCVM`l4={PkZ&sR? z@FN2{)M=ak@Gd0;9jkCo2v&(9qB#hhc0K!K@0*>tVfbnx(0{zrnwJkxxrZe-oh}}4 zs8mwV4a81|SZI@dJy%*Sau7gGPxnx%OU#Ppnd-(2LU9%Q4A1X!7kRzfqRryX@))AM zqO-*Qok1pFk)>Nk%IwWl$qg)UJmzu&^Ap2@>ytGZ9DPOxlvBamKBl7**r+H_ePI7n zmXbv-qUu|A=Q90jwd3Vde*KcPajJ+ihe$M%ip=Myp|0x-5oss-YfwY#2?=}jCiN^bMVZWAzxEYvbU=(;;L{D zMN3P&4)#4Wy(VX|yJ7=-f#*8$IsefH_f&kB2@wWT1Ftdhca1ZnbcwL)BcyFPc}(|R zQE$WN>eSpg001eN87QXX#4|KK%RwZ~n&FT4B$3U&TkXBZgaRnKY|lDhqTEGa7TDTWy&`Zv`P8^cSg!i|sD7-n`2!~W@VxvI z-%dTB4l6(55^s$+ewxLBXy{)-RR$yUpK|IkI4fVtHiBo0MZ$fc^b>>;L$Xk4icV(3 zurnkC$zn51sM~@xn5Fs|TUU!j?A*Mv&q*NRQVYKM??42!vHJL?$e9(n6fd+Z6Vqcd zQHXz)h*5`lXSact9<9NZ#d^wKLM*3_>ReXo>5n)i+DS>u)x1(%0N@?k z3gzG5kf5wvY_bnA zgy6L0R)zuO|5g(!8#(0BBzCRSJDvUIB^^iR$?1Wo4_ zDCAu>Osfw|Qgl)TQap-AW=n|F(m3kIKW3mD^!}I6E@hMAxobE%`QI1)i!i)){2q)x)-w57`{#QE`~cY!Yij|ftzuS-Qt8tLuSm&lKd zO?$Q)!sbi&MjJ!rSHt3_C$4yAYEyjhDrNSM1Mt7@C;T|ej0wivuMF9$tLE^Op906K zCzawlEbJyj^Qmb&*($9KX+|?M)%iE%KYsFY)buBkO+N`jAqoF+b|l{7 zBQnogo*+ZbjhzwND+Gq!j5h0p9>rhZPx^zJJM;Dp*H>)l>HsoDY)liO) z=ZK#OP@H&aWdw~@)k~ftome|(B?!{m8=4A#H8>G#aUsNAi0!}$x~=);b81IN?o!~c zUFHu5>|Iy!47bR|uNY{uyhb)sFsnxbNa&xoRwT&*8$DZ3AI=T(MBHr3FbDEiJpb1` zd=BMT$els*Z)>o5vDM*Zl{tNh%tf$Kp3d3G$tlo^$8{GBG1HYLT368&G&RHf`lRwE z&4QhHGPA0K$3%q*h@h+0;Df+|eKBPf{C6A#9T04KdP;PH2-&IW9Ob7|0)Cx?$-5F? zINFK6z6>&IH3^2F%~ScEStTBMpx*2Q+#!seFSfjpA63|XWu&J<5)r$DtsRzw7w#1fbk_{%$=HhfLc|iS1{iyQ_c($y7q}=Mjt#)#Yv{M1_dUw$uNVj!R zu~SNJO>Y1=EFB0F6&k2mda)}27n^7~KF3(*x)>iEF4qo746WEL3B7vaw~Y zv|rUghDZcaz7q;~>Ss^Q7RIz?p!Dv1!z;kxQ{DK~jQ}cPf^BLVpr+Kqd(~X+w7&LJy!6r+s~7suwS;bbUI^Fk) z1I3B_IB*V(N__%Z18ZZgeIlk`9l$pf@i>#2)QQL&TRFXffq-j%a^dKAno< zD5(!%iuLRh=Ks%v%TuqQgLuy-+j+3xZoadWb|8_7RTnaGBme9`TQPp#Y@4rp$pr{r zYjEJ}e+v(wYkIaVoMFGHWK9H%v*34nG<8$Wd1|E81J*SCDk-Q}?H+FIJMZ3f zU51sYe3u@x2qrdiVdp^W5XqNDJWp7pE|C^@cnZcOq}*|UoQElJUN5!Z0;DR?;thqL zP<+G>ygkbRhvt^#y!I``Hi4U``x&SjEeN*8zQOAZUiUUkz*!W zgFOpIx_gX30Pb214Non;7NJ(o73OnqJ^%iSWJ$^_CUFO#@xk(@@$jDJ63e$@I;3~h zjJ*XF=fUS~Mes~S|9n#ylRrsHrRA(3NHiB;+ERO}WI@E~@{9JLny@$RNIrtgAo{~& z#xf(Om~Q<(dyRW#>dBH`O}hcpAq$)8cYeNgO|6xxkC8I{ZC$c;dR(HmvyS#h5T5lx zfCS|{SwR=bojjn98anAcF~J)4iVsIB2Scgs{{BW-wOE;Y!|wNvUR3Nif4H~?nz(PI z+`7(ly0~IJP?QEJR8X8cap1DAxLm<}7Nv8C!85ja*M>Y`H8`OfCs979o#e@-A?YuG zsmfH(RJwDR6%=NCjp$w}XHkFPb&P{p<@jR^nC}0Nt*?xVE6UQv-Q5Za9tZ@N!ab1S zmH@$nJB7OyA!yLx8r*`rTX1&>?(Y6>rh8`IboaliKeblfbM85Nf3|HfhFl1*VWJcvgmTyu=dlXFMsv+3Qn0G<&<$x^YBzf zu4PBO`?F8{CDgK2$n+q@?rcwgrJGP|w0jABf}MB- z84v`(&~Q9j@ssv|RDSkw1q0?;lp$p@-l8n0KCw3_+Z4r>>*l5L(+dr+^#bb{=sAe7 z59-R1^wTZclZ(|zcikD=*eV8=KdsKNV@*y9ca3BsALT}ZV8NH3yrY{amh@UOEN_)R zXl%g*fXfqf#{O8laN+ z8${r{ImdQmbNsFOr*H#qrGyH!U&^}~Kbu|A5sDUDoSsJT{h{}%Wpj#| z@8jw89^TMl;>;_=McKX)e~}!(9rLKIs?B|Nb)$*p(z(ugCjD0{@h{-m(Xt^!1-y#( zak_y_Q8g_y@yN+ljmPc*8Hz%wHm~uPqfbqfqQ5-?`2JTczppiR2``IG6zR8;6>sX; zL1mXL3=NI==z%SaZ4(m{u{=$2^PoiMpQwlYsV;LUhhnHdCJ|kdjQcPTqqC=gDFc}W zY<|Do_)>|{48DazD{o8+?xO;GY2l!6;CDeZ}plD z{i)NzksZoEj+i*bHzd6&sU(&#$2HO@t{*jtc&E&yfpLgH9yf9nDB2ceF+1F0^Q~*L zl;CjX(@m4vqPsT5QGTY?G86_YPd`U1fJdDRsw=1z8E%&O)qKGy$tn}%LY3$T1=>UW+)q0<*J{!3)2uB>9pxOC~q7JevWpr$aa|*_O0*P z{RvyTYyD|)(ZKCfQI_GycqoP(z)1v!ABe%l7*bB=aokiM8Xhr#KnjNU@q>rbHkjX} zaJNjg!Vm7%mL2)01w4AsogY~quKthCWZ5PDw)?NK;3g+2H41=(1rL}QF(=@y;{$m_01}2oy4>vIw9fwrLHHXR zKVdY-j-u1+T3%890F(|x|Heibi9H(a60EuJPB<|<6q@-cEggOtwYI_2CNdJ`KK&%l z4*BNiF6VHFU^PWvEOmbp_zB^K2#)RZ!5vuQ3Bh)lCRU%W48+@c?C9zu zbAu5a`(dR{gFDq4t9@-eN&o6+3=THs_BPl?e?aMHoA*xgP*PkFTzLuOH~bZls{I5f z-~ed!RN&)mYJ!7@CVN71X}*l7!Or5eFTA%nj^^P)ZLlc!j2b(qKHtCIBY$40Wa?E+ zE>uo>jFy7HYnK85cLvS^m>rXBOyoMR4g|S(?|MxW^|sGvP~a#Okttk|`whh)Tqc~P zQcW!anB?sf#R3SpSg(Y$;T-c(#NohYtUS>*jWKV~Ud8bWOG(lzoH0yxx0i*5Ov7q3 zEx%i#RF8|7WQ#SP>lVO4w0l=2;kqm#9NT7)-@V;k^qNHhQECm_?c-<5UcRfc>o|FH zcT1Bdd&Z@Xk%mPxF;DyQSp{{*`iUIUrviv1!$>jWv4Go(dY0H2_CP zLyhE8!=~p8>v)X^X&mI2<`~J84oghzcfR5}l&oO20O1CPnPwe7e`S`>in5KOHQH&p z9AZ@9zaIwOtd7Se*nG4^fZs=$BhKJkAKyd?pXhILJowz_g8Y_BlxU78e?idq{#=ol zR{%m6J-JY`f!gXkRYU7fBYz=tan(&-FPRy#e{S8TJbg^Gl!cY&Fqh*7t|CR|@zH3}k`m140?8OnB31*WwO%kj&oPJFdUGMm+4l0uVEu|? zPcB>;VX{WyAqajh3$szu@g7v{8Y-ve@vwKI`cB&hgxZgQ=92yo6cGV+sammZ8_P$i z!DU770mo2$T3 z@MH+t{&*qXtqn0@dT+P$pTZT@Gg4{r6w`3G1Z4r2PEGMu+g9q{=+ zwAxZ4ZHJqmRhGZ1)~o#eyEBJL0OE(~{RElih^7rwOcip6>$WuEgnhcdGUKu9?|{$0 z4C41En+-!~fXg3A?>p8*&MYq!H}b+@%iu+9QT=XE38slfljmxeT%qK${)J**iq&6d z8*ue#F(oZ6>H1_%LMR`XkYMmodt`m+)5SnjrnOo&=b;@nKW(s6X@qZ%74{AGuwH-Y zmZmGS>Qk18!_WrLPy73xXjc);gb;bsbgD$ZD`^>_X!1cn>;@+kgqRB` zaK!r-tfD_y27EuP^>Ii_r}SaY5hOoU7Oc@49Jb-MOe-tBIx*iDiv&3yM+fGEX|*dh zn5UB;Zs=x5fU7#|S8D<4bNxvTz;MR>j*zfCDJLSvpSx3dCY}1IH=?aXGrjaxh52m$ ztAu(I>zn=4+v1O|bsGlxP315d8UOlfw-cUf!A7~lX#F<_Aw?_5(75-74^tis`Nmop z7?Ag0W&Y9smibew__!ub|3gKrEW^5lp}FMzyyVsy#N;yLP1nMejofUSVGJcn0EdH2 zpvy=qbh3)Z5ox_C>pjG5f2u}jGh0V6;&qYohtc$mgnoYJ!6e-y9g~)oWb4<~cRb@a zQ4GzdKn)S(Zoc*0CBvw|BEi@hcI_&f%=+~U?|{$Nk2;n{*pp86R|ry}^XSnwKA$Js&eP195>;Q!YB4`Lcc&Nrwahr^8DQME5m9xEK$m+Zb9qLK!)jwUrApPftmr3u zV)lko=|cIOqG(*Q#;I92!|pg9$y?%;(~etM2JlR8L|6M3*@gU*i^3R~<6aZm>O7Jh z*Pn2h8?&)NS0tOllx-Zu)qMjj50*HT2gNWN?E7ORR~MD7O^(UsyJ`)LNq_#7<(!kW zVKQATb3_NYN~2TgpOI?2FcqT;u34-anoLPwhJO6_kGk!Vj0Tb(HHx#_r68-6f-gr+ zECGn%?>c5vetX&;8^|996LrB8H+2-x1Sh4QNz=#f#^cAlJYICOk?JXZ`XDxy+6$FzD~;3&WY)%K@1KWRU#V#>+O_0*3W zpFTC7{P9Cc>55$If*S9~YrSG%NEjA>I?4tqs)(ruQqH(Oqd-0)UXps#ta$OowM?jd z{1|dFYiEGy<#9DEz2LVd3N;=#ih2$J6;6gvBE|8Fw7h7v6}X5;Jm4RtzO$XviSQ{GS28Q&=+_UsloF@Ig_ z`@DtOeD!E&SX1p>In?wZzI1VVf|HRnLF;_#8iRfET0#TH0BX60Q;>Bppk-=qKRw$s zCjgFxrF2{us;Gf8{yxR3LEhjpLrm_V(TF5#Raz2>m8bJyZ;5t1rya6P_yu5xx>CWL ze`szLD35;fuQzNmlvW`bX>8B*y@7N~@D&nvdKJ%)`*_>`+WhP1Otx!JiYTv7^15E=$3w zG8+Y^{`rMULUs4%nb-y_5WsO^5&H)fG7;xSx8m5fkpG@?o|=x^Xgc8o z#wg}p4~l!Z48}pS>SdONWitU`-%PY|eoOXpPSin4ZJk;4%#LOk=uqAJ;>G^@O0<1{ z8|JK*xyT!z9d7)n-YOhUEFM>=!L2~_DvzUiX2il7mM3~}IKNSBDG7D=W+)?be@niN z@OWIMq6$F!ZZEzmtvQS5utckB_jDVs(JE~uqWd7`{nmNaWUtkGaSsPM#K~=FtMtL@ zNMm%>Fq}PnPrQ5gGs&OKabKvu29EDKq7wuUxvj`dpI!zm0QEe<7zo49t#KWcMQs-& zTklAkenwBPe$+QHYG)j~ZSVv6Le3Cf%qe^i((k}#)JL}7J&1Boh%Dva4KVJEvxaip z2s8N*p|cM4)bf5i`L--hIbU!Q-v-Z7%0;j zP0$(+nl8n2oOXuhFK!?8^*b$VBxz6pV#I_~*#ZPzh8*F~%+kAk z8<)d=QL|Zb*PLUJU)zXx(0YrI;d_dOHkvA>D}ncOv=NbS*@24l;R|$F1Z+j)KQl*4 zQQWy0`+e>b!7dEr`sa=6Fi5_Kmq8%FpDaKa!MUIn3zo{m*ZDJ1h?ff%_e|v{7I?|Y zR<;3$l%ui;TSM;Y%%lrp+~01?em%PH(5cxJl>cFCU5`i5ka%kz8LC(BenFN zOO|su6PBSmp3&=V?uLeb_-T zMCrweL*-rw6r9i&hCK#Q``!4Rqkq#V>iQq6Qp5B`Zcgn9lm}36M5f`SmDt09ccPA^ zq*Y)|0{jG=w#67?VYBh?kZ-!)MRLJ(74~8=1t+5)Du3cdlP~UVJ+U`BX@#F_c0do^ zP6|izTNoe&fSBm7a7D$%xP*kkD<4}NHrRK>ioer}>!VI)tor>dBDxm|#Bn(Bek)_H ztg9Pzv{+l^A@n0qhDN~3KD6xp?fb5d$d}pdFB3R8OECeeh3_n;7Z}tn!DUEg_xytF z%+m`xsKe4c9{WIBJ*LZzJ-kDyGWa>f>j_v z@Axl~XtF@}*Om1^9!la@5GF$MCrbw6$j$XE0;{;65hD3yf(PYFefZ$w)6RMXB#&Ju z`Eg#}k09q^T+Z?dpNChYdym;FF2;NFD{N0* zSC=JjSUe5$I0!XClCAc4PkXb4KNK$KPFcL;_~GWgmd-1wz(!S6!^Q0saDZW^XrN!( z1!5c5=KjJj`&Johp!lUaq`_W>zR%rxzk0=~z6%5ZZFh0!HUFW8KSpyy0FQdroGJ#( zSF(O0UY5lN%{vhSOCPP<^jCP~zIXdBrL5kC4QadjJ;(L|Rli=PnD;z0#qtfjx;#C@ zht#0UOKyIUB9M9r#}EkX;7|!9K1o=Y6w5@8#S!Il(33Zi_2FAbbGG2wBc z2!Q!PZN^+o;ezWdsa2>co;OV``6kfj;>P~59yR8Qmx&F8e8IlcUZlA(S82@ec8ha} zg&Y!@9#U|d9+O?fRgMi{aK#^a9Nq?nY+pJa3!|P!$M#YYV>Y-sN3c=q*V+B0Pe8uC zc^B-{w`aU%UIRIy4R(buDaXADAb|Rr0K_=RzCNgvWhsG+M4}7TIoVbDr+3J{y{&1Y zyfb&_W>s)9k!^5JCyi7VKfIszaDMUn3aA7R6F(}A(&Z`A`bkET!wCTZ+(ap;xQ4@Q zB!qMi;}&S*_@+l_%&^310>mdLXwUk*dE}y0E69ZK=4iyE{1{34M*eo^Kfd_CpPDyd zjBZ%8T3j|T8y%>*olJD)M3IEJGzW{?f(G7A3Q8xl16lh)pGJ>Rm*i~lA*61`Dxb~t zLfA+dNeY}yJ@5hrj>iR`lIwaxxR(CHsp(;*3X#-QasOVjqe#G(-+60-?w>gMd$z>G zNs07IzOcNPnG{hAgzQant!yDGHk}rQr`h*7hOYlQ*j_=rcf|h7$%L7r)_NSSR3icS zJ87Xs(p!gDC7S3(EU#5mJYePX;1)jY_*5dlFpLFVqq4r^L zPB1a#{a;*vXe-KLuI#{kF#$JLo}H(aDxMXUIi2k9vxVJov8Lc$ zZ?iy9i&{6T)8Z;#OB~!p9A|V+e#iLCJ6=`E7i{auV@ z;NUVjVi&gY&0+~aM>Oj9EmS;s(Uc&*>5ukgBk@$dmoexL4^ul5jxGT|=tv5A2+W;9Oz(F7Ls?fqaEj)#B6|5?njN|o$17vN z*Wfv0gC@lF>`!DWvYj_PA~<78 z`|MK#%W|wH1JF)$H<)T9l^`)mg<^wMNA9CUpE0_vN6B#(=waQSltX!0gbv zLB5JLz|{qcW2a2NT$)a46spt67Y9nL4cZazY8<&eH2Df*v&2}6O6137t!zt?i6>X0 zV$4`G@$v$i`#P+i3(w+@`#f;`>XJ|jAp{>0R{Mt8{x!+f$;H&60m&0ogqT6bvE6)) zjUNi0hzoD%j=u5~85xv2x|E4z5&pwqVD0FPa51Ld(TND1Ot5V$#WW`?2E z-s?S~qyNs(PN&XEzYh=j0vkr~eWCu#Um^Ejs->rju4A298Y2PY@?asAEDFD_uU>2s-oh)`Ld|r+KJ1V-wjL zKs7y{|1eBx#7oei)Ng)Y+f9i_%%A-FHw!;LUYt3Wmh*KH>T$g;m<`a)hCIJ#nj|b6 z-FraRo_M+Lj%I`pR!9PlOs)JI@^{xN{**w^H?&NPDBu?m@#BZ!(9tAkgnETB#HDQn zc+Xqg3at(X$n#VT;$*PkHuECvm%Q1(;pqfi+QWc1qyMsT)<4oGGTuSlnj0Xw&bBM} z3J-Q=<6O8(!#W#Rc%Ja!R?A1{>mR5t%~6qQ@ZvwjvTJl?_dP4y)2`b`>WW{G?gOYx z=4x!vea%^;1G^R-|HC?$`RPZ3W+RTi^eHO2l>-J$MN!rjBn;UK<8t=ivu3I!D@Hb6 ztc~7q>0Ac9`+E-pV!|$K3+I5{YPkd@qM!7{Z<%!@|<**RJ*cAjd(1bm2V#P_mrpVi_j>k_`wD2b)|`1%;P9 zA5pT~8SIR$fZT5K@>NtcJ9|}_^sp$3=fjPmHypBx8HXKXQamrV{J(s$Rk?muINw>3 zBsmp|_6g?Nz?D`>e)9yhBdR$ZWX*y}!H+ zyZ&M65W_^q)1~1vhCMVe%`Xb!~m&r^78A4xaq1apn z_!)5_Qyq!Wq?!Xz83^8dV5F-^ZVKBZa}Uz+B-wIupAIuLW=%Pt(DCt8N> zrkE!mZ1rU|1W~hEng3wU85^ee1QS{@`SEDrU5FW6GoTkI zhAqS`Tki*Qodx)ayV=^r$}fv7r`t1UTh|ue-t>OlmhH~0EobmiiL3Z?F@ID}aw(cC z42%mRKZso*phKn{Nde#nG3~dfM}In&E1>py3TtLN+qmtQ|bej>sx zY*k}RRG^p99d!OkluY}6^}x#gb**w*@xNOO4ghO`R;daS?iS;@{qe4dl$6XXW&u=u zhW7@(yd>cT1tnq@7R=l#WS4u3#QP{b{Q(=*P3f-^2ThbwI774X74!Qi>NyH%Vp z=SKa8*Zky0(k2|Iylb%>i!p!uf0TOyBp6>cJ1_)ltrvpAemMd@g;%bZ1O*$Pw)0Mk zo}e4`fOpbGqp0=Hx8T;2$I_)K%&Cs}Tn^Ld{!%veGj`7%7iG!>DntLq=ZGNroLH`W z)6TqvWy~&_%%VoPpQ@{TXBw#Qz%mVh;Z^P0tfbDVyf&9l3u>P-ftK`-LaMNiM zf$$OUjSBP0zA9Ep_DMT;UbReH`wy7Un^{({{*SSs{(sQ?kB#VuV-nn);Op`i3Zh~{ z-X>6DB$+=fJkne0993qAH3?DzG( zDV?A@!eT*VSw)mTS{Deb!c=tx`53bWOVejgDMKfvrTHBm`B}f9hiqcV{`Nbo_~y*= zv_lH)64&K7JBnG>SI~mnXBO?^*Irn7KawMZZt0{z3p-V%bx4t$1XEY?l2?UrZA$_R zS&7tpjRJBFTj`lQhx?I4Rj6i4l#<>yAKKMo6xccF3B8dpZ0k;d7I1wvqcsX$dWSl; z!Dq&7q%9>+J+^&BgUjdZmA-FeZX9P_kArTB11&8}?6_YD+k{N>? zw=~32%4VF3^zRoT6J@vnGj>Qoeu%LT2qH>VL-e-3B-BPc*Pi`D{=Hs=fe8rzA`k7ytncfg`CxJcFP#N;ZZI6rNaK zK}AcekIpPw)>#5a8fY4_v_6p$pphN|Ws@pAN&t#Vu8<}ifDZt?Uq{S_tRZJI4q<6e zUg8I+Bq7yEV#VNWZ--(ZJ=?>kx4PE@d)q{H%s0l?7qM<#CjaKU<+4#6*vBaqy&&F%?po%90NhOOm8S$y# zvVP!ef1rd@TuTmk6Q?YT$Esb=#mS7N#`*oRRWWV8;vWUSs8WM^Rvp2&dW3I7ibLN$}k+Kff7D{RqKD2E$T?83+Q7oGfo9Q?cPaVJ1N* z3SMX|;TtNEb=BP5pAvbh9Okt7p9BvX{hKWu=RhtLqCTlb1#12vBnZj^O1F(k`ycVZ zEY;TrH4D0h3x9$nk@%&Mo3eC1szx>oyGOHZmQX4k7Yt^FAE0yIKBfkyqqS|7WW5Xc zFh4%_yLr}oPrkIXr8ly?CH7nX=2M+Y<+IKU1&{YV4TJnnB@y=Yh(+i(Nt?634V%}8 zi1a^zxZ>+y2xmeKJF_S|yd!=_+&2BB?U6u|jr1#v4@cGB#=28}iO|kqPaJKzK{~^=lM30J+}{4Q83kIvNDY zq%OCihJ+xIe2__)F}s}O=p>zp1PY0&%VZY938xn5Hyavc~;`w|rO;^e~dh zm?08{e#=II?+IuaZ;k7^Y^$;>s!7}*u(MaJrKHs9?y>sC<=Z&ysW15$C7k!*lfPU# zc&k0=tgCA$AA+@f#qe*(lhD4hGjscYX10w~TN}64hEge96~CiqqjU+Oc>?j%cH>KtGn8Ivw{ZWmzJ_opZn2gOXT#Kaajh>DlmvBMEP!Av!nm6Ho~F&1QIG zXqc3C3fbP?UAuueWcj4In)7@*xHqNxXm=sSO+3E1fuI<{V}(mus-s09Wx`T^VT& zO5F!{Ugd#6Tcx*9%Y8Xz=tOf&AY*DaI4d$&(l8LWScw3UZtxAfCoRwGSD7Md<4J*O zBg!#R9sjZ$wJ3kK5hO$kl-d+N01t{HtT029OT}k8&JCN zyuk!y7J69EnHg?!N$W1pjUt^cm^^Hb-?uTyh8*=!nXG#Can0uO z=}6QFP}Tzq->?Q(VZX}~AMNW?xT#r9TGh$afH+;ZFR{3@%g(aU4#k&_hWfq82b|Jx zXN8l+Z7)2QRz;_$lk8C8xvXKK1g&uN-gXLZ1pEH4T>LXz2Crr#TXD}R0LV7!V(SOe zl8EvkpuXLlt-Q49=pK3x+P>Z+HLI@zfjf4$3YGW;U2-9fU1>Bw@GCES+r)FOpPXiJ zZI}p`Jgum{@Id+t@gspmfSc=%*a`$g()9Fcbb+&BHekA+G!SfkARaxinzSqm@`en5 zgnvAm9d`lpd(_2O{)4CjB_59Qq(I~!^7)=$=6dQGavq;XVvV88MJ{ zSQEErv1QH9kGUVTbiu^%PBPU=6SJbxl9eLKfsyuf@9XWaVX&wcu!AK414TrCznqDg znQ^-xY?&Z9yPrbHB_Hrpz#uRp^lN|TCH{k(rOcH1Q(gX*0XK>LrMZYAr6|(39=Ko$ ztm2+91(`@miPg2Y0t6KM^?D|+*M))bF3gcrF+Jhs(v7{HzRx;=MJF?VI``YU+>YNX ziyD1ZE0dWhNx}8J(-6PH?o&KA*!jUH+S!_>W53<4VxC>o58}WAbTZASZ8uq39(Lhr z+@%)i#PVpB8_*Yn%OGYD0FQ(^%7h2YA3ZQ%X7GCTEeS`l`j*Q2rO8);Sg<~eML^>| zuZ(nQ?2#>T$PH2mZqCs#VoguS1o(~O)2>%!%>`kj?+1!#ejI=OXeebl_9VG{Hg1IP zV~&FH{f)e`p-OfmvtIILE7vULhynEauH1&*c(_(;E&+9aiR0>{ZaP;v7rlWoZvUP6 zis7YXQqJ;8!-NEUM0Y)Oz8~L${}0d#@ejDqRPp+dKZT~R zDw^|;XDtEQJNF3}pLXIuW$))EGFnTwha*pJ$xYXNR|U8083jw1 zEu(pbc;BNvCT-->eO;qmcia}N0fgWO27Bv`0EG)V1s0SAGg-nv(6e?eV#g=&-DFu8 zkho(-w!YgT)cmsT)W6TiWM1KPJmt`Q^PBT;5`Y#NujqTrxaogWPeHL`Qrp_}R+TVnGm(R7L*L!Q_){2#UMDUE$<{4hk}u?XQ`-3+R9R z#@FHeH&=det#M!sq>il}$cdTht-LN7)*1MNh3HQUC#QFWwZFR#?+?~Pgyag}RuMT$ zun!zzOY=>wT>zZOXXE^w{Fs}ae=mFl6<|*QAV_VH+DL$FvqGNMwl|gX^s~O1m}Hv& zOv^uUZ^%sW@oxtYv^g5gT~=8pShXv6f;5q^g#}9>lxe#+>ysea2Rf&7^BTc|d9|y! zu+sUYZF_#hw z(WfPlW%@bfX`!{`wj{xpQKdUH#7cMhzS2)CX$7LqZOj_vd7maP$=${qoP_q?8|(x# z9!suVQ{m9(={sAQ*Uf+Y&6B5!3NbO6WW;I$X%-XCzBr1H8T9Shw_Edee6a#XEIJXz z$rA$ZrWDKb+sXWdGfaG0h~UJtcj?yeOS*9R%GCyjhb~ z28#)i0ob*_x?+zdIWSo!es!QTPweN0lEeInzdAg`Y{ z7Nhp-x}YGp@HA)o%Se=|2VW{S1g#LIzjvn`8qS>ind?o?>Cn6FpfkRr-wF=)Cs?|? zcl((gk!?KV)+mvX{x>h=lb4(UG*#%Gpoh)DW?>0C!j-U=PZBKXhB}iA2%;fL^Zqc` zi#CxTyT6j ztL&FmYar=T6EiazTn>8EcNa3&c&^R~i~l{x-p=J_nFLFWA>%_K+q`IT>1}TB4BMYI zgTAIX1dG~D-`|$&>NCW5Q%h=rD)2wK`jnI$cik2X80+yZ3usG+*X`GV%(Q)0Ugi6m z!#VR^_1~FgDrOLXkg1B>-1rYx5xr8sC^E27ls8$#M1@12q81g^?5!~Ap&Z(Pshn}cG`~;>z71R z*}*bnbtUbrg=)Rl5to*VE()*%#})`QsKxfFap_sUCgqh~hh1PAXEt56!?;NLf5l?2 zw0Q=4gP*%A1AU=38kq305P6-rVpViS9$&hCa~zzTk%^RY`->KKFJp03B}3bQ6Rw|9 zu@w4@jI!L(US~A}U6gUm${@A^9x)gO|R zZipR#YWB2v>(|$EYv#l5*%-s7e~E;BMSmDRKQrHhU|(CQ0BZ$4uqW=|o@ZdK*0*ZP zh!XW`_8#$h{4?vTz8w7Co@~&`cYL9=5ifq<$At(DT#1oB4NRkwO7`2FK)a7)WSKzr z_NeaYo8<|dnTP7ZWeK`GoDez#}k<5OeDs zi5N6<7#k+sQK|=nM2|$CnvrZv0m3M%7lWw@M<8!Q7Dqd*6 zh0lGvi?hW?zAyONJr>yRoc}k#-M?Fc8i+mb9T8l+j_*`VjQ|Cw@k>q~k)_V1`2}8SEEcQ5pozrM#dm#*so`lk#&^Ga zCTIC(fjh%kq94ts(C;WkGUT|a4XO=xJIKBq+*Vd!HmlfIKe@~Nj zkf6>!DaGg^V(e;<{r?lbfzMI_Vo4m?#A#H=810CHyH1C66~0eik82x8o-;dv`Y$RF zV$Kc=ItEE+S7wdcY;eJKMn%CUh!7!sGWn;W&Vm^n6aadEe^ zOAgh!Tq*fQb$zeKn{>Mvp$2-^?~J-@Ro=^-^={kz_f11m-xR$u-@`pVttEA>SKUYk z^8PYY$W=_Fd`dGT;eBZ_6;{nt{H99S`eHOP(c4K+F>bc0zD51r!Ru81uYBxKjuC9-zxfbDtb}T)c)6Ww*Ng>#AtDTR#a_1 z+M9vi>pG8hATY=@Dt5X(J*N>jued8T_3_W@9d^=03B$z@x_Drvgl-_e$kcVT74$ZH zUZCMaUAl6m%XSxkVtpxDx}P_C>hi2J_+lCIe%{eX1^N2u79Tx7QbJ4&CK*8QSJ0v zLg9K^xvcSLzXB(`$w0bJ3!@DR8U}{O=Bd|>%nlk(#R7>*P~NdM$+tE1$~y%up{V;V$96` zchP`+aOySO&}L)VgOu&1uRG3Zh(U_uuo&KEf~7!)23l zhBi4xxWu8`&$p+~pvdDz)V|-MB%M~Ic3Ka}NJit%#PnVSt|jK?zk_J>)XAmWJO*Cu^~Ut=4AvrFoOdElMlnXoZHdkf94iCCJKnDs>|d z63qO*qk{{oUUU;ZqB~DVVOv8*k!<2tF$y1*wbN~Klle-NoKmDUNYKss3dnWXb|Gb^CRU!6?;XPtd$%fgwPpiBj@xXd{bkRTS&9#t{LBmGjBW?ZYEi zs;~nI+tb3@*%Mfvnq1UZufT9MnV(Q9kSI;L&H~)*{e)-tdH>?jODz|{R#za3XgbVr z;n#41ZVoJ4ny|kMgo49m#B$1V3ayM~$MCq@P`=`>OqcMzer3_Hj)bP_+|`-tGO2kMR2Ug6&!-mvTPdt;C%yoBuyzNn^jb5uF5 z&~0tvV%aoePRwSE5`9>_=?)=u($FzUsR;X;&4JSw&tER3`)GBf%5Awk{Wia=kyq+1 zfmu95?~|Y*layTAuiL1%DGZQ-R3o39Bog5nU^W5{DV*JEb4Xnx@k*;t-m&oBgqc`! zwZ{$}I%+pJu*E=pvxL#2NlD=X?w2@JWZx`qOa6Nc{&SmA%OiBU9O_aWm!NaH9GWYp z1)Z^jz$(ee`qfS*!Jv=3t*!@T$*zYv-EenjUeBvJBpmLzNUqPQBV1aUo|7jrB5Im7 zp{unI2Jc<>pi!Txl=I(#YyPy_@0*9h8_ne+{^qHCoJGYk3x0I>^lhII#V%}p)NP~C zJmNRiUP_Sm!IhPgO5*%Q%zM~WKy*N?xwZ_( z`0?wN)=B^guRBfer43Zlvo>qjbxVCU54($zT_R(qCJu(~Jq4$UrbOIkgZtGwX(VVa zB4fctvaJ>pZ%>Vdx0g}~P zF55@NLM@yANWyi%w`EH@RY{|o{~=0*Hb2w-$_R;}skTII{dalWiK;O(8@5AhXo}nd zYiBeHDI1qow%GXh0=3s*B0IqXgtxc>*49T;0#e*}g%BYFWC`QiXVloRSA23l18hr_w+z-C>K70S@ zek(XaAsRFi@j7cpsWd)Ec6&G>r*69P6P1+u`xKEJM`w$pm+$CGOLC5i`ubEQbB>o> zTB_y&y0O>7@wc?uD;>)^Q+rJq6gk^XDBxGsU!C6HYi7SU#UQ&W#=DS`OXY3Ee%7th z2GN$)`5T-0zPoT|MU56CK=r!34$we5K#BVPkQjs(|7fPKO1a#IDXPC}(9qMG+qz5f z0Avzj>{AML7f-4E48Xs~`Zf=NG~|o;xKVxlwe^;JdShIYHI;`{QkS7~fx*YGXFVeJRf4Sg|#g%+4s&;TYL8 zNyk*Vr;Y7rTYj^Yg}k>;Tbgp(Rc(!NEOpST3k)l!wB)+UcqoURJAeR-tZ}Etg6Rf6e^<`h%eJAs61&@6FEh zH?lY+B%w5Ez7nHrvD6!{j9;bF>3-~g1%Vu&griOGzjlq`PYoyLHKi%DNt>v5JdSgO z3<$;i@)~i|6?B3N4omF)?H(Hs!)JDd#gi|Ud3tl9ww~!w_?f!}kHy4`Uh6|C-p9B_ zIRtjpzU|A17CevcH*}`M*?X2NrHRD`6W31m}IK0EbdtIQ@1ebv-M zHX81T!snG}3sTULLs#$*~ryjg8I;s`ChL0N?jIbWi zKK|5X|M43YF#?R7(VJ{x@-L$Jjo-*L}&z)uib&k#^B`sWR zX_|L+QN7cFV93>RIEUKKE30dY*Qxs#`AP{QmllLJd@ATga)x6_9geaGK%GzeucUEX$L45AK2+eAQ-sApwb#Z+)xYHkZ zC2GjZ+1FF&4J78);Tn4WpZdlRq4O0^QN_lu2CGPQEaucVtg$s*Tzm@Amn30tjzfmW zE4{X|^9GN~)ti{li7;CE?H=JjcB%+pxtXJ=(67~Foy>>uE+Z!`R~i4e7NGd8Hr}5J$O_(65}HVzSbc3POJHJLz`o(+%6aMf>G_Ge$aR4JuXt} z(5l=&b$RNoM7Y-S2~Mht#14v&4PRm$U;1m+j$fD(qgfNa{ZYN>OE6!m1;fF^SC~FI z)`+nO1c!qDA6s7;RmZk;n;^km6WoJaaCe8`Zh=5>cemi~ZVB$eHMj96~DtweKj>Odp}u7Y~0Ik<0SJ-_v9&DV3|i zJk;t9h-1vt8KP7&(Fn+H8O-)&I_tDKNt`vL+Th8(+^SNR ze)o&(eVw`>LjTryU3w#?5L|Gb6G#;I&TSuy)=12O25sSym{UW2flzb!iLACvq=650 zz|QXF44OU#n&jc^^cI@B7`@)+64iF&F>~*HaC;=0 z*C0N?1c!l!% zw1bIn2#!hYkrDX!P(W5FJoT=Se|&E$6b< zVxa2{zcWw9Y|s_`Zmi*x9alj5?6<+jf4!jTQw)DW$8eHGy0-KyV*uHxsI3`)!k*qG z&8wCaE%QR)Zz*kw&|UbyRk-wU%Evv8YRBR-;qQ%^7L1QRTKP7bNy*9CIzKX}JYxt9 z&r`a0cx++6I#?7f5E0;JdHgFfI9yaZnJ)N3DeASAD3N0t4%=dPjvuNGly!kYXbR=rH8j~%!VJ+B zhU?p<%801y9}-86cr=t$vs(>qggeH9al{unh~2e#p33j)B;o}n!+>@_W3~A-d$Z@Q zbU<0Gh!9Hr*#>=ymJAApiC_V!Y8947fz2~Tiz?T7NNSDCiInLtfg1?DRcNf+JL}?IN4N=(gnNm;wL19 z8DLl_K1JC?LrT6a-n&fkoK7#@Pc%62MwCpVQfc%Zs3SSgv4|w1T3l)qyV4&{Nw@Ax z5($=r%MVup8}z=qRCZ>Cc`!EpRHVg5Y#VkkCKwB4=E;62 zWrlS=@wnLQ2JAK2Z#ftErfsFoJ4_4xB3Wm$8+ygv>^8B&iCPyQ)&QL!!9g=whMHIA z(%TyC=?jv&!%FQ9S(nAE-fTs!3UYS6y{9DGA@3%JyR~gY7myni!bE{;ScZ}N^_uK@ zdWJrOGW$|0bdq+aA!upL!NY%xG>H1O96oQ9nZ&&ADrf}jqQ}f55Z=3Q?7TJ5!_mj zvCcWelS4b(bl2aUi*efs&5xXVP-!I@uqoH!?!FlFP;OFDSi6oayw=vm ztUj17VajOix4GSKHonO)f!n}=_y)W>FNvP-X9L{g5&jV3>I7&Gc+sjlx-M7j&pbft@VsGpSZ zyTW01$D8w5`x}n=MG^fl}O?)oa zRZzZK^sNHTU?KwJPt+6u@Vc0Mpabx5n$Rm3{b!4px*!I*u#!iKu7-c6uz*FQIFeGj zF~Pxgu6ffyxVW{b3I?KMEI+p6Z7xN16H%fvNnqkvi&(=jlZT#G5fb(enyq*9x)3X; zK{T9Uz_YSfRZxKPBoc{cI}$dgqD7$k6t}8x_wcg)q`Iiqne4L9 z*RNJBJ6#&H#W1?VIa(1H6`1KlxQeTxX$^D-Q<*2Uh=xS65i}f5kUAahXptZgwvJl& z4-FEn5+|3%vpY95MG?9C^7%7fVRw`)0(DNk0hv9{y%dAWyx_*lsa?FE2rgq4G}RC- zZE2kN`OKJRQRLZVU62;)G!T1&k{j(~iTtD3HM?34Y=CeF5l-jfNc zidPC@uE^od<#%NE6G!?VqZx+iDE_ucrTu%h2m6Ei5)k}5%;{l1xA2>G3L}~8C zzZnx#_G~V$KYGzRZ3|*ti&57WDjJz121^{%+>FJvw2N9js(j&F`hX8w3L%9)9QFN( zD2yI0*R?9q_HYKf|2$ufe5mK~QY-5QX9~kNIABAT_e~qphcTnL7tTVTK63m0$#C^i z3S0PE4TYuKBA=tAqi9GVYPHJ0KP<67ofu1`h@`+o96&Q>%OS zKs58LOAe}|@Mxh<(`x`N4$pExjJTVlxzEB@cv6iaLYWRHQeu0He5Is=;ZO_)jtXsO+UEYs@qXy*-}8m6 z)yLCNSR)x+A4j(QHK34U&eg9m=Nf)@j6T~(s#0x8#5tc;E-l}tRsHlb|WPk6y?r$^B-J| zY@FEc!->12`2V;01IE7pubxH_tItUQo+fpWnHaT3NIJy%{G_1^7A)6T2Ch5@15-yt zWibhrgP5Z(%4frN-r5ngxvMWAlkC|XS1t2n`s^9QX!%D$OcX0&{&CEA-H@B7JNU5Z zl);1BvQ)iM+^f`r0W_^a#Uj*iu$J@LHfsD&V3W56Fe|Ne+x!8z`|B-4Bh4QEVv7h2 zGI3S#4#jOGl?rGPk32sXS*&L@b7NNzW!`p+_GB)nf`&Z!gxF&|12)0ajk~!Rlck%7 zyo}^fVvD&KDf>%*RlD$yC=U!);*z&;p>^fj3dCuSh=uv$!Ay)*-t@ky^=}Q~Nvk9L zSdD4pi#AaR*L#{oBgW|Wp7hNVW!$ABr6{OEcHLq@F- znkB?^200i>n93mU#B#k#Jxjh)0%GA5iXR_|NCF*AP&d29Y&3>aKP(bzg^ZGO6bg@O zwYf*2f{RqdfAo&&6vrGU+xRBc=4h&~g*i{APqg@@K%29qw+#FOSDg1QN)fzNJ zOeNJyi_%J#uQZa4wxU#=4eLYMP)Ayl7j!#DS@n}L{DsUuBP-VatjImoAiU0x{qIST%%8~(ANq-qg=LzJP<`r1e3o9AlOL}VXS zbJ$Pz5{7;HL)oatng-=$*&RpN1ZP5n_G40|J0wj-dJGr7Vo<>+F2<&)(1x{*@N^Gi z$xVH7E6vm>hJ4chosuJ8Ot+^)tUoDyg92sl>{3g5@ts?Z@OGXqgEw!UYIVH}ftolI zdRd5k$WQ3&Pv&16=qmFiX%+%E1pI)! z8md*VTeyM>Q>@{%j+H;X&X2b?=VL1i54OW3QN@2Sp1NMbNU@zmr7Jd<0cBRX!0k4 zt0%ZySoE)oml_3HrAGB-g#|;UeMi)pC~KZN^)>`)ZgujR-Nds}(Pnkpk=c8%VczCb zK`KxOnrt03#I20C>|PH@(CCAGzVFhf$y^{Fm0v3>rmOju%j;MRf9aG^P zQ*62JL)~}VW;N-DNqi*{!GYplaupK`nW8={;1e0q_qk1mBXbE7b3aeV{ zMg9~A;yAJdvk`Q(`(KlDKa|b)pZ}{<7sXGC%cXLcWcKxp@{OjTvjrF|m(7RXb-xM{ zK_Bkmv>qZM+)GZ|{4rea=!I0bX#Pp)z8ug3Te;w`WaI=wikX21#Um>Ch#*;ojbH~G zPZJlrjmvZ#)wrO143t@6;TU9#o-)6Jnw$M&T1ysn=c7rh3*+!;NU?@Mcw8Q9+?1T|-g6DeP56Sa6RngmC?9TFjUy9j3AV9q0nN*1=2zm%x0Ny9rSz zwy0wmfQKu>9mIZhQYAtp<2LDTro@;TJri33F;qc$;Yta z9mr|k*oht?g{gGd>2V|DgaI8=92J`BV%{~uW9O!M#{|mQ%1d~rRh*f(cwXYNwElvq zQ$!N<5a);Z-o#dhEyyi8c@?!#uEie6CIq$npqM={u#)SrfZ> zWRlP4cT?<6TckbGw-^S2S0Wki%UL2II@5&}e7;=I^@M?264}gdvnO9|DmRR+!=?08 zY*xn*+jgx6XMC*TjE-r(X-YA<@J58(5TbdInafcab=3aH!C&NdB1aCW5lTnL#9cuQ zB$;sYm*j$x^+qB)9}sD&X7=wXNf0=0!UAwOGKYS=`FxJO_0^vTkF6lqHoadf;o&py zKqtUe!7!eW_TaMK9fH|J*5Jm4zITPMi%15{qzLu1tl)F9@Rw$6X6E_6J=XS}=8GH-raW>c%d_sDwTcGW;>4QfoIV+Hc&a zk<_h+G%E@QYS5>;#mbQ~kn6f;If|z`>itVnF9!+lZB}4OwUYgD;#{qRHN1(X2CPMbw$t{*e5kq{;Rm zEEUvb8he6PH!Ww;NR_B{tWxta-v%e1k5+LyjVdf=BP#}S4r6!}r zqWJ9me70trJ_935d!-QQW9=tpH4Sxvd^o`$^Rn11`fp${sIz6sJ`J_?M!v5!K8`5e zyX=EuCUh81X2Kj~HSfHnPE_h+v4Jq@#a(QY3JB$LIAD-WEdJIp8iB+63ZMmKEmts( z380i3kh*6sDsH5pCg%8G?9bQ3*5WtiE>_vn4ziFQ(a|auBxuTZJybQ6#MKiK2L{F9 zd6jsB&n!{Ns~n-NZN%-XhLGcDxMLUido>s<4QCDzq6c>>`CBIY5vQTkT&3H(^ml?W z8Iw)!b`<8?tc~Kw72f;7B2na6X#x3_jZE4$Kn?mc8w;ZI`52I1smWEtEN66-)-g;K z81HZ<7lHSyFxLB#MgM*6j%-)~?)Huv!0PHf15r<#`Zh!Q(Tj>+fOAl7ueLtFIUX4e zf|UCc0BI{hXGMvm!NjO(Epp3iyEl98mcWu!uV0X0)b8B0w<$-&0fY^~+>qO;*hADc z@w1WYGIfRE_q~V*Dza!c0uq+wD14+FdRmw$5-A?KUC0@7Ty?cSsrBzR3SIlhh()yU z4&4x${qM&8GceQdiVjgDOs0Gbslqb-6Jkf5x?8COGum%uwxmE17ske!T@`cYt}n*)>y|)H$;zW;541-W7wG}Z zumOcHkZuES!XyM*902w|9B+A#Qkks&7X-OKf%16-4 z%tkwkBjT$6Jm&dS1zY;wgTr<~lOtOALcMkqdT3~9tW4HKsceiNCbefqP|c;*({w>5 zAV~9~*E9Zyj9_JOR2FSt`He%4t&uRBa1o}cMazQEh+tAMGRdv2u;EyULBPNc%NJ6O zX^>8Ueyp{*l}?NB%yhUCfKjt6p<-oM5rdO%rb+OM?1?)eOuGowoP+iq7b~^Zm)|=Pq6BHl5Hh;xE7OU&x6P z?{$i#zio{q9)0`8*Kd;>9+*HcYrDf@G9whgDFt*L-&w)5O#kRclKdEdQj}s~4&Y-{ zG=;dD#fF$<(<;Ra!oL?n;%LnJl=D26V_nE*D#-OBlOIw!pK1}PD}e?)nJnv5YXPb+ z8ELj7zRhMjdo$J({#+o3nIn~e@}3sEMfJe#=QO!fsX2cqcj0Bt-PvH7yOPxB7s>zg zA*6!n2t#Svx&LUKJ}jJRPV^d+PuJ_l^Mq%A{c!oeth`VIwIXv7tR4H|n$+sR`?rbI z+fO2?8q2Gx#AKDO`Mi8kaL=4JcNwrV`IvcT`Ay;e*?gYdQX9ds9_kB{ygr zzUDOEymz7AOY9H#F)%6=kwT$1JhfYnpONfCA(FtQ%mr1;--gtt*9TH>%jt`6_o%gQ zLc?M>Dcw44$-R56&X9;~WFwC${q{R9CmtwFc*(b03A(077Lw7v!+mox%35PBZVZ=; z_`rULNAOZU1X_=nKZt?s^H$Ppptqezbe}+jA8I}2vEv-~%ftklhn%Cj2*c**RqS=8 z*|`Q8`m*7eHGFICg#jsHA z@L(G>$PYn9xn^_=v8=$;=JdRt(}T}W-O7e;GYg{8}C&R`L(psKiRn0s8c2BXc22a@fYKV?2&@P0Sltf za|bq5X&L?#A+j!@Ef&Osx!^Z48ed(V_4EjVaxWa6m9;|L$S09LgPL(00Mh#6$Wq%k z?Jmb%Hh$@jM(rOQmEu4{L-UtCZNhCqD1bZ^-_N=c*%kh7^u$O=Na%~vrZ14d;l}6T zSIM23q1P4mA?_xOpfw3Sh-G%t*@O=%u5#>`-c$iufP(dMk}ARJt3O)oUuCG;iJ|!j zbH}^op#SR5`_;Co8A&X4wP>2U5xKuufGW!i&ES85KBm#dQ`&crp)Xd}#w5!EKNGX4 zIPXZ1vuI6B8j4*IPk!h|)vKiyuXqF+Ej3Y&qfv!~(|)==s77~OGbMG#e-YLReS)C< zNeuot0a3o4g!Y!{ zbLW-f^}3MV+#4W~A(iFFZ2c9h{(jp}h4docUo=)k1cq-aN3rb-Ew7|_<@X=GZ};Lw zZYu3mh`y({paNaQ2A^?^;zDbos-Gsw9q*X6?kMX*YW3(B$jnnoks=S*_RmqKLh{50 zWgnEBKa}?6v_ivn2xA6yGgj?Hf;*p|aWt|P=Ga0{SuF`1F6Fv7pwp_W1nVo1BOUuV zS?S`OK}bL|kJMQybvVNKki%_)_Y8OJ1x#3P%?fO4#W2`oJ@VYC(_yZZw`eO&h4xO@QNM-C|k%=^-(0OoDt@vxG%`y~5afnQy z|6f=uUeFvR(xY^?C0@|<+=IXsAohNHjH6Hz<*uPnwLMVmpR^W%5sxM*Xlde7XXpk* zb5erA%^wu;44>2d#5>&jn-Pnbx*NY~{GC|2T zKIL{DQbZng_DsuaXDS?NwZU_NK77Ru);Z=;j>t%Jf$M7UledKm-x(8Zh#{w2L=OA? zOyTVVD7>-&g%^t&>#sofXEo?90v2t3@FgZpIXJH}1dxNG{G- zMq0ijUm{ik>9Q`o9kVPV zXk*e5TW!|o?*g=Rn@m3?_Q<|h{hqM8>xR<%%xpt;WuW2uC4}t_AD>KKE-R)R|!Mdej*dvN?*D%I-l%tU~mA|)*cc$ zxq&P`Pgh_MyZ%3W&tLmcE7dcp_-B`hA#Q2kaQ5ff;K&7Fr)qM+h^`IetP zSD7#RV}>*>olF0fWih_?^5P?vGRn@-B3RPSbRsdFtqd{?PUmTE;l<8aV&nDWPA=c9 zL9X1}nu4P`7zaqFN;-%(5#kLto7_x>!$z~J8H;Reaz@s!eP$@1&#>squCOrIhNDl+ ziJc{)DK11VVq66=Wz5kWiP`7AOCo@(5dmJM5^ywJFew?00PP0~0r6hRFBUOZ?kDc~ zKrb4VQ&yM`MiE0*#GlP3L1GC969^l#6r<(Z+PF(Uv>V9Dwv%lDPB-Y@1qdjAW}ZHM zXhAa53a+shM%~dZ}Nm56yOMVVcfSJEfWU&51@`Ms-(8{wm zbBip2>3-jT1bjYoG%_NGVd{^V(fs-;v^e?jInf;(-@H;S`i<@a=LwoKAxre%(2Me4 z#6j9wzxna(1^f!cgfRP=JTBiB31ZBA1OCb^2t83|n=DAaI$HP&c1sy-)~;u=L`0OOG*+K-G#U=52j3TqGT%UR9#8gkbN6n@yv4^N@sxo zKA_rtkKh)IOW>IFXY%BU%D&l(^gBf*KpEETfcF}LvP98ydi0qKn+0%TGymqoc8_Em z7QYmKKXTA$Wo$=ZB?;Gl*l5@FOy18kx^940(rq3gK6`q8F7jJp?sTu4IV|}B$Mk8E zoGzu??$5vz#a|D>$x;&Zg90&E(aYHWEwW?C*aTEakqnBm|%t26tMcW;G zDGffBlnuHXt3*?sfDQueEWuo(1sY&Qixf?ooPv&+Gs(V(H%*`Y>4CsAjkXQ_Vjm!PI=|CzLHb>0fT`Z zi1|n_v>e6nP6W_xm>tdX9k=?2(iLabw<@HgwG^#U7ux_d;@1Z|dm7z_B!k-r3^Nvi zCEw+*ZsTm_IxE|EpHEMYXqC!m(9tkUB2z`rX#TMZ;E7Lye^!Dw0n|8J2uR3mJ3zL; zYmHLhg#Z6nU=+3kxgE$Uln=+%XYal%fPHv!jqtM<$6q4*I<$XW?+sRJ*^{roUe^RU%(rI?3c#J$_AW$}=Hx@BMU0$k!LfF>)rAVY%cxy3_xSk|jT@BQ5s9_znbd#nPn*nnYGq zR8*T+yPqkMW1Is6_g&dP47{o0!{e&twhJqIQqUJGa&NbeV) z+zK>WbMByj=_3K!zd@3BU;GIpqnxiA&P9jW?gyxld9D=xwly-*D&o4ldA=v{fv@?J z?rLlzpV#i7YaGzp^h*NU{Z**!4G^FJ2$&Y}u>~VPlF9uRt9_&WA00&b!>}6;`7*_O z@lR=lf4qXT<4~BD=s*kB22zE8c0nI!aYAYR-p_V3xa3;`nJ`@BoWEPfE)(3zfswKY z8GTnhU^+|*`jg8UtwxiwYL3EhY@SeH*riOHGc?f2YHjKqI6Zc!6kh+OA0x+jCnvvE zn4cpN$}8!MFDC@d8_ggJut97|72y|>Bt-^?z@DLcLs$h%$m)-0``;0i|18qyhJU4J z^9TFRR>(QUVtNFD)3%Voss6{=mpeH?z5 z1Dop}Zgc#9C=A%0VS!c2XLo!2`@X5beP_wjg!gp+%QlnvR(C-D=QAr11G1(JX9Y>V zu<6rG;6HEpL}79Q&>c~`7%HRx6yzA8_4=7QuDxCNE!vT6Z)6iekSGSX|BDBBe(bR4 z#}-PirVbIGt-NaUMt@Y*!@qdvO=r^i>G;pJuEp_Z1tYi*o&RKZdHX7FjPrUId|~YS zXYEJN5KJEYfBRePv%!;_kgqm&pgQ6;%H_{}pBcZ}pCh2x$571#U2_jVM`*y0^4}qi zY;2M|#HV-2ev`Kmd!Z*nA2o77+zGEK0`~sW>Xw?PSGR^tRNYD)1CncKbNT$wfhSjm z9bS<5AL@wWhgp>VK{LLF7?2LZ*=NrBhXP;0kO~4TMqSP7aY%fTCuo(swLh*O)my$9 z5&PrI_`&3fSBw+zRdE-QznJ2z+5qk#+=cgVI}ZX>#}1aBBxgW#P+gLW(V*)jH}w0Z zKcJ%c7_%Op_F1ac^EBGr(jYc8tS!Pg<*)btMk38Q`a%7t%>dWWK_?x8{mW@-e@};O zCRMNA_7A>RmgqyG|G5Kj;qws&BIVo9+acpDKlLR4MJ-kSN4-x>TfhlGbi)5i!}-B< zV}b81m*9&|(?oOJW3>p4{+#y;+IQn|YTJ9-L(+F}W~2QUP}MbfQqsv|-w2c^?-}TI z8$oyHaUk17sdxToNLa4mjcb^uzv}D%tvEOx_}(tw4x3RvnOT$NGS)^+bzCUp4aygS zBHV)Jt|IU;rt|{F(d@+4(OzgKik}8Gs{$?5I#zP(uaHTrZRwTkDk4wY-S=(W;wttJ zharqcKB?IEqi}4YJRprZHDr5vpGvf>ir85(_K3T>FNZ4HWWCaT*VQ(->CyQwWtT*> z5-sVU&I{P^8w#@*zXQU==V#k~Ypqb20fd~lCcpyL*nDO(1ZM4j!p$6mazE0+RTo>J z-q9qS7WYOCgbFMCR>-eiM}+X7nrP$;*TLgj;^r#S11s- z^`6!Lm)Sc5!B)2UU4;JvwqM2iX)G^gwMsSVoO>w+KYkj4#KGOk)l_zW!FFFpJMQ^i z{;l~vxUYRu0C7%AbjJkh_v-Ya0*!-VGJzqq9 zfj$Y8$aykULpst3TJ~|bQUc_W?ql(q{I`k+kcIJg6OS|}D2#2<#r};p% zK4}uTOBeZ#$NNw8MFcab_g*b$_2pqyfLqfL9Vb#@0*G$Z(JlOnPHmENd7d zz7aUJ7D<4;mI2({VrcU8xY=I(vJ+hSxRqmfRjGqY@>2AQx5jV*%0A;>GJX4UxTK

t|&IZ4i3KKMRz*yaYjX(dM*4t&CFjh(%$uqwL(H0;7hR_Dk!?3|p0gE^vmix7_Ux0Lg+-2|NO}C+2d=|3bgR#d(|1G-R zb?lCo9RAhANmoV$-s950d{!L$IWY{g37ILi|GOeZ*hd$r*$`TH(41oj;$=Nq#g-L) z^`pq~$tWCG`{12d^Vp&Y{eAOXGNg~s&#lg-0_`4H#4DqE+Yy4-KAfAwj1z+PuTQSV zhypgf2=wvk!J}_`dy6ti;Kp<9eKy*!kf0&J{OrXu4$dQaU_ujy79qBbvVTN3^UUDi zbPms>mw}oY9XGj@K*OkBBi2EvLlLpM26I3C9Iki+NyZtDcmn@eT73et@S7{ z8*aJiw+g(RAI=WgwUN7P#_LU6=jR1>gMXZnN}c#=m-xhfJE~ zf`0&={A4!mEvpK-^BpJhTb!L`&Epu%8mRr>G(@2W0&wP;X^1BKI{~FfeDivjmh{cn zm{1T5CGoW6_O;BowGTUDLvt6ed+9+`l|_o$IAjdL*5vDBZdW*AUo1im5+5qMwU?br zOU)jyME7mVPo%`S!psdqE2()_oA0#?4yj_e-VW1_WaW?oBY>Xq6s!m5J$R9?$mTyd zN@nlfI7~p*mq525&K)f-(M_Jhl@mN{w}xBei@2WDlRWP(ge%b+xLgk%ht$Av2Em0t zWnY{r!SDMVH3Z-l?=4AzT$P8{9Phb{2)y@*e(kgq-|vH9R4AiM*#KCPm|S5okKvRkkwyGKPY3VbVtzx5jcnFT zofMq_c{F`-h@8=Zhw$V$q3`yI#_V=N^fVG6mTX}iCo?_2+lNDv02N%SfO%eJi$}UP zNYl95F8Vf?BF^QbJ}^Z^AgU869TsC*>NI^;u}sWObeAt{(t+o!9evGX)ZG0`?D4B( z-zo7>5I00dNe%WAF;w68T42%8}VO;%ufV441 z(JR%Y)%&eI^;+Ql+-~xoS08bXVzMvh4rqTtZ{fSuu0pcDITV)?Rl@y`)6fEf=wz|z zcicxnHXUUM{k~UruOi@IqxTQ|F|Ru#UWgfBrb_{7!`fyAX^AK#n&TOrj9R#{8IZky zivM}v#+%hUROh;CWV|&pJY_S06I7h9r4Ls>;=o_`cCF`B0krwcG=gVhBD%4Y;%vpy&VgDYgd~aXZS^v=ZEs7nVP?(v@N?g3?63^cz`53ykn-#K_6L z4D#1%@n8wU3L7hX+zBZLMVd09$g!Pm=qmnQ1ammteDH zvAhKw;sklaW==*zUe%BEIG-Xj&D4tz2Y#QgqX{vmu&ZZ}$Wh9d1?phm85?oB$Nt7m zez$iW{TODNsu29840KtB%OM7mC`Tw?n5q_?ahB2}aRGW;-r1nX0tikLk=|l^kE^g;mSxn0UJQ(H?ap?H z62FNB{ZVQDr@WB^wv(>>G4U)5Mrf|X;vA19vdowmGmh^L?zA>R@EqCwLfxNm!SCGh*GWxwU8D(w8lo_FWDU>V+=6}SWpyxiKbuAV=?ApGvO7Pa^ zw3g2lVXUsudC3rPD}oT2kRTSJl>9F5+hc~#OCHbnpM$j($KUN0Gaq0qM%?NtX=;X9 zWHNPt7E*W!5PW66)ZKLZ9gG#$vP>VIs|d!R{&X}{*#@0wvAqjtMRg#v0BnFRI8=)zk> zqoQ$rhrfQzo7j!YkB3(E`T9Uw7k01RnvYaC-Pnksg7BkvX~MJMO5)YT{8J)G?4Eru zw&I8GqU_y%z8+}1-j@DVEa$=}aXXyLrSm$@Z%-Sub9~GQR9I+qoU_x98)0uhMY0`( zk9oE7LM%2Nc8Ei*pElGEyv|K*3*7gK)W0h4Sy7$!7{F@5nb&iq*A!}t^JWnsn74Fw z(!8*uF}HU<;YOZI_2hlr#o<*R&UeT?7%K_c_`uX2TIXYZBTs*To1BXH8ID}>35mm# z`2JxgGR(B}>L`|Q!}PR2zGmPx_?Fw(Br)=W+3kQaZqz!AV=;+C&#p&Ph#L=@>iuPs zU$3|&g!FrN{KB7Jz==Q+*Zb_j>+KTnW&UbWeA%@H;Y?Bbnr``_O8%IW1rkDbLg2_+ zg_(&^I7r*1^?L=Vq}12t%TNNszKS#Vr=3pym1dIfrc%yYbk_RF_{h|PtcgS#gd=XU zM3kxTdB>Ls56tZ=d*$MV(V<&!j_W8$Vv?V(PaV%v2qc{*;JEO;zV$X}zDD*D4GH2v zh&b>g7&zH%I+J)9cHgsE+0gl@r?!6aH+gS7ufo?;8<7d^vM!9$Yx8wH;r_ zuD*KSLG9?t%Md(TNMdQin;Q8M;(fd^ZeeGsDW-kHIu)vD#Rt*eTBeg0eWgrgntd!6 zbjsz?$=Bhjw0lj};bQn|;OQC7^mga{=j^Zlq16C+7|7nu-q zOY#L#pm&4;eeW_06`pnp_k>s|t-OQ*lTP+2GA?v*Jo}@p2XB0chtBGW2p_zuX{rA! zN`RK!wfIZty=ouWk=^*uZy`GG)!3M(DC(dp&cD%{B20y1`aBu>7mnpG%%wiQUlO4| z;8r_+#f78jzOV~<#68AkB7A}@FW~n3n8FPBPCs{xOq}`mU}4ib9HLs}wvQ*0>8oqJ zXUZO}ZA-5cFrQc?ZErR#akq7n&)P@FKpSTc~Eg~^g z@U@5^5204l%XqY~*I0?ZW5)pOlc;xOdj3O)r?3%z0GIP3fTTrjKs$fK=pFWE0R8%C zE@3HI(fOhRMJ|T{j!K*9jJ19zrcbdNhM_45rt8Oltjlw{Z&a?AkEyRevoZNz^JAMg z{rB?=C$+UL1eq^U*IQPnqLFjQA4yf2(sWZW$r_uLA}~-~gb$*t_KqXJaP5}ziv7BR zZy!EC(?%YDD8PbWYWnOQ9uHH-2hSf^cpO*USugHU{PnwFS(p*K%@lzJd24sLm@A54 zNDwA**q07bXPRz{AE#MJTIq-J6{uDAX;+AYj?M-PHUe{%tnD#zf!F`aX48Q~5xqeTN) zKsJ*&e)S}vAT-lmel9gKHhJlU;|mqy$S{FECi{LjX`SoWXqCg&Fi$o?H9*+0shDSlkoui5YGIX-e;Z(iDE- ze;;h;Nw|yhDe_M+7(l+((4Jv-lMkrYn(!UN53cQQ*<@sh#a4@Day-Jkz=X}cxYGIh zYAC-s&t52&m7fxCx}*qAR0q=yxZSoC%>J@X*B?GMWTtSc6W3HgkMY6e<}He<4^5HR zp|uX^$J50ZqPBc1C*a*9Y&MINj^I|m@HoGmSzX?LENXkJm|9rb>TX!uZk2-TGzke&o#SWAJ7q;Z@$1XiGu>!M%6$&41C?19m!YzzCv^yLmc^vi#GJ z$4LkR@aVqT5QW2;$g7Fxz7irzLjpSyfPMz}gOILYN80*(d0yMHsVai*#KP01hri=q61KSN7A_k|$-1>_1ow@HY?Rk$t83ac=*vnDv4EVlix zIIy}VRI1cp1{2QLNKiHh@UU1&b_dp1FIrnbF@^P9Obv>Yt~df~`svr3_l%_e{o&_u z#O=UzeAyVk1{Vf4hnJ1Ce_(H}q>5MrPvTvS7XWsY60|crJfiBX%^z*<22u>V1B!p{ z_Iz9Zds5)ZJR_uhO_dq+DM0Q&omEUECTs9_e;#g}FW+z7M?n&k;5YL+KQS~uoZqcn zwu8WrBkv!-Q6NJt6Yz|O8W>wAEw=-xbwMVDIdF8*-_HVEkh(s%CLWM`J)safWHl+O zM|fv;{Fz!d5}nlo7WxCv{brz!kpOpoh`ttEB5?8AcwnSEj>O*$T#;}Ej*)yC^fCV5 z-ua)4Aed(i+xHV{dFS69Wh29)IA@KK`Nr=q!lCSey6<&JOf3Spl<> zaJx4sxelGSoKmLR^>(i_?NLiFdD-e-^Ys>c1LzUvT2ueglmRQ%HlMz;LnAWp)d&siyI*9@*d5gM9qw zq-s-MtV>?(HD)@V7nPISy`p{;CeY)p)XWzF+(@ryV&Re zN1wqbmR$9j#6#Md>uBK`iPPaAXu;{|YeTARerIDh2nI&R_SBhjMuQ%BcmhT+6&00e)BSzb@{^TSAr>fNH|cLwZ>&LA7WVto z7+j7AVF^E1*4O>=WK#6b;wbwv`Mm1$A8t-bmRdbR!`?EB*1DLs6)6|%bp@dmD10}K zihpDZt9WxC*6(HVIDqQm~({9WW8BEZh#!6j(>Q0B27f>^x zIp5eBBXPdpVYJ;IMkq$7wB88fa=kjhutdA)Kx~6L0e$SaFc29q;GXE(Q8uP0BnhEF0D>xQeI?Y;q~ zHcdw1@7V3GaADEkBU>`#b31zrfJ5mGCeWI;#ZfA*O}6DnlZYXbG0Pi%TBtDN|#$v-t<0;Z)u`0cW1tag5-Uz%e zHwNIid^V`vAsC>N)_UwG`Xk{WSV5fiKqP`3m0$1bUC#67o2*wm3Uu3E8;xWHVA1Oa zEckD@jg#$;yoTk3ks2NA86;U};O-aim6cxBRU>#2s-pN00PjQN(u!3o{U5zS{}))n z)(o@6WqLZ9PZUH5{76IyFv6sIyoAl<9Hp_CC~{BWIT}bAm;XQ}om9_XO&sC7{r}p! z&S~{7-U*^dl<1uhLiEn)-RO++Zq|E0 zBJ0hsJ2U6pd)L|f?DOsZD??f;IKTn^9_4l}xa(ZS$qBJ=LdEP>JHh*{4kF-lm2ONd+q#o|g|LLzFbED>VGF%w0l zsX|UkNjXZJg#MUy`Gw1yI)?|cVI-YbsMBaJT}Zojn4gqP;m&l8LC-?#KJxg<{{2Uy ziO3oFQ}OSQ>=gERss?aTvs<=-DsL%<;!7i131ZpWC4k3#_pVY<*rSeyR}~ zs)L)I{nSZzxHpRkOBv6e?aA)p&5VaAI5f}J+eR#k;KPL5PSxD;p5p*){`~s5`h=&b zlsTvIp}m}CFluGd>r#5_yXOu?s#5&dS}$Cu-w+T?rXW$w4c|AN;5S5R`yEgR_q+}B zO`SVA@uOEGLPc-BA5gPGfGFcDH_PHHF35H7nW>ZH7HUdVWfy{)Yj6bz;E*;=8wktm zL{-?He*7F6D!i4GGfH1=gmTg-d5yQ%B+^$}&NvPpU@-o4b5T(bOTdDA=YTTHm)0RDbqy(AD;N+2WqMrnD&ko!ZD2m1+^N^ zS9QrC?m{px4?^+lbw<52DnV<}kEa`cvN)vK&U;!=y-!4jtdX?iM!(WUg38NzaVd_3 zH^p>G(}bPn-oGdN=&<)0XWFDMPSY9AdE$lae5cprx!6fcI8s;D#LX~tu<}T?@)cM8 zcy+f971tlm{zb}D7|h^^LJ2^=!+lXFoq#At&bit1nIUALRszZ292MG$7bHM_0B0O@wJ3Q zM*9mjqwGvqU}vSg=kY^X9wO?^d8N=4!}5245ZoOZa%iIB83@Udk1||nxf>i7(PZ*i#QaU;IcM6ff+HDn`B};t;%+Y z@RZd-wg)mq%8V@IgdI2E&Y@`6uk0^76o~^b?qM;qn_<;CZ3TiD<-T~=Se`!?ll_er zNg+nzhc$gO)^y(qn!?}uWz=G@&e0-;w{{$vfaS$y_p?5A>Y^SDOv%>Sr-Pt!B*4GE%~kXiQ-0qK))q(3B370ywQF-DYcxg z2)mt4%`?;uj+xePjXT=qZ+*DWl#;o0*lL`u?8o7)G9)3L4?e7n7hPE00K%=k-c#)8 z0lr)rtZX4cpSuWMYHF}lPo&3FAVtuoE2(l#waT0r+IjM!I}(`C(((zAeLkG`l%_Z) zhgXj!{cMlcL3(6fM?>Wn+TX9F0F;YsQ_a$tak4YfbQ@K~)xEb!BoeOk* zU3oP48A@3%?Y=1WB^0V#V=WLuiGB}Prd=*VLjg1ZgTYAFPPQk}x5i7Zl2g??xG{0>Urpk?Tv}+~Rx0HNJO8V1dPb{2g+j z>HN4ygiH(gt~^Q4bhXC=_0P&(0U11qjeK+ZOj(?x%}JargDegy1@qeEbQ(5TgOFT! zxN3gyh}p0$!1XB;3XSkkM)^oOb7i7eC9pjFJfrRx1vq^$K9a2jPQ5_xG!cV|^2PhK z!bAiKY}Vkg9Qlaqjg(*$1X^rGd}EYx>RNxS_^aw*n~;di%&{q2kVt@E?F<7W!x5F8 z@`FAwaG(Kjr$8;|o)goK%>05lWSfKw7Cted{mm7LX_X_C=d?GM(@gvHf?5ujHgKX= z$lu@pZSV4rWQgi6*nl4SI#q+cn)LMjqoZ;I zKIdJklnT^FKCRWoRQ(GZQCcE@Etm%}o(E;NeAMpQdQKFdxdv0Z``3G+B#v!URTjrp zk!ivvuHo|GR3Wbg#75O>3|Cl5))ed7A}Ar+xhvrONQ;02;kEwMr#Jv?ksFV>qFLq# z>dGzn5UO@ZTCBDW3@v*o1j9nEM8H?EF%NfiPiPeSwApVXl{*h6{wN#;XVVzgI3f{4 z!|{=6&3JLd%jRe_(ld?*!)#$BJd&uB9f8s4inUN7-t6qI&6YWq2 zoo=g1n%wx<3>NFC6FWVR=gxCf*euVOn`j83WPn&i*#vU=wpvY?<_% zL!&pbaiqGW&ZMoq-3p=mh6NN2TAwQs$?l4jef~T&6#567KnIBJ%m8d&ryrf0U)inV zqCnC1e0P^n^0_~?T~*Kb`I~)oni3fGQdgALT1v;Ojo*1kO*#$lqrWB!n{bSk>KQpp z(1Y?{-@7+wr=RUcev{#OIm8`Ctti6TRjQ3d*m~yXLQ{#@Ut**{tUFF6sM>eSn&sL9 zn_QevERP5t3?YdU;6Rhz(LotYbI2Ik~T6=-UUMjuEwk=ck=FWK#8m8RNRQmongrG0u{iMaMBY@5a z>yI}J-8*^y%Qr$N4&_)Z7#+2@jMhhCFV-k`Vgj1Qc-8r7N^v}QLUQsfoa}=LSDiPo znq7On`zD4ktEItKS`i?u7>r18A$ARlaSy+eUIpBy=#G|k9qwVyjX z=quA${+PQg!Dn^haI|%rEL$JZzrFJD*I1FMGxl7)bJ>^x3kP__g#0-G}b#**g zj^g<8NAtoFztBD%RSb?A*U&>W5hlbdJtcLd3@K+^+nuc;jen#isj3R^JeFyx{kZc5 zn@mMERU*s?Pv!yPUm|9 zk~DgV24~Xm$7Rzei^(u>bC+i#YVJ*=gv|PMtsVL&ZoR;PG?6UD*w2L42)UT8#C-K9 zQ=a*JkW{OOs2@Qwj@A@UAERO6Z;xLElW@Puy&;v)5K6Ot(_A|6ZFjbQ!`%+K zc<<&?G_GEp;B#$Ac`W_AsYgUk2h(7Dl+rB|O(fL2Ez&6JMd|xA#fgn6GO0v44V zBA2r=-l7N6qM{;W(!&`chtE#R$b>SRu{kr!bfXr;8=)w8aP8r!R>ehxhvGPkEKF%4 zxqv;}*iI`{2*1OX*Yi>+Jhb5oa}f@|u0>Acr2;UmQ|j=*q>uOQGcTwQvn&E*EA* zo1NI7QsIGeO3{uJ;@g{+Q2I1-6=Lj_Qf)PDe$iSp=MEmXxeNrg2Tga>gj*@xTyN&` zg@Py%M=?$*q~x*ELHLv9;KjnXtx_CLC9q18lGjzg`E3#3q8)sYNaSYXV}yvf_+9BB zyitA|hV7m!M)rPgSY7ePPPk$2MJ%cd^?hwSAhF4*Y>DU8@3@iM?wy#^34+ONO~v8r zw0U&Llt}Sh$m7C+7!gjEDr$mlEJ1Vlp@y$iwUwBs%F1Ypb|Yet*FP{2F{<^k5N-kc zteh5DJ}9WnqZr!tqRw&|B_VN>7@)X&b{UI+;F`{`Z5s=>A_@MnJ>vtxyphs)^1GJd zH8o}P!^a+m`q)R$Dz|jcLbL85u4IrW^`;{O>8UvaI|}dQ$6x%-&@+Y;ZbQR)n&$+|al#gP`ZkH)SvLLS+waW@>$G z@Ts2R5fDJTjfu7_bWfiO95-My8t-W10e}_4x^XbfdWS>;*U`eGBnRqDVyQBr5vD@usFDk%E4%_W1eL>AVHDpaI-cODd@9XX>YZhrrpC7F335S3EHBdntY4%0H;62Qk-o1ZwR6#{WF0VJ9j+<7o zk7Z+z$#L#8H08)vYaF{^L+8nGoaN)WuVvBq(MOB4Y83pK*%xHs1$y<>8^0e19wcv@ zF-Ta96&LQ(`$dm1bAuO|N;wS}j(c6Z&R}&epnUb0M~;iT+uN3ckmYOy0?uz6!#yxn zWx~ICalA#*y~@nR)$h>Y$oU*s`?S9S-+*B}-ldv!9*clRV-0?uNLCS@oczW2ebVnd zHN;{Nax4B(fmU-9`*r_hNQy$JP=)#E7n9IOAf1T1JQ(P}-+3roR@3_mSc8!WMzpnb zF*km^3`fiDi3+`06X!>#!q;P`b(hwAP1TX$jiPT_t5M8hq+2j$hSVRmp>Xz6E^^W> zyPun6eRc@Xht_iDk|BNle0&tBUE)V=mG7V>`nwk7^Szp1);)H=wI0mXQ9ij&!=Um3 zOcwHFnAJZ2n81qw|G05HQB*ynkE@cz3!6)2a#6d22a3_pnppOAi}Vj1Zw_CsnGC%l z>Fd;_#|MG1wG~>_?kgm>_?J8&q*uh~8PKbHdwq$CKO~yJK8;o`%^w^==fg>;P{{s zu3JH-cOERe0^01sC86mHENI(6tJmFCHD=vyvcl1RFN%J|{Er}Hbj2~@Ueq*|kc2Ve z!e!5S^daM#e3OLnG|DIx#hY3vbQwvTdfHJUZ<|0TE@AR(3A5~WULTV!7MeS+fy)dj zT1^18SLU<3bYf`|KYZ+3P~L}k*6t3Vr(y{a*wXY6A3h63*Ft`vI5|LwH_u8pw( zAIYTTQ`$G}Oxu^^d)3D++C99*_bO$Yc8o){=cuIS4<-He#f8a{j z>47m&J6sV@bU5uWbL|9SoGbLY5v6@o07ya!5gSUtQ z+Kqiwi;K{0wdYwR@Ox~|*WA$udT)P)woJcs|-N3{)nHpV>$DzAX&1@;qn+7u+3WHDn=}ps80mLWRZ6kjW zmpmbLs^e!5*bJJMJdqlh0E=ARq@c~uG0sdpO7UW)?E?I1>z^l6X*t(q2Qu8f7EtK( zv}f1wv^|0t-yMyrAI_IEcim*cckW=gD#}P|Kn+;6L_d|^%I;!<+W`L3AA-_X3&Q5y z+rbw3Q&}grP32vdd5}trq67P&K;+(X`&rNBy~V3ml;0`v%%KY5p zJiIU|4xr9y=FWfD!n~pDBT$US^t6Gl@^hs zg=iU&xyjA#5W?DHNx7Yj#RTsLX!~OcJymbEPkDl`RG+~LD^!0nNJ>y$?al@ebXYS- znb@qmORP+@tVU;)k1@;Vi)?XuE4uHw;NEf$-@`h+^kmDBw;{e{Elt>OAzu5ez@xg( z;~_H#uAynw^F6yfN?1oS{j*jmMQ;L@)7W?7rCZi*D&lhOrGIb2<-336R14i#oLgr< zT>cMZFrp_7&jO1w%-FK^_aspTU=j_J-Wc>2a%HmR0XF{(4i}9@f;Rvd^sQ{>C*-(9 z7VLfr=ibijbBzSVu&7|}_H808zXopnVQ28EO8ftI{(X=m&0%o~UTnsR49%p=N&WH_ zBw`IouF%8khrZ;4d@HWVOG8Ewjy25#-M{kPBV9ekT%vbF&|j@O$}~~8t+95y;u-_4)2q1~+Hx_9ne)OW0peXgT*kWL=XU2wFY7 z13Ykn4asZMCF1T`=M@Di{wIfhfd{zr4}Fa8VQVm4!_rIq2%hmY7Olp*b8qH5*FduR z)rGj{1||-q+B2=&l#o6H7yplZT>}qf{E@H8ztDlNe(0UjoEhl1UKz--r_2AtJ{zEg z(^4{bB1ZDicv467MVr5lD!*B2N j+Z6!*`$`{@TW; Date: Mon, 20 Feb 2023 16:22:16 +0800 Subject: [PATCH 091/760] [KYUUBI #4372] Support to return null value for OperationsResource rowset ### _Why are the changes needed?_ With restful api, for query `select cast(null as int) c1` #### Before the result is `0` #### After the result is `null`. ### _How was this patch tested?_ - [x] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [x] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4372 from turboFei/null_value. Closes #4372 b15f1eb12 [fwang12] nit 5f1685591 [fwang12] fix 45c60dded [fwang12] check is set Authored-by: fwang12 Signed-off-by: fwang12 --- .../server/api/v1/OperationsResource.scala | 42 +++++++++++++++---- .../api/v1/OperationsResourceSuite.scala | 24 +++++++++++ 2 files changed, 59 insertions(+), 7 deletions(-) diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/OperationsResource.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/OperationsResource.scala index 99f1afdc3..015da7657 100644 --- a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/OperationsResource.scala +++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/OperationsResource.scala @@ -183,19 +183,47 @@ private[v1] class OperationsResource extends ApiRequestContext with Logging { i.getSetField.name(), i.getSetField match { case TColumnValue._Fields.STRING_VAL => - i.getStringVal.getFieldValue(TStringValue._Fields.VALUE) + if (i.getStringVal.isSetValue) { + i.getStringVal.getFieldValue(TStringValue._Fields.VALUE) + } else { + null + } case TColumnValue._Fields.BOOL_VAL => - i.getBoolVal.getFieldValue(TBoolValue._Fields.VALUE) + if (i.getBoolVal.isSetValue) { + i.getBoolVal.getFieldValue(TBoolValue._Fields.VALUE) + } else { + null + } case TColumnValue._Fields.BYTE_VAL => - i.getByteVal.getFieldValue(TByteValue._Fields.VALUE) + if (i.getByteVal.isSetValue) { + i.getByteVal.getFieldValue(TByteValue._Fields.VALUE) + } else { + null + } case TColumnValue._Fields.DOUBLE_VAL => - i.getDoubleVal.getFieldValue(TDoubleValue._Fields.VALUE) + if (i.getDoubleVal.isSetValue) { + i.getDoubleVal.getFieldValue(TDoubleValue._Fields.VALUE) + } else { + null + } case TColumnValue._Fields.I16_VAL => - i.getI16Val.getFieldValue(TI16Value._Fields.VALUE) + if (i.getI16Val.isSetValue) { + i.getI16Val.getFieldValue(TI16Value._Fields.VALUE) + } else { + null + } case TColumnValue._Fields.I32_VAL => - i.getI32Val.getFieldValue(TI32Value._Fields.VALUE) + if (i.getI32Val.isSetValue) { + i.getI32Val.getFieldValue(TI32Value._Fields.VALUE) + } else { + null + } case TColumnValue._Fields.I64_VAL => - i.getI64Val.getFieldValue(TI64Value._Fields.VALUE) + if (i.getI64Val.isSetValue) { + i.getI64Val.getFieldValue(TI64Value._Fields.VALUE) + } else { + null + } }) }).asJava) }) diff --git a/kyuubi-server/src/test/scala/org/apache/kyuubi/server/api/v1/OperationsResourceSuite.scala b/kyuubi-server/src/test/scala/org/apache/kyuubi/server/api/v1/OperationsResourceSuite.scala index 238203b0b..328d012a7 100644 --- a/kyuubi-server/src/test/scala/org/apache/kyuubi/server/api/v1/OperationsResourceSuite.scala +++ b/kyuubi-server/src/test/scala/org/apache/kyuubi/server/api/v1/OperationsResourceSuite.scala @@ -126,6 +126,30 @@ class OperationsResourceSuite extends KyuubiFunSuite with RestFrontendTestHelper assert(logRowSet.getRowCount == 1) } + test("test get result row set with null value") { + val opHandleStr = getOpHandleStr( + s""" + |select + |cast(null as string) as c1, + |cast(null as boolean) as c2, + |cast(null as byte) as c3, + |cast(null as double) as c4, + |cast(null as short) as c5, + |cast(null as int) as c6, + |cast(null as bigint) as c7 + |""".stripMargin) + checkOpState(opHandleStr, FINISHED) + val response = webTarget.path( + s"api/v1/operations/$opHandleStr/rowset") + .queryParam("maxrows", "2") + .queryParam("fetchorientation", "FETCH_NEXT") + .request(MediaType.APPLICATION_JSON).get() + assert(200 == response.getStatus) + val logRowSet = response.readEntity(classOf[ResultRowSet]) + assert(logRowSet.getRows.asScala.head.getFields.asScala.forall(_.getValue == null)) + assert(logRowSet.getRowCount == 1) + } + def getOpHandleStr(statement: String = "show tables"): String = { val sessionHandle = fe.be.openSession( HIVE_CLI_SERVICE_PROTOCOL_V2, From 0dce65d3867e728fdd6b6894ae4efcd6ff4f7362 Mon Sep 17 00:00:00 2001 From: Cheng Pan Date: Mon, 20 Feb 2023 17:05:32 +0800 Subject: [PATCH 092/760] [KYUUBI #4373] Using SVN_STAGING_REPO instead of SVN_STAGING_REPO in the release script to fix echo message ### _Why are the changes needed?_ The uploading destination is the svn repo location, not the local temp folder. ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [ ] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4373 from pan3793/minor. Closes #4373 96aa1d4c9 [Cheng Pan] Fix echo messeage in release script Authored-by: Cheng Pan Signed-off-by: Cheng Pan --- build/release/release.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/release/release.sh b/build/release/release.sh index 4afac3865..504bcfaf1 100755 --- a/build/release/release.sh +++ b/build/release/release.sh @@ -85,7 +85,7 @@ upload_svn_staging() { svn add "${SVN_STAGING_DIR}/${RELEASE_TAG}" - echo "Uploading release tarballs to ${SVN_STAGING_DIR}/${RELEASE_TAG}" + echo "Uploading release tarballs to ${SVN_STAGING_REPO}/${RELEASE_TAG}" ( cd "${SVN_STAGING_DIR}" && \ svn commit --username "${ASF_USERNAME}" --password "${ASF_PASSWORD}" --message "Apache Kyuubi ${RELEASE_TAG}" From ae374c1b3064b3278f23924a48eaff344f648676 Mon Sep 17 00:00:00 2001 From: Cheng Pan Date: Mon, 20 Feb 2023 19:31:30 +0800 Subject: [PATCH 093/760] [KYUUBI #4152][FOLLOWUP] LDAP configurations should be server-only ### _Why are the changes needed?_ Filter out the LDAP configurations to suppress potential security issues. ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [ ] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4383 from pan3793/auth-conf. Closes #4152 032245f8 [Cheng Pan] [KYUUBI #4152] LDAP configurations should be server-only Authored-by: Cheng Pan Signed-off-by: Cheng Pan --- .../org/apache/kyuubi/config/KyuubiConf.scala | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala b/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala index 05b6a056f..fcf50ffa8 100644 --- a/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala +++ b/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala @@ -773,6 +773,7 @@ object KyuubiConf { .doc("User-defined authentication implementation of " + "org.apache.kyuubi.service.authentication.PasswdAuthenticationProvider") .version("1.3.0") + .serverOnly .stringConf .createOptional @@ -780,6 +781,7 @@ object KyuubiConf { buildConf("kyuubi.authentication.ldap.url") .doc("SPACE character separated LDAP connection URL(s).") .version("1.0.0") + .serverOnly .stringConf .createOptional @@ -788,6 +790,7 @@ object KyuubiConf { .withAlternative("kyuubi.authentication.ldap.base.dn") .doc("LDAP base DN.") .version("1.7.0") + .serverOnly .stringConf .createOptional @@ -795,6 +798,7 @@ object KyuubiConf { buildConf("kyuubi.authentication.ldap.domain") .doc("LDAP domain.") .version("1.0.0") + .serverOnly .stringConf .createOptional @@ -804,6 +808,7 @@ object KyuubiConf { "this directory. Use %s where the actual group name is to be substituted for. " + "For example: CN=%s,CN=Groups,DC=subdomain,DC=domain,DC=com.") .version("1.7.0") + .serverOnly .stringConf .createOptional @@ -813,6 +818,7 @@ object KyuubiConf { "Use %s where the actual group name is to be substituted for. " + "For example: CN=%s,CN=Users,DC=subdomain,DC=domain,DC=com.") .version("1.7.0") + .serverOnly .stringConf .createOptional @@ -821,6 +827,7 @@ object KyuubiConf { .doc("COMMA-separated list of LDAP Group names (short name not full DNs). " + "For example: HiveAdmins,HadoopAdmins,Administrators") .version("1.7.0") + .serverOnly .stringConf .toSequence() .createWithDefault(Nil) @@ -830,6 +837,7 @@ object KyuubiConf { .doc("COMMA-separated list of LDAP usernames (just short names, not full DNs). " + "For example: hiveuser,impalauser,hiveadmin,hadoopadmin") .version("1.7.0") + .serverOnly .stringConf .toSequence() .createWithDefault(Nil) @@ -839,6 +847,7 @@ object KyuubiConf { .doc("LDAP attribute name whose values are unique in this LDAP server. " + "For example: uid or CN.") .version("1.2.0") + .serverOnly .stringConf .createWithDefault("uid") @@ -848,6 +857,7 @@ object KyuubiConf { "names for the user, group, and contact objects that are members of the group. " + "For example: member, uniqueMember or memberUid") .version("1.7.0") + .serverOnly .stringConf .createWithDefault("member") @@ -857,6 +867,7 @@ object KyuubiConf { "a direct member, except for the primary group, which is represented by the " + "primaryGroupId. For example: memberOf") .version("1.7.0") + .serverOnly .stringConf .createOptional @@ -865,6 +876,7 @@ object KyuubiConf { .doc("LDAP attribute name on the group entry that is to be used in LDAP group searches. " + "For example: group, groupOfNames or groupOfUniqueNames.") .version("1.7.0") + .serverOnly .stringConf .createWithDefault("groupOfNames") @@ -878,6 +890,7 @@ object KyuubiConf { "(|(memberOf=CN=Domain Admins,CN=Users,DC=domain,DC=com)" + "(memberOf=CN=Administrators,CN=Builtin,DC=domain,DC=com))))`") .version("1.7.0") + .serverOnly .stringConf .createOptional @@ -889,6 +902,7 @@ object KyuubiConf { "the user being authenticated will be used as the bind user. " + "For example: CN=bindUser,CN=Users,DC=subdomain,DC=domain,DC=com") .version("1.7.0") + .serverOnly .stringConf .createOptional @@ -898,6 +912,7 @@ object KyuubiConf { "user being authenticated. If the username is specified, this parameter must also be " + "specified.") .version("1.7.0") + .serverOnly .stringConf .createOptional @@ -905,6 +920,7 @@ object KyuubiConf { buildConf("kyuubi.authentication.jdbc.driver.class") .doc("Driver class name for JDBC Authentication Provider.") .version("1.6.0") + .serverOnly .stringConf .createOptional @@ -912,6 +928,7 @@ object KyuubiConf { buildConf("kyuubi.authentication.jdbc.url") .doc("JDBC URL for JDBC Authentication Provider.") .version("1.6.0") + .serverOnly .stringConf .createOptional @@ -919,6 +936,7 @@ object KyuubiConf { buildConf("kyuubi.authentication.jdbc.user") .doc("Database user for JDBC Authentication Provider.") .version("1.6.0") + .serverOnly .stringConf .createOptional @@ -926,6 +944,7 @@ object KyuubiConf { buildConf("kyuubi.authentication.jdbc.password") .doc("Database password for JDBC Authentication Provider.") .version("1.6.0") + .serverOnly .stringConf .createOptional @@ -937,6 +956,7 @@ object KyuubiConf { "The SQL statement must start with the `SELECT` clause. " + "Available placeholders are `${user}` and `${password}`.") .version("1.6.0") + .serverOnly .stringConf .createOptional @@ -975,6 +995,7 @@ object KyuubiConf { "

  • auth-conf - authentication plus integrity and confidentiality protection. This is" + " applicable only if Kyuubi is configured to use Kerberos authentication.
  • ") .version("1.0.0") + .serverOnly .stringConf .checkValues(SaslQOP.values.map(_.toString)) .transform(_.toLowerCase(Locale.ROOT)) From e5ade907f4fcec0a1a093ac57c3335530aa61779 Mon Sep 17 00:00:00 2001 From: Tianlin Liao Date: Tue, 21 Feb 2023 08:44:20 +0800 Subject: [PATCH 094/760] [KYUUBI #4352] Support System.gc() with periodic GC interval ### _Why are the changes needed?_ ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [ ] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4382 from lightning-L/kyuubi-4352. Closes #4352 2beb1ef40 [Tianlin Liao] update config name a9eb126d8 [Tianlin Liao] update config setting 75cdfbb02 [Tianlin Liao] [KYUUBI #4352] Support System.gc() with periodic GC interval Authored-by: Tianlin Liao Signed-off-by: fwang12 --- docs/deployment/settings.md | 25 ++++++----- .../org/apache/kyuubi/config/KyuubiConf.scala | 8 ++++ .../apache/kyuubi/server/KyuubiServer.scala | 3 ++ .../kyuubi/server/PeriodicGCService.scala | 45 +++++++++++++++++++ 4 files changed, 69 insertions(+), 12 deletions(-) create mode 100644 kyuubi-server/src/main/scala/org/apache/kyuubi/server/PeriodicGCService.scala diff --git a/docs/deployment/settings.md b/docs/deployment/settings.md index 1e0567860..3603ac8f2 100644 --- a/docs/deployment/settings.md +++ b/docs/deployment/settings.md @@ -449,18 +449,19 @@ You can configure the Kyuubi properties in `$KYUUBI_HOME/conf/kyuubi-defaults.co ### Server -| Key | Default | Meaning | Type | Since | -|----------------------------------------------------------|-------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------|-------| -| kyuubi.server.batch.limit.connections.per.ipaddress | <undefined> | Maximum kyuubi server batch connections per ipaddress. Any user exceeding this limit will not be allowed to connect. | int | 1.7.0 | -| kyuubi.server.batch.limit.connections.per.user | <undefined> | Maximum kyuubi server batch connections per user. Any user exceeding this limit will not be allowed to connect. | int | 1.7.0 | -| kyuubi.server.batch.limit.connections.per.user.ipaddress | <undefined> | Maximum kyuubi server batch connections per user:ipaddress combination. Any user-ipaddress exceeding this limit will not be allowed to connect. | int | 1.7.0 | -| kyuubi.server.info.provider | ENGINE | The server information provider name, some clients may rely on this information to check the server compatibilities and functionalities.
  • SERVER: Return Kyuubi server information.
  • ENGINE: Return Kyuubi engine information.
  • | string | 1.6.1 | -| kyuubi.server.limit.connections.per.ipaddress | <undefined> | Maximum kyuubi server connections per ipaddress. Any user exceeding this limit will not be allowed to connect. | int | 1.6.0 | -| kyuubi.server.limit.connections.per.user | <undefined> | Maximum kyuubi server connections per user. Any user exceeding this limit will not be allowed to connect. | int | 1.6.0 | -| kyuubi.server.limit.connections.per.user.ipaddress | <undefined> | Maximum kyuubi server connections per user:ipaddress combination. Any user-ipaddress exceeding this limit will not be allowed to connect. | int | 1.6.0 | -| kyuubi.server.limit.connections.user.unlimited.list || The maximum connections of the user in the white list will not be limited. | seq | 1.7.0 | -| kyuubi.server.name | <undefined> | The name of Kyuubi Server. | string | 1.5.0 | -| kyuubi.server.redaction.regex | <undefined> | Regex to decide which Kyuubi contain sensitive information. When this regex matches a property key or value, the value is redacted from the various logs. || 1.6.0 | +| Key | Default | Meaning | Type | Since | +|----------------------------------------------------------|-------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------|-------| +| kyuubi.server.batch.limit.connections.per.ipaddress | <undefined> | Maximum kyuubi server batch connections per ipaddress. Any user exceeding this limit will not be allowed to connect. | int | 1.7.0 | +| kyuubi.server.batch.limit.connections.per.user | <undefined> | Maximum kyuubi server batch connections per user. Any user exceeding this limit will not be allowed to connect. | int | 1.7.0 | +| kyuubi.server.batch.limit.connections.per.user.ipaddress | <undefined> | Maximum kyuubi server batch connections per user:ipaddress combination. Any user-ipaddress exceeding this limit will not be allowed to connect. | int | 1.7.0 | +| kyuubi.server.info.provider | ENGINE | The server information provider name, some clients may rely on this information to check the server compatibilities and functionalities.
  • SERVER: Return Kyuubi server information.
  • ENGINE: Return Kyuubi engine information.
  • | string | 1.6.1 | +| kyuubi.server.limit.connections.per.ipaddress | <undefined> | Maximum kyuubi server connections per ipaddress. Any user exceeding this limit will not be allowed to connect. | int | 1.6.0 | +| kyuubi.server.limit.connections.per.user | <undefined> | Maximum kyuubi server connections per user. Any user exceeding this limit will not be allowed to connect. | int | 1.6.0 | +| kyuubi.server.limit.connections.per.user.ipaddress | <undefined> | Maximum kyuubi server connections per user:ipaddress combination. Any user-ipaddress exceeding this limit will not be allowed to connect. | int | 1.6.0 | +| kyuubi.server.limit.connections.user.unlimited.list || The maximum connections of the user in the white list will not be limited. | seq | 1.7.0 | +| kyuubi.server.name | <undefined> | The name of Kyuubi Server. | string | 1.5.0 | +| kyuubi.server.periodicGC.interval | PT30M | How often to trigger a garbage collection. | duration | 1.7.0 | +| kyuubi.server.redaction.regex | <undefined> | Regex to decide which Kyuubi contain sensitive information. When this regex matches a property key or value, the value is redacted from the various logs. || 1.6.0 | ### Session diff --git a/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala b/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala index fcf50ffa8..faa86b203 100644 --- a/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala +++ b/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala @@ -2412,6 +2412,14 @@ object KyuubiConf { .regexConf .createOptional + val SERVER_PERIODIC_GC_INTERVAL: ConfigEntry[Long] = + buildConf("kyuubi.server.periodicGC.interval") + .doc("How often to trigger a garbage collection.") + .version("1.7.0") + .serverOnly + .timeConf + .createWithDefaultString("PT30M") + val OPERATION_SPARK_LISTENER_ENABLED: ConfigEntry[Boolean] = buildConf("kyuubi.operation.spark.listener.enabled") .doc("When set to true, Spark engine registers an SQLOperationListener before executing " + diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/KyuubiServer.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/KyuubiServer.scala index a27e18bbf..e81240a96 100644 --- a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/KyuubiServer.scala +++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/KyuubiServer.scala @@ -169,6 +169,9 @@ class KyuubiServer(name: String) extends Serverable(name) { val kinit = new KinitAuxiliaryService() addService(kinit) + val periodicGCService = new PeriodicGCService + addService(periodicGCService) + if (conf.get(MetricsConf.METRICS_ENABLED)) { addService(new MetricsSystem) } diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/PeriodicGCService.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/PeriodicGCService.scala new file mode 100644 index 000000000..a4035b689 --- /dev/null +++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/PeriodicGCService.scala @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.kyuubi.server + +import java.util.concurrent.TimeUnit + +import org.apache.kyuubi.config.KyuubiConf +import org.apache.kyuubi.service.AbstractService +import org.apache.kyuubi.util.ThreadUtils + +class PeriodicGCService(name: String) extends AbstractService(name) { + def this() = this(classOf[PeriodicGCService].getSimpleName) + + private val gcTrigger = ThreadUtils.newDaemonSingleThreadScheduledExecutor("periodic-gc-trigger") + + override def start(): Unit = { + startGcTrigger() + super.start() + } + + override def stop(): Unit = { + super.stop() + ThreadUtils.shutdown(gcTrigger) + } + + private def startGcTrigger(): Unit = { + val interval = conf.get(KyuubiConf.SERVER_PERIODIC_GC_INTERVAL) + gcTrigger.scheduleWithFixedDelay(() => System.gc(), interval, interval, TimeUnit.MILLISECONDS) + } +} From 7eac6ea0f9437386d1ba2b58902269187e6a52c5 Mon Sep 17 00:00:00 2001 From: Cheng Pan Date: Tue, 21 Feb 2023 09:47:45 +0800 Subject: [PATCH 095/760] [KYUUBI #4385] [DOCS] Refine release process ### _Why are the changes needed?_ Refine the release process docs, including - rename "major release" to "feature release", the latter is more meaningful - clarify the release manager should commit first, then go back to continue the rest release process. ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [x] Add screenshots for manual tests if appropriate image - [ ] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4385 from pan3793/release-doc. Closes #4385 b8c5089d [Cheng Pan] [DOCS] Refine release process Authored-by: Cheng Pan Signed-off-by: Cheng Pan --- docs/community/release.md | 46 +++++++++++++++++++++++++++------------ 1 file changed, 32 insertions(+), 14 deletions(-) diff --git a/docs/community/release.md b/docs/community/release.md index ffbdda9c1..163c575ff 100644 --- a/docs/community/release.md +++ b/docs/community/release.md @@ -43,7 +43,7 @@ The release process consists of several steps: 1. Decide to release 2. Prepare for the release -3. Cut branch off for __major__ release +3. Cut branch off for __feature__ release 4. Build a release candidate 5. Vote on the release candidate 6. If necessary, fix any issues and go back to step 3. @@ -153,12 +153,12 @@ gpg --keyserver hkp://keyserver.ubuntu.com --send-keys ${PUBLIC_KEY} # send publ gpg --keyserver hkp://keyserver.ubuntu.com --recv-keys ${PUBLIC_KEY} # verify ``` -## Cut branch if for major release +## Cut branch if for feature release Kyuubi use version pattern `{MAJOR_VERSION}.{MINOR_VERSION}.{PATCH_VERSION}[-{OPTIONAL_SUFFIX}]`, e.g. `1.7.0`. -__Major Release__ means `MAJOR_VERSION` or `MINOR_VERSION` changed, and __Patch Release__ means `PATCH_VERSION` changed. +__Feature Release__ means `MAJOR_VERSION` or `MINOR_VERSION` changed, and __Patch Release__ means `PATCH_VERSION` changed. -The main step towards preparing a major release is to create a release branch. This is done via standard Git branching +The main step towards preparing a feature release is to create a release branch. This is done via standard Git branching mechanism and should be announced to the community once the branch is created. > Note: If you are releasing a patch version, you can ignore this step. @@ -171,29 +171,47 @@ After cutting release branch, don't forget bump version in `master` branch. > Don't forget to switch to the release branch! -1. Set environment variables. +- Set environment variables. ```shell export RELEASE_VERSION= export RELEASE_RC_NO= +export NEXT_VERSION= ``` -2. Bump version. +- Bump version, and create a git tag for the release candidate. + +Considering that other committers may merge PRs during your release period, you should accomplish the version change +first, and then come back to the release candidate tag to continue the rest release process. + +The tag pattern is `v${RELEASE_VERSION}-rc${RELEASE_RC_NO}`, e.g. `v1.7.0-rc0` + +> NOTE: After all the voting passed, be sure to create a final tag with the pattern: `v${RELEASE_VERSION}` ```shell +# Bump to the release version build/mvn versions:set -DgenerateBackupPoms=false -DnewVersion="${RELEASE_VERSION}" - git commit -am "[RELEASE] Bump ${RELEASE_VERSION}" -``` -3. Create a git tag for the release candidate. +# Create tag +git tag v${RELEASE_VERSION}-rc${RELEASE_RC_NO} -The tag pattern is `v${RELEASE_VERSION}-rc${RELEASE_RC_NO}`, e.g. `v1.7.0-rc0` +# Prepare for the next development version +build/mvn versions:set -DgenerateBackupPoms=false -DnewVersion="${NEXT_VERSION}-SNAPSHOT" +git commit -am "[RELEASE] Bump ${NEXT_VERSION}-SNAPSHOT" -> NOTE: After all the voting passed, be sure to create a final tag with the pattern: `v${RELEASE_VERSION}` +# Push branch to apache remote repo +git push apache + +# Push tag to apache remote repo +git push apache v${RELEASE_VERSION}-rc${RELEASE_RC_NO} + +# Go back to release candidate tag +git checkout v${RELEASE_VERSION}-rc${RELEASE_RC_NO} +``` -4. Package the release binaries & sources, and upload them to the Apache staging SVN repo. Publish jars to the Apache - staging Maven repo. +- Package source and binary artifacts, and upload them to the Apache staging SVN repo. Publish jars to the Apache + staging Maven repo. ```shell build/release/release.sh publish @@ -201,7 +219,7 @@ build/release/release.sh publish To make your release available in the staging repository, you must close the staging repo in the [Apache Nexus](https://repository.apache.org/#stagingRepositories). Until you close, you can re-run deploying to staging multiple times. But once closed, it will create a new staging repo. So ensure you close this, so that the next RC (if need be) is on a new repo. Once everything is good, close the staging repository on Apache Nexus. -5. Generate a pre-release note from GitHub for the subsequent voting. +- Generate a pre-release note from GitHub for the subsequent voting. Goto the [release page](https://github.com/apache/kyuubi/releases) and click the "Draft a new release" button, then it would jump to a new page to prepare the release. From 89cc8c1679590cc7032a4056004424fe5ed721f9 Mon Sep 17 00:00:00 2001 From: Hanna Liashchuk Date: Tue, 21 Feb 2023 10:17:34 +0800 Subject: [PATCH 096/760] [KYUUBI #4267] Show warning if SessionHandle is invalid ### _Why are the changes needed?_ If SessionManager tries to close a session, that was previously closed - it throws an error `org.apache.kyuubi.KyuubiSQLException: Invalid SessionHandle` which causes spark session exit with non-zero code. ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [X] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4386 from hanna-liashchuk/catch-invalid-sessionhandle. Closes #4267 bf364bad [Hanna Liashchuk] Show warning if SessionHandle is invalid Authored-by: Hanna Liashchuk Signed-off-by: Cheng Pan --- .../engine/spark/session/SparkSQLSessionManager.scala | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/session/SparkSQLSessionManager.scala b/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/session/SparkSQLSessionManager.scala index 76c6a6505..cb8702df3 100644 --- a/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/session/SparkSQLSessionManager.scala +++ b/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/session/SparkSQLSessionManager.scala @@ -164,7 +164,12 @@ class SparkSQLSessionManager private (name: String, spark: SparkSession) } } } - super.closeSession(sessionHandle) + try { + super.closeSession(sessionHandle) + } catch { + case e: KyuubiSQLException => + warn(s"Error closing session ${sessionHandle}", e) + } if (shareLevel == ShareLevel.CONNECTION) { info("Session stopped due to shared level is Connection.") stopSession() From 81f16dfb17d9e30bc86f137e2d2c0c9dce7ef8d1 Mon Sep 17 00:00:00 2001 From: Cheng Pan Date: Tue, 21 Feb 2023 10:26:47 +0800 Subject: [PATCH 097/760] [KYUUBI #4374] Release uploading should include kyuubi-spark-connector-hive ### _Why are the changes needed?_ The module is missed because it's only included when `spark-3.3` is activated. ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [ ] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4374 from pan3793/release. Closes #4374 ffc0dc22 [Cheng Pan] fix d21738f1 [Cheng Pan] Release uploading should include kyuubi-spark-connector-hive Authored-by: Cheng Pan Signed-off-by: Cheng Pan --- build/release/release.sh | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/build/release/release.sh b/build/release/release.sh index 504bcfaf1..fefcce6a9 100755 --- a/build/release/release.sh +++ b/build/release/release.sh @@ -94,8 +94,6 @@ upload_svn_staging() { } upload_nexus_staging() { - ${KYUUBI_DIR}/build/mvn clean deploy -DskipTests -Papache-release,flink-provided,spark-provided,hive-provided \ - -s "${KYUUBI_DIR}/build/release/asf-settings.xml" ${KYUUBI_DIR}/build/mvn clean deploy -DskipTests -Papache-release,flink-provided,spark-provided,hive-provided,spark-3.1 \ -s "${KYUUBI_DIR}/build/release/asf-settings.xml" \ -pl extensions/spark/kyuubi-extension-spark-3-1 -am @@ -103,8 +101,7 @@ upload_nexus_staging() { -s "${KYUUBI_DIR}/build/release/asf-settings.xml" \ -pl extensions/spark/kyuubi-extension-spark-3-2 -am ${KYUUBI_DIR}/build/mvn clean deploy -DskipTests -Papache-release,flink-provided,spark-provided,hive-provided,spark-3.3 \ - -s "${KYUUBI_DIR}/build/release/asf-settings.xml" \ - -pl extensions/spark/kyuubi-extension-spark-3-3 -am + -s "${KYUUBI_DIR}/build/release/asf-settings.xml" } finalize_svn() { From 16c779284d6392424d76b8eb38b0a124f01f0c68 Mon Sep 17 00:00:00 2001 From: Cheng Pan Date: Tue, 21 Feb 2023 10:28:47 +0800 Subject: [PATCH 098/760] [KYUUBI #4377] Grant execute permission to release scripts ### _Why are the changes needed?_ The release-related shell scripts should have execution permission. ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [ ] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4377 from pan3793/x. Closes #4377 fb727adc [Cheng Pan] Grant execute permission to release scripts Authored-by: Cheng Pan Signed-off-by: Cheng Pan --- build/release/script/announce.sh | 0 build/release/script/dev_kyuubi_vote.sh | 0 2 files changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 build/release/script/announce.sh mode change 100644 => 100755 build/release/script/dev_kyuubi_vote.sh diff --git a/build/release/script/announce.sh b/build/release/script/announce.sh old mode 100644 new mode 100755 diff --git a/build/release/script/dev_kyuubi_vote.sh b/build/release/script/dev_kyuubi_vote.sh old mode 100644 new mode 100755 From d288a2bd33d00811392d7aca2594b5b184f268f8 Mon Sep 17 00:00:00 2001 From: fwang12 Date: Tue, 21 Feb 2023 13:00:04 +0800 Subject: [PATCH 099/760] [KYUUBI #3951][FOLLOWUP] Audit the rest request params ### _Why are the changes needed?_ Before: ``` user=anonymous(auth:BASIC) ip=127.0.0.1 proxyIp=null method=GET uri=/api/v1/operations/5e286c5d-2880-443f-a4e8-633964dcd699/rowset protocol=HTTP/1.1 status=200 ``` After: ``` user=anonymous(auth:BASIC) ip=127.0.0.1 proxyIp=null method=GET uri=/api/v1/operations/5e286c5d-2880-443f-a4e8-633964dcd699/rowset params=maxrows=2&fetchorientation=FETCH_NEXT protocol=HTTP/1.1 status=200 ``` ``` params=maxrows=2&fetchorientation=FETCH_NEXT ``` ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [x] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4389 from turboFei/rest_params. Closes #3951 6ffc1adbd [fwang12] comments 61e12b1b1 [fwang12] nit 0632860d2 [fwang12] Audit the request params Authored-by: fwang12 Signed-off-by: fwang12 --- .../server/http/authentication/AuthenticationAuditLogger.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/http/authentication/AuthenticationAuditLogger.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/http/authentication/AuthenticationAuditLogger.scala index ac1ee2a63..ac74c449b 100644 --- a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/http/authentication/AuthenticationAuditLogger.scala +++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/http/authentication/AuthenticationAuditLogger.scala @@ -35,6 +35,7 @@ object AuthenticationAuditLogger extends Logging { sb.append(s"proxyIp=${HTTP_PROXY_HEADER_CLIENT_IP_ADDRESS.get()}").append("\t") sb.append(s"method=${request.getMethod}").append("\t") sb.append(s"uri=${request.getRequestURI}").append("\t") + sb.append(s"params=${request.getQueryString}").append("\t") sb.append(s"protocol=${request.getProtocol}").append("\t") sb.append(s"status=${response.getStatus}") info(sb.toString()) From 3b73e1d64af8cb6351936d277ff57d98faa30c41 Mon Sep 17 00:00:00 2001 From: Yikf Date: Tue, 21 Feb 2023 17:24:53 +0800 Subject: [PATCH 100/760] [KYUUBI #4391] Improve code for hive-connector FileWriterFactory ### _Why are the changes needed?_ This pr aims to improve code for hive-connector FileWriterFactory, the main goal is to reduce duplicate copies of spark code. ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [x] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4391 from Yikf/improve-code. Closes #4391 7991f145 [Yikf] improve code for hive-connector FileWriterFactory Authored-by: Yikf Signed-off-by: Cheng Pan --- .../hive/write/FileWriterFactory.scala | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/extensions/spark/kyuubi-spark-connector-hive/src/main/scala/org/apache/kyuubi/spark/connector/hive/write/FileWriterFactory.scala b/extensions/spark/kyuubi-spark-connector-hive/src/main/scala/org/apache/kyuubi/spark/connector/hive/write/FileWriterFactory.scala index c8e8f9b69..6ebb55f14 100644 --- a/extensions/spark/kyuubi-spark-connector-hive/src/main/scala/org/apache/kyuubi/spark/connector/hive/write/FileWriterFactory.scala +++ b/extensions/spark/kyuubi-spark-connector-hive/src/main/scala/org/apache/kyuubi/spark/connector/hive/write/FileWriterFactory.scala @@ -19,7 +19,6 @@ package org.apache.kyuubi.spark.connector.hive.write import java.util.Date -import org.apache.hadoop.mapred.JobID import org.apache.hadoop.mapreduce.{TaskAttemptID, TaskID, TaskType} import org.apache.hadoop.mapreduce.task.TaskAttemptContextImpl import org.apache.spark.internal.io.FileCommitProtocol @@ -35,7 +34,7 @@ case class FileWriterFactory( description: WriteJobDescription, committer: FileCommitProtocol) extends DataWriterFactory { - private val jobTrackerId = sparkHadoopWriterUtils.createJobTrackerID(new Date) + @transient private lazy val jobId = sparkHadoopWriterUtils.createJobID(new Date, 0) override def createWriter(partitionId: Int, realTaskId: Long): DataWriter[InternalRow] = { val taskAttemptContext = createTaskAttemptContext(partitionId) @@ -48,7 +47,6 @@ case class FileWriterFactory( } private def createTaskAttemptContext(partitionId: Int): TaskAttemptContextImpl = { - val jobId = createJobID(jobTrackerId, 0) val taskId = new TaskID(jobId, TaskType.MAP, partitionId) val taskAttemptId = new TaskAttemptID(taskId, 0) // Set up the configuration object @@ -61,18 +59,4 @@ case class FileWriterFactory( new TaskAttemptContextImpl(hadoopConf, taskAttemptId) } - - /** - * Create a job ID. - * - * @param jobTrackerID unique job track id - * @param id job number - * @return a job ID - */ - def createJobID(jobTrackerID: String, id: Int): JobID = { - if (id < 0) { - throw new IllegalArgumentException("Job number is negative") - } - new JobID(jobTrackerID, id) - } } From 3191fa22fc034ce4a33ced66bdbcacc8c5f58bbb Mon Sep 17 00:00:00 2001 From: Cheng Pan Date: Tue, 21 Feb 2023 22:00:43 +0800 Subject: [PATCH 101/760] [KYUUBI #4106][FOLLOWUP] Rename conf key of uploaded batch resource ### _Why are the changes needed?_ As discussed https://github.com/apache/kyuubi/pull/4144#discussion_r1112957149, we should put the conf key into namespace `kyuubi.batch.` ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [x] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4394 from pan3793/4106-followup. Closes #4106 27c9ed870 [Cheng Pan] nit 66283ac4b [Cheng Pan] [KYUUBI #4106][FOLLOUP] Rename conf key of uploaded batch resource Authored-by: Cheng Pan Signed-off-by: Cheng Pan --- .../scala/org/apache/kyuubi/config/KyuubiReservedKeys.scala | 2 +- .../scala/org/apache/kyuubi/server/api/v1/BatchesResource.scala | 2 +- .../org/apache/kyuubi/session/KyuubiBatchSessionImpl.scala | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiReservedKeys.scala b/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiReservedKeys.scala index 335253925..dfbdd9449 100644 --- a/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiReservedKeys.scala +++ b/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiReservedKeys.scala @@ -25,8 +25,8 @@ object KyuubiReservedKeys { final val KYUUBI_SESSION_SIGN_PUBLICKEY = "kyuubi.session.sign.publickey" final val KYUUBI_SESSION_USER_SIGN = "kyuubi.session.user.sign" final val KYUUBI_SESSION_REAL_USER_KEY = "kyuubi.session.real.user" - final val KYUUBI_SESSION_BATCH_RESOURCE_UPLOADED_KEY = "kyuubi.session.batch.resource.uploaded" final val KYUUBI_SESSION_CONNECTION_URL_KEY = "kyuubi.session.connection.url" + final val KYUUBI_BATCH_RESOURCE_UPLOADED_KEY = "kyuubi.batch.resource.uploaded" final val KYUUBI_STATEMENT_ID_KEY = "kyuubi.statement.id" final val KYUUBI_ENGINE_ID = "kyuubi.engine.id" final val KYUUBI_ENGINE_NAME = "kyuubi.engine.name" diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/BatchesResource.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/BatchesResource.scala index d308c26d5..924e7b89c 100644 --- a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/BatchesResource.scala +++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/BatchesResource.scala @@ -214,10 +214,10 @@ private[v1] class BatchesResource extends ApiRequestContext with Logging { val ipAddress = fe.getIpAddress request.setConf( (request.getConf.asScala ++ Map( + KYUUBI_BATCH_RESOURCE_UPLOADED_KEY -> isResourceFromUpload.toString, KYUUBI_CLIENT_IP_KEY -> ipAddress, KYUUBI_SERVER_IP_KEY -> fe.host, KYUUBI_SESSION_CONNECTION_URL_KEY -> fe.connectionUrl, - KYUUBI_SESSION_BATCH_RESOURCE_UPLOADED_KEY -> isResourceFromUpload.toString, KYUUBI_SESSION_REAL_USER_KEY -> fe.getRealUser())).asJava) val sessionHandle = sessionManager.openBatchSession( userName, diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/session/KyuubiBatchSessionImpl.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/session/KyuubiBatchSessionImpl.scala index 5ed851ce0..7864d61f3 100644 --- a/kyuubi-server/src/main/scala/org/apache/kyuubi/session/KyuubiBatchSessionImpl.scala +++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/session/KyuubiBatchSessionImpl.scala @@ -79,7 +79,7 @@ class KyuubiBatchSessionImpl( // whether the resource file is from uploading private[kyuubi] val isResourceUploaded: Boolean = batchRequest.getConf - .getOrDefault(KyuubiReservedKeys.KYUUBI_SESSION_BATCH_RESOURCE_UPLOADED_KEY, "false").toBoolean + .getOrDefault(KyuubiReservedKeys.KYUUBI_BATCH_RESOURCE_UPLOADED_KEY, "false").toBoolean private[kyuubi] lazy val batchJobSubmissionOp = sessionManager.operationManager .newBatchJobSubmissionOperation( From f0acff315c68356f5138f99fdf0438e565ce5f06 Mon Sep 17 00:00:00 2001 From: Fu Chen Date: Wed, 22 Feb 2023 23:00:30 +0800 Subject: [PATCH 102/760] [KYUUBI #4392] [ARROW] Assign a new execution id for arrow-based result MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### _Why are the changes needed?_ assign a new execution id for arrow-based result, so that we can track the arrow-based queries on the UI tab. ```sql set kyuubi.operation.result.format=arrow; select 1; ``` Before this PR: ![截屏2023-02-21 下午5 23 08](https://user-images.githubusercontent.com/8537877/220303920-fbaf978b-ead7-4708-9094-bcc84e8fb47c.png) ![截屏2023-02-21 下午5 23 19](https://user-images.githubusercontent.com/8537877/220303966-cb8dfeae-cd10-4c4f-add6-2650619fc5f9.png) After this PR: ![截屏2023-02-22 上午10 21 53](https://user-images.githubusercontent.com/8537877/220504608-f67a5f70-8c64-4e3b-89c2-c2ea54676217.png) ![截屏2023-02-21 下午5 20 50](https://user-images.githubusercontent.com/8537877/220304021-9b845f44-96c3-41f2-a48a-a428f8c4823f.png) ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [x] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4392 from cfmcgrady/arrow-execution-id-2. Closes #4392 481118a4 [Fu Chen] enable ut c90674ee [Fu Chen] address comment 6cc7af44 [Fu Chen] address comment 3f8a3ab8 [Fu Chen] fix ut 223a2469 [Fu Chen] add KyuubiSparkContextHelper bb7b28f5 [Fu Chen] fix style 879a1502 [Fu Chen] unnecessary changes a2b04f83 [Fu Chen] fix Authored-by: Fu Chen Signed-off-by: Cheng Pan --- .../spark/operation/ExecuteStatement.scala | 114 ++++++++++++------ .../spark/operation/SparkOperation.scala | 16 +-- .../operation/SparkSQLOperationManager.scala | 19 ++- .../kyuubi/engine/spark/schema/RowSet.scala | 21 ++-- .../SparkArrowbasedOperationSuite.scala | 36 +++++- .../engine/spark/schema/RowSetSuite.scala | 9 +- .../spark/KyuubiSparkContextHelper.scala | 30 +++++ .../kyuubi/operation/SparkQueryTests.scala | 4 +- 8 files changed, 178 insertions(+), 71 deletions(-) create mode 100644 externals/kyuubi-spark-sql-engine/src/test/scala/org/apache/spark/KyuubiSparkContextHelper.scala diff --git a/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/operation/ExecuteStatement.scala b/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/operation/ExecuteStatement.scala index 2b90525c1..6ebcce377 100644 --- a/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/operation/ExecuteStatement.scala +++ b/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/operation/ExecuteStatement.scala @@ -22,13 +22,14 @@ import java.util.concurrent.RejectedExecutionException import scala.collection.JavaConverters._ import org.apache.spark.sql.DataFrame +import org.apache.spark.sql.execution.SQLExecution import org.apache.spark.sql.kyuubi.SparkDatasetHelper import org.apache.spark.sql.types._ import org.apache.kyuubi.{KyuubiSQLException, Logging} import org.apache.kyuubi.config.KyuubiConf.OPERATION_RESULT_MAX_ROWS import org.apache.kyuubi.engine.spark.KyuubiSparkUtil._ -import org.apache.kyuubi.operation.{ArrayFetchIterator, IterableFetchIterator, OperationState} +import org.apache.kyuubi.operation.{ArrayFetchIterator, FetchIterator, IterableFetchIterator, OperationState} import org.apache.kyuubi.operation.log.OperationLog import org.apache.kyuubi.session.Session @@ -62,49 +63,49 @@ class ExecuteStatement( OperationLog.removeCurrentOperationLog() } - private def executeStatement(): Unit = withLocalProperties { + protected def incrementalCollectResult(resultDF: DataFrame): Iterator[Any] = { + resultDF.toLocalIterator().asScala + } + + protected def fullCollectResult(resultDF: DataFrame): Array[_] = { + resultDF.collect() + } + + protected def takeResult(resultDF: DataFrame, maxRows: Int): Array[_] = { + resultDF.take(maxRows) + } + + protected def collectAsIterator(resultDF: DataFrame): FetchIterator[_] = { + val resultMaxRows = spark.conf.getOption(OPERATION_RESULT_MAX_ROWS.key).map(_.toInt) + .getOrElse(session.sessionManager.getConf.get(OPERATION_RESULT_MAX_ROWS)) + if (incrementalCollect) { + if (resultMaxRows > 0) { + warn(s"Ignore ${OPERATION_RESULT_MAX_ROWS.key} on incremental collect mode.") + } + info("Execute in incremental collect mode") + new IterableFetchIterator[Any](new Iterable[Any] { + override def iterator: Iterator[Any] = incrementalCollectResult(resultDF) + }) + } else { + val internalArray = if (resultMaxRows <= 0) { + info("Execute in full collect mode") + fullCollectResult(resultDF) + } else { + info(s"Execute with max result rows[$resultMaxRows]") + takeResult(resultDF, resultMaxRows) + } + new ArrayFetchIterator(internalArray) + } + } + + protected def executeStatement(): Unit = withLocalProperties { try { setState(OperationState.RUNNING) info(diagnostics) Thread.currentThread().setContextClassLoader(spark.sharedState.jarClassLoader) addOperationListener() result = spark.sql(statement) - - val resultMaxRows = spark.conf.getOption(OPERATION_RESULT_MAX_ROWS.key).map(_.toInt) - .getOrElse(session.sessionManager.getConf.get(OPERATION_RESULT_MAX_ROWS)) - iter = if (incrementalCollect) { - if (resultMaxRows > 0) { - warn(s"Ignore ${OPERATION_RESULT_MAX_ROWS.key} on incremental collect mode.") - } - info("Execute in incremental collect mode") - def internalIterator(): Iterator[Any] = if (arrowEnabled) { - SparkDatasetHelper.toArrowBatchRdd(convertComplexType(result)).toLocalIterator - } else { - result.toLocalIterator().asScala - } - new IterableFetchIterator[Any](new Iterable[Any] { - override def iterator: Iterator[Any] = internalIterator() - }) - } else { - val internalArray = if (resultMaxRows <= 0) { - info("Execute in full collect mode") - if (arrowEnabled) { - SparkDatasetHelper.toArrowBatchRdd(convertComplexType(result)).collect() - } else { - result.collect() - } - } else { - info(s"Execute with max result rows[$resultMaxRows]") - if (arrowEnabled) { - // this will introduce shuffle and hurt performance - val limitedResult = result.limit(resultMaxRows) - SparkDatasetHelper.toArrowBatchRdd(convertComplexType(limitedResult)).collect() - } else { - result.take(resultMaxRows) - } - } - new ArrayFetchIterator(internalArray) - } + iter = collectAsIterator(result) setCompiledStateIfNeeded() setState(OperationState.FINISHED) } catch { @@ -171,3 +172,40 @@ class ExecuteStatement( s"__kyuubi_operation_result_format__=$resultFormat", s"__kyuubi_operation_result_arrow_timestampAsString__=$timestampAsString") } + +class ArrowBasedExecuteStatement( + session: Session, + override val statement: String, + override val shouldRunAsync: Boolean, + queryTimeout: Long, + incrementalCollect: Boolean) + extends ExecuteStatement(session, statement, shouldRunAsync, queryTimeout, incrementalCollect) { + + override protected def incrementalCollectResult(resultDF: DataFrame): Iterator[Any] = { + SparkDatasetHelper.toArrowBatchRdd(convertComplexType(resultDF)).toLocalIterator + } + + override protected def fullCollectResult(resultDF: DataFrame): Array[_] = { + SparkDatasetHelper.toArrowBatchRdd(convertComplexType(resultDF)).collect() + } + + override protected def takeResult(resultDF: DataFrame, maxRows: Int): Array[_] = { + // this will introduce shuffle and hurt performance + val limitedResult = resultDF.limit(maxRows) + SparkDatasetHelper.toArrowBatchRdd(convertComplexType(limitedResult)).collect() + } + + /** + * assign a new execution id for arrow-based operation. + */ + override protected def collectAsIterator(resultDF: DataFrame): FetchIterator[_] = { + SQLExecution.withNewExecutionId(resultDF.queryExecution, Some("collectAsArrow")) { + resultDF.queryExecution.executedPlan.resetMetrics() + super.collectAsIterator(resultDF) + } + } + + override protected def isArrowBasedOperation: Boolean = true + + override val resultFormat = "arrow" +} diff --git a/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/operation/SparkOperation.scala b/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/operation/SparkOperation.scala index a6a7fc896..eb58407d4 100644 --- a/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/operation/SparkOperation.scala +++ b/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/operation/SparkOperation.scala @@ -245,7 +245,7 @@ abstract class SparkOperation(session: Session) case FETCH_FIRST => iter.fetchAbsolute(0); } resultRowSet = - if (arrowEnabled) { + if (isArrowBasedOperation) { if (iter.hasNext) { val taken = iter.next().asInstanceOf[Array[Byte]] RowSet.toTRowSet(taken, getProtocolVersion) @@ -257,8 +257,7 @@ abstract class SparkOperation(session: Session) RowSet.toTRowSet( taken.toSeq.asInstanceOf[Seq[Row]], resultSchema, - getProtocolVersion, - timeZone) + getProtocolVersion) } resultRowSet.setStartRowOffset(iter.getPosition) } catch onError(cancel = true) @@ -268,16 +267,9 @@ abstract class SparkOperation(session: Session) override def shouldRunAsync: Boolean = false - protected def arrowEnabled: Boolean = { - resultFormat.equalsIgnoreCase("arrow") && - // TODO: (fchen) make all operation support arrow - getClass.getCanonicalName == classOf[ExecuteStatement].getCanonicalName - } + protected def isArrowBasedOperation: Boolean = false - protected def resultFormat: String = { - // TODO: respect the config of the operation ExecuteStatement, if it was set. - spark.conf.get("kyuubi.operation.result.format", "thrift") - } + protected def resultFormat: String = "thrift" protected def timestampAsString: Boolean = { spark.conf.get("kyuubi.operation.result.arrow.timestampAsString", "false").toBoolean diff --git a/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/operation/SparkSQLOperationManager.scala b/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/operation/SparkSQLOperationManager.scala index 5c5ed0f98..4743f147c 100644 --- a/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/operation/SparkSQLOperationManager.scala +++ b/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/operation/SparkSQLOperationManager.scala @@ -82,7 +82,24 @@ class SparkSQLOperationManager private (name: String) extends OperationManager(n case NoneMode => val incrementalCollect = spark.conf.getOption(OPERATION_INCREMENTAL_COLLECT.key) .map(_.toBoolean).getOrElse(operationIncrementalCollectDefault) - new ExecuteStatement(session, statement, runAsync, queryTimeout, incrementalCollect) + // TODO: respect the config of the operation ExecuteStatement, if it was set. + val resultFormat = spark.conf.get("kyuubi.operation.result.format", "thrift") + resultFormat.toLowerCase match { + case "arrow" => + new ArrowBasedExecuteStatement( + session, + statement, + runAsync, + queryTimeout, + incrementalCollect) + case _ => + new ExecuteStatement( + session, + statement, + runAsync, + queryTimeout, + incrementalCollect) + } case mode => new PlanOnlyStatement(session, statement, mode) } diff --git a/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/schema/RowSet.scala b/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/schema/RowSet.scala index 7be70403d..4f935ce49 100644 --- a/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/schema/RowSet.scala +++ b/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/schema/RowSet.scala @@ -18,7 +18,6 @@ package org.apache.kyuubi.engine.spark.schema import java.nio.ByteBuffer -import java.time.ZoneId import scala.collection.JavaConverters._ @@ -61,16 +60,15 @@ object RowSet { def toTRowSet( rows: Seq[Row], schema: StructType, - protocolVersion: TProtocolVersion, - timeZone: ZoneId): TRowSet = { + protocolVersion: TProtocolVersion): TRowSet = { if (protocolVersion.getValue < TProtocolVersion.HIVE_CLI_SERVICE_PROTOCOL_V6.getValue) { - toRowBasedSet(rows, schema, timeZone) + toRowBasedSet(rows, schema) } else { - toColumnBasedSet(rows, schema, timeZone) + toColumnBasedSet(rows, schema) } } - def toRowBasedSet(rows: Seq[Row], schema: StructType, timeZone: ZoneId): TRowSet = { + def toRowBasedSet(rows: Seq[Row], schema: StructType): TRowSet = { val rowSize = rows.length val tRows = new java.util.ArrayList[TRow](rowSize) var i = 0 @@ -80,7 +78,7 @@ object RowSet { var j = 0 val columnSize = row.length while (j < columnSize) { - val columnValue = toTColumnValue(j, row, schema, timeZone) + val columnValue = toTColumnValue(j, row, schema) tRow.addToColVals(columnValue) j += 1 } @@ -90,21 +88,21 @@ object RowSet { new TRowSet(0, tRows) } - def toColumnBasedSet(rows: Seq[Row], schema: StructType, timeZone: ZoneId): TRowSet = { + def toColumnBasedSet(rows: Seq[Row], schema: StructType): TRowSet = { val rowSize = rows.length val tRowSet = new TRowSet(0, new java.util.ArrayList[TRow](rowSize)) var i = 0 val columnSize = schema.length while (i < columnSize) { val field = schema(i) - val tColumn = toTColumn(rows, i, field.dataType, timeZone) + val tColumn = toTColumn(rows, i, field.dataType) tRowSet.addToColumns(tColumn) i += 1 } tRowSet } - private def toTColumn(rows: Seq[Row], ordinal: Int, typ: DataType, timeZone: ZoneId): TColumn = { + private def toTColumn(rows: Seq[Row], ordinal: Int, typ: DataType): TColumn = { val nulls = new java.util.BitSet() typ match { case BooleanType => @@ -186,8 +184,7 @@ object RowSet { private def toTColumnValue( ordinal: Int, row: Row, - types: StructType, - timeZone: ZoneId): TColumnValue = { + types: StructType): TColumnValue = { types(ordinal).dataType match { case BooleanType => val boolValue = new TBoolValue diff --git a/externals/kyuubi-spark-sql-engine/src/test/scala/org/apache/kyuubi/engine/spark/operation/SparkArrowbasedOperationSuite.scala b/externals/kyuubi-spark-sql-engine/src/test/scala/org/apache/kyuubi/engine/spark/operation/SparkArrowbasedOperationSuite.scala index 60cc52891..30cdeca5a 100644 --- a/externals/kyuubi-spark-sql-engine/src/test/scala/org/apache/kyuubi/engine/spark/operation/SparkArrowbasedOperationSuite.scala +++ b/externals/kyuubi-spark-sql-engine/src/test/scala/org/apache/kyuubi/engine/spark/operation/SparkArrowbasedOperationSuite.scala @@ -19,8 +19,14 @@ package org.apache.kyuubi.engine.spark.operation import java.sql.Statement +import org.apache.spark.KyuubiSparkContextHelper +import org.apache.spark.sql.catalyst.plans.logical.{LogicalPlan, Project} +import org.apache.spark.sql.execution.QueryExecution +import org.apache.spark.sql.util.QueryExecutionListener + import org.apache.kyuubi.config.KyuubiConf -import org.apache.kyuubi.engine.spark.WithSparkSQLEngine +import org.apache.kyuubi.engine.spark.{SparkSQLEngine, WithSparkSQLEngine} +import org.apache.kyuubi.engine.spark.session.SparkSessionImpl import org.apache.kyuubi.operation.SparkDataTypeTests class SparkArrowbasedOperationSuite extends WithSparkSQLEngine with SparkDataTypeTests { @@ -85,6 +91,34 @@ class SparkArrowbasedOperationSuite extends WithSparkSQLEngine with SparkDataTyp } } + test("assign a new execution id for arrow-based result") { + var plan: LogicalPlan = null + + val listener = new QueryExecutionListener { + override def onSuccess(funcName: String, qe: QueryExecution, durationNs: Long): Unit = { + plan = qe.analyzed + } + override def onFailure(funcName: String, qe: QueryExecution, exception: Exception): Unit = {} + } + withJdbcStatement() { statement => + // since all the new sessions have their owner listener bus, we should register the listener + // in the current session. + SparkSQLEngine.currentEngine.get + .backendService + .sessionManager + .allSessions() + .foreach(_.asInstanceOf[SparkSessionImpl].spark.listenerManager.register(listener)) + + val result = statement.executeQuery("select 1 as c1") + assert(result.next()) + assert(result.getInt("c1") == 1) + } + + KyuubiSparkContextHelper.waitListenerBus(spark) + spark.listenerManager.unregister(listener) + assert(plan.isInstanceOf[Project]) + } + private def checkResultSetFormat(statement: Statement, expectFormat: String): Unit = { val query = s""" diff --git a/externals/kyuubi-spark-sql-engine/src/test/scala/org/apache/kyuubi/engine/spark/schema/RowSetSuite.scala b/externals/kyuubi-spark-sql-engine/src/test/scala/org/apache/kyuubi/engine/spark/schema/RowSetSuite.scala index a999563ea..5d2ba4a0d 100644 --- a/externals/kyuubi-spark-sql-engine/src/test/scala/org/apache/kyuubi/engine/spark/schema/RowSetSuite.scala +++ b/externals/kyuubi-spark-sql-engine/src/test/scala/org/apache/kyuubi/engine/spark/schema/RowSetSuite.scala @@ -20,7 +20,7 @@ package org.apache.kyuubi.engine.spark.schema import java.nio.ByteBuffer import java.nio.charset.StandardCharsets import java.sql.{Date, Timestamp} -import java.time.{Instant, LocalDate, ZoneId} +import java.time.{Instant, LocalDate} import scala.collection.JavaConverters._ @@ -96,10 +96,9 @@ class RowSetSuite extends KyuubiFunSuite { .add("q", "timestamp") private val rows: Seq[Row] = (0 to 10).map(genRow) ++ Seq(Row.fromSeq(Seq.fill(17)(null))) - private val zoneId: ZoneId = ZoneId.systemDefault() test("column based set") { - val tRowSet = RowSet.toColumnBasedSet(rows, schema, zoneId) + val tRowSet = RowSet.toColumnBasedSet(rows, schema) assert(tRowSet.getColumns.size() === schema.size) assert(tRowSet.getRowsSize === 0) @@ -204,7 +203,7 @@ class RowSetSuite extends KyuubiFunSuite { } test("row based set") { - val tRowSet = RowSet.toRowBasedSet(rows, schema, zoneId) + val tRowSet = RowSet.toRowBasedSet(rows, schema) assert(tRowSet.getColumnCount === 0) assert(tRowSet.getRowsSize === rows.size) val iter = tRowSet.getRowsIterator @@ -250,7 +249,7 @@ class RowSetSuite extends KyuubiFunSuite { test("to row set") { TProtocolVersion.values().foreach { proto => - val set = RowSet.toTRowSet(rows, schema, proto, zoneId) + val set = RowSet.toTRowSet(rows, schema, proto) if (proto.getValue < TProtocolVersion.HIVE_CLI_SERVICE_PROTOCOL_V6.getValue) { assert(!set.isSetColumns, proto.toString) assert(set.isSetRows, proto.toString) diff --git a/externals/kyuubi-spark-sql-engine/src/test/scala/org/apache/spark/KyuubiSparkContextHelper.scala b/externals/kyuubi-spark-sql-engine/src/test/scala/org/apache/spark/KyuubiSparkContextHelper.scala new file mode 100644 index 000000000..8293123ea --- /dev/null +++ b/externals/kyuubi-spark-sql-engine/src/test/scala/org/apache/spark/KyuubiSparkContextHelper.scala @@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.spark + +import org.apache.spark.sql.SparkSession + +/** + * A place to invoke non-public APIs of [[SparkContext]], for test only. + */ +object KyuubiSparkContextHelper { + + def waitListenerBus(spark: SparkSession): Unit = { + spark.sparkContext.listenerBus.waitUntilEmpty() + } +} diff --git a/kyuubi-common/src/test/scala/org/apache/kyuubi/operation/SparkQueryTests.scala b/kyuubi-common/src/test/scala/org/apache/kyuubi/operation/SparkQueryTests.scala index e297e6281..a42b05473 100644 --- a/kyuubi-common/src/test/scala/org/apache/kyuubi/operation/SparkQueryTests.scala +++ b/kyuubi-common/src/test/scala/org/apache/kyuubi/operation/SparkQueryTests.scala @@ -433,13 +433,13 @@ trait SparkQueryTests extends SparkDataTypeTests with HiveJDBCTestHelper { expectedFormat = "thrift") checkStatusAndResultSetFormatHint( sql = "set kyuubi.operation.result.format=arrow", - expectedFormat = "arrow") + expectedFormat = "thrift") checkStatusAndResultSetFormatHint( sql = "SELECT 1", expectedFormat = "arrow") checkStatusAndResultSetFormatHint( sql = "set kyuubi.operation.result.format=thrift", - expectedFormat = "thrift") + expectedFormat = "arrow") checkStatusAndResultSetFormatHint( sql = "set kyuubi.operation.result.format", expectedFormat = "thrift") From e4d0962d5dd6b5fcf1599b633dcd0597bc4491cd Mon Sep 17 00:00:00 2001 From: edddddy Date: Thu, 23 Feb 2023 01:49:24 +0800 Subject: [PATCH 103/760] [KYUUBI #4399] Remove redundant config in Trino frontend ### _Why are the changes needed?_ It seems to be redundant to keep these configurations in the Trino frontend module. Thus, to my mind, removing them make things better. ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [ ] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4399 from edddddy/master. Closes #4399 81dab6b2 [edddddy] remove some redundant config in Trino front-end Authored-by: edddddy Signed-off-by: Cheng Pan --- .../kyuubi/server/KyuubiTrinoFrontendService.scala | 9 --------- 1 file changed, 9 deletions(-) diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/KyuubiTrinoFrontendService.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/KyuubiTrinoFrontendService.scala index fca8b8a87..573bb948f 100644 --- a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/KyuubiTrinoFrontendService.scala +++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/KyuubiTrinoFrontendService.scala @@ -64,15 +64,6 @@ class KyuubiTrinoFrontendService(override val serverable: Serverable) private def startInternal(): Unit = { val contextHandler = ApiRootResource.getServletHandler(this) server.addHandler(contextHandler) - - server.addStaticHandler("org/apache/kyuubi/ui/static", "/static/") - server.addRedirectHandler("/", "/static/") - server.addRedirectHandler("/static", "/static/") - server.addStaticHandler("META-INF/resources/webjars/swagger-ui/4.9.1/", "/swagger-static/") - server.addStaticHandler("org/apache/kyuubi/ui/swagger", "/swagger/") - server.addRedirectHandler("/docs", "/swagger/") - server.addRedirectHandler("/docs/", "/swagger/") - server.addRedirectHandler("/swagger", "/swagger/") } override def start(): Unit = synchronized { From 27a8674f756ca41dece6f5d76a792d186b0d5040 Mon Sep 17 00:00:00 2001 From: Tianlin Liao Date: Thu, 23 Feb 2023 09:17:10 +0800 Subject: [PATCH 104/760] [KYUUBI #4388] Limit the max rows for get nextRowSet api ### _Why are the changes needed?_ ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [ ] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4395 from lightning-L/kyuubi-4388. Closes #4388 4f4ae8e59 [Tianlin Liao] create as optional 6b85f6ae6 [Tianlin Liao] refactor 7c2cd5661 [Tianlin Liao] rename conf 2f00da0f4 [Tianlin Liao] throw error in AbstractBackendService to enable it for all kinds of clients 3feabf78c [Tianlin Liao] [KYUUBI #4388] Limit the max rows for get nextRowSet api Authored-by: Tianlin Liao Signed-off-by: fwang12 --- docs/deployment/settings.md | 1 + .../org/apache/kyuubi/config/KyuubiConf.scala | 9 +++++++++ .../kyuubi/service/AbstractBackendService.scala | 9 ++++++++- .../kyuubi/server/api/v1/OperationsResource.scala | 6 ++++-- .../server/api/v1/OperationsResourceSuite.scala | 14 ++++++++++++++ 5 files changed, 36 insertions(+), 3 deletions(-) diff --git a/docs/deployment/settings.md b/docs/deployment/settings.md index 3603ac8f2..da5faadf5 100644 --- a/docs/deployment/settings.md +++ b/docs/deployment/settings.md @@ -455,6 +455,7 @@ You can configure the Kyuubi properties in `$KYUUBI_HOME/conf/kyuubi-defaults.co | kyuubi.server.batch.limit.connections.per.user | <undefined> | Maximum kyuubi server batch connections per user. Any user exceeding this limit will not be allowed to connect. | int | 1.7.0 | | kyuubi.server.batch.limit.connections.per.user.ipaddress | <undefined> | Maximum kyuubi server batch connections per user:ipaddress combination. Any user-ipaddress exceeding this limit will not be allowed to connect. | int | 1.7.0 | | kyuubi.server.info.provider | ENGINE | The server information provider name, some clients may rely on this information to check the server compatibilities and functionalities.
  • SERVER: Return Kyuubi server information.
  • ENGINE: Return Kyuubi engine information.
  • | string | 1.6.1 | +| kyuubi.server.limit.client.fetch.max.rows | <undefined> | Max rows limit for getting result row set operation. If the max rows specified by client-side is larger than the limit, request will fail directly. | int | 1.8.0 | | kyuubi.server.limit.connections.per.ipaddress | <undefined> | Maximum kyuubi server connections per ipaddress. Any user exceeding this limit will not be allowed to connect. | int | 1.6.0 | | kyuubi.server.limit.connections.per.user | <undefined> | Maximum kyuubi server connections per user. Any user exceeding this limit will not be allowed to connect. | int | 1.6.0 | | kyuubi.server.limit.connections.per.user.ipaddress | <undefined> | Maximum kyuubi server connections per user:ipaddress combination. Any user-ipaddress exceeding this limit will not be allowed to connect. | int | 1.6.0 | diff --git a/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala b/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala index faa86b203..ced1bd612 100644 --- a/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala +++ b/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala @@ -2396,6 +2396,15 @@ object KyuubiConf { .intConf .createOptional + val SERVER_LIMIT_CLIENT_FETCH_MAX_ROWS: OptionalConfigEntry[Int] = + buildConf("kyuubi.server.limit.client.fetch.max.rows") + .doc("Max rows limit for getting result row set operation. If the max rows specified " + + "by client-side is larger than the limit, request will fail directly.") + .version("1.8.0") + .serverOnly + .intConf + .createOptional + val SESSION_PROGRESS_ENABLE: ConfigEntry[Boolean] = buildConf("kyuubi.operation.progress.enabled") .doc("Whether to enable the operation progress. When true," + diff --git a/kyuubi-common/src/main/scala/org/apache/kyuubi/service/AbstractBackendService.scala b/kyuubi-common/src/main/scala/org/apache/kyuubi/service/AbstractBackendService.scala index b9e254508..171e04901 100644 --- a/kyuubi-common/src/main/scala/org/apache/kyuubi/service/AbstractBackendService.scala +++ b/kyuubi-common/src/main/scala/org/apache/kyuubi/service/AbstractBackendService.scala @@ -21,7 +21,7 @@ import java.util.concurrent.{ExecutionException, TimeoutException, TimeUnit} import scala.concurrent.CancellationException -import org.apache.hive.service.rpc.thrift.{TGetInfoType, TGetInfoValue, TGetResultSetMetadataResp, TProtocolVersion, TRowSet} +import org.apache.hive.service.rpc.thrift._ import org.apache.kyuubi.config.KyuubiConf import org.apache.kyuubi.operation.{OperationHandle, OperationStatus} @@ -35,6 +35,7 @@ abstract class AbstractBackendService(name: String) extends CompositeService(name) with BackendService { private lazy val timeout = conf.get(KyuubiConf.OPERATION_STATUS_POLLING_TIMEOUT) + private lazy val maxRowsLimit = conf.get(KyuubiConf.SERVER_LIMIT_CLIENT_FETCH_MAX_ROWS) override def openSession( protocol: TProtocolVersion, @@ -201,6 +202,12 @@ abstract class AbstractBackendService(name: String) orientation: FetchOrientation, maxRows: Int, fetchLog: Boolean): TRowSet = { + maxRowsLimit.foreach(limit => + if (maxRows > limit) { + throw new IllegalArgumentException(s"Max rows for fetching results " + + s"operation should not exceed the limit: $limit") + }) + sessionManager.operationManager .getOperation(operationHandle) .getSession diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/OperationsResource.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/OperationsResource.scala index 015da7657..70a6d3a28 100644 --- a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/OperationsResource.scala +++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/OperationsResource.scala @@ -28,8 +28,7 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse import io.swagger.v3.oas.annotations.tags.Tag import org.apache.hive.service.rpc.thrift._ -import org.apache.kyuubi.KyuubiSQLException -import org.apache.kyuubi.Logging +import org.apache.kyuubi.{KyuubiSQLException, Logging} import org.apache.kyuubi.client.api.v1.dto._ import org.apache.kyuubi.events.KyuubiOperationEvent import org.apache.kyuubi.operation.{FetchOrientation, KyuubiOperation, OperationHandle} @@ -229,6 +228,9 @@ private[v1] class OperationsResource extends ApiRequestContext with Logging { }) new ResultRowSet(rows.asJava, rows.size) } catch { + case e: IllegalArgumentException => + error(e.getMessage, e) + throw new BadRequestException(e.getMessage) case NonFatal(e) => val errorMsg = s"Error getting result row set for operation handle $operationHandleStr" error(errorMsg, e) diff --git a/kyuubi-server/src/test/scala/org/apache/kyuubi/server/api/v1/OperationsResourceSuite.scala b/kyuubi-server/src/test/scala/org/apache/kyuubi/server/api/v1/OperationsResourceSuite.scala index 328d012a7..51701b231 100644 --- a/kyuubi-server/src/test/scala/org/apache/kyuubi/server/api/v1/OperationsResourceSuite.scala +++ b/kyuubi-server/src/test/scala/org/apache/kyuubi/server/api/v1/OperationsResourceSuite.scala @@ -29,12 +29,16 @@ import org.scalatest.time.SpanSugar.convertIntToGrainOfTime import org.apache.kyuubi.{KyuubiFunSuite, RestFrontendTestHelper} import org.apache.kyuubi.client.api.v1.dto._ +import org.apache.kyuubi.config.KyuubiConf import org.apache.kyuubi.events.KyuubiOperationEvent import org.apache.kyuubi.operation.{ExecuteStatement, OperationState} import org.apache.kyuubi.operation.OperationState.{FINISHED, OperationState} class OperationsResourceSuite extends KyuubiFunSuite with RestFrontendTestHelper { + override protected lazy val conf: KyuubiConf = KyuubiConf() + .set(KyuubiConf.SERVER_LIMIT_CLIENT_FETCH_MAX_ROWS, 5000) + test("get an operation event") { val catalogsHandleStr = getOpHandleStr("") checkOpState(catalogsHandleStr, FINISHED) @@ -126,6 +130,16 @@ class OperationsResourceSuite extends KyuubiFunSuite with RestFrontendTestHelper assert(logRowSet.getRowCount == 1) } + test("test invalid max rows") { + val opHandleStr = getOpHandleStr("select \"test\", 1, 0.32d, true") + checkOpState(opHandleStr, FINISHED) + val response = webTarget.path( + s"api/v1/operations/$opHandleStr/rowset") + .queryParam("maxrows", "10000") + .request(MediaType.APPLICATION_JSON).get() + assert(400 == response.getStatus) + } + test("test get result row set with null value") { val opHandleStr = getOpHandleStr( s""" From 171473ec718aeabe7d9e8e61fc63e2009e43cbd7 Mon Sep 17 00:00:00 2001 From: fwang12 Date: Thu, 23 Feb 2023 09:21:34 +0800 Subject: [PATCH 105/760] [KYUUBI #3957][FOLLOWUP] Rename the config prefix from kyuubi.server.batch.limit to kyuubi.server.limit.batch ### _Why are the changes needed?_ ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [ ] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4398 from turboFei/rename_batch_limit. Closes #3957 28228e4ed [fwang12] 3957 followup ef1ad6ea5 [fwang12] save Authored-by: fwang12 Signed-off-by: fwang12 --- docs/deployment/settings.md | 6 +++--- .../main/scala/org/apache/kyuubi/config/KyuubiConf.scala | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/deployment/settings.md b/docs/deployment/settings.md index da5faadf5..82c8111b6 100644 --- a/docs/deployment/settings.md +++ b/docs/deployment/settings.md @@ -451,10 +451,10 @@ You can configure the Kyuubi properties in `$KYUUBI_HOME/conf/kyuubi-defaults.co | Key | Default | Meaning | Type | Since | |----------------------------------------------------------|-------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------|-------| -| kyuubi.server.batch.limit.connections.per.ipaddress | <undefined> | Maximum kyuubi server batch connections per ipaddress. Any user exceeding this limit will not be allowed to connect. | int | 1.7.0 | -| kyuubi.server.batch.limit.connections.per.user | <undefined> | Maximum kyuubi server batch connections per user. Any user exceeding this limit will not be allowed to connect. | int | 1.7.0 | -| kyuubi.server.batch.limit.connections.per.user.ipaddress | <undefined> | Maximum kyuubi server batch connections per user:ipaddress combination. Any user-ipaddress exceeding this limit will not be allowed to connect. | int | 1.7.0 | | kyuubi.server.info.provider | ENGINE | The server information provider name, some clients may rely on this information to check the server compatibilities and functionalities.
  • SERVER: Return Kyuubi server information.
  • ENGINE: Return Kyuubi engine information.
  • | string | 1.6.1 | +| kyuubi.server.limit.batch.connections.per.ipaddress | <undefined> | Maximum kyuubi server batch connections per ipaddress. Any user exceeding this limit will not be allowed to connect. | int | 1.7.0 | +| kyuubi.server.limit.batch.connections.per.user | <undefined> | Maximum kyuubi server batch connections per user. Any user exceeding this limit will not be allowed to connect. | int | 1.7.0 | +| kyuubi.server.limit.batch.connections.per.user.ipaddress | <undefined> | Maximum kyuubi server batch connections per user:ipaddress combination. Any user-ipaddress exceeding this limit will not be allowed to connect. | int | 1.7.0 | | kyuubi.server.limit.client.fetch.max.rows | <undefined> | Max rows limit for getting result row set operation. If the max rows specified by client-side is larger than the limit, request will fail directly. | int | 1.8.0 | | kyuubi.server.limit.connections.per.ipaddress | <undefined> | Maximum kyuubi server connections per ipaddress. Any user exceeding this limit will not be allowed to connect. | int | 1.6.0 | | kyuubi.server.limit.connections.per.user | <undefined> | Maximum kyuubi server connections per user. Any user exceeding this limit will not be allowed to connect. | int | 1.6.0 | diff --git a/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala b/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala index ced1bd612..70919d6e8 100644 --- a/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala +++ b/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala @@ -2370,7 +2370,7 @@ object KyuubiConf { .createWithDefault(Nil) val SERVER_LIMIT_BATCH_CONNECTIONS_PER_USER: OptionalConfigEntry[Int] = - buildConf("kyuubi.server.batch.limit.connections.per.user") + buildConf("kyuubi.server.limit.batch.connections.per.user") .doc("Maximum kyuubi server batch connections per user." + " Any user exceeding this limit will not be allowed to connect.") .version("1.7.0") @@ -2379,7 +2379,7 @@ object KyuubiConf { .createOptional val SERVER_LIMIT_BATCH_CONNECTIONS_PER_IPADDRESS: OptionalConfigEntry[Int] = - buildConf("kyuubi.server.batch.limit.connections.per.ipaddress") + buildConf("kyuubi.server.limit.batch.connections.per.ipaddress") .doc("Maximum kyuubi server batch connections per ipaddress." + " Any user exceeding this limit will not be allowed to connect.") .version("1.7.0") @@ -2388,7 +2388,7 @@ object KyuubiConf { .createOptional val SERVER_LIMIT_BATCH_CONNECTIONS_PER_USER_IPADDRESS: OptionalConfigEntry[Int] = - buildConf("kyuubi.server.batch.limit.connections.per.user.ipaddress") + buildConf("kyuubi.server.limit.batch.connections.per.user.ipaddress") .doc("Maximum kyuubi server batch connections per user:ipaddress combination." + " Any user-ipaddress exceeding this limit will not be allowed to connect.") .version("1.7.0") From fafd017df53bbd529f49b3d7c036363711d8ee02 Mon Sep 17 00:00:00 2001 From: Cheng Pan Date: Thu, 23 Feb 2023 11:45:58 +0800 Subject: [PATCH 106/760] [KYUUBI #4397] [BUILD] `build/dist` supports `--web-ui` ### _Why are the changes needed?_ ``` Usage: +----------------------------------------------------------------------------------------------+ | ./build/dist [--name ] [--tgz] [--web-ui] [--flink-provided] [--hive-provided] | | [--spark-provided] [--mvn ] | +----------------------------------------------------------------------------------------------+ name: - custom binary name, using project version if undefined tgz: - whether to make a whole bundled package web-ui: - whether to include web ui flink-provided: - whether to make a package without Flink binary hive-provided: - whether to make a package without Hive binary spark-provided: - whether to make a package without Spark binary mvn: - external maven executable location ``` ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [x] Add screenshots for manual tests if appropriate Create binary artifacts using `build/dist --tgz --web-ui` and run, then open `http://0.0.0.0:10099/ui` image - [ ] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4397 from pan3793/webui-build. Closes #4397 97901d63e [Cheng Pan] doc 37d5e2ad3 [Cheng Pan] mirror-cdn c5751dd5b [Cheng Pan] remove unused dep d308defb7 [Cheng Pan] nit 9abca4705 [Cheng Pan] nit c1d184afd [Cheng Pan] nit 7091d5bf5 [Cheng Pan] regex f0ac16b3c [Cheng Pan] [BUILD] `build/dist` support --web-ui Authored-by: Cheng Pan Signed-off-by: Cheng Pan --- .gitignore | 1 + .rat-excludes | 2 + bin/kyuubi | 16 ++++++-- build/dist | 25 ++++++++--- build/release/create-package.sh | 2 +- docs/develop_tools/distribution.md | 11 ++--- kyuubi-server/pom.xml | 41 +++++++++++++++++++ .../src/main/resources/dist/index.html | 28 +++++++++++++ .../server/KyuubiRestFrontendService.scala | 18 ++++++-- .../authentication/AuthenticationFilter.scala | 1 - kyuubi-server/web-ui/.gitignore | 1 + kyuubi-server/web-ui/README.md | 2 +- kyuubi-server/web-ui/index.html | 2 +- kyuubi-server/web-ui/package-lock.json | 4 +- kyuubi-server/web-ui/package.json | 2 +- kyuubi-server/web-ui/src/router/index.ts | 2 +- kyuubi-server/web-ui/vite.config.ts | 1 + pom.xml | 30 +++++++++++++- 18 files changed, 163 insertions(+), 26 deletions(-) create mode 100644 kyuubi-server/src/main/resources/dist/index.html diff --git a/.gitignore b/.gitignore index bb2df4fd0..9a115ab0a 100644 --- a/.gitignore +++ b/.gitignore @@ -56,6 +56,7 @@ hs_err_pid* spark-warehouse/ metastore_db derby.log +rest-audit.log **/dependency-reduced-pom.xml metrics/report.json metrics/.report.json.crc diff --git a/.rat-excludes b/.rat-excludes index 2906e7cea..7a841cf9c 100644 --- a/.rat-excludes +++ b/.rat-excludes @@ -51,6 +51,8 @@ build/scala-*/** **/metadata-store-schema*.sql **/*.derby.sql **/*.mysql.sql +**/node/** +**/web-ui/dist/** **/pnpm-lock.yaml **/node_modules/** **/gen/* diff --git a/bin/kyuubi b/bin/kyuubi index 414bdeb86..09c8e9373 100755 --- a/bin/kyuubi +++ b/bin/kyuubi @@ -87,10 +87,18 @@ if [[ -z "$KYUUBI_JAR_DIR" ]]; then fi fi -if [[ -z ${YARN_CONF_DIR} ]]; then - KYUUBI_CLASSPATH="${KYUUBI_JAR_DIR}/*:${KYUUBI_CONF_DIR}:${HADOOP_CONF_DIR}" -else - KYUUBI_CLASSPATH="${KYUUBI_JAR_DIR}/*:${KYUUBI_CONF_DIR}:${HADOOP_CONF_DIR}:${YARN_CONF_DIR}" +## Find the WebUI dist +if [[ -z "$KYUUBI_WEBUI_DIR" ]]; then + KYUUBI_WEBUI_DIR="$KYUUBI_HOME/web-ui" + if [[ ! -d ${KYUUBI_WEBUI_DIR} ]]; then + echo -e "\nCandidate Kyuubi WebUI dist $KYUUBI_WEBUI_DIR doesn't exist, searching development environment..." + KYUUBI_WEBUI_DIR="$KYUUBI_HOME/kyuubi-server/web-ui" + fi +fi + +KYUUBI_CLASSPATH="${KYUUBI_CONF_DIR}:${KYUUBI_WEBUI_DIR}:${KYUUBI_JAR_DIR}/*:${HADOOP_CONF_DIR}" +if [[ -n ${YARN_CONF_DIR} ]]; then + KYUUBI_CLASSPATH="${KYUUBI_CLASSPATH}:${YARN_CONF_DIR}" fi cmd="${RUNNER} ${KYUUBI_JAVA_OPTS} -cp ${KYUUBI_CLASSPATH} $CLASS" diff --git a/build/dist b/build/dist index 7b51886df..0620c7d08 100755 --- a/build/dist +++ b/build/dist @@ -31,6 +31,7 @@ set -x KYUUBI_HOME="$(cd "`dirname "$0"`/.."; pwd)" DISTDIR="$KYUUBI_HOME/dist" MAKE_TGZ=false +ENABLE_WEBUI=false FLINK_PROVIDED=false SPARK_PROVIDED=false HIVE_PROVIDED=false @@ -42,15 +43,16 @@ function usage { echo "./build/dist - Tool for making binary distributions of Kyuubi" echo "" echo "Usage:" - echo "+------------------------------------------------------------------------------------------------------+" - echo "| ./build/dist [--name ] [--tgz] [--flink-provided] [--spark-provided] [--hive-provided] |" - echo "| [--mvn ] |" - echo "+------------------------------------------------------------------------------------------------------+" + echo "+----------------------------------------------------------------------------------------------+" + echo "| ./build/dist [--name ] [--tgz] [--web-ui] [--flink-provided] [--hive-provided] |" + echo "| [--spark-provided] [--mvn ] |" + echo "+----------------------------------------------------------------------------------------------+" echo "name: - custom binary name, using project version if undefined" echo "tgz: - whether to make a whole bundled package" + echo "web-ui: - whether to include web ui" echo "flink-provided: - whether to make a package without Flink binary" - echo "spark-provided: - whether to make a package without Spark binary" echo "hive-provided: - whether to make a package without Hive binary" + echo "spark-provided: - whether to make a package without Spark binary" echo "mvn: - external maven executable location" echo "" } @@ -67,6 +69,9 @@ while (( "$#" )); do --tgz) MAKE_TGZ=true ;; + --web-ui) + ENABLE_WEBUI=true + ;; --flink-provided) FLINK_PROVIDED=true ;; @@ -212,6 +217,10 @@ fi MVN_DIST_OPT="-DskipTests" +if [[ "$ENABLE_WEBUI" == "true" ]]; then + MVN_DIST_OPT="$MVN_DIST_OPT -Pweb-ui" +fi + if [[ "$SPARK_PROVIDED" == "true" ]]; then MVN_DIST_OPT="$MVN_DIST_OPT -Pspark-provided" fi @@ -321,6 +330,12 @@ for SPARK_EXTENSION_VERSION in ${SPARK_EXTENSION_VERSIONS[@]}; do fi done +if [[ "$ENABLE_WEBUI" == "true" ]]; then + # Copy web ui dist + mkdir -p "$DISTDIR/web-ui" + cp -r "$KYUUBI_HOME/kyuubi-server/web-ui/dist" "$DISTDIR/web-ui/" +fi + if [[ "$FLINK_PROVIDED" != "true" ]]; then # Copy flink binary dist FLINK_BUILTIN="$(find "$KYUUBI_HOME/externals/kyuubi-download/target" -name 'flink-*' -type d)" diff --git a/build/release/create-package.sh b/build/release/create-package.sh index c98e7c0f8..28a89165e 100755 --- a/build/release/create-package.sh +++ b/build/release/create-package.sh @@ -75,7 +75,7 @@ package_binary() { echo "Creating binary release tarball ${BIN_TGZ_FILE}" - ${KYUUBI_DIR}/build/dist --tgz --spark-provided --flink-provided --hive-provided + ${KYUUBI_DIR}/build/dist --tgz --web-ui --spark-provided --flink-provided --hive-provided cp "${BIN_TGZ_FILE}" "${RELEASE_DIR}" diff --git a/docs/develop_tools/distribution.md b/docs/develop_tools/distribution.md index abc2ac91b..217f0a417 100644 --- a/docs/develop_tools/distribution.md +++ b/docs/develop_tools/distribution.md @@ -26,15 +26,16 @@ For more information on usage, run `./build/dist --help` ./build/dist - Tool for making binary distributions of Kyuubi Usage: -+------------------------------------------------------------------------------------------------------+ -| ./build/dist [--name ] [--tgz] [--flink-provided] [--spark-provided] [--hive-provided] | -| [--mvn ] | -+------------------------------------------------------------------------------------------------------+ ++----------------------------------------------------------------------------------------------+ +| ./build/dist [--name ] [--tgz] [--web-ui] [--flink-provided] [--hive-provided] | +| [--spark-provided] [--mvn ] | ++----------------------------------------------------------------------------------------------+ name: - custom binary name, using project version if undefined tgz: - whether to make a whole bundled package +web-ui: - whether to include web ui flink-provided: - whether to make a package without Flink binary -spark-provided: - whether to make a package without Spark binary hive-provided: - whether to make a package without Hive binary +spark-provided: - whether to make a package without Spark binary mvn: - external maven executable location ``` diff --git a/kyuubi-server/pom.xml b/kyuubi-server/pom.xml index 53b8a9601..aebce5dda 100644 --- a/kyuubi-server/pom.xml +++ b/kyuubi-server/pom.xml @@ -543,6 +543,47 @@ + + + com.github.eirslett + frontend-maven-plugin + + web-ui + + + + install node and pnpm + + install-node-and-pnpm + + + ${webui.skip} + + + + pnpm install + + pnpm + + generate-resources + + ${webui.skip} + install + + + + pnpm run build + + pnpm + + package + + ${webui.skip} + run build + + + + target/scala-${scala.binary.version}/classes target/scala-${scala.binary.version}/test-classes diff --git a/kyuubi-server/src/main/resources/dist/index.html b/kyuubi-server/src/main/resources/dist/index.html new file mode 100644 index 000000000..ab54fc14a --- /dev/null +++ b/kyuubi-server/src/main/resources/dist/index.html @@ -0,0 +1,28 @@ + + + + + + + Apache Kyuubi Dashboard + + +
    This is a dummy page for development.
    + + diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/KyuubiRestFrontendService.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/KyuubiRestFrontendService.scala index 29f4cf304..7019d8a6a 100644 --- a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/KyuubiRestFrontendService.scala +++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/KyuubiRestFrontendService.scala @@ -26,14 +26,14 @@ import javax.ws.rs.core.Response.Status import com.google.common.annotations.VisibleForTesting import org.apache.hadoop.conf.Configuration -import org.eclipse.jetty.servlet.FilterHolder +import org.eclipse.jetty.servlet.{ErrorPageErrorHandler, FilterHolder} import org.apache.kyuubi.{KyuubiException, Utils} import org.apache.kyuubi.config.KyuubiConf -import org.apache.kyuubi.config.KyuubiConf.{FRONTEND_REST_BIND_HOST, FRONTEND_REST_BIND_PORT, FRONTEND_REST_MAX_WORKER_THREADS, METADATA_RECOVERY_THREADS} +import org.apache.kyuubi.config.KyuubiConf._ import org.apache.kyuubi.server.api.v1.ApiRootResource import org.apache.kyuubi.server.http.authentication.{AuthenticationFilter, KyuubiHttpAuthenticationFactory} -import org.apache.kyuubi.server.ui.JettyServer +import org.apache.kyuubi.server.ui.{JettyServer, JettyUtils} import org.apache.kyuubi.service.{AbstractFrontendService, Serverable, Service, ServiceUtils} import org.apache.kyuubi.service.authentication.KyuubiAuthenticationFactory import org.apache.kyuubi.session.{KyuubiSessionManager, SessionHandle} @@ -95,6 +95,18 @@ class KyuubiRestFrontendService(override val serverable: Serverable) server.addRedirectHandler("/docs", "/swagger/") server.addRedirectHandler("/docs/", "/swagger/") server.addRedirectHandler("/swagger", "/swagger/") + + installWebUI() + } + + private def installWebUI(): Unit = { + val servletHandler = JettyUtils.createStaticHandler("dist", "/ui") + // HTML5 Web History Mode requires redirect any url path under Web UI Servlet to the main page. + // See more details at https://router.vuejs.org/guide/essentials/history-mode.html#html5-mode + val errorHandler = new ErrorPageErrorHandler + errorHandler.addErrorPage(404, "/") + servletHandler.setErrorHandler(errorHandler) + server.addHandler(servletHandler) } private def startBatchChecker(): Unit = { diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/http/authentication/AuthenticationFilter.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/http/authentication/AuthenticationFilter.scala index 740937d8e..3c4065a7b 100644 --- a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/http/authentication/AuthenticationFilter.scala +++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/http/authentication/AuthenticationFilter.scala @@ -79,7 +79,6 @@ class AuthenticationFilter(conf: KyuubiConf) extends Filter with Logging { override def init(filterConfig: FilterConfig): Unit = { initAuthHandlers() - super.init(filterConfig) } private[kyuubi] def getMatchedHandler(authorization: String): Option[AuthenticationHandler] = { diff --git a/kyuubi-server/web-ui/.gitignore b/kyuubi-server/web-ui/.gitignore index be5bdb236..c6cab4b86 100644 --- a/kyuubi-server/web-ui/.gitignore +++ b/kyuubi-server/web-ui/.gitignore @@ -14,6 +14,7 @@ # limitations under the License. .DS_Store +node node_modules /dist /coverage diff --git a/kyuubi-server/web-ui/README.md b/kyuubi-server/web-ui/README.md index 6d808ecde..f93373414 100644 --- a/kyuubi-server/web-ui/README.md +++ b/kyuubi-server/web-ui/README.md @@ -15,7 +15,7 @@ npm install ### Development Project -To do this you can change the VITE_APP_DEV_WEB_URL parameter variable as the service url in `.env.development` in the project root directory, such as http://127.0. 0.1:8090 +To do this you can change the VITE_APP_DEV_WEB_URL parameter variable as the service url in `.env.development` in the project root directory, such as http://127.0.0.1:8090 ```shell npm run dev diff --git a/kyuubi-server/web-ui/index.html b/kyuubi-server/web-ui/index.html index bd4f50672..2c4579eb0 100644 --- a/kyuubi-server/web-ui/index.html +++ b/kyuubi-server/web-ui/index.html @@ -22,7 +22,7 @@ - Vite + Vue + TS + Apache Kyuubi Dashboard
    diff --git a/kyuubi-server/web-ui/package-lock.json b/kyuubi-server/web-ui/package-lock.json index 77a3991e5..0a2feeba1 100644 --- a/kyuubi-server/web-ui/package-lock.json +++ b/kyuubi-server/web-ui/package-lock.json @@ -1,12 +1,12 @@ { "name": "kyuubi-ui", - "version": "1.7.0-SNAPSHOT", + "version": "1.8.0-SNAPSHOT", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "kyuubi-ui", - "version": "1.7.0-SNAPSHOT", + "version": "1.8.0-SNAPSHOT", "dependencies": { "@element-plus/icons-vue": "^2.0.9", "axios": "^0.27.2", diff --git a/kyuubi-server/web-ui/package.json b/kyuubi-server/web-ui/package.json index 2ae37cdb9..63fdc7221 100644 --- a/kyuubi-server/web-ui/package.json +++ b/kyuubi-server/web-ui/package.json @@ -1,7 +1,7 @@ { "name": "kyuubi-ui", "private": true, - "version": "1.7.0-SNAPSHOT", + "version": "1.8.0-SNAPSHOT", "type": "module", "scripts": { "dev": "vue-tsc --noEmit && vite --port 9090", diff --git a/kyuubi-server/web-ui/src/router/index.ts b/kyuubi-server/web-ui/src/router/index.ts index 836dbc5d0..207a22d56 100644 --- a/kyuubi-server/web-ui/src/router/index.ts +++ b/kyuubi-server/web-ui/src/router/index.ts @@ -44,7 +44,7 @@ const routes = [ ] const router = createRouter({ - history: createWebHistory(), + history: createWebHistory('/ui'), routes }) diff --git a/kyuubi-server/web-ui/vite.config.ts b/kyuubi-server/web-ui/vite.config.ts index 1b3e99a12..92af99741 100644 --- a/kyuubi-server/web-ui/vite.config.ts +++ b/kyuubi-server/web-ui/vite.config.ts @@ -20,6 +20,7 @@ import Vue from '@vitejs/plugin-vue' import path from 'path' export default defineConfig({ + base: '/ui/', plugins: [Vue()], resolve: { alias: [ diff --git a/pom.xml b/pom.xml index 1a583eb78..ecb397a03 100644 --- a/pom.xml +++ b/pom.xml @@ -202,6 +202,11 @@ 1.1 3.4.14 + + true + v16.19.1 + v7.27.1 + 2.12 2.2.0 @@ -215,6 +220,7 @@ 1.6.8 1.6.1 + 1.12.1 4.8.0 3.0.0-M8 2.2.0 @@ -1991,6 +1997,9 @@ ${project.basedir}/spark-warehouse + + ${project.basedir}/web-ui/dist + @@ -2144,6 +2153,16 @@ + + + com.github.eirslett + frontend-maven-plugin + ${maven.plugin.frontend.version} + + ${node.version} + ${pnpm.version} + + @@ -2228,9 +2247,11 @@ mirror-cdn - + https://dlcdn.apache.org + https://npmmirror.com/mirrors/node/ + https://registry.npmmirror.com/pnpm/-/ @@ -2421,6 +2442,13 @@ + + web-ui + + false + + + apache-release From 3016f431bf0d4b881d8a1979aa13b23432a58468 Mon Sep 17 00:00:00 2001 From: Fu Chen Date: Thu, 23 Feb 2023 17:04:29 +0800 Subject: [PATCH 107/760] [KYUUBI #4402] [ARROW] Make arrow-based query metrics trackable in SQL UI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### _Why are the changes needed?_ Currently, the SQL metrics are missing from the SQL UI tab, this is because we mistakenly bound QueryExecution in [PR-4392](https://github.com/apache/kyuubi/pull/4392), before this PR, it was `resultDF.queryExecution` that was bound to `SQLExecution.withNewExecutionId()`, But the executed Dataset is `resultDF.select(cols: _*)`, this PR passed the correct QueryExecution `resultDF.select(cols: _*).queryExecution` to solve this problem. ```sql set kyuubi.operation.result.format=arrow; select 1; ``` Before this PR: ![截屏2023-02-23 下午1 47 34](https://user-images.githubusercontent.com/8537877/220832155-4277ccf7-1cfe-40db-a6e5-e1ed4a6d2e29.png) After this PR: ![截屏2023-02-23 下午2 07 23](https://user-images.githubusercontent.com/8537877/220832184-b9871b4b-f408-42ac-91ca-30e5cd503b24.png) ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [x] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4402 from cfmcgrady/arrow-metrics. Closes #4402 e0cde3b1 [Fu Chen] fix style b35cbfdc [Fu Chen] fix 542414ef [Fu Chen] make arrow-based query metrics trackable in SQL UI Authored-by: Fu Chen Signed-off-by: Cheng Pan --- .../spark/operation/ExecuteStatement.scala | 79 +++++++++++-------- .../SparkArrowbasedOperationSuite.scala | 51 ++++++++++-- 2 files changed, 88 insertions(+), 42 deletions(-) diff --git a/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/operation/ExecuteStatement.scala b/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/operation/ExecuteStatement.scala index 6ebcce377..fa517a8b1 100644 --- a/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/operation/ExecuteStatement.scala +++ b/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/operation/ExecuteStatement.scala @@ -21,6 +21,7 @@ import java.util.concurrent.RejectedExecutionException import scala.collection.JavaConverters._ +import org.apache.spark.rdd.RDD import org.apache.spark.sql.DataFrame import org.apache.spark.sql.execution.SQLExecution import org.apache.spark.sql.kyuubi.SparkDatasetHelper @@ -75,29 +76,6 @@ class ExecuteStatement( resultDF.take(maxRows) } - protected def collectAsIterator(resultDF: DataFrame): FetchIterator[_] = { - val resultMaxRows = spark.conf.getOption(OPERATION_RESULT_MAX_ROWS.key).map(_.toInt) - .getOrElse(session.sessionManager.getConf.get(OPERATION_RESULT_MAX_ROWS)) - if (incrementalCollect) { - if (resultMaxRows > 0) { - warn(s"Ignore ${OPERATION_RESULT_MAX_ROWS.key} on incremental collect mode.") - } - info("Execute in incremental collect mode") - new IterableFetchIterator[Any](new Iterable[Any] { - override def iterator: Iterator[Any] = incrementalCollectResult(resultDF) - }) - } else { - val internalArray = if (resultMaxRows <= 0) { - info("Execute in full collect mode") - fullCollectResult(resultDF) - } else { - info(s"Execute with max result rows[$resultMaxRows]") - takeResult(resultDF, resultMaxRows) - } - new ArrayFetchIterator(internalArray) - } - } - protected def executeStatement(): Unit = withLocalProperties { try { setState(OperationState.RUNNING) @@ -163,14 +141,33 @@ class ExecuteStatement( } } - def convertComplexType(df: DataFrame): DataFrame = { - SparkDatasetHelper.convertTopLevelComplexTypeToHiveString(df, timestampAsString) - } - override def getResultSetMetadataHints(): Seq[String] = Seq( s"__kyuubi_operation_result_format__=$resultFormat", s"__kyuubi_operation_result_arrow_timestampAsString__=$timestampAsString") + + private def collectAsIterator(resultDF: DataFrame): FetchIterator[_] = { + val resultMaxRows = spark.conf.getOption(OPERATION_RESULT_MAX_ROWS.key).map(_.toInt) + .getOrElse(session.sessionManager.getConf.get(OPERATION_RESULT_MAX_ROWS)) + if (incrementalCollect) { + if (resultMaxRows > 0) { + warn(s"Ignore ${OPERATION_RESULT_MAX_ROWS.key} on incremental collect mode.") + } + info("Execute in incremental collect mode") + new IterableFetchIterator[Any](new Iterable[Any] { + override def iterator: Iterator[Any] = incrementalCollectResult(resultDF) + }) + } else { + val internalArray = if (resultMaxRows <= 0) { + info("Execute in full collect mode") + fullCollectResult(resultDF) + } else { + info(s"Execute with max result rows[$resultMaxRows]") + takeResult(resultDF, resultMaxRows) + } + new ArrayFetchIterator(internalArray) + } + } } class ArrowBasedExecuteStatement( @@ -182,30 +179,42 @@ class ArrowBasedExecuteStatement( extends ExecuteStatement(session, statement, shouldRunAsync, queryTimeout, incrementalCollect) { override protected def incrementalCollectResult(resultDF: DataFrame): Iterator[Any] = { - SparkDatasetHelper.toArrowBatchRdd(convertComplexType(resultDF)).toLocalIterator + collectAsArrow(convertComplexType(resultDF)) { rdd => + rdd.toLocalIterator + } } override protected def fullCollectResult(resultDF: DataFrame): Array[_] = { - SparkDatasetHelper.toArrowBatchRdd(convertComplexType(resultDF)).collect() + collectAsArrow(convertComplexType(resultDF)) { rdd => + rdd.collect() + } } override protected def takeResult(resultDF: DataFrame, maxRows: Int): Array[_] = { // this will introduce shuffle and hurt performance val limitedResult = resultDF.limit(maxRows) - SparkDatasetHelper.toArrowBatchRdd(convertComplexType(limitedResult)).collect() + collectAsArrow(convertComplexType(limitedResult)) { rdd => + rdd.collect() + } } /** - * assign a new execution id for arrow-based operation. + * refer to org.apache.spark.sql.Dataset#withAction(), assign a new execution id for arrow-based + * operation, so that we can track the arrow-based queries on the UI tab. */ - override protected def collectAsIterator(resultDF: DataFrame): FetchIterator[_] = { - SQLExecution.withNewExecutionId(resultDF.queryExecution, Some("collectAsArrow")) { - resultDF.queryExecution.executedPlan.resetMetrics() - super.collectAsIterator(resultDF) + private def collectAsArrow[T](df: DataFrame)(action: RDD[Array[Byte]] => T): T = { + SQLExecution.withNewExecutionId(df.queryExecution, Some("collectAsArrow")) { + df.queryExecution.executedPlan.resetMetrics() + action(SparkDatasetHelper.toArrowBatchRdd(df)) } } override protected def isArrowBasedOperation: Boolean = true override val resultFormat = "arrow" + + private def convertComplexType(df: DataFrame): DataFrame = { + SparkDatasetHelper.convertTopLevelComplexTypeToHiveString(df, timestampAsString) + } + } diff --git a/externals/kyuubi-spark-sql-engine/src/test/scala/org/apache/kyuubi/engine/spark/operation/SparkArrowbasedOperationSuite.scala b/externals/kyuubi-spark-sql-engine/src/test/scala/org/apache/kyuubi/engine/spark/operation/SparkArrowbasedOperationSuite.scala index 30cdeca5a..ae6237bb5 100644 --- a/externals/kyuubi-spark-sql-engine/src/test/scala/org/apache/kyuubi/engine/spark/operation/SparkArrowbasedOperationSuite.scala +++ b/externals/kyuubi-spark-sql-engine/src/test/scala/org/apache/kyuubi/engine/spark/operation/SparkArrowbasedOperationSuite.scala @@ -103,22 +103,41 @@ class SparkArrowbasedOperationSuite extends WithSparkSQLEngine with SparkDataTyp withJdbcStatement() { statement => // since all the new sessions have their owner listener bus, we should register the listener // in the current session. - SparkSQLEngine.currentEngine.get - .backendService - .sessionManager - .allSessions() - .foreach(_.asInstanceOf[SparkSessionImpl].spark.listenerManager.register(listener)) + registerListener(listener) val result = statement.executeQuery("select 1 as c1") assert(result.next()) assert(result.getInt("c1") == 1) } - KyuubiSparkContextHelper.waitListenerBus(spark) - spark.listenerManager.unregister(listener) + unregisterListener(listener) assert(plan.isInstanceOf[Project]) } + test("arrow-based query metrics") { + var queryExecution: QueryExecution = null + + val listener = new QueryExecutionListener { + override def onSuccess(funcName: String, qe: QueryExecution, durationNs: Long): Unit = { + queryExecution = qe + } + override def onFailure(funcName: String, qe: QueryExecution, exception: Exception): Unit = {} + } + withJdbcStatement() { statement => + registerListener(listener) + val result = statement.executeQuery("select 1 as c1") + assert(result.next()) + assert(result.getInt("c1") == 1) + } + + KyuubiSparkContextHelper.waitListenerBus(spark) + unregisterListener(listener) + + val metrics = queryExecution.executedPlan.collectLeaves().head.metrics + assert(metrics.contains("numOutputRows")) + assert(metrics("numOutputRows").value === 1) + } + private def checkResultSetFormat(statement: Statement, expectFormat: String): Unit = { val query = s""" @@ -140,4 +159,22 @@ class SparkArrowbasedOperationSuite extends WithSparkSQLEngine with SparkDataTyp assert(resultSet.next()) assert(resultSet.getString("col") === expect) } + + private def registerListener(listener: QueryExecutionListener): Unit = { + // since all the new sessions have their owner listener bus, we should register the listener + // in the current session. + SparkSQLEngine.currentEngine.get + .backendService + .sessionManager + .allSessions() + .foreach(_.asInstanceOf[SparkSessionImpl].spark.listenerManager.register(listener)) + } + + private def unregisterListener(listener: QueryExecutionListener): Unit = { + SparkSQLEngine.currentEngine.get + .backendService + .sessionManager + .allSessions() + .foreach(_.asInstanceOf[SparkSessionImpl].spark.listenerManager.unregister(listener)) + } } From 86dc59c4cf878df78dd1c0be65e281946641fc8c Mon Sep 17 00:00:00 2001 From: fwang12 Date: Fri, 24 Feb 2023 08:27:23 +0800 Subject: [PATCH 108/760] [KYUUBI #3876][FOLLOWUP] Update the rest api docs for open session response ### _Why are the changes needed?_ ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [ ] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4396 from turboFei/rest_doc. Closes #3876 de23e30e5 [fwang12] nit 41bd4ca4e [fwang12] save: Authored-by: fwang12 Signed-off-by: fwang12 --- docs/client/rest/rest_api.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/client/rest/rest_api.md b/docs/client/rest/rest_api.md index 02a914277..59e2d8535 100644 --- a/docs/client/rest/rest_api.md +++ b/docs/client/rest/rest_api.md @@ -99,9 +99,10 @@ Create a session #### Response Body -| Name | Description | Type | -|:-----------|:------------------------------|:-------| -| identifier | The session handle identifier | String | +| Name | Description | Type | +|:---------------|:---------------------------------------------------------------------------------------------------|:-------| +| identifier | The session handle identifier | String | +| kyuubiInstance | The Kyuubi instance that holds the session and to call for the following operations in the session | String | ### DELETE /sessions/${sessionHandle} From 0175e4649a66c7e0e7cda5914e730382c5c763b0 Mon Sep 17 00:00:00 2001 From: Fu Chen Date: Fri, 24 Feb 2023 13:20:13 +0800 Subject: [PATCH 109/760] [KYUUBI #4407] Adapt slf4j-2.x ### _Why are the changes needed?_ to adapt slf4j-2.x, Spark bumped the slf4j to version 2.0.3 in https://github.com/apache/spark/pull/37844. https://github.com/apache/kyuubi/pull/4381#discussion_r1116449560 ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [ ] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4407 from cfmcgrady/spark34-slf4j. Closes #4407 ecf1488c [Fu Chen] unused import f4098b97 [Fu Chen] fix ut Authored-by: Fu Chen Signed-off-by: Cheng Pan --- .../src/main/scala/org/apache/kyuubi/Logging.scala | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/kyuubi-common/src/main/scala/org/apache/kyuubi/Logging.scala b/kyuubi-common/src/main/scala/org/apache/kyuubi/Logging.scala index d70df0952..1df598132 100644 --- a/kyuubi-common/src/main/scala/org/apache/kyuubi/Logging.scala +++ b/kyuubi-common/src/main/scala/org/apache/kyuubi/Logging.scala @@ -22,7 +22,6 @@ import org.apache.logging.log4j.core.{Logger => Log4jLogger, LoggerContext} import org.apache.logging.log4j.core.config.DefaultConfiguration import org.slf4j.{Logger, LoggerFactory} import org.slf4j.bridge.SLF4JBridgeHandler -import org.slf4j.impl.StaticLoggerBinder import org.apache.kyuubi.util.ClassUtils @@ -117,16 +116,16 @@ object Logging { // This distinguishes the log4j 1.2 binding, currently // org.slf4j.impl.Log4jLoggerFactory, from the log4j 2.0 binding, currently // org.apache.logging.slf4j.Log4jLoggerFactory - val binderClass = StaticLoggerBinder.getSingleton.getLoggerFactoryClassStr - "org.slf4j.impl.Log4jLoggerFactory".equals(binderClass) + "org.slf4j.impl.Log4jLoggerFactory" + .equals(LoggerFactory.getILoggerFactory.getClass.getName) } private[kyuubi] def isLog4j2: Boolean = { // This distinguishes the log4j 1.2 binding, currently // org.slf4j.impl.Log4jLoggerFactory, from the log4j 2.0 binding, currently // org.apache.logging.slf4j.Log4jLoggerFactory - val binderClass = StaticLoggerBinder.getSingleton.getLoggerFactoryClassStr - "org.apache.logging.slf4j.Log4jLoggerFactory".equals(binderClass) + "org.apache.logging.slf4j.Log4jLoggerFactory" + .equals(LoggerFactory.getILoggerFactory.getClass.getName) } /** From b39caed98f56d5d99dca2a07eb3aa1c56d2ffb88 Mon Sep 17 00:00:00 2001 From: Fu Chen Date: Fri, 24 Feb 2023 13:35:36 +0800 Subject: [PATCH 110/760] =?UTF-8?q?[KYUUBI=20#4406]=20Revert=20"[KYUUBI=20?= =?UTF-8?q?#4305][Bug]=20Backport=20HIVE-15820:=20comment=20at=20the=20hea?= =?UTF-8?q?d=20=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit …of beeline -e" This reverts commit c489e29697c73e3eec96aa3ff602e40f9a2c798a. ### _Why are the changes needed?_ https://github.com/apache/kyuubi/pull/4333#issuecomment-1441438302 ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [ ] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4406 from cfmcgrady/revert-4333. Closes #4406 Closes #4305 c7fff72a [Fu Chen] Revert "[KYUUBI #4305][Bug] Backport HIVE-15820: comment at the head of beeline -e" Authored-by: Fu Chen Signed-off-by: Cheng Pan --- .../java/org/apache/hive/beeline/KyuubiBeeLine.java | 6 ------ .../org/apache/hive/beeline/KyuubiCommands.java | 6 ++---- .../org/apache/hive/beeline/KyuubiBeeLineTest.java | 13 ------------- 3 files changed, 2 insertions(+), 23 deletions(-) diff --git a/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/KyuubiBeeLine.java b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/KyuubiBeeLine.java index 92b96ccb4..7ca767148 100644 --- a/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/KyuubiBeeLine.java +++ b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/KyuubiBeeLine.java @@ -29,7 +29,6 @@ import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; -import org.apache.hive.common.util.HiveStringUtils; public class KyuubiBeeLine extends BeeLine { public static final String KYUUBI_BEELINE_DEFAULT_JDBC_DRIVER = @@ -193,9 +192,4 @@ int initArgs(String[] args) { } return code; } - - @Override - boolean dispatch(String line) { - return super.dispatch(HiveStringUtils.removeComments(line)); - } } diff --git a/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/KyuubiCommands.java b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/KyuubiCommands.java index 1a15638f1..aaa32739a 100644 --- a/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/KyuubiCommands.java +++ b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/KyuubiCommands.java @@ -23,7 +23,6 @@ import java.sql.*; import java.util.*; import org.apache.hive.beeline.logs.KyuubiBeelineInPlaceUpdateStream; -import org.apache.hive.common.util.HiveStringUtils; import org.apache.kyuubi.jdbc.hive.KyuubiStatement; import org.apache.kyuubi.jdbc.hive.Utils; import org.apache.kyuubi.jdbc.hive.logs.InPlaceUpdateStream; @@ -500,7 +499,7 @@ public boolean connect(Properties props) throws IOException { @Override public String handleMultiLineCmd(String line) throws IOException { - line = HiveStringUtils.removeComments(line); + int[] startQuote = {-1}; Character mask = (System.getProperty("jline.terminal", "").equals("jline.UnsupportedTerminal")) ? null @@ -531,8 +530,7 @@ public String handleMultiLineCmd(String line) throws IOException { if (extra == null) { // it happens when using -f and the line of cmds does not end with ; break; } - extra = HiveStringUtils.removeComments(extra); - if (extra != null && !extra.isEmpty()) { + if (!extra.isEmpty()) { line += "\n" + extra; } } diff --git a/kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/KyuubiBeeLineTest.java b/kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/KyuubiBeeLineTest.java index d571d9362..b144c95c6 100644 --- a/kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/KyuubiBeeLineTest.java +++ b/kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/KyuubiBeeLineTest.java @@ -29,17 +29,4 @@ public void testKyuubiBeelineWithoutArgs() { int result = kyuubiBeeLine.initArgs(new String[0]); assertEquals(0, result); } - - @Test - public void testKyuubiBeelineComment() { - KyuubiBeeLine kyuubiBeeLine = new KyuubiBeeLine(); - int result = kyuubiBeeLine.initArgsFromCliVars(new String[] {"-e", "--comment show database;"}); - assertEquals(0, result); - result = kyuubiBeeLine.initArgsFromCliVars(new String[] {"-e", "--comment\n show database;"}); - assertEquals(1, result); - result = - kyuubiBeeLine.initArgsFromCliVars( - new String[] {"-e", "--comment line 1 \n --comment line 2 \n show database;"}); - assertEquals(1, result); - } } From 0c88e7564fcc90d9155e4c85bee30d647a125b32 Mon Sep 17 00:00:00 2001 From: Luning Wang Date: Fri, 24 Feb 2023 13:46:15 +0800 Subject: [PATCH 111/760] [KYUUBI #4379][BUILD] Exclude python virtual environments in the spotless maven plugin ### _Why are the changes needed?_ Exclude python virtual environments in the spotless maven plugin ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [x] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4384 from a49a/exclude-py-check. Closes #4379 5f7db6a0 [Luning Wang] change path more general a2c5abf2 [Luning Wang] [KYUUBI #4379][BUILD] Exclude python virtual environments in the spotless maven plugin Authored-by: Luning Wang Signed-off-by: Cheng Pan --- pom.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pom.xml b/pom.xml index ecb397a03..2ebed48b3 100644 --- a/pom.xml +++ b/pom.xml @@ -2083,6 +2083,9 @@ */README.md docker/playground/README.md + + docs/*/lib/python*/**/*.md + ${flexmark.version} From 427771017462a2e32f87296d3215d90b774736d0 Mon Sep 17 00:00:00 2001 From: odone Date: Fri, 24 Feb 2023 15:37:08 +0800 Subject: [PATCH 112/760] [KYUUBI #4393] [Kyuubi #4332] Fix some bugs with `Groupby` and `CacheTable` close #4332 ### _Why are the changes needed?_ For the case where the table name has been resolved and an `Expand` logical plan exists ``` InsertIntoHiveTable `default`.`t1`, org.apache.hadoop.hive.serde2.lazy.LazySimpleSerDe, false, false, [a, b] +- Aggregate [a#0], [a#0, ansi_cast((count(if ((gid#9 = 1)) spark_catalog.default.t2.`b`#10 else null) * count(if ((gid#9 = 2)) spark_catalog.default.t2.`c`#11 else null)) as string) AS b#8] +- Aggregate [a#0, spark_catalog.default.t2.`b`#10, spark_catalog.default.t2.`c`#11, gid#9], [a#0, spark_catalog.default.t2.`b`#10, spark_catalog.default.t2.`c`#11, gid#9] +- Expand [ArrayBuffer(a#0, b#1, null, 1), ArrayBuffer(a#0, null, c#2, 2)], [a#0, spark_catalog.default.t2.`b`#10, spark_catalog.default.t2.`c`#11, gid#9] +- HiveTableRelation [`default`.`t2`, org.apache.hadoop.hive.serde2.lazy.LazySimpleSerDe, Data Cols: [a#0, b#1, c#2], Partition Cols: []] ``` For the case `CacheTable` with `window` function ``` InsertIntoHiveTable `default`.`t1`, org.apache.hadoop.hive.serde2.lazy.LazySimpleSerDe, true, false, [a, b] +- Project [a#98, b#99] +- InMemoryRelation [a#98, b#99, rank#100], StorageLevel(disk, memory, deserialized, 1 replicas) +- *(2) Filter (isnotnull(rank#4) AND (rank#4 = 1)) +- Window [row_number() windowspecdefinition(a#9, b#10 ASC NULLS FIRST, specifiedwindowframe(RowFrame, unboundedpreceding$(), currentrow$())) AS rank#4], [a#9], [b#10 ASC NULLS FIRST] +- *(1) Sort [a#9 ASC NULLS FIRST, b#10 ASC NULLS FIRST], false, 0 +- Exchange hashpartitioning(a#9, 200), ENSURE_REQUIREMENTS, [id=#38] +- Scan hive default.t2 [a#9, b#10], HiveTableRelation [`default`.`t2`, org.apache.hadoop.hive.serde2.lazy.LazySimpleSerDe, Data Cols: [a#9, b#10], Partition Cols: []] ``` ### _How was this patch tested?_ - [x] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [x] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4393 from iodone/kyuubi-4332. Closes #4393 d2afdabd [odone] fix cache table bug 443af798 [odone] fix some bugs with groupby Authored-by: odone Signed-off-by: ulyssesyou --- .../helper/SparkSQLLineageParseHelper.scala | 39 +++++- .../SparkSQLLineageParserHelperSuite.scala | 119 ++++++++++++++++++ 2 files changed, 155 insertions(+), 3 deletions(-) diff --git a/extensions/spark/kyuubi-spark-lineage/src/main/scala/org/apache/kyuubi/plugin/lineage/helper/SparkSQLLineageParseHelper.scala b/extensions/spark/kyuubi-spark-lineage/src/main/scala/org/apache/kyuubi/plugin/lineage/helper/SparkSQLLineageParseHelper.scala index f70e09126..a58653113 100644 --- a/extensions/spark/kyuubi-spark-lineage/src/main/scala/org/apache/kyuubi/plugin/lineage/helper/SparkSQLLineageParseHelper.scala +++ b/extensions/spark/kyuubi-spark-lineage/src/main/scala/org/apache/kyuubi/plugin/lineage/helper/SparkSQLLineageParseHelper.scala @@ -25,8 +25,7 @@ import org.apache.spark.sql.SparkSession import org.apache.spark.sql.catalyst.TableIdentifier import org.apache.spark.sql.catalyst.analysis.{NamedRelation, PersistedView, ViewType} import org.apache.spark.sql.catalyst.catalog.{CatalogStorageFormat, CatalogTable, HiveTableRelation} -import org.apache.spark.sql.catalyst.expressions.{Alias, Attribute, AttributeSet, Expression, NamedExpression} -import org.apache.spark.sql.catalyst.expressions.ScalarSubquery +import org.apache.spark.sql.catalyst.expressions.{Alias, Attribute, AttributeSet, Expression, NamedExpression, ScalarSubquery} import org.apache.spark.sql.catalyst.expressions.aggregate.Count import org.apache.spark.sql.catalyst.plans.{LeftAnti, LeftSemi} import org.apache.spark.sql.catalyst.plans.logical._ @@ -128,7 +127,7 @@ trait LineageParser { exp.toAttribute, if (!containsCountAll(exp.child)) references else references + exp.toAttribute.withName(AGGREGATE_COUNT_COLUMN_IDENTIFIER)) - case a: Attribute => a -> a.references + case a: Attribute => a -> AttributeSet(a) } ListMap(exps: _*) } @@ -149,6 +148,9 @@ trait LineageParser { attr.withQualifier(attr.qualifier.init) case attr if attr.name.equalsIgnoreCase(AGGREGATE_COUNT_COLUMN_IDENTIFIER) => attr.withQualifier(qualifier) + case attr if isNameWithQualifier(attr, qualifier) => + val newName = attr.name.split('.').last.stripPrefix("`").stripSuffix("`") + attr.withName(newName).withQualifier(qualifier) }) } } else { @@ -160,6 +162,12 @@ trait LineageParser { } } + private def isNameWithQualifier(attr: Attribute, qualifier: Seq[String]): Boolean = { + val nameTokens = attr.name.split('.') + val namespace = nameTokens.init.mkString(".") + nameTokens.length > 1 && namespace.endsWith(qualifier.mkString(".")) + } + private def mergeRelationColumnLineage( parentColumnsLineage: AttributeMap[AttributeSet], relationOutput: Seq[Attribute], @@ -327,6 +335,31 @@ trait LineageParser { joinColumnsLineage(parentColumnsLineage, getSelectColumnLineage(p.aggregateExpressions)) p.children.map(extractColumnsLineage(_, nextColumnsLineage)).reduce(mergeColumnsLineage) + case p: Expand => + val references = + p.projections.transpose.map(_.flatMap(x => x.references)).map(AttributeSet(_)) + + val childColumnsLineage = ListMap(p.output.zip(references): _*) + val nextColumnsLineage = + joinColumnsLineage(parentColumnsLineage, childColumnsLineage) + p.children.map(extractColumnsLineage(_, nextColumnsLineage)).reduce(mergeColumnsLineage) + + case p: Window => + val windowColumnsLineage = + ListMap(p.windowExpressions.map(exp => (exp.toAttribute, exp.references)): _*) + + val nextColumnsLineage = if (parentColumnsLineage.isEmpty) { + ListMap(p.child.output.map(attr => (attr, attr.references)): _*) ++ windowColumnsLineage + } else { + parentColumnsLineage.map { + case (k, _) if windowColumnsLineage.contains(k) => + k -> windowColumnsLineage(k) + case (k, attrs) => + k -> AttributeSet(attrs.flatten(attr => + windowColumnsLineage.getOrElse(attr, AttributeSet(attr)))) + } + } + p.children.map(extractColumnsLineage(_, nextColumnsLineage)).reduce(mergeColumnsLineage) case p: Join => p.joinType match { case LeftSemi | LeftAnti => diff --git a/extensions/spark/kyuubi-spark-lineage/src/test/scala/org/apache/kyuubi/plugin/lineage/helper/SparkSQLLineageParserHelperSuite.scala b/extensions/spark/kyuubi-spark-lineage/src/test/scala/org/apache/kyuubi/plugin/lineage/helper/SparkSQLLineageParserHelperSuite.scala index 6652be9ea..050f3ddc9 100644 --- a/extensions/spark/kyuubi-spark-lineage/src/test/scala/org/apache/kyuubi/plugin/lineage/helper/SparkSQLLineageParserHelperSuite.scala +++ b/extensions/spark/kyuubi-spark-lineage/src/test/scala/org/apache/kyuubi/plugin/lineage/helper/SparkSQLLineageParserHelperSuite.scala @@ -1094,6 +1094,125 @@ class SparkSQLLineageParserHelperSuite extends KyuubiFunSuite } } + test("test group by") { + withTable("t1", "t2", "v2_catalog.db.t1", "v2_catalog.db.t2") { _ => + spark.sql("CREATE TABLE t1 (a string, b string, c string) USING hive") + spark.sql("CREATE TABLE t2 (a string, b string, c string) USING hive") + spark.sql("CREATE TABLE v2_catalog.db.t1 (a string, b string, c string)") + spark.sql("CREATE TABLE v2_catalog.db.t2 (a string, b string, c string)") + val ret0 = + exectractLineage( + s"insert into table t1 select a," + + s"concat_ws('/', collect_set(b))," + + s"count(distinct(b)) * count(distinct(c))" + + s"from t2 group by a") + assert(ret0 == Lineage( + List("default.t2"), + List("default.t1"), + List( + ("default.t1.a", Set("default.t2.a")), + ("default.t1.b", Set("default.t2.b")), + ("default.t1.c", Set("default.t2.b", "default.t2.c"))))) + + val ret1 = + exectractLineage( + s"insert into table v2_catalog.db.t1 select a," + + s"concat_ws('/', collect_set(b))," + + s"count(distinct(b)) * count(distinct(c))" + + s"from v2_catalog.db.t2 group by a") + assert(ret1 == Lineage( + List("v2_catalog.db.t2"), + List("v2_catalog.db.t1"), + List( + ("v2_catalog.db.t1.a", Set("v2_catalog.db.t2.a")), + ("v2_catalog.db.t1.b", Set("v2_catalog.db.t2.b")), + ("v2_catalog.db.t1.c", Set("v2_catalog.db.t2.b", "v2_catalog.db.t2.c"))))) + + val ret2 = + exectractLineage( + s"insert into table v2_catalog.db.t1 select a," + + s"count(distinct(b+c))," + + s"count(distinct(b)) * count(distinct(c))" + + s"from v2_catalog.db.t2 group by a") + assert(ret2 == Lineage( + List("v2_catalog.db.t2"), + List("v2_catalog.db.t1"), + List( + ("v2_catalog.db.t1.a", Set("v2_catalog.db.t2.a")), + ("v2_catalog.db.t1.b", Set("v2_catalog.db.t2.b", "v2_catalog.db.t2.c")), + ("v2_catalog.db.t1.c", Set("v2_catalog.db.t2.b", "v2_catalog.db.t2.c"))))) + } + } + + test("test grouping sets") { + withTable("t1", "t2") { _ => + spark.sql("CREATE TABLE t1 (a string, b string, c string) USING hive") + spark.sql("CREATE TABLE t2 (a string, b string, c string, d string) USING hive") + val ret0 = + exectractLineage( + s"insert into table t1 select a,b,GROUPING__ID " + + s"from t2 group by a,b,c,d grouping sets ((a,b,c), (a,b,d))") + assert(ret0 == Lineage( + List("default.t2"), + List("default.t1"), + List( + ("default.t1.a", Set("default.t2.a")), + ("default.t1.b", Set("default.t2.b")), + ("default.t1.c", Set())))) + } + } + + test("test catch table with window function") { + withTable("t1", "t2") { _ => + spark.sql("CREATE TABLE t1 (a string, b string) USING hive") + spark.sql("CREATE TABLE t2 (a string, b string) USING hive") + + spark.sql( + s"cache table c1 select * from (" + + s"select a, b, row_number() over (partition by a order by b asc ) rank from t2)" + + s" where rank=1") + val ret0 = exectractLineage("insert overwrite table t1 select a, b from c1") + assert(ret0 == Lineage( + List("default.t2"), + List("default.t1"), + List( + ("default.t1.a", Set("default.t2.a")), + ("default.t1.b", Set("default.t2.b"))))) + + val ret1 = exectractLineage("insert overwrite table t1 select a, rank from c1") + assert(ret1 == Lineage( + List("default.t2"), + List("default.t1"), + List( + ("default.t1.a", Set("default.t2.a")), + ("default.t1.b", Set("default.t2.a", "default.t2.b"))))) + + spark.sql( + s"cache table c2 select * from (" + + s"select b, a, row_number() over (partition by a order by b asc ) rank from t2)" + + s" where rank=1") + val ret2 = exectractLineage("insert overwrite table t1 select a, b from c2") + assert(ret2 == Lineage( + List("default.t2"), + List("default.t1"), + List( + ("default.t1.a", Set("default.t2.a")), + ("default.t1.b", Set("default.t2.b"))))) + + spark.sql( + s"cache table c3 select * from (" + + s"select a as aa, b as bb, row_number() over (partition by a order by b asc ) rank" + + s" from t2) where rank=1") + val ret3 = exectractLineage("insert overwrite table t1 select aa, bb from c3") + assert(ret3 == Lineage( + List("default.t2"), + List("default.t1"), + List( + ("default.t1.a", Set("default.t2.a")), + ("default.t1.b", Set("default.t2.b"))))) + } + } + private def exectractLineageWithoutExecuting(sql: String): Lineage = { val parsed = spark.sessionState.sqlParser.parsePlan(sql) val analyzed = spark.sessionState.analyzer.execute(parsed) From c0241052ae156971a07ee30d89bc4a02d6115d3e Mon Sep 17 00:00:00 2001 From: fwang12 Date: Sat, 25 Feb 2023 14:04:38 +0800 Subject: [PATCH 113/760] [KYUUBI #4412] Align the server session handle and engine session handle for Spark engine ### _Why are the changes needed?_ Align the server session handle and engine session handle for Spark engine. It make it easy to recovery the engine session in any kyuubi instance easy. ### _How was this patch tested?_ - [x] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [x] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4412 from turboFei/server_engine_handle_align. Closes #4412 a20e0f155 [fwang12] fix 9d590e38b [fwang12] fix 94267e583 [fwang12] save 7012c2bef [fwang12] align Authored-by: fwang12 Signed-off-by: fwang12 --- .../session/SparkSQLSessionManager.scala | 31 ++++++++++--------- .../spark/session/SparkSessionImpl.scala | 5 ++- .../kyuubi/config/KyuubiReservedKeys.scala | 1 + .../client/KyuubiSyncThriftClient.scala | 3 ++ .../kyuubi/session/KyuubiSessionImpl.scala | 7 +++-- .../KyuubiOperationPerUserSuite.scala | 9 ++++++ 6 files changed, 38 insertions(+), 18 deletions(-) diff --git a/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/session/SparkSQLSessionManager.scala b/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/session/SparkSQLSessionManager.scala index cb8702df3..091149d21 100644 --- a/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/session/SparkSQLSessionManager.scala +++ b/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/session/SparkSQLSessionManager.scala @@ -24,6 +24,7 @@ import org.apache.spark.sql.SparkSession import org.apache.kyuubi.KyuubiSQLException import org.apache.kyuubi.config.KyuubiConf._ +import org.apache.kyuubi.config.KyuubiReservedKeys.KYUUBI_SESSION_HANDLE_KEY import org.apache.kyuubi.engine.ShareLevel import org.apache.kyuubi.engine.ShareLevel._ import org.apache.kyuubi.engine.spark.{KyuubiSparkUtil, SparkSQLEngine} @@ -135,21 +136,23 @@ class SparkSQLSessionManager private (name: String, spark: SparkSession) password: String, ipAddress: String, conf: Map[String, String]): Session = { - val sparkSession = - try { - getOrNewSparkSession(user) - } catch { - case e: Exception => throw KyuubiSQLException(e) - } + getSessionOption(SessionHandle.fromUUID(conf(KYUUBI_SESSION_HANDLE_KEY))).getOrElse { + val sparkSession = + try { + getOrNewSparkSession(user) + } catch { + case e: Exception => throw KyuubiSQLException(e) + } - new SparkSessionImpl( - protocol, - user, - password, - ipAddress, - conf, - this, - sparkSession) + new SparkSessionImpl( + protocol, + user, + password, + ipAddress, + conf, + this, + sparkSession) + } } override def closeSession(sessionHandle: SessionHandle): Unit = { diff --git a/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/session/SparkSessionImpl.scala b/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/session/SparkSessionImpl.scala index 5bf1ec084..30155c8f2 100644 --- a/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/session/SparkSessionImpl.scala +++ b/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/session/SparkSessionImpl.scala @@ -21,13 +21,14 @@ import org.apache.hive.service.rpc.thrift.{TGetInfoType, TGetInfoValue, TProtoco import org.apache.spark.sql.{AnalysisException, SparkSession} import org.apache.kyuubi.KyuubiSQLException +import org.apache.kyuubi.config.KyuubiReservedKeys.KYUUBI_SESSION_HANDLE_KEY import org.apache.kyuubi.engine.spark.events.SessionEvent import org.apache.kyuubi.engine.spark.operation.SparkSQLOperationManager import org.apache.kyuubi.engine.spark.shim.SparkCatalogShim import org.apache.kyuubi.engine.spark.udf.KDFRegistry import org.apache.kyuubi.events.EventBus import org.apache.kyuubi.operation.{Operation, OperationHandle} -import org.apache.kyuubi.session.{AbstractSession, SessionManager} +import org.apache.kyuubi.session.{AbstractSession, SessionHandle, SessionManager} class SparkSessionImpl( protocol: TProtocolVersion, @@ -39,6 +40,8 @@ class SparkSessionImpl( val spark: SparkSession) extends AbstractSession(protocol, user, password, ipAddress, conf, sessionManager) { + override val handle: SessionHandle = SessionHandle.fromUUID(conf(KYUUBI_SESSION_HANDLE_KEY)) + private def setModifiableConfig(key: String, value: String): Unit = { try { spark.conf.set(key, value) diff --git a/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiReservedKeys.scala b/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiReservedKeys.scala index dfbdd9449..85874d3b9 100644 --- a/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiReservedKeys.scala +++ b/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiReservedKeys.scala @@ -33,6 +33,7 @@ object KyuubiReservedKeys { final val KYUUBI_ENGINE_URL = "kyuubi.engine.url" final val KYUUBI_ENGINE_SUBMIT_TIME_KEY = "kyuubi.engine.submit.time" final val KYUUBI_ENGINE_CREDENTIALS_KEY = "kyuubi.engine.credentials" + final val KYUUBI_SESSION_HANDLE_KEY = "kyuubi.session.handle" final val KYUUBI_SESSION_ENGINE_LAUNCH_HANDLE_GUID = "kyuubi.session.engine.launch.handle.guid" final val KYUUBI_SESSION_ENGINE_LAUNCH_HANDLE_SECRET = diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/client/KyuubiSyncThriftClient.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/client/KyuubiSyncThriftClient.scala index babe73745..dbca8ca32 100644 --- a/kyuubi-server/src/main/scala/org/apache/kyuubi/client/KyuubiSyncThriftClient.scala +++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/client/KyuubiSyncThriftClient.scala @@ -53,6 +53,9 @@ class KyuubiSyncThriftClient private ( private val lock = new ReentrantLock() + // Visible for testing. + private[kyuubi] def remoteSessionHandle: TSessionHandle = _remoteSessionHandle + @volatile private var _aliveProbeSessionHandle: TSessionHandle = _ @volatile private var remoteEngineBroken: Boolean = false private val engineAliveProbeClient = engineAliveProbeProtocol.map(new TCLIService.Client(_)) diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/session/KyuubiSessionImpl.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/session/KyuubiSessionImpl.scala index ce94b0275..e4203b301 100644 --- a/kyuubi-server/src/main/scala/org/apache/kyuubi/session/KyuubiSessionImpl.scala +++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/session/KyuubiSessionImpl.scala @@ -27,7 +27,7 @@ import org.apache.kyuubi.KyuubiSQLException import org.apache.kyuubi.client.KyuubiSyncThriftClient import org.apache.kyuubi.config.KyuubiConf import org.apache.kyuubi.config.KyuubiConf._ -import org.apache.kyuubi.config.KyuubiReservedKeys.{KYUUBI_ENGINE_CREDENTIALS_KEY, KYUUBI_SESSION_SIGN_PUBLICKEY, KYUUBI_SESSION_USER_SIGN} +import org.apache.kyuubi.config.KyuubiReservedKeys.{KYUUBI_ENGINE_CREDENTIALS_KEY, KYUUBI_SESSION_HANDLE_KEY, KYUUBI_SESSION_SIGN_PUBLICKEY, KYUUBI_SESSION_USER_SIGN} import org.apache.kyuubi.engine.{EngineRef, KyuubiApplicationManager} import org.apache.kyuubi.events.{EventBus, KyuubiSessionEvent} import org.apache.kyuubi.ha.client.DiscoveryClientProvider._ @@ -121,11 +121,12 @@ class KyuubiSessionImpl( private[kyuubi] def openEngineSession(extraEngineLog: Option[OperationLog] = None): Unit = handleSessionException { withDiscoveryClient(sessionConf) { discoveryClient => - var openEngineSessionConf = optimizedConf + var openEngineSessionConf = + optimizedConf ++ Map(KYUUBI_SESSION_HANDLE_KEY -> handle.identifier.toString) if (engineCredentials.nonEmpty) { sessionConf.set(KYUUBI_ENGINE_CREDENTIALS_KEY, engineCredentials) openEngineSessionConf = - optimizedConf ++ Map(KYUUBI_ENGINE_CREDENTIALS_KEY -> engineCredentials) + openEngineSessionConf ++ Map(KYUUBI_ENGINE_CREDENTIALS_KEY -> engineCredentials) } if (sessionConf.get(SESSION_USER_SIGN_ENABLED)) { diff --git a/kyuubi-server/src/test/scala/org/apache/kyuubi/operation/KyuubiOperationPerUserSuite.scala b/kyuubi-server/src/test/scala/org/apache/kyuubi/operation/KyuubiOperationPerUserSuite.scala index 2ece2379a..26fc89ba2 100644 --- a/kyuubi-server/src/test/scala/org/apache/kyuubi/operation/KyuubiOperationPerUserSuite.scala +++ b/kyuubi-server/src/test/scala/org/apache/kyuubi/operation/KyuubiOperationPerUserSuite.scala @@ -364,4 +364,13 @@ class KyuubiOperationPerUserSuite val snapshot = MetricsSystem.histogramSnapshot(metric).get assert(snapshot.getMax > 0 && snapshot.getMedian > 0) } + + test("align the server session handle and engine session handle for Spark engine") { + withJdbcStatement() { _ => + val session = + server.backendService.sessionManager.allSessions().head.asInstanceOf[KyuubiSessionImpl] + val engineSessionHandle = SessionHandle.apply(session.client.remoteSessionHandle) + assert(session.handle === engineSessionHandle) + } + } } From 787f86c88af91eb1a13a29b56e86bf391fc2d099 Mon Sep 17 00:00:00 2001 From: fwang12 Date: Sat, 25 Feb 2023 22:05:42 +0800 Subject: [PATCH 114/760] [KYUUBI #4412][FOLLOWUP] Fallback to new engine session handle for UT ### _Why are the changes needed?_ ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [ ] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4414 from turboFei/get_or_else. Closes #4412 e7d45ef24 [fwang12] refactor f8583ec94 [fwang12] use different handle for alive probe 33a776cf0 [fwang12] launch sync 967ae41fa [fwang12] [KYUUBI #4412][FOLLOWUP] Fallback to new engine session handle for UT Authored-by: fwang12 Signed-off-by: fwang12 --- .../spark/session/SparkSQLSessionManager.scala | 3 ++- .../engine/spark/session/SparkSessionImpl.scala | 3 ++- .../kyuubi/client/KyuubiSyncThriftClient.scala | 5 ++++- .../ServerJsonLoggingEventHandlerSuite.scala | 4 ++-- .../operation/KyuubiOperationPerUserSuite.scala | 14 +++++++++----- 5 files changed, 19 insertions(+), 10 deletions(-) diff --git a/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/session/SparkSQLSessionManager.scala b/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/session/SparkSQLSessionManager.scala index 091149d21..677af9a03 100644 --- a/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/session/SparkSQLSessionManager.scala +++ b/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/session/SparkSQLSessionManager.scala @@ -136,7 +136,8 @@ class SparkSQLSessionManager private (name: String, spark: SparkSession) password: String, ipAddress: String, conf: Map[String, String]): Session = { - getSessionOption(SessionHandle.fromUUID(conf(KYUUBI_SESSION_HANDLE_KEY))).getOrElse { + conf.get(KYUUBI_SESSION_HANDLE_KEY).map(SessionHandle.fromUUID).flatMap( + getSessionOption).getOrElse { val sparkSession = try { getOrNewSparkSession(user) diff --git a/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/session/SparkSessionImpl.scala b/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/session/SparkSessionImpl.scala index 30155c8f2..78164ff5f 100644 --- a/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/session/SparkSessionImpl.scala +++ b/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/session/SparkSessionImpl.scala @@ -40,7 +40,8 @@ class SparkSessionImpl( val spark: SparkSession) extends AbstractSession(protocol, user, password, ipAddress, conf, sessionManager) { - override val handle: SessionHandle = SessionHandle.fromUUID(conf(KYUUBI_SESSION_HANDLE_KEY)) + override val handle: SessionHandle = + conf.get(KYUUBI_SESSION_HANDLE_KEY).map(SessionHandle.fromUUID).getOrElse(SessionHandle()) private def setModifiableConfig(key: String, value: String): Unit = { try { diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/client/KyuubiSyncThriftClient.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/client/KyuubiSyncThriftClient.scala index dbca8ca32..e3e66960b 100644 --- a/kyuubi-server/src/main/scala/org/apache/kyuubi/client/KyuubiSyncThriftClient.scala +++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/client/KyuubiSyncThriftClient.scala @@ -17,6 +17,7 @@ package org.apache.kyuubi.client +import java.util.UUID import java.util.concurrent.{ExecutorService, ScheduledExecutorService, TimeUnit} import java.util.concurrent.locks.ReentrantLock @@ -183,7 +184,9 @@ class KyuubiSyncThriftClient private ( engineAliveProbeClient.foreach { aliveProbeClient => val sessionName = SessionHandle.apply(_remoteSessionHandle).identifier + "_aliveness_probe" Utils.tryLogNonFatalError { - req.setConfiguration((configs ++ Map(KyuubiConf.SESSION_NAME.key -> sessionName)).asJava) + req.setConfiguration((configs ++ Map( + KyuubiConf.SESSION_NAME.key -> sessionName, + KYUUBI_SESSION_HANDLE_KEY -> UUID.randomUUID().toString)).asJava) val resp = aliveProbeClient.OpenSession(req) ThriftUtils.verifyTStatus(resp.getStatus) _aliveProbeSessionHandle = resp.getSessionHandle diff --git a/kyuubi-server/src/test/scala/org/apache/kyuubi/events/handler/ServerJsonLoggingEventHandlerSuite.scala b/kyuubi-server/src/test/scala/org/apache/kyuubi/events/handler/ServerJsonLoggingEventHandlerSuite.scala index d0c9924dc..df8ea1083 100644 --- a/kyuubi-server/src/test/scala/org/apache/kyuubi/events/handler/ServerJsonLoggingEventHandlerSuite.scala +++ b/kyuubi-server/src/test/scala/org/apache/kyuubi/events/handler/ServerJsonLoggingEventHandlerSuite.scala @@ -157,7 +157,7 @@ class ServerJsonLoggingEventHandlerSuite extends WithKyuubiServer with HiveJDBCT } } - test("engine session id is not same with server session id") { + test("engine session id is same with server session id") { val name = UUID.randomUUID().toString withSessionConf()(Map.empty)(Map(KyuubiConf.SESSION_NAME.key -> name)) { withJdbcStatement() { statement => @@ -181,7 +181,7 @@ class ServerJsonLoggingEventHandlerSuite extends WithKyuubiServer with HiveJDBCT val res2 = statement.executeQuery( s"SELECT * FROM `json`.`$engineSessionEventPath` " + s"where sessionId = '$serverSessionId' limit 1") - assert(!res2.next()) + assert(res2.next()) } } } diff --git a/kyuubi-server/src/test/scala/org/apache/kyuubi/operation/KyuubiOperationPerUserSuite.scala b/kyuubi-server/src/test/scala/org/apache/kyuubi/operation/KyuubiOperationPerUserSuite.scala index 26fc89ba2..56700759c 100644 --- a/kyuubi-server/src/test/scala/org/apache/kyuubi/operation/KyuubiOperationPerUserSuite.scala +++ b/kyuubi-server/src/test/scala/org/apache/kyuubi/operation/KyuubiOperationPerUserSuite.scala @@ -366,11 +366,15 @@ class KyuubiOperationPerUserSuite } test("align the server session handle and engine session handle for Spark engine") { - withJdbcStatement() { _ => - val session = - server.backendService.sessionManager.allSessions().head.asInstanceOf[KyuubiSessionImpl] - val engineSessionHandle = SessionHandle.apply(session.client.remoteSessionHandle) - assert(session.handle === engineSessionHandle) + withSessionConf(Map( + KyuubiConf.SESSION_ENGINE_LAUNCH_ASYNC.key -> "false"))(Map.empty)(Map.empty) { + withJdbcStatement() { _ => + val session = + server.backendService.sessionManager.allSessions().head.asInstanceOf[KyuubiSessionImpl] + eventually(timeout(10.seconds)) { + assert(session.handle === SessionHandle.apply(session.client.remoteSessionHandle)) + } + } } } } From ceaa2bf3cbd8bbec5562f2a776884a6ec8e4ece6 Mon Sep 17 00:00:00 2001 From: Cheng Pan Date: Sun, 26 Feb 2023 17:10:18 +0800 Subject: [PATCH 115/760] [KYUUBI #4413] [BUILD] Build docker image should include web UI ### _Why are the changes needed?_ `bin/docker-image-tool.sh` should include `web-ui` when build image ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [x] Add screenshots for manual tests if appropriate ``` build/dist --web-ui cd dist bin/docker-image-tool.sh -t test build cat > conf/kyuubi-defaults.conf << EOF kyuubi.frontend.protocols REST EOF docker run --rm -v $PWD/conf:/opt/kyuubi/conf -p10099:10099 kyuubi:test ``` ![image](https://user-images.githubusercontent.com/26535726/221345080-6cc7f06b-0c02-4dda-8e8d-51a36ca5ea33.png) - [ ] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4413 from pan3793/web-ui-package. Closes #4413 787f89515 [Cheng Pan] [BUILD] Build docker image should include web UI Authored-by: Cheng Pan Signed-off-by: Cheng Pan --- build/dist | 6 +++--- docker/Dockerfile | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/build/dist b/build/dist index 0620c7d08..c155dce3f 100755 --- a/build/dist +++ b/build/dist @@ -247,14 +247,15 @@ echo -e "\$ ${BUILD_COMMAND[@]}\n" rm -rf "$DISTDIR" mkdir -p "$DISTDIR/pid" mkdir -p "$DISTDIR/logs" -mkdir -p "$DISTDIR/jars" mkdir -p "$DISTDIR/work" +mkdir -p "$DISTDIR/jars" +mkdir -p "$DISTDIR/beeline-jars" +mkdir -p "$DISTDIR/web-ui" mkdir -p "$DISTDIR/externals/engines/flink" mkdir -p "$DISTDIR/externals/engines/spark" mkdir -p "$DISTDIR/externals/engines/trino" mkdir -p "$DISTDIR/externals/engines/hive" mkdir -p "$DISTDIR/externals/engines/jdbc" -mkdir -p "$DISTDIR/beeline-jars" echo "Kyuubi $VERSION $GITREVSTRING built for" > "$DISTDIR/RELEASE" echo "Java $JAVA_VERSION" >> "$DISTDIR/RELEASE" echo "Scala $SCALA_VERSION" >> "$DISTDIR/RELEASE" @@ -332,7 +333,6 @@ done if [[ "$ENABLE_WEBUI" == "true" ]]; then # Copy web ui dist - mkdir -p "$DISTDIR/web-ui" cp -r "$KYUUBI_HOME/kyuubi-server/web-ui/dist" "$DISTDIR/web-ui/" fi diff --git a/docker/Dockerfile b/docker/Dockerfile index 190500825..0440022de 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -60,6 +60,7 @@ COPY LICENSE NOTICE RELEASE ${KYUUBI_HOME}/ COPY bin ${KYUUBI_HOME}/bin COPY jars ${KYUUBI_HOME}/jars COPY beeline-jars ${KYUUBI_HOME}/beeline-jars +COPY web-ui ${KYUUBI_HOME}/web-ui COPY externals/engines/spark ${KYUUBI_HOME}/externals/engines/spark WORKDIR ${KYUUBI_HOME} From 43309b86f1997b028e8fde5cb4e6449d818f4f73 Mon Sep 17 00:00:00 2001 From: fwang12 Date: Sun, 26 Feb 2023 20:51:54 +0800 Subject: [PATCH 116/760] [KYUUBI #4415] Align the server/engine ExecuteStatement operation handle for Spark engine ### _Why are the changes needed?_ ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [ ] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4415 from turboFei/operation_handle_align. Closes #4415 71721797a [fwang12] refactor c8b667a89 [fwang12] refactor d3fbf05f3 [fwang12] stmt handle Authored-by: fwang12 Signed-off-by: fwang12 --- .../spark/operation/ExecuteStatement.scala | 16 ++++++++++++---- .../operation/SparkSQLOperationManager.scala | 11 ++++++++--- .../kyuubi/config/KyuubiReservedKeys.scala | 1 + .../kyuubi/operation/AbstractOperation.scala | 2 +- .../kyuubi/operation/ExecuteStatement.scala | 3 ++- .../kyuubi/operation/KyuubiOperation.scala | 3 +++ .../operation/KyuubiOperationPerUserSuite.scala | 8 +++++++- 7 files changed, 34 insertions(+), 10 deletions(-) diff --git a/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/operation/ExecuteStatement.scala b/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/operation/ExecuteStatement.scala index fa517a8b1..b29d2ca9a 100644 --- a/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/operation/ExecuteStatement.scala +++ b/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/operation/ExecuteStatement.scala @@ -30,7 +30,7 @@ import org.apache.spark.sql.types._ import org.apache.kyuubi.{KyuubiSQLException, Logging} import org.apache.kyuubi.config.KyuubiConf.OPERATION_RESULT_MAX_ROWS import org.apache.kyuubi.engine.spark.KyuubiSparkUtil._ -import org.apache.kyuubi.operation.{ArrayFetchIterator, FetchIterator, IterableFetchIterator, OperationState} +import org.apache.kyuubi.operation.{ArrayFetchIterator, FetchIterator, IterableFetchIterator, OperationHandle, OperationState} import org.apache.kyuubi.operation.log.OperationLog import org.apache.kyuubi.session.Session @@ -39,7 +39,8 @@ class ExecuteStatement( override val statement: String, override val shouldRunAsync: Boolean, queryTimeout: Long, - incrementalCollect: Boolean) + incrementalCollect: Boolean, + override protected val handle: OperationHandle) extends SparkOperation(session) with Logging { private val operationLog: OperationLog = OperationLog.createOperationLog(session, getHandle) @@ -175,8 +176,15 @@ class ArrowBasedExecuteStatement( override val statement: String, override val shouldRunAsync: Boolean, queryTimeout: Long, - incrementalCollect: Boolean) - extends ExecuteStatement(session, statement, shouldRunAsync, queryTimeout, incrementalCollect) { + incrementalCollect: Boolean, + override protected val handle: OperationHandle) + extends ExecuteStatement( + session, + statement, + shouldRunAsync, + queryTimeout, + incrementalCollect, + handle) { override protected def incrementalCollectResult(resultDF: DataFrame): Iterator[Any] = { collectAsArrow(convertComplexType(resultDF)) { rdd => diff --git a/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/operation/SparkSQLOperationManager.scala b/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/operation/SparkSQLOperationManager.scala index 4743f147c..8fd58b338 100644 --- a/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/operation/SparkSQLOperationManager.scala +++ b/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/operation/SparkSQLOperationManager.scala @@ -23,10 +23,11 @@ import scala.collection.JavaConverters._ import org.apache.kyuubi.KyuubiSQLException import org.apache.kyuubi.config.KyuubiConf._ +import org.apache.kyuubi.config.KyuubiReservedKeys.KYUUBI_OPERATION_HANDLE_KEY import org.apache.kyuubi.engine.spark.repl.KyuubiSparkILoop import org.apache.kyuubi.engine.spark.session.SparkSessionImpl import org.apache.kyuubi.engine.spark.shim.SparkCatalogShim -import org.apache.kyuubi.operation.{NoneMode, Operation, OperationManager, PlanOnlyMode} +import org.apache.kyuubi.operation.{NoneMode, Operation, OperationHandle, OperationManager, PlanOnlyMode} import org.apache.kyuubi.session.{Session, SessionHandle} class SparkSQLOperationManager private (name: String) extends OperationManager(name) { @@ -70,6 +71,8 @@ class SparkSQLOperationManager private (name: String) extends OperationManager(n val lang = OperationLanguages(confOverlay.getOrElse( OPERATION_LANGUAGE.key, spark.conf.get(OPERATION_LANGUAGE.key, operationLanguageDefault))) + val opHandle = confOverlay.get(KYUUBI_OPERATION_HANDLE_KEY).map( + OperationHandle.apply).getOrElse(OperationHandle()) val operation = lang match { case OperationLanguages.SQL => @@ -91,14 +94,16 @@ class SparkSQLOperationManager private (name: String) extends OperationManager(n statement, runAsync, queryTimeout, - incrementalCollect) + incrementalCollect, + opHandle) case _ => new ExecuteStatement( session, statement, runAsync, queryTimeout, - incrementalCollect) + incrementalCollect, + opHandle) } case mode => new PlanOnlyStatement(session, statement, mode) diff --git a/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiReservedKeys.scala b/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiReservedKeys.scala index 85874d3b9..8b42e659f 100644 --- a/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiReservedKeys.scala +++ b/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiReservedKeys.scala @@ -42,4 +42,5 @@ object KyuubiReservedKeys { final val KYUUBI_OPERATION_GET_CURRENT_CATALOG = "kyuubi.operation.get.current.catalog" final val KYUUBI_OPERATION_SET_CURRENT_DATABASE = "kyuubi.operation.set.current.database" final val KYUUBI_OPERATION_GET_CURRENT_DATABASE = "kyuubi.operation.get.current.database" + final val KYUUBI_OPERATION_HANDLE_KEY = "kyuubi.operation.handle" } diff --git a/kyuubi-common/src/main/scala/org/apache/kyuubi/operation/AbstractOperation.scala b/kyuubi-common/src/main/scala/org/apache/kyuubi/operation/AbstractOperation.scala index 9cdd6a8f0..d50cb8e24 100644 --- a/kyuubi-common/src/main/scala/org/apache/kyuubi/operation/AbstractOperation.scala +++ b/kyuubi-common/src/main/scala/org/apache/kyuubi/operation/AbstractOperation.scala @@ -36,7 +36,7 @@ abstract class AbstractOperation(session: Session) extends Operation with Loggin final protected val opType: String = getClass.getSimpleName final protected val createTime = System.currentTimeMillis() - final private val handle = OperationHandle() + protected val handle = OperationHandle() final private val operationTimeout: Long = { session.sessionManager.getConf.get(OPERATION_IDLE_TIMEOUT) } diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/operation/ExecuteStatement.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/operation/ExecuteStatement.scala index b09c2b174..4767cbf12 100644 --- a/kyuubi-server/src/main/scala/org/apache/kyuubi/operation/ExecuteStatement.scala +++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/operation/ExecuteStatement.scala @@ -63,7 +63,8 @@ class ExecuteStatement( // We need to avoid executing query in sync mode, because there is no heartbeat mechanism // in thrift protocol, in sync mode, we cannot distinguish between long-run query and // engine crash without response before socket read timeout. - _remoteOpHandle = client.executeStatement(statement, confOverlay, true, queryTimeout) + _remoteOpHandle = + client.executeStatement(statement, confOverlay ++ operationHandleConf, true, queryTimeout) setHasResultSet(_remoteOpHandle.isHasResultSet) } catch onError() } diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/operation/KyuubiOperation.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/operation/KyuubiOperation.scala index a6b960847..106a11e4b 100644 --- a/kyuubi-server/src/main/scala/org/apache/kyuubi/operation/KyuubiOperation.scala +++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/operation/KyuubiOperation.scala @@ -26,6 +26,7 @@ import org.apache.thrift.TException import org.apache.thrift.transport.TTransportException import org.apache.kyuubi.{KyuubiSQLException, Utils} +import org.apache.kyuubi.config.KyuubiReservedKeys.KYUUBI_OPERATION_HANDLE_KEY import org.apache.kyuubi.events.{EventBus, KyuubiOperationEvent} import org.apache.kyuubi.metrics.MetricsConstants.{OPERATION_FAIL, OPERATION_OPEN, OPERATION_STATE, OPERATION_TOTAL} import org.apache.kyuubi.metrics.MetricsSystem @@ -46,6 +47,8 @@ abstract class KyuubiOperation(session: Session) extends AbstractOperation(sessi protected[operation] lazy val client = session.asInstanceOf[KyuubiSessionImpl].client + protected val operationHandleConf = Map(KYUUBI_OPERATION_HANDLE_KEY -> handle.identifier.toString) + @volatile protected var _remoteOpHandle: TOperationHandle = _ def remoteOpHandle(): TOperationHandle = _remoteOpHandle diff --git a/kyuubi-server/src/test/scala/org/apache/kyuubi/operation/KyuubiOperationPerUserSuite.scala b/kyuubi-server/src/test/scala/org/apache/kyuubi/operation/KyuubiOperationPerUserSuite.scala index 56700759c..40bb165b8 100644 --- a/kyuubi-server/src/test/scala/org/apache/kyuubi/operation/KyuubiOperationPerUserSuite.scala +++ b/kyuubi-server/src/test/scala/org/apache/kyuubi/operation/KyuubiOperationPerUserSuite.scala @@ -365,7 +365,7 @@ class KyuubiOperationPerUserSuite assert(snapshot.getMax > 0 && snapshot.getMedian > 0) } - test("align the server session handle and engine session handle for Spark engine") { + test("align the server/engine session/executeStatement handle for Spark engine") { withSessionConf(Map( KyuubiConf.SESSION_ENGINE_LAUNCH_ASYNC.key -> "false"))(Map.empty)(Map.empty) { withJdbcStatement() { _ => @@ -374,6 +374,12 @@ class KyuubiOperationPerUserSuite eventually(timeout(10.seconds)) { assert(session.handle === SessionHandle.apply(session.client.remoteSessionHandle)) } + val opHandle = session.executeStatement("SELECT engine_id()", Map.empty, true, 0L) + eventually(timeout(10.seconds)) { + val operation = session.sessionManager.operationManager.getOperation( + opHandle).asInstanceOf[KyuubiOperation] + assert(opHandle == OperationHandle.apply(operation.remoteOpHandle())) + } } } } From 59c1875bc16a4fa5a1f8e3e9d1c0c0ad77431e7e Mon Sep 17 00:00:00 2001 From: Tianlin Liao Date: Sun, 26 Feb 2023 21:09:57 +0800 Subject: [PATCH 117/760] [KYUUBI #4376] Support to config the kyuubi service administrator with kyuubi conf ### _Why are the changes needed?_ Close #4376 ### _How was this patch tested?_ - [x] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [ ] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4405 from lightning-L/kyuubi-4376. Closes #4376 1a01a75a8 [Tianlin Liao] rename and refactor 7324cab3d [Tianlin Liao] [KYUUBI #4376] Support to config the kyuubi service administrator with kyuubi conf Authored-by: Tianlin Liao Signed-off-by: fwang12 --- docs/deployment/settings.md | 1 + .../org/apache/kyuubi/config/KyuubiConf.scala | 10 +++++++++ .../kyuubi/server/api/v1/AdminResource.scala | 13 ++++++++---- .../server/api/v1/AdminResourceSuite.scala | 21 +++++++++++++++++++ 4 files changed, 41 insertions(+), 4 deletions(-) diff --git a/docs/deployment/settings.md b/docs/deployment/settings.md index 82c8111b6..dac72825e 100644 --- a/docs/deployment/settings.md +++ b/docs/deployment/settings.md @@ -451,6 +451,7 @@ You can configure the Kyuubi properties in `$KYUUBI_HOME/conf/kyuubi-defaults.co | Key | Default | Meaning | Type | Since | |----------------------------------------------------------|-------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------|-------| +| kyuubi.server.administrators || Comma-separated list of Kyuubi service administrators. We use this config to grant admin permission to any service accounts. | seq | 1.8.0 | | kyuubi.server.info.provider | ENGINE | The server information provider name, some clients may rely on this information to check the server compatibilities and functionalities.
  • SERVER: Return Kyuubi server information.
  • ENGINE: Return Kyuubi engine information.
  • | string | 1.6.1 | | kyuubi.server.limit.batch.connections.per.ipaddress | <undefined> | Maximum kyuubi server batch connections per ipaddress. Any user exceeding this limit will not be allowed to connect. | int | 1.7.0 | | kyuubi.server.limit.batch.connections.per.user | <undefined> | Maximum kyuubi server batch connections per user. Any user exceeding this limit will not be allowed to connect. | int | 1.7.0 | diff --git a/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala b/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala index 70919d6e8..517d92db3 100644 --- a/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala +++ b/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala @@ -2429,6 +2429,16 @@ object KyuubiConf { .timeConf .createWithDefaultString("PT30M") + val SERVER_ADMINISTRATORS: ConfigEntry[Seq[String]] = + buildConf("kyuubi.server.administrators") + .doc("Comma-separated list of Kyuubi service administrators. " + + "We use this config to grant admin permission to any service accounts.") + .version("1.8.0") + .serverOnly + .stringConf + .toSequence() + .createWithDefault(Nil) + val OPERATION_SPARK_LISTENER_ENABLED: ConfigEntry[Boolean] = buildConf("kyuubi.operation.spark.listener.enabled") .doc("When set to true, Spark engine registers an SQLOperationListener before executing " + diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/AdminResource.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/AdminResource.scala index 6e05ee27c..104dd1045 100644 --- a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/AdminResource.scala +++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/AdminResource.scala @@ -41,7 +41,8 @@ import org.apache.kyuubi.server.api.ApiRequestContext @Tag(name = "Admin") @Produces(Array(MediaType.APPLICATION_JSON)) private[v1] class AdminResource extends ApiRequestContext with Logging { - private lazy val administrator = Utils.currentUser + private lazy val administrators = fe.getConf.get(KyuubiConf.SERVER_ADMINISTRATORS).toSet + + Utils.currentUser @ApiResponse( responseCode = "200", @@ -54,7 +55,7 @@ private[v1] class AdminResource extends ApiRequestContext with Logging { val userName = fe.getSessionUser(Map.empty[String, String]) val ipAddress = fe.getIpAddress info(s"Receive refresh Kyuubi server hadoop conf request from $userName/$ipAddress") - if (!userName.equals(administrator)) { + if (!isAdministrator(userName)) { throw new NotAllowedException( s"$userName is not allowed to refresh the Kyuubi server hadoop conf") } @@ -73,7 +74,7 @@ private[v1] class AdminResource extends ApiRequestContext with Logging { val userName = fe.getSessionUser(Map.empty[String, String]) val ipAddress = fe.getIpAddress info(s"Receive refresh user defaults conf request from $userName/$ipAddress") - if (!userName.equals(administrator)) { + if (!isAdministrator(userName)) { throw new NotAllowedException( s"$userName is not allowed to refresh the user defaults conf") } @@ -92,7 +93,7 @@ private[v1] class AdminResource extends ApiRequestContext with Logging { val userName = fe.getSessionUser(Map.empty[String, String]) val ipAddress = fe.getIpAddress info(s"Receive refresh unlimited users request from $userName/$ipAddress") - if (!userName.equals(administrator)) { + if (!isAdministrator(userName)) { throw new NotAllowedException( s"$userName is not allowed to refresh the unlimited users") } @@ -212,4 +213,8 @@ private[v1] class AdminResource extends ApiRequestContext with Logging { engine.getUser, engine.getSubdomain) } + + private def isAdministrator(userName: String): Boolean = { + administrators.contains(userName); + } } diff --git a/kyuubi-server/src/test/scala/org/apache/kyuubi/server/api/v1/AdminResourceSuite.scala b/kyuubi-server/src/test/scala/org/apache/kyuubi/server/api/v1/AdminResourceSuite.scala index d7cd4840e..ffd4a9140 100644 --- a/kyuubi-server/src/test/scala/org/apache/kyuubi/server/api/v1/AdminResourceSuite.scala +++ b/kyuubi-server/src/test/scala/org/apache/kyuubi/server/api/v1/AdminResourceSuite.scala @@ -37,6 +37,9 @@ class AdminResourceSuite extends KyuubiFunSuite with RestFrontendTestHelper { private val engineMgr = new KyuubiApplicationManager() + override protected lazy val conf: KyuubiConf = KyuubiConf() + .set(KyuubiConf.SERVER_ADMINISTRATORS, Seq("admin001")) + override def beforeAll(): Unit = { super.beforeAll() engineMgr.initialize(KyuubiConf()) @@ -64,6 +67,24 @@ class AdminResourceSuite extends KyuubiFunSuite with RestFrontendTestHelper { .header(AUTHORIZATION_HEADER, s"BASIC $encodeAuthorization") .post(null) assert(200 == response.getStatus) + + val admin001AuthHeader = new String( + Base64.getEncoder.encode("admin001".getBytes()), + "UTF-8") + response = webTarget.path("api/v1/admin/refresh/hadoop_conf") + .request() + .header(AUTHORIZATION_HEADER, s"BASIC $admin001AuthHeader") + .post(null) + assert(200 == response.getStatus) + + val admin002AuthHeader = new String( + Base64.getEncoder.encode("admin002".getBytes()), + "UTF-8") + response = webTarget.path("api/v1/admin/refresh/hadoop_conf") + .request() + .header(AUTHORIZATION_HEADER, s"BASIC $admin002AuthHeader") + .post(null) + assert(405 == response.getStatus) } test("refresh user defaults config of the kyuubi server") { From 15a83e16eb302917fa71fd8c11cd11125550c28c Mon Sep 17 00:00:00 2001 From: Fu Chen Date: Mon, 27 Feb 2023 01:01:51 +0800 Subject: [PATCH 118/760] [KYUUBI #4381] Refine unit tests to adapt Spark-3.4 ### _Why are the changes needed?_ 1. get spark engine runtime version instead of compile version 2. moved `PySparkTests` from the module `kyuubi-spark-sql-engine` to `kyuubi-server` to ensure that the python progress loading library PYSPARK has the same version as the launched Spark engine. see https://github.com/apache/kyuubi/pull/4381#issuecomment-1442871106 ### _How was this patch tested?_ Pass Github Action. Closes #4381 from cfmcgrady/spark-3.4.0. Closes #4381 2711f51b3 [Fu Chen] remove verify spark-3.4 binary a93b6d13e [Fu Chen] mv PySparkTests and enabled 6d5aad537 [Fu Chen] fix style 2da641561 [Fu Chen] fix style 3c9e300ce [Fu Chen] spark compile version -> runtime version a8e7b7481 [Fu Chen] unused import 6be502ca6 [Fu Chen] fix ut c1a1e1a8e [Fu Chen] skip pyspark tests 0049c23b7 [Fu Chen] verify spark-3.4.0 binary Authored-by: Fu Chen Signed-off-by: Cheng Pan --- .../spark/operation/SparkOperationSuite.scala | 7 ++- .../operation/IcebergMetadataTests.scala | 10 ++-- .../kyuubi/operation/SparkDataTypeTests.scala | 46 +++++++++---------- .../kyuubi/operation/SparkQueryTests.scala | 3 +- .../apache/kyuubi/util/SparkVersionUtil.scala | 19 ++++++-- .../kyuubi/engine/spark}/PySparkTests.scala | 8 ++-- 6 files changed, 51 insertions(+), 42 deletions(-) rename {externals/kyuubi-spark-sql-engine/src/test/scala/org/apache/kyuubi/engine/spark/operation => kyuubi-server/src/test/scala/org/apache/kyuubi/engine/spark}/PySparkTests.scala (96%) diff --git a/externals/kyuubi-spark-sql-engine/src/test/scala/org/apache/kyuubi/engine/spark/operation/SparkOperationSuite.scala b/externals/kyuubi-spark-sql-engine/src/test/scala/org/apache/kyuubi/engine/spark/operation/SparkOperationSuite.scala index 30bbf8b77..af514ceb3 100644 --- a/externals/kyuubi-spark-sql-engine/src/test/scala/org/apache/kyuubi/engine/spark/operation/SparkOperationSuite.scala +++ b/externals/kyuubi-spark-sql-engine/src/test/scala/org/apache/kyuubi/engine/spark/operation/SparkOperationSuite.scala @@ -39,7 +39,6 @@ import org.apache.kyuubi.engine.spark.shim.SparkCatalogShim import org.apache.kyuubi.operation.{HiveMetadataTests, SparkQueryTests} import org.apache.kyuubi.operation.meta.ResultSetSchemaConstant._ import org.apache.kyuubi.util.KyuubiHadoopUtils -import org.apache.kyuubi.util.SparkVersionUtil.isSparkVersionAtLeast class SparkOperationSuite extends WithSparkSQLEngine with HiveMetadataTests with SparkQueryTests { @@ -93,12 +92,12 @@ class SparkOperationSuite extends WithSparkSQLEngine with HiveMetadataTests with .add("c17", "struct", nullable = true, "17") // since spark3.3.0 - if (SPARK_ENGINE_VERSION >= "3.3") { + if (SPARK_ENGINE_RUNTIME_VERSION >= "3.3") { schema = schema.add("c18", "interval day", nullable = true, "18") .add("c19", "interval year", nullable = true, "19") } // since spark3.4.0 - if (SPARK_ENGINE_VERSION >= "3.4") { + if (SPARK_ENGINE_RUNTIME_VERSION >= "3.4") { schema = schema.add("c20", "timestamp_ntz", nullable = true, "20") } @@ -511,7 +510,7 @@ class SparkOperationSuite extends WithSparkSQLEngine with HiveMetadataTests with val status = tOpenSessionResp.getStatus val errorMessage = status.getErrorMessage assert(status.getStatusCode === TStatusCode.ERROR_STATUS) - if (isSparkVersionAtLeast("3.4")) { + if (SPARK_ENGINE_RUNTIME_VERSION >= "3.4") { assert(errorMessage.contains("[SCHEMA_NOT_FOUND]")) assert(errorMessage.contains(s"The schema `$dbName` cannot be found.")) } else { diff --git a/kyuubi-common/src/test/scala/org/apache/kyuubi/operation/IcebergMetadataTests.scala b/kyuubi-common/src/test/scala/org/apache/kyuubi/operation/IcebergMetadataTests.scala index d14224a84..e3bb4ccb7 100644 --- a/kyuubi-common/src/test/scala/org/apache/kyuubi/operation/IcebergMetadataTests.scala +++ b/kyuubi-common/src/test/scala/org/apache/kyuubi/operation/IcebergMetadataTests.scala @@ -17,11 +17,11 @@ package org.apache.kyuubi.operation -import org.apache.kyuubi.IcebergSuiteMixin +import org.apache.kyuubi.{IcebergSuiteMixin, SPARK_COMPILE_VERSION} import org.apache.kyuubi.operation.meta.ResultSetSchemaConstant._ -import org.apache.kyuubi.util.SparkVersionUtil.isSparkVersionAtLeast +import org.apache.kyuubi.util.SparkVersionUtil -trait IcebergMetadataTests extends HiveJDBCTestHelper with IcebergSuiteMixin { +trait IcebergMetadataTests extends HiveJDBCTestHelper with IcebergSuiteMixin with SparkVersionUtil { test("get catalogs") { withJdbcStatement() { statement => @@ -153,11 +153,11 @@ trait IcebergMetadataTests extends HiveJDBCTestHelper with IcebergSuiteMixin { "date", "timestamp", // SPARK-37931 - if (isSparkVersionAtLeast("3.3")) "struct" + if (SPARK_ENGINE_RUNTIME_VERSION >= "3.3") "struct" else "struct<`X`: bigint, `Y`: double>", "binary", // SPARK-37931 - if (isSparkVersionAtLeast("3.3")) "struct" else "struct<`X`: string>") + if (SPARK_COMPILE_VERSION >= "3.3") "struct" else "struct<`X`: string>") val cols = dataTypes.zipWithIndex.map { case (dt, idx) => s"c$idx" -> dt } val (colNames, _) = cols.unzip diff --git a/kyuubi-common/src/test/scala/org/apache/kyuubi/operation/SparkDataTypeTests.scala b/kyuubi-common/src/test/scala/org/apache/kyuubi/operation/SparkDataTypeTests.scala index 3164ae496..f0dd3e723 100644 --- a/kyuubi-common/src/test/scala/org/apache/kyuubi/operation/SparkDataTypeTests.scala +++ b/kyuubi-common/src/test/scala/org/apache/kyuubi/operation/SparkDataTypeTests.scala @@ -19,15 +19,16 @@ package org.apache.kyuubi.operation import java.sql.{Date, Timestamp} -import org.apache.kyuubi.engine.SemanticVersion +import org.apache.kyuubi.util.SparkVersionUtil -trait SparkDataTypeTests extends HiveJDBCTestHelper { - protected lazy val SPARK_ENGINE_VERSION = sparkEngineMajorMinorVersion +trait SparkDataTypeTests extends HiveJDBCTestHelper with SparkVersionUtil { def resultFormat: String = "thrift" test("execute statement - select null") { - assume(resultFormat == "thrift" || (resultFormat == "arrow" && SPARK_ENGINE_VERSION >= "3.2")) + assume( + resultFormat == "thrift" || + (resultFormat == "arrow" && SPARK_ENGINE_RUNTIME_VERSION >= "3.2")) withJdbcStatement() { statement => val resultSet = statement.executeQuery("SELECT NULL AS col") assert(resultSet.next()) @@ -199,7 +200,7 @@ trait SparkDataTypeTests extends HiveJDBCTestHelper { } test("execute statement - select timestamp_ntz") { - assume(SPARK_ENGINE_VERSION >= "3.4") + assume(SPARK_ENGINE_RUNTIME_VERSION >= "3.4") withJdbcStatement() { statement => val resultSet = statement.executeQuery( "SELECT make_timestamp_ntz(2022, 03, 24, 18, 08, 31.8888) AS col") @@ -213,7 +214,9 @@ trait SparkDataTypeTests extends HiveJDBCTestHelper { } test("execute statement - select daytime interval") { - assume(resultFormat == "thrift" || (resultFormat == "arrow" && SPARK_ENGINE_VERSION >= "3.3")) + assume( + resultFormat == "thrift" || + (resultFormat == "arrow" && SPARK_ENGINE_RUNTIME_VERSION >= "3.3")) withJdbcStatement() { statement => Map( "interval 1 day 1 hour -60 minutes 30 seconds" -> @@ -242,7 +245,7 @@ trait SparkDataTypeTests extends HiveJDBCTestHelper { assert(resultSet.next()) val result = resultSet.getString("col") val metaData = resultSet.getMetaData - if (SPARK_ENGINE_VERSION < "3.2") { + if (SPARK_ENGINE_RUNTIME_VERSION < "3.2") { // for spark 3.1 and backwards assert(result === kv._2._2) assert(metaData.getPrecision(1) === Int.MaxValue) @@ -258,7 +261,9 @@ trait SparkDataTypeTests extends HiveJDBCTestHelper { } test("execute statement - select year/month interval") { - assume(resultFormat == "thrift" || (resultFormat == "arrow" && SPARK_ENGINE_VERSION >= "3.3")) + assume( + resultFormat == "thrift" || + (resultFormat == "arrow" && SPARK_ENGINE_RUNTIME_VERSION >= "3.3")) withJdbcStatement() { statement => Map( "INTERVAL 2022 YEAR" -> Tuple2("2022-0", "2022 years"), @@ -271,7 +276,7 @@ trait SparkDataTypeTests extends HiveJDBCTestHelper { assert(resultSet.next()) val result = resultSet.getString("col") val metaData = resultSet.getMetaData - if (SPARK_ENGINE_VERSION < "3.2") { + if (SPARK_ENGINE_RUNTIME_VERSION < "3.2") { // for spark 3.1 and backwards assert(result === kv._2._2) assert(metaData.getPrecision(1) === Int.MaxValue) @@ -287,7 +292,9 @@ trait SparkDataTypeTests extends HiveJDBCTestHelper { } test("execute statement - select array") { - assume(resultFormat == "thrift" || (resultFormat == "arrow" && SPARK_ENGINE_VERSION >= "3.2")) + assume( + resultFormat == "thrift" || + (resultFormat == "arrow" && SPARK_ENGINE_RUNTIME_VERSION >= "3.2")) withJdbcStatement() { statement => val resultSet = statement.executeQuery( "SELECT array() AS col1, array(1) AS col2, array(null) AS col3") @@ -305,7 +312,9 @@ trait SparkDataTypeTests extends HiveJDBCTestHelper { } test("execute statement - select map") { - assume(resultFormat == "thrift" || (resultFormat == "arrow" && SPARK_ENGINE_VERSION >= "3.2")) + assume( + resultFormat == "thrift" || + (resultFormat == "arrow" && SPARK_ENGINE_RUNTIME_VERSION >= "3.2")) withJdbcStatement() { statement => val resultSet = statement.executeQuery( "SELECT map() AS col1, map(1, 2, 3, 4) AS col2, map(1, null) AS col3") @@ -323,7 +332,9 @@ trait SparkDataTypeTests extends HiveJDBCTestHelper { } test("execute statement - select struct") { - assume(resultFormat == "thrift" || (resultFormat == "arrow" && SPARK_ENGINE_VERSION >= "3.2")) + assume( + resultFormat == "thrift" || + (resultFormat == "arrow" && SPARK_ENGINE_RUNTIME_VERSION >= "3.2")) withJdbcStatement() { statement => val resultSet = statement.executeQuery( "SELECT struct('1', '2') AS col1," + @@ -342,15 +353,4 @@ trait SparkDataTypeTests extends HiveJDBCTestHelper { assert(metaData.getScale(2) == 0) } } - - def sparkEngineMajorMinorVersion: SemanticVersion = { - var sparkRuntimeVer = "" - withJdbcStatement() { stmt => - val result = stmt.executeQuery("SELECT version()") - assert(result.next()) - sparkRuntimeVer = result.getString(1) - assert(!result.next()) - } - SemanticVersion(sparkRuntimeVer) - } } diff --git a/kyuubi-common/src/test/scala/org/apache/kyuubi/operation/SparkQueryTests.scala b/kyuubi-common/src/test/scala/org/apache/kyuubi/operation/SparkQueryTests.scala index a42b05473..ff8b12481 100644 --- a/kyuubi-common/src/test/scala/org/apache/kyuubi/operation/SparkQueryTests.scala +++ b/kyuubi-common/src/test/scala/org/apache/kyuubi/operation/SparkQueryTests.scala @@ -28,7 +28,6 @@ import org.apache.hive.service.rpc.thrift.{TExecuteStatementReq, TFetchResultsRe import org.apache.kyuubi.{KYUUBI_VERSION, Utils} import org.apache.kyuubi.config.KyuubiConf -import org.apache.kyuubi.util.SparkVersionUtil.isSparkVersionAtLeast trait SparkQueryTests extends SparkDataTypeTests with HiveJDBCTestHelper { @@ -187,7 +186,7 @@ trait SparkQueryTests extends SparkDataTypeTests with HiveJDBCTestHelper { withJdbcStatement("t") { statement => try { val assertTableOrViewNotfound: (Exception, String) => Unit = (e, tableName) => { - if (isSparkVersionAtLeast("3.4")) { + if (SPARK_ENGINE_RUNTIME_VERSION >= "3.4") { assert(e.getMessage.contains("[TABLE_OR_VIEW_NOT_FOUND]")) assert(e.getMessage.contains(s"The table or view `$tableName` cannot be found.")) } else { diff --git a/kyuubi-common/src/test/scala/org/apache/kyuubi/util/SparkVersionUtil.scala b/kyuubi-common/src/test/scala/org/apache/kyuubi/util/SparkVersionUtil.scala index cd8409d10..785015cc3 100644 --- a/kyuubi-common/src/test/scala/org/apache/kyuubi/util/SparkVersionUtil.scala +++ b/kyuubi-common/src/test/scala/org/apache/kyuubi/util/SparkVersionUtil.scala @@ -17,13 +17,22 @@ package org.apache.kyuubi.util -import org.apache.kyuubi.SPARK_COMPILE_VERSION import org.apache.kyuubi.engine.SemanticVersion +import org.apache.kyuubi.operation.HiveJDBCTestHelper -object SparkVersionUtil { - lazy val sparkSemanticVersion: SemanticVersion = SemanticVersion(SPARK_COMPILE_VERSION) +trait SparkVersionUtil { + this: HiveJDBCTestHelper => - def isSparkVersionAtLeast(ver: String): Boolean = { - sparkSemanticVersion.isVersionAtLeast(ver) + protected lazy val SPARK_ENGINE_RUNTIME_VERSION = sparkEngineMajorMinorVersion + + def sparkEngineMajorMinorVersion: SemanticVersion = { + var sparkRuntimeVer = "" + withJdbcStatement() { stmt => + val result = stmt.executeQuery("SELECT version()") + assert(result.next()) + sparkRuntimeVer = result.getString(1) + assert(!result.next()) + } + SemanticVersion(sparkRuntimeVer) } } diff --git a/externals/kyuubi-spark-sql-engine/src/test/scala/org/apache/kyuubi/engine/spark/operation/PySparkTests.scala b/kyuubi-server/src/test/scala/org/apache/kyuubi/engine/spark/PySparkTests.scala similarity index 96% rename from externals/kyuubi-spark-sql-engine/src/test/scala/org/apache/kyuubi/engine/spark/operation/PySparkTests.scala rename to kyuubi-server/src/test/scala/org/apache/kyuubi/engine/spark/PySparkTests.scala index e2dd2609d..6af7e21e2 100644 --- a/externals/kyuubi-spark-sql-engine/src/test/scala/org/apache/kyuubi/engine/spark/operation/PySparkTests.scala +++ b/kyuubi-server/src/test/scala/org/apache/kyuubi/engine/spark/PySparkTests.scala @@ -24,17 +24,19 @@ import java.util.Properties import scala.sys.process._ -import org.apache.kyuubi.engine.spark.WithSparkSQLEngine +import org.apache.kyuubi.WithKyuubiServer +import org.apache.kyuubi.config.KyuubiConf import org.apache.kyuubi.jdbc.KyuubiHiveDriver import org.apache.kyuubi.jdbc.hive.{KyuubiSQLException, KyuubiStatement} import org.apache.kyuubi.operation.HiveJDBCTestHelper import org.apache.kyuubi.tags.PySparkTest @PySparkTest -class PySparkTests extends WithSparkSQLEngine with HiveJDBCTestHelper { +class PySparkTests extends WithKyuubiServer with HiveJDBCTestHelper { override protected def jdbcUrl: String = getJdbcUrl - override def withKyuubiConf: Map[String, String] = Map.empty + + override protected val conf: KyuubiConf = new KyuubiConf test("pyspark support") { val code = "print(1)" From 83af5ba814d9742bd461d8e9aed55ae6cc8fc731 Mon Sep 17 00:00:00 2001 From: Cheng Pan Date: Mon, 27 Feb 2023 11:25:56 +0800 Subject: [PATCH 119/760] [KYUUBI #4419] Implement simple EngineSecuritySecretProvider ### _Why are the changes needed?_ This PR implements a simple `EngineSecuritySecretProvider` beside the existing zookeeper implementation, which simplifies the user threshold to use RESTful API w/ HA mode, and this PR also allows the user set `kyuubi.engine.security.secret.provider` using short name 'simple' or 'zookeeper' as well as the full class name. ### _How was this patch tested?_ - [x] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [ ] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4419 from pan3793/simple-secret-provider. Closes #4419 32b1f966a [Cheng Pan] Update kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala 1af12b99d [Cheng Pan] nit 28a228eea [Cheng Pan] nit 65f14494c [Cheng Pan] Implement simple EngineSecuritySecretProvider Authored-by: Cheng Pan Signed-off-by: Cheng Pan --- .../org/apache/kyuubi/config/KyuubiConf.scala | 19 +++++++++++++++++-- .../EngineSecuritySecretProvider.scala | 17 ++++++++++++++++- .../InternalSecurityAccessorSuite.scala | 5 ++--- .../server/api/v1/BatchesResourceSuite.scala | 7 +++---- 4 files changed, 38 insertions(+), 10 deletions(-) diff --git a/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala b/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala index 517d92db3..f59cb2d98 100644 --- a/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala +++ b/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala @@ -2068,8 +2068,23 @@ object KyuubiConf { "subclass of `EngineSecuritySecretProvider`.") .version("1.5.0") .stringConf - .createWithDefault( - "org.apache.kyuubi.service.authentication.ZooKeeperEngineSecuritySecretProviderImpl") + .transform { + case "simple" => + "org.apache.kyuubi.service.authentication.SimpleEngineSecuritySecretProviderImpl" + case "zookeeper" => + "org.apache.kyuubi.service.authentication.ZooKeeperEngineSecuritySecretProviderImpl" + case other => other + } + .createWithDefault("zookeeper") + + val SIMPLE_SECURITY_SECRET_PROVIDER_PROVIDER_SECRET: OptionalConfigEntry[String] = + buildConf("kyuubi.engine.security.secret.provider.simple.secret") + .internal + .doc("The secret key used for internal security access. Only take affects when " + + s"${ENGINE_SECURITY_SECRET_PROVIDER.key} is 'simple'") + .version("1.7.0") + .stringConf + .createOptional val ENGINE_SECURITY_CRYPTO_KEY_LENGTH: ConfigEntry[Int] = buildConf("kyuubi.engine.security.crypto.keyLength") diff --git a/kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/EngineSecuritySecretProvider.scala b/kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/EngineSecuritySecretProvider.scala index 5bd9e4092..2bcfe9a67 100644 --- a/kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/EngineSecuritySecretProvider.scala +++ b/kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/EngineSecuritySecretProvider.scala @@ -18,7 +18,7 @@ package org.apache.kyuubi.service.authentication import org.apache.kyuubi.config.KyuubiConf -import org.apache.kyuubi.config.KyuubiConf.ENGINE_SECURITY_SECRET_PROVIDER +import org.apache.kyuubi.config.KyuubiConf._ trait EngineSecuritySecretProvider { @@ -33,6 +33,21 @@ trait EngineSecuritySecretProvider { def getSecret(): String } +class SimpleEngineSecuritySecretProviderImpl extends EngineSecuritySecretProvider { + + private var _conf: KyuubiConf = _ + + override def initialize(conf: KyuubiConf): Unit = _conf = conf + + override def getSecret(): String = { + _conf.get(SIMPLE_SECURITY_SECRET_PROVIDER_PROVIDER_SECRET).getOrElse { + throw new IllegalArgumentException( + s"${SIMPLE_SECURITY_SECRET_PROVIDER_PROVIDER_SECRET.key} must be configured " + + s"when ${ENGINE_SECURITY_SECRET_PROVIDER.key} is `simple`.") + } + } +} + object EngineSecuritySecretProvider { def create(conf: KyuubiConf): EngineSecuritySecretProvider = { val providerClass = Class.forName(conf.get(ENGINE_SECURITY_SECRET_PROVIDER)) diff --git a/kyuubi-common/src/test/scala/org/apache/kyuubi/service/authentication/InternalSecurityAccessorSuite.scala b/kyuubi-common/src/test/scala/org/apache/kyuubi/service/authentication/InternalSecurityAccessorSuite.scala index e6c4c8506..e92ac7e61 100644 --- a/kyuubi-common/src/test/scala/org/apache/kyuubi/service/authentication/InternalSecurityAccessorSuite.scala +++ b/kyuubi-common/src/test/scala/org/apache/kyuubi/service/authentication/InternalSecurityAccessorSuite.scala @@ -22,9 +22,8 @@ import org.apache.kyuubi.config.KyuubiConf class InternalSecurityAccessorSuite extends KyuubiFunSuite { private val conf = KyuubiConf() - conf.set( - KyuubiConf.ENGINE_SECURITY_SECRET_PROVIDER, - classOf[UserDefinedEngineSecuritySecretProvider].getCanonicalName) + .set(KyuubiConf.ENGINE_SECURITY_SECRET_PROVIDER, "simple") + .set(KyuubiConf.SIMPLE_SECURITY_SECRET_PROVIDER_PROVIDER_SECRET, "ENGINE____SECRET") test("test encrypt/decrypt, issue token/auth token") { Seq("AES/CBC/PKCS5PADDING", "AES/CTR/NoPadding").foreach { cipher => diff --git a/kyuubi-server/src/test/scala/org/apache/kyuubi/server/api/v1/BatchesResourceSuite.scala b/kyuubi-server/src/test/scala/org/apache/kyuubi/server/api/v1/BatchesResourceSuite.scala index c77d364f3..ce05cbd6b 100644 --- a/kyuubi-server/src/test/scala/org/apache/kyuubi/server/api/v1/BatchesResourceSuite.scala +++ b/kyuubi-server/src/test/scala/org/apache/kyuubi/server/api/v1/BatchesResourceSuite.scala @@ -43,15 +43,14 @@ import org.apache.kyuubi.operation.OperationState.OperationState import org.apache.kyuubi.server.KyuubiRestFrontendService import org.apache.kyuubi.server.http.authentication.AuthenticationHandler.AUTHORIZATION_HEADER import org.apache.kyuubi.server.metadata.api.Metadata -import org.apache.kyuubi.service.authentication.{KyuubiAuthenticationFactory, UserDefinedEngineSecuritySecretProvider} +import org.apache.kyuubi.service.authentication.KyuubiAuthenticationFactory import org.apache.kyuubi.session.{KyuubiBatchSessionImpl, KyuubiSessionManager, SessionHandle, SessionType} class BatchesResourceSuite extends KyuubiFunSuite with RestFrontendTestHelper with BatchTestHelper { override protected lazy val conf: KyuubiConf = KyuubiConf() .set(KyuubiConf.ENGINE_SECURITY_ENABLED, true) - .set( - KyuubiConf.ENGINE_SECURITY_SECRET_PROVIDER, - classOf[UserDefinedEngineSecuritySecretProvider].getName) + .set(KyuubiConf.ENGINE_SECURITY_SECRET_PROVIDER, "simple") + .set(KyuubiConf.SIMPLE_SECURITY_SECRET_PROVIDER_PROVIDER_SECRET, "ENGINE____SECRET") .set( KyuubiConf.SESSION_LOCAL_DIR_ALLOW_LIST, Seq(Paths.get(sparkBatchTestResource.get).getParent.toString)) From 5aed270c453516fd3559aa668e56b2e1badf1c93 Mon Sep 17 00:00:00 2001 From: Kent Yao Date: Mon, 27 Feb 2023 14:50:39 +0800 Subject: [PATCH 120/760] [KYUUBI #4202] Fix reference resolution when data masking enabled for V2 relations ### _Why are the changes needed?_ Follow up to close #4304 with a complete solution. Before this PR, we did row filtering, and data masking in the same analyzer rule as both of them added patterns upon the scans, which are leaf nodes. After this, we separate them into two different rules. Because the filtering still adds filter patterns upon the leaves, but the masking now adds masker expressions upon the leaves first and later on the root of a query. The reason why we move the maskers from leaves to the root is to fix attribute resolution errors. The errors #4202 occur when we try to insert a node with resolved expressions between the root and its child, while if the parents are resolved ahead, the newly added expressions may become missing attributes. The new approach can avoid the bug. The full data masking rule contains two separate stages. * Step1: RuleApplyDataMaskingStage0 - lookup the full plan for supported scans - once found, get masker configuration from external column by column - use spark sql parser to generate an unresolved expression for each masker - add a projection with new output on the right top of the original scan if the output has changed - Add DataMaskingStage0Marker to track the original expression and its masker expression. * Step2: Spark native rules will resolve our newly added maskers * Step3: RuleApplyDataMaskingStage1 - It will fulfill the missing attributes with a related masker expression buffered by DataMaskingStage0Marker. ### _How was this patch tested?_ - [x] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [ ] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4358 from yaooqinn/PR_4304. Closes #4202 18d6a9b4a [Kent Yao] nit fc11f0250 [liangbowen] nit 39a72afb6 [Kent Yao] fix 3.0 ad1209abe [Kent Yao] iceberg catalog 3.1 c0db77d93 [Kent Yao] jdbc catalog 3.1 75ee799d4 [Kent Yao] perm view 7f8b929a7 [liangbowen] unused imports a03fc690a [liangbowen] skip cleanup derby test db in iceberg suite 20ffed95e [liangbowen] sort union result d397cc1d6 [Kent Yao] add more tests for data masking 72143798f [Kent Yao] add more tests for data masking 96f521574 [Kent Yao] new approach temp commit 5271f7d0e [Kent Yao] addr ce5ac0c32 [Kent Yao] Update extensions/spark/kyuubi-spark-authz/src/main/scala/org/apache/kyuubi/plugin/spark/authz/ranger/RuleApplyRowFilter.scala 7776b807e [Kent Yao] fix aecbc0080 [Kent Yao] fix 0f710a4b2 [Kent Yao] [KYUUBI #4202] Fix Datamasking for V2 relations 213705145 [Kent Yao] Merge branch 'master' into PR_4304 138dd0d63 [Kent Yao] [KYUUBI #4202] Fix Datamasking for V2 relations 42ca7e4d9 [Kent Yao] [KYUUBI #4202] Fix Datamasking for V2 relations fd3bec37d [Kent Yao] [KYUUBI #4202] Fix Datamasking for V2 relations 5880da8b6 [liangbowen] add ut for v2jdbc ce49feef5 [liangbowen] add ut of column masking for iceberg Lead-authored-by: Kent Yao Co-authored-by: liangbowen Signed-off-by: Kent Yao --- .../main/resources/table_command_spec.json | 17 ++ .../authz/ranger/RangerSparkExtension.scala | 6 +- .../RuleApplyRowFilterAndDataMasking.scala | 90 ------ .../spark/authz/ranger/RuleHelper.scala | 52 ++++ .../authz/ranger/SparkRangerAdminPlugin.scala | 3 +- .../datamasking/DataMaskingStage0Marker.scala | 37 +++ .../datamasking/DataMaskingStage1Marker.scala | 31 ++ .../RuleApplyDataMaskingStage0.scala | 74 +++++ .../RuleApplyDataMaskingStage1.scala | 84 ++++++ .../rowfilter/RowFilterMarker.scala} | 10 +- .../ranger/rowfilter/RuleApplyRowFilter.scala | 50 ++++ .../authz/util/RuleEliminateMarker.scala | 16 +- .../test/resources/sparkSql_hive_jenkins.json | 7 +- .../spark/authz/SparkSessionProvider.scala | 24 ++ .../spark/authz/gen/TableCommands.scala | 8 + .../ranger/RangerSparkExtensionSuite.scala | 159 ----------- ...ableCatalogRangerSparkExtensionSuite.scala | 2 +- .../DataMaskingForHiveHiveParquetSuite.scala | 23 ++ .../DataMaskingForHiveParquetSuite.scala | 23 ++ .../DataMaskingForIcebergSuite.scala | 64 +++++ .../DataMaskingForInMemoryParquetSuite.scala | 24 ++ .../DataMaskingForJDBCV2Suite.scala | 65 +++++ .../datamasking/DataMaskingTestBase.scala | 267 ++++++++++++++++++ 23 files changed, 875 insertions(+), 261 deletions(-) delete mode 100644 extensions/spark/kyuubi-spark-authz/src/main/scala/org/apache/kyuubi/plugin/spark/authz/ranger/RuleApplyRowFilterAndDataMasking.scala create mode 100644 extensions/spark/kyuubi-spark-authz/src/main/scala/org/apache/kyuubi/plugin/spark/authz/ranger/RuleHelper.scala create mode 100644 extensions/spark/kyuubi-spark-authz/src/main/scala/org/apache/kyuubi/plugin/spark/authz/ranger/datamasking/DataMaskingStage0Marker.scala create mode 100644 extensions/spark/kyuubi-spark-authz/src/main/scala/org/apache/kyuubi/plugin/spark/authz/ranger/datamasking/DataMaskingStage1Marker.scala create mode 100644 extensions/spark/kyuubi-spark-authz/src/main/scala/org/apache/kyuubi/plugin/spark/authz/ranger/datamasking/RuleApplyDataMaskingStage0.scala create mode 100644 extensions/spark/kyuubi-spark-authz/src/main/scala/org/apache/kyuubi/plugin/spark/authz/ranger/datamasking/RuleApplyDataMaskingStage1.scala rename extensions/spark/kyuubi-spark-authz/src/main/scala/org/apache/kyuubi/plugin/spark/authz/{util/RowFilterAndDataMaskingMarker.scala => ranger/rowfilter/RowFilterMarker.scala} (80%) create mode 100644 extensions/spark/kyuubi-spark-authz/src/main/scala/org/apache/kyuubi/plugin/spark/authz/ranger/rowfilter/RuleApplyRowFilter.scala create mode 100644 extensions/spark/kyuubi-spark-authz/src/test/scala/org/apache/kyuubi/plugin/spark/authz/ranger/datamasking/DataMaskingForHiveHiveParquetSuite.scala create mode 100644 extensions/spark/kyuubi-spark-authz/src/test/scala/org/apache/kyuubi/plugin/spark/authz/ranger/datamasking/DataMaskingForHiveParquetSuite.scala create mode 100644 extensions/spark/kyuubi-spark-authz/src/test/scala/org/apache/kyuubi/plugin/spark/authz/ranger/datamasking/DataMaskingForIcebergSuite.scala create mode 100644 extensions/spark/kyuubi-spark-authz/src/test/scala/org/apache/kyuubi/plugin/spark/authz/ranger/datamasking/DataMaskingForInMemoryParquetSuite.scala create mode 100644 extensions/spark/kyuubi-spark-authz/src/test/scala/org/apache/kyuubi/plugin/spark/authz/ranger/datamasking/DataMaskingForJDBCV2Suite.scala create mode 100644 extensions/spark/kyuubi-spark-authz/src/test/scala/org/apache/kyuubi/plugin/spark/authz/ranger/datamasking/DataMaskingTestBase.scala diff --git a/extensions/spark/kyuubi-spark-authz/src/main/resources/table_command_spec.json b/extensions/spark/kyuubi-spark-authz/src/main/resources/table_command_spec.json index d36690bcf..f1c2297b3 100644 --- a/extensions/spark/kyuubi-spark-authz/src/main/resources/table_command_spec.json +++ b/extensions/spark/kyuubi-spark-authz/src/main/resources/table_command_spec.json @@ -1176,6 +1176,23 @@ } ], "opType" : "TRUNCATETABLE", "queryDescs" : [ ] +}, { + "classname" : "org.apache.spark.sql.execution.datasources.CreateTable", + "tableDescs" : [ { + "fieldName" : "tableDesc", + "fieldExtractor" : "CatalogTableTableExtractor", + "columnDesc" : null, + "actionTypeDesc" : null, + "tableTypeDesc" : null, + "catalogDesc" : null, + "isInput" : false, + "setCurrentDatabaseIfMissing" : false + } ], + "opType" : "CREATETABLE", + "queryDescs" : [ { + "fieldName" : "query", + "fieldExtractor" : "LogicalPlanOptionQueryExtractor" + } ] }, { "classname" : "org.apache.spark.sql.execution.datasources.CreateTempViewUsing", "tableDescs" : [ ], diff --git a/extensions/spark/kyuubi-spark-authz/src/main/scala/org/apache/kyuubi/plugin/spark/authz/ranger/RangerSparkExtension.scala b/extensions/spark/kyuubi-spark-authz/src/main/scala/org/apache/kyuubi/plugin/spark/authz/ranger/RangerSparkExtension.scala index 5708dfeaf..f8e941d9d 100644 --- a/extensions/spark/kyuubi-spark-authz/src/main/scala/org/apache/kyuubi/plugin/spark/authz/ranger/RangerSparkExtension.scala +++ b/extensions/spark/kyuubi-spark-authz/src/main/scala/org/apache/kyuubi/plugin/spark/authz/ranger/RangerSparkExtension.scala @@ -19,6 +19,8 @@ package org.apache.kyuubi.plugin.spark.authz.ranger import org.apache.spark.sql.SparkSessionExtensions +import org.apache.kyuubi.plugin.spark.authz.ranger.datamasking.{RuleApplyDataMaskingStage0, RuleApplyDataMaskingStage1} +import org.apache.kyuubi.plugin.spark.authz.ranger.rowfilter.RuleApplyRowFilter import org.apache.kyuubi.plugin.spark.authz.util.{RuleEliminateMarker, RuleEliminateViewMarker} /** @@ -42,7 +44,9 @@ class RangerSparkExtension extends (SparkSessionExtensions => Unit) { v1.injectCheckRule(AuthzConfigurationChecker) v1.injectResolutionRule(_ => new RuleReplaceShowObjectCommands()) v1.injectResolutionRule(_ => new RuleApplyPermanentViewMarker()) - v1.injectResolutionRule(new RuleApplyRowFilterAndDataMasking(_)) + v1.injectResolutionRule(RuleApplyRowFilter) + v1.injectResolutionRule(RuleApplyDataMaskingStage0) + v1.injectResolutionRule(RuleApplyDataMaskingStage1) v1.injectOptimizerRule(_ => new RuleEliminateMarker()) v1.injectOptimizerRule(new RuleAuthorization(_)) v1.injectOptimizerRule(_ => new RuleEliminateViewMarker()) diff --git a/extensions/spark/kyuubi-spark-authz/src/main/scala/org/apache/kyuubi/plugin/spark/authz/ranger/RuleApplyRowFilterAndDataMasking.scala b/extensions/spark/kyuubi-spark-authz/src/main/scala/org/apache/kyuubi/plugin/spark/authz/ranger/RuleApplyRowFilterAndDataMasking.scala deleted file mode 100644 index b6961c924..000000000 --- a/extensions/spark/kyuubi-spark-authz/src/main/scala/org/apache/kyuubi/plugin/spark/authz/ranger/RuleApplyRowFilterAndDataMasking.scala +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.kyuubi.plugin.spark.authz.ranger - -import org.apache.spark.sql.SparkSession -import org.apache.spark.sql.catalyst.expressions.Alias -import org.apache.spark.sql.catalyst.plans.logical.{Filter, LogicalPlan, Project} -import org.apache.spark.sql.catalyst.rules.Rule - -import org.apache.kyuubi.plugin.spark.authz.ObjectType -import org.apache.kyuubi.plugin.spark.authz.serde._ -import org.apache.kyuubi.plugin.spark.authz.util.{PermanentViewMarker, RowFilterAndDataMaskingMarker} -import org.apache.kyuubi.plugin.spark.authz.util.AuthZUtils._ - -class RuleApplyRowFilterAndDataMasking(spark: SparkSession) extends Rule[LogicalPlan] { - private def mapChildren(plan: LogicalPlan)(f: LogicalPlan => LogicalPlan): LogicalPlan = { - val newChildren = plan match { - case cmd if isKnownTableCommand(cmd) => - val tableCommandSpec = getTableCommandSpec(cmd) - val queries = tableCommandSpec.queries(cmd) - cmd.children.map { - case c if queries.contains(c) => f(c) - case other => other - } - case _ => - plan.children.map(f) - } - plan.withNewChildren(newChildren) - } - - override def apply(plan: LogicalPlan): LogicalPlan = { - mapChildren(plan) { - case p: RowFilterAndDataMaskingMarker => p - case scan if isKnownScan(scan) && scan.resolved => - val tables = getScanSpec(scan).tables(scan, spark) - tables.headOption.map(applyFilterAndMasking(scan, _)).getOrElse(scan) - case other => apply(other) - } - } - - private def applyFilterAndMasking( - plan: LogicalPlan, - table: Table): LogicalPlan = { - val ugi = getAuthzUgi(spark.sparkContext) - val opType = operationType(plan) - val parse = spark.sessionState.sqlParser.parseExpression _ - val are = AccessResource(ObjectType.TABLE, table.database.orNull, table.table, null) - val art = AccessRequest(are, ugi, opType, AccessType.SELECT) - val filterExprStr = SparkRangerAdminPlugin.getFilterExpr(art) - val newOutput = plan.output.map { attr => - val are = - AccessResource(ObjectType.COLUMN, table.database.orNull, table.table, attr.name) - val art = AccessRequest(are, ugi, opType, AccessType.SELECT) - val maskExprStr = SparkRangerAdminPlugin.getMaskingExpr(art) - if (maskExprStr.isEmpty) { - attr - } else { - val maskExpr = parse(maskExprStr.get) - plan match { - case _: PermanentViewMarker => - Alias(maskExpr, attr.name)(exprId = attr.exprId) - case _ => - Alias(maskExpr, attr.name)() - } - } - } - - if (filterExprStr.isEmpty) { - Project(newOutput, RowFilterAndDataMaskingMarker(plan)) - } else { - val filterExpr = parse(filterExprStr.get) - Project(newOutput, Filter(filterExpr, RowFilterAndDataMaskingMarker(plan))) - } - } -} diff --git a/extensions/spark/kyuubi-spark-authz/src/main/scala/org/apache/kyuubi/plugin/spark/authz/ranger/RuleHelper.scala b/extensions/spark/kyuubi-spark-authz/src/main/scala/org/apache/kyuubi/plugin/spark/authz/ranger/RuleHelper.scala new file mode 100644 index 000000000..3cfe2b940 --- /dev/null +++ b/extensions/spark/kyuubi-spark-authz/src/main/scala/org/apache/kyuubi/plugin/spark/authz/ranger/RuleHelper.scala @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.kyuubi.plugin.spark.authz.ranger + +import org.apache.hadoop.security.UserGroupInformation +import org.apache.spark.sql.SparkSession +import org.apache.spark.sql.catalyst.expressions.Expression +import org.apache.spark.sql.catalyst.plans.logical.LogicalPlan +import org.apache.spark.sql.catalyst.rules.Rule + +import org.apache.kyuubi.plugin.spark.authz.serde.{getTableCommandSpec, isKnownTableCommand} +import org.apache.kyuubi.plugin.spark.authz.util.AuthZUtils + +trait RuleHelper extends Rule[LogicalPlan] { + + def spark: SparkSession + + final protected val parse: String => Expression = spark.sessionState.sqlParser.parseExpression _ + + protected def mapChildren(plan: LogicalPlan)(f: LogicalPlan => LogicalPlan): LogicalPlan = { + val newChildren = plan match { + case cmd if isKnownTableCommand(cmd) => + val tableCommandSpec = getTableCommandSpec(cmd) + val queries = tableCommandSpec.queries(cmd) + cmd.children.map { + case c if queries.contains(c) => f(c) + case other => other + } + case _ => + plan.children.map(f) + } + plan.withNewChildren(newChildren) + } + + def ugi: UserGroupInformation = AuthZUtils.getAuthzUgi(spark.sparkContext) + +} diff --git a/extensions/spark/kyuubi-spark-authz/src/main/scala/org/apache/kyuubi/plugin/spark/authz/ranger/SparkRangerAdminPlugin.scala b/extensions/spark/kyuubi-spark-authz/src/main/scala/org/apache/kyuubi/plugin/spark/authz/ranger/SparkRangerAdminPlugin.scala index 3d46563aa..78e59ff89 100644 --- a/extensions/spark/kyuubi-spark-authz/src/main/scala/org/apache/kyuubi/plugin/spark/authz/ranger/SparkRangerAdminPlugin.scala +++ b/extensions/spark/kyuubi-spark-authz/src/main/scala/org/apache/kyuubi/plugin/spark/authz/ranger/SparkRangerAdminPlugin.scala @@ -18,8 +18,7 @@ package org.apache.kyuubi.plugin.spark.authz.ranger import scala.collection.JavaConverters._ -import scala.collection.mutable.ArrayBuffer -import scala.collection.mutable.LinkedHashMap +import scala.collection.mutable.{ArrayBuffer, LinkedHashMap} import org.apache.hadoop.util.ShutdownHookManager import org.apache.ranger.plugin.policyengine.RangerAccessRequest diff --git a/extensions/spark/kyuubi-spark-authz/src/main/scala/org/apache/kyuubi/plugin/spark/authz/ranger/datamasking/DataMaskingStage0Marker.scala b/extensions/spark/kyuubi-spark-authz/src/main/scala/org/apache/kyuubi/plugin/spark/authz/ranger/datamasking/DataMaskingStage0Marker.scala new file mode 100644 index 000000000..b43149383 --- /dev/null +++ b/extensions/spark/kyuubi-spark-authz/src/main/scala/org/apache/kyuubi/plugin/spark/authz/ranger/datamasking/DataMaskingStage0Marker.scala @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.kyuubi.plugin.spark.authz.ranger.datamasking + +import org.apache.spark.sql.catalyst.expressions.{Attribute, ExprId} +import org.apache.spark.sql.catalyst.plans.logical.{LogicalPlan, UnaryNode} + +import org.apache.kyuubi.plugin.spark.authz.util.WithInternalChild +case class DataMaskingStage0Marker(child: LogicalPlan, scan: LogicalPlan) + extends UnaryNode with WithInternalChild { + + def exprToMaskers(): Map[ExprId, Attribute] = { + scan.output.map(_.exprId).zip(child.output).flatMap { case (id, expr) => + if (id == expr.exprId) None else Some(id -> expr) + }.toMap + } + + override def output: Seq[Attribute] = child.output + + override def withNewChildInternal(newChild: LogicalPlan): LogicalPlan = copy(child = newChild) + +} diff --git a/extensions/spark/kyuubi-spark-authz/src/main/scala/org/apache/kyuubi/plugin/spark/authz/ranger/datamasking/DataMaskingStage1Marker.scala b/extensions/spark/kyuubi-spark-authz/src/main/scala/org/apache/kyuubi/plugin/spark/authz/ranger/datamasking/DataMaskingStage1Marker.scala new file mode 100644 index 000000000..aed0ac693 --- /dev/null +++ b/extensions/spark/kyuubi-spark-authz/src/main/scala/org/apache/kyuubi/plugin/spark/authz/ranger/datamasking/DataMaskingStage1Marker.scala @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.kyuubi.plugin.spark.authz.ranger.datamasking + +import org.apache.spark.sql.catalyst.expressions.Attribute +import org.apache.spark.sql.catalyst.plans.logical.{LogicalPlan, UnaryNode} + +import org.apache.kyuubi.plugin.spark.authz.util.WithInternalChild + +case class DataMaskingStage1Marker(child: LogicalPlan) extends UnaryNode with WithInternalChild { + + override def output: Seq[Attribute] = child.output + + override def withNewChildInternal(newChild: LogicalPlan): LogicalPlan = copy(child = newChild) + +} diff --git a/extensions/spark/kyuubi-spark-authz/src/main/scala/org/apache/kyuubi/plugin/spark/authz/ranger/datamasking/RuleApplyDataMaskingStage0.scala b/extensions/spark/kyuubi-spark-authz/src/main/scala/org/apache/kyuubi/plugin/spark/authz/ranger/datamasking/RuleApplyDataMaskingStage0.scala new file mode 100644 index 000000000..de125550a --- /dev/null +++ b/extensions/spark/kyuubi-spark-authz/src/main/scala/org/apache/kyuubi/plugin/spark/authz/ranger/datamasking/RuleApplyDataMaskingStage0.scala @@ -0,0 +1,74 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.kyuubi.plugin.spark.authz.ranger.datamasking + +import org.apache.spark.sql.SparkSession +import org.apache.spark.sql.catalyst.expressions.Alias +import org.apache.spark.sql.catalyst.plans.logical.{LogicalPlan, Project} + +import org.apache.kyuubi.plugin.spark.authz.ObjectType +import org.apache.kyuubi.plugin.spark.authz.OperationType.QUERY +import org.apache.kyuubi.plugin.spark.authz.ranger._ +import org.apache.kyuubi.plugin.spark.authz.serde._ + +/** + * The full data masking rule contains two separate stages. + * + * Step1: RuleApplyDataMaskingStage0 + * - lookup the full plan for supported scans + * - once found, get masker configuration from external column by column + * - use spark sql parser to generate an unresolved expression for each masker + * - add a projection with new output on the right top of the original scan if the output has + * changed + * - Add DataMaskingStage0Marker to track the original expression and its masker expression. + * + * Step2: Spark native rules will resolve our newly added maskers + * + * Step3: [[RuleApplyDataMaskingStage1]] + */ +case class RuleApplyDataMaskingStage0(spark: SparkSession) extends RuleHelper { + + override def apply(plan: LogicalPlan): LogicalPlan = { + val newPlan = mapChildren(plan) { + case p: DataMaskingStage0Marker => p + case p: DataMaskingStage1Marker => p + case scan if isKnownScan(scan) && scan.resolved => + val tables = getScanSpec(scan).tables(scan, spark) + tables.headOption.map(applyMasking(scan, _)).getOrElse(scan) + case other => apply(other) + } + newPlan + } + + private def applyMasking( + plan: LogicalPlan, + table: Table): LogicalPlan = { + val newOutput = plan.output.map { attr => + val are = + AccessResource(ObjectType.COLUMN, table.database.orNull, table.table, attr.name) + val art = AccessRequest(are, ugi, QUERY, AccessType.SELECT) + val maskExprStr = SparkRangerAdminPlugin.getMaskingExpr(art) + maskExprStr.map(parse).map(Alias(_, attr.name)()).getOrElse(attr) + } + if (newOutput == plan.output) { + plan + } else { + DataMaskingStage0Marker(Project(newOutput, plan), plan) + } + } +} diff --git a/extensions/spark/kyuubi-spark-authz/src/main/scala/org/apache/kyuubi/plugin/spark/authz/ranger/datamasking/RuleApplyDataMaskingStage1.scala b/extensions/spark/kyuubi-spark-authz/src/main/scala/org/apache/kyuubi/plugin/spark/authz/ranger/datamasking/RuleApplyDataMaskingStage1.scala new file mode 100644 index 000000000..9589be2e9 --- /dev/null +++ b/extensions/spark/kyuubi-spark-authz/src/main/scala/org/apache/kyuubi/plugin/spark/authz/ranger/datamasking/RuleApplyDataMaskingStage1.scala @@ -0,0 +1,84 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.kyuubi.plugin.spark.authz.ranger.datamasking + +import org.apache.spark.sql.SparkSession +import org.apache.spark.sql.catalyst.expressions.NamedExpression +import org.apache.spark.sql.catalyst.plans.logical.{Command, LogicalPlan} + +import org.apache.kyuubi.plugin.spark.authz.ranger.RuleHelper +import org.apache.kyuubi.plugin.spark.authz.serde._ + +/** + * See [[RuleApplyDataMaskingStage0]] also. + * + * This is the second step for data masking. It will fulfill the missing attributes that + * have a related masker expression buffered by DataMaskingStage0Marker. + */ +case class RuleApplyDataMaskingStage1(spark: SparkSession) extends RuleHelper { + + override def apply(plan: LogicalPlan): LogicalPlan = { + + plan match { + case marker0: DataMaskingStage0Marker => marker0 + case marker1: DataMaskingStage1Marker => marker1 + case cmd if isKnownTableCommand(cmd) => + val tableCommandSpec = getTableCommandSpec(cmd) + val queries = tableCommandSpec.queries(cmd) + cmd.mapChildren { + case marker0: DataMaskingStage0Marker => marker0 + case marker1: DataMaskingStage1Marker => marker1 + case query if queries.contains(query) && query.resolved => + applyDataMasking(query) + case o => o + } + case cmd: Command if cmd.childrenResolved => + cmd.mapChildren(applyDataMasking) + case cmd: Command => cmd + case other if other.resolved => applyDataMasking(other) + case other => other + } + } + + private def applyDataMasking(plan: LogicalPlan): LogicalPlan = { + assert(plan.resolved, "the current masking approach relies on a resolved plan") + def replaceOriginExprWithMasker(plan: LogicalPlan): LogicalPlan = plan match { + case m: DataMaskingStage0Marker => m + case m: DataMaskingStage1Marker => m + case p => + val maskerExprs = p.collect { + case marker: DataMaskingStage0Marker if marker.resolved => marker.exprToMaskers() + }.flatten.toMap + if (maskerExprs.isEmpty) { + p + } else { + val t = p.transformExpressionsUp { + case e: NamedExpression => maskerExprs.getOrElse(e.exprId, e) + } + t.withNewChildren(t.children.map(replaceOriginExprWithMasker)) + } + } + val newPlan = replaceOriginExprWithMasker(plan) + + if (newPlan == plan) { + plan + } else { + DataMaskingStage1Marker(newPlan) + } + } +} diff --git a/extensions/spark/kyuubi-spark-authz/src/main/scala/org/apache/kyuubi/plugin/spark/authz/util/RowFilterAndDataMaskingMarker.scala b/extensions/spark/kyuubi-spark-authz/src/main/scala/org/apache/kyuubi/plugin/spark/authz/ranger/rowfilter/RowFilterMarker.scala similarity index 80% rename from extensions/spark/kyuubi-spark-authz/src/main/scala/org/apache/kyuubi/plugin/spark/authz/util/RowFilterAndDataMaskingMarker.scala rename to extensions/spark/kyuubi-spark-authz/src/main/scala/org/apache/kyuubi/plugin/spark/authz/ranger/rowfilter/RowFilterMarker.scala index 357e9bfc2..8817958b5 100644 --- a/extensions/spark/kyuubi-spark-authz/src/main/scala/org/apache/kyuubi/plugin/spark/authz/util/RowFilterAndDataMaskingMarker.scala +++ b/extensions/spark/kyuubi-spark-authz/src/main/scala/org/apache/kyuubi/plugin/spark/authz/ranger/rowfilter/RowFilterMarker.scala @@ -15,17 +15,17 @@ * limitations under the License. */ -package org.apache.kyuubi.plugin.spark.authz.util +package org.apache.kyuubi.plugin.spark.authz.ranger.rowfilter import org.apache.spark.sql.catalyst.expressions.Attribute import org.apache.spark.sql.catalyst.plans.logical.{LogicalPlan, UnaryNode} -case class RowFilterAndDataMaskingMarker(child: LogicalPlan) extends UnaryNode - with WithInternalChild { +import org.apache.kyuubi.plugin.spark.authz.util.WithInternalChild + +case class RowFilterMarker(child: LogicalPlan) extends UnaryNode with WithInternalChild { override def output: Seq[Attribute] = child.output - override def withNewChildInternal(newChild: LogicalPlan): LogicalPlan = - copy(child = newChild) + override def withNewChildInternal(newChild: LogicalPlan): LogicalPlan = copy(child = newChild) } diff --git a/extensions/spark/kyuubi-spark-authz/src/main/scala/org/apache/kyuubi/plugin/spark/authz/ranger/rowfilter/RuleApplyRowFilter.scala b/extensions/spark/kyuubi-spark-authz/src/main/scala/org/apache/kyuubi/plugin/spark/authz/ranger/rowfilter/RuleApplyRowFilter.scala new file mode 100644 index 000000000..22bcfae49 --- /dev/null +++ b/extensions/spark/kyuubi-spark-authz/src/main/scala/org/apache/kyuubi/plugin/spark/authz/ranger/rowfilter/RuleApplyRowFilter.scala @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.kyuubi.plugin.spark.authz.ranger.rowfilter + +import org.apache.spark.sql.SparkSession +import org.apache.spark.sql.catalyst.plans.logical.{Filter, LogicalPlan} + +import org.apache.kyuubi.plugin.spark.authz.ObjectType +import org.apache.kyuubi.plugin.spark.authz.OperationType.QUERY +import org.apache.kyuubi.plugin.spark.authz.ranger._ +import org.apache.kyuubi.plugin.spark.authz.serde._ + +case class RuleApplyRowFilter(spark: SparkSession) extends RuleHelper { + + override def apply(plan: LogicalPlan): LogicalPlan = { + val newPlan = mapChildren(plan) { + case p: RowFilterMarker => p + case scan if isKnownScan(scan) && scan.resolved => + val tables = getScanSpec(scan).tables(scan, spark) + tables.headOption.map(applyFilter(scan, _)).getOrElse(scan) + case other => apply(other) + } + newPlan + } + + private def applyFilter( + plan: LogicalPlan, + table: Table): LogicalPlan = { + val are = AccessResource(ObjectType.TABLE, table.database.orNull, table.table, null) + val art = AccessRequest(are, ugi, QUERY, AccessType.SELECT) + val filterExpr = SparkRangerAdminPlugin.getFilterExpr(art).map(parse) + val filtered = filterExpr.foldLeft(plan)((p, expr) => Filter(expr, RowFilterMarker(p))) + filtered + } +} diff --git a/extensions/spark/kyuubi-spark-authz/src/main/scala/org/apache/kyuubi/plugin/spark/authz/util/RuleEliminateMarker.scala b/extensions/spark/kyuubi-spark-authz/src/main/scala/org/apache/kyuubi/plugin/spark/authz/util/RuleEliminateMarker.scala index d2da72570..448439b84 100644 --- a/extensions/spark/kyuubi-spark-authz/src/main/scala/org/apache/kyuubi/plugin/spark/authz/util/RuleEliminateMarker.scala +++ b/extensions/spark/kyuubi-spark-authz/src/main/scala/org/apache/kyuubi/plugin/spark/authz/util/RuleEliminateMarker.scala @@ -17,11 +17,25 @@ package org.apache.kyuubi.plugin.spark.authz.util +import org.apache.spark.sql.catalyst.expressions.SubqueryExpression import org.apache.spark.sql.catalyst.plans.logical.LogicalPlan import org.apache.spark.sql.catalyst.rules.Rule +import org.apache.kyuubi.plugin.spark.authz.ranger.datamasking.{DataMaskingStage0Marker, DataMaskingStage1Marker} +import org.apache.kyuubi.plugin.spark.authz.ranger.rowfilter.RowFilterMarker + class RuleEliminateMarker extends Rule[LogicalPlan] { override def apply(plan: LogicalPlan): LogicalPlan = { - plan.transformUp { case rf: RowFilterAndDataMaskingMarker => rf.child } + plan.transformUp { case p => + p.transformExpressionsUp { + case p: SubqueryExpression => + p.withNewPlan(apply(p.plan)) + } match { + case marker0: DataMaskingStage0Marker => marker0.child + case marker1: DataMaskingStage1Marker => marker1.child + case rf: RowFilterMarker => rf.child + case other => other + } + } } } diff --git a/extensions/spark/kyuubi-spark-authz/src/test/resources/sparkSql_hive_jenkins.json b/extensions/spark/kyuubi-spark-authz/src/test/resources/sparkSql_hive_jenkins.json index b5b069c46..84b0e30eb 100644 --- a/extensions/spark/kyuubi-spark-authz/src/test/resources/sparkSql_hive_jenkins.json +++ b/extensions/spark/kyuubi-spark-authz/src/test/resources/sparkSql_hive_jenkins.json @@ -280,7 +280,8 @@ "values": [ "default", "spark_catalog", - "iceberg_ns" + "iceberg_ns", + "ns1" ], "isExcludes": false, "isRecursive": false @@ -900,7 +901,9 @@ "database": { "values": [ "default", - "spark_catalog" + "spark_catalog", + "iceberg_ns", + "ns1" ], "isExcludes": false, "isRecursive": false diff --git a/extensions/spark/kyuubi-spark-authz/src/test/scala/org/apache/kyuubi/plugin/spark/authz/SparkSessionProvider.scala b/extensions/spark/kyuubi-spark-authz/src/test/scala/org/apache/kyuubi/plugin/spark/authz/SparkSessionProvider.scala index 0ab88917b..a1f2d7197 100644 --- a/extensions/spark/kyuubi-spark-authz/src/test/scala/org/apache/kyuubi/plugin/spark/authz/SparkSessionProvider.scala +++ b/extensions/spark/kyuubi-spark-authz/src/test/scala/org/apache/kyuubi/plugin/spark/authz/SparkSessionProvider.scala @@ -71,4 +71,28 @@ trait SparkSessionProvider { protected val sql: String => DataFrame = spark.sql + protected def doAs[T](user: String, f: => T): T = { + UserGroupInformation.createRemoteUser(user).doAs[T]( + new PrivilegedExceptionAction[T] { + override def run(): T = f + }) + } + protected def withCleanTmpResources[T](res: Seq[(String, String)])(f: => T): T = { + try { + f + } finally { + res.foreach { + case (t, "table") => doAs("admin", sql(s"DROP TABLE IF EXISTS $t")) + case (db, "database") => doAs("admin", sql(s"DROP DATABASE IF EXISTS $db")) + case (fn, "function") => doAs("admin", sql(s"DROP FUNCTION IF EXISTS $fn")) + case (view, "view") => doAs("admin", sql(s"DROP VIEW IF EXISTS $view")) + case (cacheTable, "cache") => if (isSparkV32OrGreater) { + doAs("admin", sql(s"UNCACHE TABLE IF EXISTS $cacheTable")) + } + case (_, e) => + throw new RuntimeException(s"the resource whose resource type is $e cannot be cleared") + } + } + } + } diff --git a/extensions/spark/kyuubi-spark-authz/src/test/scala/org/apache/kyuubi/plugin/spark/authz/gen/TableCommands.scala b/extensions/spark/kyuubi-spark-authz/src/test/scala/org/apache/kyuubi/plugin/spark/authz/gen/TableCommands.scala index d24583e76..a8b8121e2 100644 --- a/extensions/spark/kyuubi-spark-authz/src/test/scala/org/apache/kyuubi/plugin/spark/authz/gen/TableCommands.scala +++ b/extensions/spark/kyuubi-spark-authz/src/test/scala/org/apache/kyuubi/plugin/spark/authz/gen/TableCommands.scala @@ -346,6 +346,13 @@ object TableCommands { TableCommandSpec(cmd, Nil, CREATEVIEW) } + val CreateTable = { + val cmd = "org.apache.spark.sql.execution.datasources.CreateTable" + val tableDesc = TableDesc("tableDesc", classOf[CatalogTableTableExtractor]) + val queryDesc = QueryDesc("query", "LogicalPlanOptionQueryExtractor") + TableCommandSpec(cmd, Seq(tableDesc), CREATETABLE, queryDescs = Seq(queryDesc)) + } + val CreateDataSourceTable = { val cmd = "org.apache.spark.sql.execution.command.CreateDataSourceTableCommand" val tableDesc = TableDesc("table", classOf[CatalogTableTableExtractor]) @@ -607,6 +614,7 @@ object TableCommands { CreateHiveTableAsSelect, CreateHiveTableAsSelect.copy(classname = "org.apache.spark.sql.hive.execution.OptimizedCreateHiveTableAsSelectCommand"), + CreateTable, CreateTableLike, CreateTableV2, CreateTableV2.copy(classname = diff --git a/extensions/spark/kyuubi-spark-authz/src/test/scala/org/apache/kyuubi/plugin/spark/authz/ranger/RangerSparkExtensionSuite.scala b/extensions/spark/kyuubi-spark-authz/src/test/scala/org/apache/kyuubi/plugin/spark/authz/ranger/RangerSparkExtensionSuite.scala index 8f95a3f9f..48f374255 100644 --- a/extensions/spark/kyuubi-spark-authz/src/test/scala/org/apache/kyuubi/plugin/spark/authz/ranger/RangerSparkExtensionSuite.scala +++ b/extensions/spark/kyuubi-spark-authz/src/test/scala/org/apache/kyuubi/plugin/spark/authz/ranger/RangerSparkExtensionSuite.scala @@ -17,12 +17,8 @@ package org.apache.kyuubi.plugin.spark.authz.ranger -import java.security.PrivilegedExceptionAction -import java.sql.Timestamp - import scala.util.Try -import org.apache.commons.codec.digest.DigestUtils import org.apache.hadoop.security.UserGroupInformation import org.apache.spark.sql.{Row, SparkSessionExtensions} import org.apache.spark.sql.catalyst.analysis.NoSuchTableException @@ -43,13 +39,6 @@ abstract class RangerSparkExtensionSuite extends AnyFunSuite // scalastyle:on override protected val extension: SparkSessionExtensions => Unit = new RangerSparkExtension - protected def doAs[T](user: String, f: => T): T = { - UserGroupInformation.createRemoteUser(user).doAs[T]( - new PrivilegedExceptionAction[T] { - override def run(): T = f - }) - } - override def afterAll(): Unit = { spark.stop() super.afterAll() @@ -62,24 +51,6 @@ abstract class RangerSparkExtensionSuite extends AnyFunSuite s"Permission denied: user [$user] does not have [$privilege] privilege on [$resource]" } - protected def withCleanTmpResources[T](res: Seq[(String, String)])(f: => T): T = { - try { - f - } finally { - res.foreach { - case (t, "table") => doAs("admin", sql(s"DROP TABLE IF EXISTS $t")) - case (db, "database") => doAs("admin", sql(s"DROP DATABASE IF EXISTS $db")) - case (fn, "function") => doAs("admin", sql(s"DROP FUNCTION IF EXISTS $fn")) - case (view, "view") => doAs("admin", sql(s"DROP VIEW IF EXISTS $view")) - case (cacheTable, "cache") => if (isSparkV32OrGreater) { - doAs("admin", sql(s"UNCACHE TABLE IF EXISTS $cacheTable")) - } - case (_, e) => - throw new RuntimeException(s"the resource whose resource type is $e cannot be cleared") - } - } - } - /** * Drops temporary view `viewNames` after calling `f`. */ @@ -324,135 +295,6 @@ abstract class RangerSparkExtensionSuite extends AnyFunSuite } } - test("data masking") { - val db = "default" - val table = "src" - val col = "key" - val create = - s"CREATE TABLE IF NOT EXISTS $db.$table" + - s" ($col int, value1 int, value2 string, value3 string, value4 timestamp, value5 string)" + - s" USING $format" - - withCleanTmpResources(Seq( - (s"$db.${table}2", "table"), - (s"$db.$table", "table"))) { - doAs("admin", assert(Try { sql(create) }.isSuccess)) - doAs( - "admin", - sql( - s"INSERT INTO $db.$table SELECT 1, 1, 'hello', 'world', " + - s"timestamp'2018-11-17 12:34:56', 'World'")) - doAs( - "admin", - sql( - s"INSERT INTO $db.$table SELECT 20, 2, 'kyuubi', 'y', " + - s"timestamp'2018-11-17 12:34:56', 'world'")) - doAs( - "admin", - sql( - s"INSERT INTO $db.$table SELECT 30, 3, 'spark', 'a'," + - s" timestamp'2018-11-17 12:34:56', 'world'")) - - doAs( - "kent", - assert(sql(s"SELECT key FROM $db.$table order by key").collect() === - Seq(Row(1), Row(20), Row(30)))) - - Seq( - s"SELECT value1, value2, value3, value4, value5 FROM $db.$table", - s"SELECT value1 as key, value2, value3, value4, value5 FROM $db.$table", - s"SELECT max(value1), max(value2), max(value3), max(value4), max(value5) FROM $db.$table", - s"SELECT coalesce(max(value1), 1), coalesce(max(value2), 1), coalesce(max(value3), 1), " + - s"coalesce(max(value4), timestamp '2018-01-01 22:33:44'), coalesce(max(value5), 1) " + - s"FROM $db.$table", - s"SELECT value1, value2, value3, value4, value5 FROM $db.$table WHERE value2 in" + - s" (SELECT value2 as key FROM $db.$table)") - .foreach { q => - doAs( - "bob", { - withClue(q) { - assert(sql(q).collect() === - Seq( - Row( - DigestUtils.md5Hex("1"), - "xxxxx", - "worlx", - Timestamp.valueOf("2018-01-01 00:00:00"), - "Xorld"))) - } - }) - } - doAs( - "bob", { - sql(s"CREATE TABLE $db.src2 using $format AS SELECT value1 FROM $db.$table") - assert(sql(s"SELECT value1 FROM $db.${table}2").collect() === - Seq(Row(DigestUtils.md5Hex("1")))) - }) - } - } - - test("[KYUUBI #3581]: data masking on permanent view") { - assume(isSparkV31OrGreater) - - val db = "default" - val table = "src" - val permView = "perm_view" - val col = "key" - val create = - s"CREATE TABLE IF NOT EXISTS $db.$table" + - s" ($col int, value1 int, value2 string)" + - s" USING $format" - - val createView = - s"CREATE OR REPLACE VIEW $db.$permView" + - s" AS SELECT * FROM $db.$table" - - withCleanTmpResources(Seq( - (s"$db.$table", "table"), - (s"$db.$permView", "view"))) { - doAs("admin", assert(Try { sql(create) }.isSuccess)) - doAs("admin", assert(Try { sql(createView) }.isSuccess)) - doAs( - "admin", - sql( - s"INSERT INTO $db.$table SELECT 1, 1, 'hello'")) - - Seq( - s"SELECT value1, value2 FROM $db.$permView") - .foreach { q => - doAs( - "perm_view_user", { - withClue(q) { - assert(sql(q).collect() === - Seq( - Row( - DigestUtils.md5Hex("1"), - "hello"))) - } - }) - } - } - } - - test("KYUUBI #2390: RuleEliminateMarker stays in analyze phase for data masking") { - val db = "default" - val table = "src" - val create = - s"CREATE TABLE IF NOT EXISTS $db.$table (key int, value1 int) USING $format" - - withCleanTmpResources(Seq((s"$db.$table", "table"))) { - doAs("admin", sql(create)) - doAs("admin", sql(s"INSERT INTO $db.$table SELECT 1, 1")) - // scalastyle: off - doAs( - "bob", { - assert(sql(s"select * from $db.$table").collect() === - Seq(Row(1, DigestUtils.md5Hex("1")))) - assert(Try(sql(s"select * from $db.$table").show(1)).isSuccess) - }) - } - } - test("show tables") { val db = "default2" val table = "src" @@ -680,7 +522,6 @@ class InMemoryCatalogRangerSparkExtensionSuite extends RangerSparkExtensionSuite class HiveCatalogRangerSparkExtensionSuite extends RangerSparkExtensionSuite { override protected val catalogImpl: String = "hive" - test("table stats must be specified") { val table = "hive_src" withCleanTmpResources(Seq((table, "table"))) { diff --git a/extensions/spark/kyuubi-spark-authz/src/test/scala/org/apache/kyuubi/plugin/spark/authz/ranger/V2JdbcTableCatalogRangerSparkExtensionSuite.scala b/extensions/spark/kyuubi-spark-authz/src/test/scala/org/apache/kyuubi/plugin/spark/authz/ranger/V2JdbcTableCatalogRangerSparkExtensionSuite.scala index 9f980c27a..73a13bc1c 100644 --- a/extensions/spark/kyuubi-spark-authz/src/test/scala/org/apache/kyuubi/plugin/spark/authz/ranger/V2JdbcTableCatalogRangerSparkExtensionSuite.scala +++ b/extensions/spark/kyuubi-spark-authz/src/test/scala/org/apache/kyuubi/plugin/spark/authz/ranger/V2JdbcTableCatalogRangerSparkExtensionSuite.scala @@ -104,7 +104,7 @@ class V2JdbcTableCatalogRangerSparkExtensionSuite extends RangerSparkExtensionSu val e1 = intercept[AccessControlException]( doAs("someone", sql(s"select city, id from $catalogV2.$namespace1.$table1").explain())) assert(e1.getMessage.contains(s"does not have [select] privilege" + - s" on [$namespace1/$table1/id]")) + s" on [$namespace1/$table1/city]")) } test("[KYUUBI #4255] DESCRIBE TABLE") { diff --git a/extensions/spark/kyuubi-spark-authz/src/test/scala/org/apache/kyuubi/plugin/spark/authz/ranger/datamasking/DataMaskingForHiveHiveParquetSuite.scala b/extensions/spark/kyuubi-spark-authz/src/test/scala/org/apache/kyuubi/plugin/spark/authz/ranger/datamasking/DataMaskingForHiveHiveParquetSuite.scala new file mode 100644 index 000000000..ccc694f9b --- /dev/null +++ b/extensions/spark/kyuubi-spark-authz/src/test/scala/org/apache/kyuubi/plugin/spark/authz/ranger/datamasking/DataMaskingForHiveHiveParquetSuite.scala @@ -0,0 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.kyuubi.plugin.spark.authz.ranger.datamasking + +class DataMaskingForHiveHiveParquetSuite extends DataMaskingTestBase { + override protected val catalogImpl: String = "hive" + override protected def format: String = "USING hive OPTIONS(fileFormat='parquet')" +} diff --git a/extensions/spark/kyuubi-spark-authz/src/test/scala/org/apache/kyuubi/plugin/spark/authz/ranger/datamasking/DataMaskingForHiveParquetSuite.scala b/extensions/spark/kyuubi-spark-authz/src/test/scala/org/apache/kyuubi/plugin/spark/authz/ranger/datamasking/DataMaskingForHiveParquetSuite.scala new file mode 100644 index 000000000..ba254abbd --- /dev/null +++ b/extensions/spark/kyuubi-spark-authz/src/test/scala/org/apache/kyuubi/plugin/spark/authz/ranger/datamasking/DataMaskingForHiveParquetSuite.scala @@ -0,0 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.kyuubi.plugin.spark.authz.ranger.datamasking + +class DataMaskingForHiveParquetSuite extends DataMaskingTestBase { + override protected val catalogImpl: String = "hive" + override protected def format: String = "USING parquet" +} diff --git a/extensions/spark/kyuubi-spark-authz/src/test/scala/org/apache/kyuubi/plugin/spark/authz/ranger/datamasking/DataMaskingForIcebergSuite.scala b/extensions/spark/kyuubi-spark-authz/src/test/scala/org/apache/kyuubi/plugin/spark/authz/ranger/datamasking/DataMaskingForIcebergSuite.scala new file mode 100644 index 000000000..99b7eb973 --- /dev/null +++ b/extensions/spark/kyuubi-spark-authz/src/test/scala/org/apache/kyuubi/plugin/spark/authz/ranger/datamasking/DataMaskingForIcebergSuite.scala @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.kyuubi.plugin.spark.authz.ranger.datamasking + +import org.apache.spark.SparkConf +import org.scalatest.Outcome + +import org.apache.kyuubi.Utils + +class DataMaskingForIcebergSuite extends DataMaskingTestBase { + override protected val extraSparkConf: SparkConf = { + val conf = new SparkConf() + + if (isSparkV31OrGreater) { + conf + .set("spark.sql.defaultCatalog", "testcat") + .set( + "spark.sql.catalog.testcat", + "org.apache.iceberg.spark.SparkCatalog") + .set(s"spark.sql.catalog.testcat.type", "hadoop") + .set( + "spark.sql.catalog.testcat.warehouse", + Utils.createTempDir("iceberg-hadoop").toString) + } + conf + + } + + override protected val catalogImpl: String = "in-memory" + + override protected def format: String = "USING iceberg" + + override def beforeAll(): Unit = { + if (isSparkV31OrGreater) { + super.beforeAll() + } + } + + override def afterAll(): Unit = { + if (isSparkV31OrGreater) { + super.afterAll() + } + } + + override def withFixture(test: NoArgTest): Outcome = { + assume(isSparkV31OrGreater) + test() + } +} diff --git a/extensions/spark/kyuubi-spark-authz/src/test/scala/org/apache/kyuubi/plugin/spark/authz/ranger/datamasking/DataMaskingForInMemoryParquetSuite.scala b/extensions/spark/kyuubi-spark-authz/src/test/scala/org/apache/kyuubi/plugin/spark/authz/ranger/datamasking/DataMaskingForInMemoryParquetSuite.scala new file mode 100644 index 000000000..1bfb71e79 --- /dev/null +++ b/extensions/spark/kyuubi-spark-authz/src/test/scala/org/apache/kyuubi/plugin/spark/authz/ranger/datamasking/DataMaskingForInMemoryParquetSuite.scala @@ -0,0 +1,24 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.kyuubi.plugin.spark.authz.ranger.datamasking + +class DataMaskingForInMemoryParquetSuite extends DataMaskingTestBase { + + override protected val catalogImpl: String = "in-memory" + override protected def format: String = "USING parquet" +} diff --git a/extensions/spark/kyuubi-spark-authz/src/test/scala/org/apache/kyuubi/plugin/spark/authz/ranger/datamasking/DataMaskingForJDBCV2Suite.scala b/extensions/spark/kyuubi-spark-authz/src/test/scala/org/apache/kyuubi/plugin/spark/authz/ranger/datamasking/DataMaskingForJDBCV2Suite.scala new file mode 100644 index 000000000..894daeaf7 --- /dev/null +++ b/extensions/spark/kyuubi-spark-authz/src/test/scala/org/apache/kyuubi/plugin/spark/authz/ranger/datamasking/DataMaskingForJDBCV2Suite.scala @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.kyuubi.plugin.spark.authz.ranger.datamasking +import java.sql.DriverManager + +import scala.util.Try + +import org.apache.spark.SparkConf +import org.scalatest.Outcome + +class DataMaskingForJDBCV2Suite extends DataMaskingTestBase { + override protected val extraSparkConf: SparkConf = { + val conf = new SparkConf() + if (isSparkV31OrGreater) { + conf + .set("spark.sql.defaultCatalog", "testcat") + .set( + "spark.sql.catalog.testcat", + "org.apache.spark.sql.execution.datasources.v2.jdbc.JDBCTableCatalog") + .set(s"spark.sql.catalog.testcat.url", "jdbc:derby:memory:testcat;create=true") + .set( + s"spark.sql.catalog.testcat.driver", + "org.apache.derby.jdbc.AutoloadedDriver") + } + conf + } + + override protected val catalogImpl: String = "in-memory" + + override protected def format: String = "" + + override def beforeAll(): Unit = { + if (isSparkV31OrGreater) super.beforeAll() + } + + override def afterAll(): Unit = { + if (isSparkV31OrGreater) { + super.afterAll() + // cleanup db + Try { + DriverManager.getConnection(s"jdbc:derby:memory:testcat;shutdown=true") + } + } + } + + override def withFixture(test: NoArgTest): Outcome = { + assume(isSparkV31OrGreater) + test() + } +} diff --git a/extensions/spark/kyuubi-spark-authz/src/test/scala/org/apache/kyuubi/plugin/spark/authz/ranger/datamasking/DataMaskingTestBase.scala b/extensions/spark/kyuubi-spark-authz/src/test/scala/org/apache/kyuubi/plugin/spark/authz/ranger/datamasking/DataMaskingTestBase.scala new file mode 100644 index 000000000..c13362617 --- /dev/null +++ b/extensions/spark/kyuubi-spark-authz/src/test/scala/org/apache/kyuubi/plugin/spark/authz/ranger/datamasking/DataMaskingTestBase.scala @@ -0,0 +1,267 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.kyuubi.plugin.spark.authz.ranger.datamasking + +// scalastyle:off +import java.sql.Timestamp + +import scala.util.Try + +import org.apache.commons.codec.digest.DigestUtils.md5Hex +import org.apache.spark.sql.{Row, SparkSessionExtensions} +import org.scalatest.{Assertion, BeforeAndAfterAll} +import org.scalatest.funsuite.AnyFunSuite + +import org.apache.kyuubi.plugin.spark.authz.SparkSessionProvider +import org.apache.kyuubi.plugin.spark.authz.ranger.RangerSparkExtension + +/** + * Base trait for data masking tests, derivative classes shall name themselves following: + * DataMaskingFor CatalogImpl? FileFormat? Additions? Suite + */ +trait DataMaskingTestBase extends AnyFunSuite with SparkSessionProvider with BeforeAndAfterAll { +// scalastyle:on + override protected val extension: SparkSessionExtensions => Unit = new RangerSparkExtension + + private def setup(): Unit = { + sql(s"CREATE TABLE IF NOT EXISTS default.src" + + "(key int," + + " value1 int," + + " value2 string," + + " value3 string," + + " value4 timestamp," + + " value5 string)" + + s" $format") + + // NOTICE: `bob` has a row filter `key < 20` + sql("INSERT INTO default.src " + + "SELECT 1, 1, 'hello', 'world', timestamp'2018-11-17 12:34:56', 'World'") + sql("INSERT INTO default.src " + + "SELECT 20, 2, 'kyuubi', 'y', timestamp'2018-11-17 12:34:56', 'world'") + sql("INSERT INTO default.src " + + "SELECT 30, 3, 'spark', 'a', timestamp'2018-11-17 12:34:56', 'world'") + sql(s"CREATE TABLE default.unmasked $format AS SELECT * FROM default.src") + } + + private def cleanup(): Unit = { + sql("DROP TABLE IF EXISTS default.src") + sql("DROP TABLE IF EXISTS default.unmasked") + } + + override def beforeAll(): Unit = { + doAs("admin", setup()) + super.beforeAll() + } + override def afterAll(): Unit = { + doAs("admin", cleanup()) + spark.stop + super.afterAll() + } + + protected def checkAnswer(user: String, query: String, result: Seq[Row]): Assertion = { + doAs(user, assert(sql(query).collect() === result)) + } + + test("simple query with a user doesn't have mask rules") { + checkAnswer("kent", "SELECT key FROM default.src order by key", Seq(Row(1), Row(20), Row(30))) + } + + test("simple query with a user has mask rules") { + val result = + Seq(Row(md5Hex("1"), "xxxxx", "worlx", Timestamp.valueOf("2018-01-01 00:00:00"), "Xorld")) + checkAnswer("bob", "SELECT value1, value2, value3, value4, value5 FROM default.src", result) + checkAnswer( + "bob", + "SELECT value1 as key, value2, value3, value4, value5 FROM default.src", + result) + } + + test("star") { + val result = + Seq(Row(1, md5Hex("1"), "xxxxx", "worlx", Timestamp.valueOf("2018-01-01 00:00:00"), "Xorld")) + checkAnswer("bob", "SELECT * FROM default.src", result) + } + + test("simple udf") { + val result = + Seq(Row(md5Hex("1"), "xxxxx", "worlx", Timestamp.valueOf("2018-01-01 00:00:00"), "Xorld")) + checkAnswer( + "bob", + "SELECT max(value1), max(value2), max(value3), max(value4), max(value5) FROM default.src", + result) + } + + test("complex udf") { + val result = + Seq(Row(md5Hex("1"), "xxxxx", "worlx", Timestamp.valueOf("2018-01-01 00:00:00"), "Xorld")) + checkAnswer( + "bob", + "SELECT coalesce(max(value1), 1), coalesce(max(value2), 1), coalesce(max(value3), 1), " + + "coalesce(max(value4), timestamp '2018-01-01 22:33:44'), coalesce(max(value5), 1) " + + "FROM default.src", + result) + } + + test("in subquery") { + val result = + Seq(Row(md5Hex("1"), "xxxxx", "worlx", Timestamp.valueOf("2018-01-01 00:00:00"), "Xorld")) + checkAnswer( + "bob", + "SELECT value1, value2, value3, value4, value5 FROM default.src WHERE value2 in " + + "(SELECT value2 as key FROM default.src)", + result) + } + + test("create a unmasked table as select from a masked one") { + withCleanTmpResources(Seq(("default.src2", "table"))) { + doAs("bob", sql(s"CREATE TABLE default.src2 $format AS SELECT value1 FROM default.src")) + checkAnswer("bob", "SELECT value1 FROM default.src2", Seq(Row(md5Hex("1")))) + } + } + + test("insert into a unmasked table from a masked one") { + withCleanTmpResources(Seq(("default.src2", "table"), ("default.src3", "table"))) { + doAs("bob", sql(s"CREATE TABLE default.src2 (value1 string) $format")) + doAs("bob", sql(s"INSERT INTO default.src2 SELECT value1 from default.src")) + doAs("bob", sql(s"INSERT INTO default.src2 SELECT value1 as v from default.src")) + checkAnswer("bob", "SELECT value1 FROM default.src2", Seq(Row(md5Hex("1")), Row(md5Hex("1")))) + doAs("bob", sql(s"CREATE TABLE default.src3 (k int, value string) $format")) + doAs("bob", sql(s"INSERT INTO default.src3 SELECT key, value1 from default.src")) + doAs("bob", sql(s"INSERT INTO default.src3 SELECT key, value1 as v from default.src")) + checkAnswer("bob", "SELECT value FROM default.src3", Seq(Row(md5Hex("1")), Row(md5Hex("1")))) + } + } + + test("join on an unmasked table") { + val s = "SELECT a.value1, b.value1 FROM default.src a" + + " join default.unmasked b on a.value1=b.value1" + checkAnswer("bob", s, Nil) + checkAnswer("bob", s, Nil) // just for testing query multiple times, don't delete it + } + + test("self join on a masked table") { + val s = "SELECT a.value1, b.value1 FROM default.src a" + + " join default.src b on a.value1=b.value1" + checkAnswer("bob", s, Seq(Row(md5Hex("1"), md5Hex("1")))) + // just for testing query multiple times, don't delete it + checkAnswer("bob", s, Seq(Row(md5Hex("1"), md5Hex("1")))) + } + + test("self join on a masked table and filter the masked column with original value") { + val s = "SELECT a.value1, b.value1 FROM default.src a" + + " join default.src b on a.value1=b.value1" + + " where a.value1='1' and b.value1='1'" + checkAnswer("bob", s, Nil) + checkAnswer("bob", s, Nil) // just for testing query multiple times, don't delete it + } + + test("self join on a masked table and filter the masked column with masked value") { + // scalastyle:off + val s = "SELECT a.value1, b.value1 FROM default.src a" + + " join default.src b on a.value1=b.value1" + + s" where a.value1='${md5Hex("1")}' and b.value1='${md5Hex("1")}'" + // TODO: The v1 an v2 relations generate different implicit type cast rules for filters + // so the bellow test failed in derivative classes that us v2 data source, e.g., DataMaskingForIcebergSuite + // For the issue itself, we might need check the spark logic first + // DataMaskingStage1Marker Project [value1#178, value1#183] + // +- Project [value1#178, value1#183] + // +- Filter ((cast(value1#178 as int) = cast(c4ca4238a0b923820dcc509a6f75849b as int)) AND (cast(value1#183 as int) = cast(c4ca4238a0b923820dcc509a6f75849b as int))) + // +- Join Inner, (value1#178 = value1#183) + // :- SubqueryAlias a + // : +- SubqueryAlias testcat.default.src + // : +- Filter (key#166 < 20) + // : +- RowFilterMarker + // : +- DataMaskingStage0Marker RelationV2[key#166, value1#167, value2#168, value3#169, value4#170, value5#171] default.src + // : +- Project [key#166, md5(cast(cast(value1#167 as string) as binary)) AS value1#178, regexp_replace(regexp_replace(regexp_replace(value2#168, [A-Z], X, 1), [a-z], x, 1), [0-9], n, 1) AS value2#179, regexp_replace(regexp_replace(regexp_replace(value3#169, [A-Z], X, 5), [a-z], x, 5), [0-9], n, 5) AS value3#180, date_trunc(YEAR, value4#170, Some(Asia/Shanghai)) AS value4#181, concat(regexp_replace(regexp_replace(regexp_replace(left(value5#171, (length(value5#171) - 4)), [A-Z], X, 1), [a-z], x, 1), [0-9], n, 1), right(value5#171, 4)) AS value5#182] + // : +- RelationV2[key#166, value1#167, value2#168, value3#169, value4#170, value5#171] default.src + // +- SubqueryAlias b + // +- SubqueryAlias testcat.default.src + // +- Filter (key#172 < 20) + // +- RowFilterMarker + // +- DataMaskingStage0Marker RelationV2[key#172, value1#173, value2#174, value3#175, value4#176, value5#177] default.src + // +- Project [key#172, md5(cast(cast(value1#173 as string) as binary)) AS value1#183, regexp_replace(regexp_replace(regexp_replace(value2#174, [A-Z], X, 1), [a-z], x, 1), [0-9], n, 1) AS value2#184, regexp_replace(regexp_replace(regexp_replace(value3#175, [A-Z], X, 5), [a-z], x, 5), [0-9], n, 5) AS value3#185, date_trunc(YEAR, value4#176, Some(Asia/Shanghai)) AS value4#186, concat(regexp_replace(regexp_replace(regexp_replace(left(value5#177, (length(value5#177) - 4)), [A-Z], X, 1), [a-z], x, 1), [0-9], n, 1), right(value5#177, 4)) AS value5#187] + // +- RelationV2[key#172, value1#173, value2#174, value3#175, value4#176, value5#177] default.src + // + // + // Project [value1#143, value1#148] + // +- Filter ((value1#143 = c4ca4238a0b923820dcc509a6f75849b) AND (value1#148 = c4ca4238a0b923820dcc509a6f75849b)) + // +- Join Inner, (value1#143 = value1#148) + // :- SubqueryAlias a + // : +- SubqueryAlias spark_catalog.default.src + // : +- Filter (key#60 < 20) + // : +- RowFilterMarker + // : +- DataMaskingStage0Marker Relation default.src[key#60,value1#61,value2#62,value3#63,value4#64,value5#65] parquet + // : +- Project [key#60, md5(cast(cast(value1#61 as string) as binary)) AS value1#143, regexp_replace(regexp_replace(regexp_replace(value2#62, [A-Z], X, 1), [a-z], x, 1), [0-9], n, 1) AS value2#144, regexp_replace(regexp_replace(regexp_replace(value3#63, [A-Z], X, 5), [a-z], x, 5), [0-9], n, 5) AS value3#145, date_trunc(YEAR, value4#64, Some(Asia/Shanghai)) AS value4#146, concat(regexp_replace(regexp_replace(regexp_replace(left(value5#65, (length(value5#65) - 4)), [A-Z], X, 1), [a-z], x, 1), [0-9], n, 1), right(value5#65, 4)) AS value5#147] + // : +- Relation default.src[key#60,value1#61,value2#62,value3#63,value4#64,value5#65] parquet + // +- SubqueryAlias b + // +- SubqueryAlias spark_catalog.default.src + // +- Filter (key#153 < 20) + // +- RowFilterMarker + // +- DataMaskingStage0Marker Relation default.src[key#60,value1#61,value2#62,value3#63,value4#64,value5#65] parquet + // +- Project [key#153, md5(cast(cast(value1#154 as string) as binary)) AS value1#148, regexp_replace(regexp_replace(regexp_replace(value2#155, [A-Z], X, 1), [a-z], x, 1), [0-9], n, 1) AS value2#149, regexp_replace(regexp_replace(regexp_replace(value3#156, [A-Z], X, 5), [a-z], x, 5), [0-9], n, 5) AS value3#150, date_trunc(YEAR, value4#157, Some(Asia/Shanghai)) AS value4#151, concat(regexp_replace(regexp_replace(regexp_replace(left(value5#158, (length(value5#158) - 4)), [A-Z], X, 1), [a-z], x, 1), [0-9], n, 1), right(value5#158, 4)) AS value5#152] + // +- Relation default.src[key#153,value1#154,value2#155,value3#156,value4#157,value5#158] parquet + // checkAnswer("bob", s, Seq(Row(md5Hex("1"), md5Hex("1")))) + // + // + // scalastyle:on + + // So here we use value2 to avoid type casting + val s2 = "SELECT a.value1, b.value1 FROM default.src a" + + " join default.src b on a.value1=b.value1" + + s" where a.value2='xxxxx' and b.value2='xxxxx'" + checkAnswer("bob", s2, Seq(Row(md5Hex("1"), md5Hex("1")))) + // just for testing query multiple times, don't delete it + checkAnswer("bob", s2, Seq(Row(md5Hex("1"), md5Hex("1")))) + } + + test("union an unmasked table") { + val s = """ + SELECT value1 from ( + SELECT a.value1 FROM default.src a + union + (SELECT b.value1 FROM default.unmasked b) + ) c order by value1 + """ + checkAnswer("bob", s, Seq(Row("1"), Row("2"), Row("3"), Row(md5Hex("1")))) + } + + test("union a masked table") { + val s = "SELECT a.value1 FROM default.src a union" + + " (SELECT b.value1 FROM default.src b)" + checkAnswer("bob", s, Seq(Row(md5Hex("1")))) + } + + test("KYUUBI #3581: permanent view should lookup rule on itself not the ") { + assume(isSparkV31OrGreater) + val supported = doAs( + "perm_view_user", + Try(sql("CREATE OR REPLACE VIEW default.perm_view AS SELECT * FROM default.src")).isSuccess) + assume(supported, s"view support for '$format' has not been implemented yet") + + withCleanTmpResources(Seq(("default.perm_view", "view"))) { + checkAnswer( + "perm_view_user", + "SELECT value1, value2 FROM default.src where key < 20", + Seq(Row(1, "hello"))) + checkAnswer( + "perm_view_user", + "SELECT value1, value2 FROM default.perm_view where key < 20", + Seq(Row(md5Hex("1"), "hello"))) + } + } +} From 7a9eb969ffe3b72323cd3d4eee6a7c89fd81e4ec Mon Sep 17 00:00:00 2001 From: Cheng Pan Date: Mon, 27 Feb 2023 19:08:52 +0800 Subject: [PATCH 121/760] [KYUUBI #4410] Improve `kyuubi-defaults.conf.template` ### _Why are the changes needed?_ This PR changes the `kyuubi-defaults.conf.template` because - remove deprecated conf `kyuubi.frontend.bind.port` - add some properties which were frequently asked by users ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [x] Add screenshots for manual tests if appropriate image - [ ] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4410 from pan3793/conf-template. Closes #4410 aab459753 [Cheng Pan] nit d3d8a9a28 [Cheng Pan] nit e278fa98d [Cheng Pan] update 169db65d4 [Cheng Pan] nit 15a1f656a [Cheng Pan] Improve kyuubi-defaults.conf.template Authored-by: Cheng Pan Signed-off-by: Cheng Pan --- conf/kyuubi-defaults.conf.template | 15 +- docs/deployment/settings.md | 172 +----------------- .../config/AllKyuubiConfiguration.scala | 13 +- 3 files changed, 22 insertions(+), 178 deletions(-) diff --git a/conf/kyuubi-defaults.conf.template b/conf/kyuubi-defaults.conf.template index d3e6026d9..6522e32e4 100644 --- a/conf/kyuubi-defaults.conf.template +++ b/conf/kyuubi-defaults.conf.template @@ -18,9 +18,18 @@ ## Kyuubi Configurations # -# kyuubi.authentication NONE -# kyuubi.frontend.bind.host localhost -# kyuubi.frontend.bind.port 10009 +# kyuubi.authentication NONE +# +# kyuubi.frontend.bind.host 10.0.0.1 +# kyuubi.frontend.protocols THRIFT_BINARY +# kyuubi.frontend.thrift.binary.bind.port 10009 +# +# kyuubi.engine.type SPARK_SQL +# kyuubi.engine.share.level USER +# kyuubi.session.engine.initialize.timeout PT3M +# +# kyuubi.ha.addresses zk1:2181,zk2:2181,zk3:2181 +# kyuubi.ha.namespace kyuubi # # Details in https://kyuubi.readthedocs.io/en/master/deployment/settings.html diff --git a/docs/deployment/settings.md b/docs/deployment/settings.md index dac72825e..1b593bde1 100644 --- a/docs/deployment/settings.md +++ b/docs/deployment/settings.md @@ -22,113 +22,12 @@ Kyuubi provides several ways to configure the system and corresponding engines. ## Environments -You can configure the environment variables in `$KYUUBI_HOME/conf/kyuubi-env.sh`, e.g, `JAVA_HOME`, then this java runtime will be used both for Kyuubi server instance and the applications it launches. You can also change the variable in the subprocess's env configuration file, e.g.`$SPARK_HOME/conf/spark-env.sh` to use more specific ENV for SQL engine applications. - -```bash -#!/usr/bin/env bash -# -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. -# The ASF licenses this file to You under the Apache License, Version 2.0 -# (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# -# - JAVA_HOME Java runtime to use. By default use "java" from PATH. -# -# -# - KYUUBI_CONF_DIR Directory containing the Kyuubi configurations to use. -# (Default: $KYUUBI_HOME/conf) -# - KYUUBI_LOG_DIR Directory for Kyuubi server-side logs. -# (Default: $KYUUBI_HOME/logs) -# - KYUUBI_PID_DIR Directory stores the Kyuubi instance pid file. -# (Default: $KYUUBI_HOME/pid) -# - KYUUBI_MAX_LOG_FILES Maximum number of Kyuubi server logs can rotate to. -# (Default: 5) -# - KYUUBI_JAVA_OPTS JVM options for the Kyuubi server itself in the form "-Dx=y". -# (Default: none). -# - KYUUBI_CTL_JAVA_OPTS JVM options for the Kyuubi ctl itself in the form "-Dx=y". -# (Default: none). -# - KYUUBI_BEELINE_OPTS JVM options for the Kyuubi BeeLine in the form "-Dx=Y". -# (Default: none) -# - KYUUBI_NICENESS The scheduling priority for Kyuubi server. -# (Default: 0) -# - KYUUBI_WORK_DIR_ROOT Root directory for launching sql engine applications. -# (Default: $KYUUBI_HOME/work) -# - HADOOP_CONF_DIR Directory containing the Hadoop / YARN configuration to use. -# - YARN_CONF_DIR Directory containing the YARN configuration to use. -# -# - SPARK_HOME Spark distribution which you would like to use in Kyuubi. -# - SPARK_CONF_DIR Optional directory where the Spark configuration lives. -# (Default: $SPARK_HOME/conf) -# - FLINK_HOME Flink distribution which you would like to use in Kyuubi. -# - FLINK_CONF_DIR Optional directory where the Flink configuration lives. -# (Default: $FLINK_HOME/conf) -# - FLINK_HADOOP_CLASSPATH Required Hadoop jars when you use the Kyuubi Flink engine. -# - HIVE_HOME Hive distribution which you would like to use in Kyuubi. -# - HIVE_CONF_DIR Optional directory where the Hive configuration lives. -# (Default: $HIVE_HOME/conf) -# - HIVE_HADOOP_CLASSPATH Required Hadoop jars when you use the Kyuubi Hive engine. -# - - -## Examples ## - -# export JAVA_HOME=/usr/jdk64/jdk1.8.0_152 -# export SPARK_HOME=/opt/spark -# export FLINK_HOME=/opt/flink -# export HIVE_HOME=/opt/hive -# export FLINK_HADOOP_CLASSPATH=/path/to/hadoop-client-runtime-3.3.2.jar:/path/to/hadoop-client-api-3.3.2.jar -# export HIVE_HADOOP_CLASSPATH=${HADOOP_HOME}/share/hadoop/common/lib/commons-collections-3.2.2.jar:${HADOOP_HOME}/share/hadoop/client/hadoop-client-runtime-3.1.0.jar:${HADOOP_HOME}/share/hadoop/client/hadoop-client-api-3.1.0.jar:${HADOOP_HOME}/share/hadoop/common/lib/htrace-core4-4.1.0-incubating.jar -# export HADOOP_CONF_DIR=/usr/ndp/current/mapreduce_client/conf -# export YARN_CONF_DIR=/usr/ndp/current/yarn/conf -# export KYUUBI_JAVA_OPTS="-Xmx10g -XX:+UnlockDiagnosticVMOptions -XX:ParGCCardsPerStrideChunk=4096 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+CMSConcurrentMTEnabled -XX:CMSInitiatingOccupancyFraction=70 -XX:+UseCMSInitiatingOccupancyOnly -XX:+CMSClassUnloadingEnabled -XX:+CMSParallelRemarkEnabled -XX:+UseCondCardMark -XX:MaxDirectMemorySize=1024m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./logs -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintTenuringDistribution -Xloggc:./logs/kyuubi-server-gc-%t.log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10 -XX:GCLogFileSize=5M -XX:NewRatio=3 -XX:MetaspaceSize=512m" -# export KYUUBI_BEELINE_OPTS="-Xmx2g -XX:+UnlockDiagnosticVMOptions -XX:ParGCCardsPerStrideChunk=4096 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+CMSConcurrentMTEnabled -XX:CMSInitiatingOccupancyFraction=70 -XX:+UseCMSInitiatingOccupancyOnly -XX:+CMSClassUnloadingEnabled -XX:+CMSParallelRemarkEnabled -XX:+UseCondCardMark" -``` - +You can configure the environment variables in `$KYUUBI_HOME/conf/kyuubi-env.sh`, e.g, `JAVA_HOME`, then this java runtime will be used both for Kyuubi server instance and the applications it launches. You can also change the variable in the subprocess's env configuration file, e.g.`$SPARK_HOME/conf/spark-env.sh` to use more specific ENV for SQL engine applications. see `$KYUUBI_HOME/conf/kyuubi-env.sh.template` as an example. For the environment variables that only needed to be transferred into engine side, you can set it with a Kyuubi configuration item formatted `kyuubi.engineEnv.VAR_NAME`. For example, with `kyuubi.engineEnv.SPARK_DRIVER_MEMORY=4g`, the environment variable `SPARK_DRIVER_MEMORY` with value `4g` would be transferred into engine side. With `kyuubi.engineEnv.SPARK_CONF_DIR=/apache/confs/spark/conf`, the value of `SPARK_CONF_DIR` on the engine side is set to `/apache/confs/spark/conf`. ## Kyuubi Configurations -You can configure the Kyuubi properties in `$KYUUBI_HOME/conf/kyuubi-defaults.conf`. For example: - -```bash -# -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. -# The ASF licenses this file to You under the Apache License, Version 2.0 -# (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -## Kyuubi Configurations - -# -# kyuubi.authentication NONE -# kyuubi.frontend.bind.host localhost -# kyuubi.frontend.bind.port 10009 -# - -# Details in https://kyuubi.readthedocs.io/en/master/deployment/settings.html -``` +You can configure the Kyuubi properties in `$KYUUBI_HOME/conf/kyuubi-defaults.conf`, see `$KYUUBI_HOME/conf/kyuubi-defaults.conf.template` as an example. ### Authentication @@ -584,72 +483,7 @@ Please refer to the Flink official online documentation for [SET Statements](htt ## Logging -Kyuubi uses [log4j](https://logging.apache.org/log4j/2.x/) for logging. You can configure it using `$KYUUBI_HOME/conf/log4j2.xml`. - -```bash - - - - - - - - rest-audit.log - rest-audit-%d{yyyy-MM-dd}-%i.log - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -``` +Kyuubi uses [log4j](https://logging.apache.org/log4j/2.x/) for logging. You can configure it using `$KYUUBI_HOME/conf/log4j2.xml`, see `$KYUUBI_HOME/conf/log4j2.xml.template` as an example. ## Other Configurations diff --git a/kyuubi-server/src/test/scala/org/apache/kyuubi/config/AllKyuubiConfiguration.scala b/kyuubi-server/src/test/scala/org/apache/kyuubi/config/AllKyuubiConfiguration.scala index 1d0e09544..bb183c00c 100644 --- a/kyuubi-server/src/test/scala/org/apache/kyuubi/config/AllKyuubiConfiguration.scala +++ b/kyuubi-server/src/test/scala/org/apache/kyuubi/config/AllKyuubiConfiguration.scala @@ -75,9 +75,8 @@ class AllKyuubiConfiguration extends KyuubiFunSuite { | e.g, `JAVA_HOME`, then this java runtime will be used both for Kyuubi server instance and | the applications it launches. You can also change the variable in the subprocess's env | configuration file, e.g.`$SPARK_HOME/conf/spark-env.sh` to use more specific ENV for - | SQL engine applications. + | SQL engine applications. see `$KYUUBI_HOME/conf/kyuubi-env.sh.template` as an example. | """) - .fileWithBlock(Paths.get(kyuubiHome, "conf", "kyuubi-env.sh.template")) .line( """ | For the environment variables that only needed to be transferred into engine @@ -89,8 +88,9 @@ class AllKyuubiConfiguration extends KyuubiFunSuite { | """) .line("## Kyuubi Configurations") .line(""" You can configure the Kyuubi properties in - | `$KYUUBI_HOME/conf/kyuubi-defaults.conf`. For example: """) - .fileWithBlock(Paths.get(kyuubiHome, "conf", "kyuubi-defaults.conf.template")) + | `$KYUUBI_HOME/conf/kyuubi-defaults.conf`, see + | `$KYUUBI_HOME/conf/kyuubi-defaults.conf.template` as an example. + | """) KyuubiConf.getConfigEntries().asScala .toStream @@ -188,8 +188,9 @@ class AllKyuubiConfiguration extends KyuubiFunSuite { builder .line("## Logging") .line("""Kyuubi uses [log4j](https://logging.apache.org/log4j/2.x/) for logging. - | You can configure it using `$KYUUBI_HOME/conf/log4j2.xml`.""") - .fileWithBlock(Paths.get(kyuubiHome, "conf", "log4j2.xml.template")) + | You can configure it using `$KYUUBI_HOME/conf/log4j2.xml`, see + | `$KYUUBI_HOME/conf/log4j2.xml.template` as an example. + | """) builder .lines(""" From 0a7c45b8eec4998bb8ff35e65e2b805ea7370bf1 Mon Sep 17 00:00:00 2001 From: fwang12 Date: Mon, 27 Feb 2023 20:57:19 +0800 Subject: [PATCH 122/760] [KYUUBI #4412][FOLLOWUP] Align the server/engine session handle for flink/hive/trino/jdbc engines ### _Why are the changes needed?_ #4412 follow up ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [ ] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4422 from turboFei/align_session_id. Closes #4412 319373296 [fwang12] save Authored-by: fwang12 Signed-off-by: fwang12 --- .../session/FlinkSQLSessionManager.scala | 20 +++++--- .../flink/session/FlinkSessionImpl.scala | 6 ++- .../hive/session/HiveSessionManager.scala | 50 +++++++++++-------- .../engine/jdbc/session/JdbcSessionImpl.scala | 6 ++- .../jdbc/session/JdbcSessionManager.scala | 6 ++- .../trino/session/TrinoSessionImpl.scala | 6 ++- .../trino/session/TrinoSessionManager.scala | 6 ++- 7 files changed, 65 insertions(+), 35 deletions(-) diff --git a/externals/kyuubi-flink-sql-engine/src/main/scala/org/apache/kyuubi/engine/flink/session/FlinkSQLSessionManager.scala b/externals/kyuubi-flink-sql-engine/src/main/scala/org/apache/kyuubi/engine/flink/session/FlinkSQLSessionManager.scala index 8a3fc7446..07971e39f 100644 --- a/externals/kyuubi-flink-sql-engine/src/main/scala/org/apache/kyuubi/engine/flink/session/FlinkSQLSessionManager.scala +++ b/externals/kyuubi-flink-sql-engine/src/main/scala/org/apache/kyuubi/engine/flink/session/FlinkSQLSessionManager.scala @@ -21,6 +21,7 @@ import org.apache.flink.table.client.gateway.context.DefaultContext import org.apache.flink.table.client.gateway.local.LocalExecutor import org.apache.hive.service.rpc.thrift.TProtocolVersion +import org.apache.kyuubi.config.KyuubiReservedKeys.KYUUBI_SESSION_HANDLE_KEY import org.apache.kyuubi.engine.flink.operation.FlinkSQLOperationManager import org.apache.kyuubi.session.{Session, SessionHandle, SessionManager} @@ -43,14 +44,17 @@ class FlinkSQLSessionManager(engineContext: DefaultContext) password: String, ipAddress: String, conf: Map[String, String]): Session = { - new FlinkSessionImpl( - protocol, - user, - password, - ipAddress, - conf, - this, - executor) + conf.get(KYUUBI_SESSION_HANDLE_KEY).map(SessionHandle.fromUUID).flatMap( + getSessionOption).getOrElse { + new FlinkSessionImpl( + protocol, + user, + password, + ipAddress, + conf, + this, + executor) + } } override def closeSession(sessionHandle: SessionHandle): Unit = { diff --git a/externals/kyuubi-flink-sql-engine/src/main/scala/org/apache/kyuubi/engine/flink/session/FlinkSessionImpl.scala b/externals/kyuubi-flink-sql-engine/src/main/scala/org/apache/kyuubi/engine/flink/session/FlinkSessionImpl.scala index 03d9ce42e..75087b48c 100644 --- a/externals/kyuubi-flink-sql-engine/src/main/scala/org/apache/kyuubi/engine/flink/session/FlinkSessionImpl.scala +++ b/externals/kyuubi-flink-sql-engine/src/main/scala/org/apache/kyuubi/engine/flink/session/FlinkSessionImpl.scala @@ -26,8 +26,9 @@ import org.apache.flink.table.client.gateway.local.LocalExecutor import org.apache.hive.service.rpc.thrift.{TGetInfoType, TGetInfoValue, TProtocolVersion} import org.apache.kyuubi.KyuubiSQLException +import org.apache.kyuubi.config.KyuubiReservedKeys.KYUUBI_SESSION_HANDLE_KEY import org.apache.kyuubi.engine.flink.FlinkEngineUtils -import org.apache.kyuubi.session.{AbstractSession, SessionManager} +import org.apache.kyuubi.session.{AbstractSession, SessionHandle, SessionManager} class FlinkSessionImpl( protocol: TProtocolVersion, @@ -39,6 +40,9 @@ class FlinkSessionImpl( val executor: LocalExecutor) extends AbstractSession(protocol, user, password, ipAddress, conf, sessionManager) { + override val handle: SessionHandle = + conf.get(KYUUBI_SESSION_HANDLE_KEY).map(SessionHandle.fromUUID).getOrElse(SessionHandle()) + lazy val sessionContext: SessionContext = { FlinkEngineUtils.getSessionContext(executor, handle.identifier.toString) } diff --git a/externals/kyuubi-hive-sql-engine/src/main/scala/org/apache/kyuubi/engine/hive/session/HiveSessionManager.scala b/externals/kyuubi-hive-sql-engine/src/main/scala/org/apache/kyuubi/engine/hive/session/HiveSessionManager.scala index dc807429c..d09912770 100644 --- a/externals/kyuubi-hive-sql-engine/src/main/scala/org/apache/kyuubi/engine/hive/session/HiveSessionManager.scala +++ b/externals/kyuubi-hive-sql-engine/src/main/scala/org/apache/kyuubi/engine/hive/session/HiveSessionManager.scala @@ -28,6 +28,7 @@ import org.apache.hive.service.cli.session.{HiveSessionImplwithUGI => ImportedHi import org.apache.hive.service.rpc.thrift.TProtocolVersion import org.apache.kyuubi.config.KyuubiConf.ENGINE_SHARE_LEVEL +import org.apache.kyuubi.config.KyuubiReservedKeys.KYUUBI_SESSION_HANDLE_KEY import org.apache.kyuubi.engine.ShareLevel import org.apache.kyuubi.engine.hive.HiveSQLEngine import org.apache.kyuubi.engine.hive.operation.HiveOperationManager @@ -72,33 +73,38 @@ class HiveSessionManager(engine: HiveSQLEngine) extends SessionManager("HiveSess password: String, ipAddress: String, conf: Map[String, String]): Session = { - val sessionHandle = SessionHandle() - val hive = { - val sessionWithUGI = new ImportedHiveSessionImpl( - new ImportedSessionHandle(sessionHandle.toTSessionHandle, protocol), + conf.get(KYUUBI_SESSION_HANDLE_KEY).map(SessionHandle.fromUUID).flatMap( + getSessionOption).getOrElse { + val sessionHandle = + conf.get(KYUUBI_SESSION_HANDLE_KEY).map(SessionHandle.fromUUID).getOrElse(SessionHandle()) + val hive = { + val sessionWithUGI = new ImportedHiveSessionImpl( + new ImportedSessionHandle(sessionHandle.toTSessionHandle, protocol), + protocol, + user, + password, + HiveSQLEngine.hiveConf, + ipAddress, + null, + Seq(ipAddress).asJava) + val proxy = HiveSessionProxy.getProxy(sessionWithUGI, sessionWithUGI.getSessionUgi) + sessionWithUGI.setProxySession(proxy) + proxy + } + hive.setSessionManager(internalSessionManager) + hive.setOperationManager(internalSessionManager.getOperationManager) + operationLogRoot.foreach(dir => hive.setOperationLogSessionDir(new File(dir))) + new HiveSessionImpl( protocol, user, password, - HiveSQLEngine.hiveConf, ipAddress, - null, - Seq(ipAddress).asJava) - val proxy = HiveSessionProxy.getProxy(sessionWithUGI, sessionWithUGI.getSessionUgi) - sessionWithUGI.setProxySession(proxy) - proxy + conf, + this, + sessionHandle, + hive) } - hive.setSessionManager(internalSessionManager) - hive.setOperationManager(internalSessionManager.getOperationManager) - operationLogRoot.foreach(dir => hive.setOperationLogSessionDir(new File(dir))) - new HiveSessionImpl( - protocol, - user, - password, - ipAddress, - conf, - this, - sessionHandle, - hive) + } override def closeSession(sessionHandle: SessionHandle): Unit = { diff --git a/externals/kyuubi-jdbc-engine/src/main/scala/org/apache/kyuubi/engine/jdbc/session/JdbcSessionImpl.scala b/externals/kyuubi-jdbc-engine/src/main/scala/org/apache/kyuubi/engine/jdbc/session/JdbcSessionImpl.scala index 63fb2dd07..f8cd40412 100644 --- a/externals/kyuubi-jdbc-engine/src/main/scala/org/apache/kyuubi/engine/jdbc/session/JdbcSessionImpl.scala +++ b/externals/kyuubi-jdbc-engine/src/main/scala/org/apache/kyuubi/engine/jdbc/session/JdbcSessionImpl.scala @@ -23,8 +23,9 @@ import scala.util.{Failure, Success, Try} import org.apache.hive.service.rpc.thrift.{TGetInfoType, TGetInfoValue, TProtocolVersion} import org.apache.kyuubi.KyuubiSQLException +import org.apache.kyuubi.config.KyuubiReservedKeys.KYUUBI_SESSION_HANDLE_KEY import org.apache.kyuubi.engine.jdbc.connection.ConnectionProvider -import org.apache.kyuubi.session.{AbstractSession, SessionManager} +import org.apache.kyuubi.session.{AbstractSession, SessionHandle, SessionManager} class JdbcSessionImpl( protocol: TProtocolVersion, @@ -35,6 +36,9 @@ class JdbcSessionImpl( sessionManager: SessionManager) extends AbstractSession(protocol, user, password, ipAddress, conf, sessionManager) { + override val handle: SessionHandle = + conf.get(KYUUBI_SESSION_HANDLE_KEY).map(SessionHandle.fromUUID).getOrElse(SessionHandle()) + private[jdbc] var sessionConnection: Connection = _ private var databaseMetaData: DatabaseMetaData = _ diff --git a/externals/kyuubi-jdbc-engine/src/main/scala/org/apache/kyuubi/engine/jdbc/session/JdbcSessionManager.scala b/externals/kyuubi-jdbc-engine/src/main/scala/org/apache/kyuubi/engine/jdbc/session/JdbcSessionManager.scala index db8f60c3c..09958e050 100644 --- a/externals/kyuubi-jdbc-engine/src/main/scala/org/apache/kyuubi/engine/jdbc/session/JdbcSessionManager.scala +++ b/externals/kyuubi-jdbc-engine/src/main/scala/org/apache/kyuubi/engine/jdbc/session/JdbcSessionManager.scala @@ -20,6 +20,7 @@ import org.apache.hive.service.rpc.thrift.TProtocolVersion import org.apache.kyuubi.config.KyuubiConf import org.apache.kyuubi.config.KyuubiConf.ENGINE_SHARE_LEVEL +import org.apache.kyuubi.config.KyuubiReservedKeys.KYUUBI_SESSION_HANDLE_KEY import org.apache.kyuubi.engine.ShareLevel import org.apache.kyuubi.engine.jdbc.JdbcSQLEngine import org.apache.kyuubi.engine.jdbc.operation.JdbcOperationManager @@ -46,7 +47,10 @@ class JdbcSessionManager(name: String) password: String, ipAddress: String, conf: Map[String, String]): Session = { - new JdbcSessionImpl(protocol, user, password, ipAddress, conf, this) + conf.get(KYUUBI_SESSION_HANDLE_KEY).map(SessionHandle.fromUUID).flatMap( + getSessionOption).getOrElse { + new JdbcSessionImpl(protocol, user, password, ipAddress, conf, this) + } } override def closeSession(sessionHandle: SessionHandle): Unit = { diff --git a/externals/kyuubi-trino-engine/src/main/scala/org/apache/kyuubi/engine/trino/session/TrinoSessionImpl.scala b/externals/kyuubi-trino-engine/src/main/scala/org/apache/kyuubi/engine/trino/session/TrinoSessionImpl.scala index a19d74d58..81f973b1b 100644 --- a/externals/kyuubi-trino-engine/src/main/scala/org/apache/kyuubi/engine/trino/session/TrinoSessionImpl.scala +++ b/externals/kyuubi-trino-engine/src/main/scala/org/apache/kyuubi/engine/trino/session/TrinoSessionImpl.scala @@ -30,11 +30,12 @@ import org.apache.hive.service.rpc.thrift.{TGetInfoType, TGetInfoValue, TProtoco import org.apache.kyuubi.KyuubiSQLException import org.apache.kyuubi.Utils.currentUser import org.apache.kyuubi.config.{KyuubiConf, KyuubiReservedKeys} +import org.apache.kyuubi.config.KyuubiReservedKeys.KYUUBI_SESSION_HANDLE_KEY import org.apache.kyuubi.engine.trino.{TrinoConf, TrinoContext, TrinoStatement} import org.apache.kyuubi.engine.trino.event.TrinoSessionEvent import org.apache.kyuubi.events.EventBus import org.apache.kyuubi.operation.{Operation, OperationHandle} -import org.apache.kyuubi.session.{AbstractSession, SessionManager} +import org.apache.kyuubi.session.{AbstractSession, SessionHandle, SessionManager} class TrinoSessionImpl( protocol: TProtocolVersion, @@ -45,6 +46,9 @@ class TrinoSessionImpl( sessionManager: SessionManager) extends AbstractSession(protocol, user, password, ipAddress, conf, sessionManager) { + override val handle: SessionHandle = + conf.get(KYUUBI_SESSION_HANDLE_KEY).map(SessionHandle.fromUUID).getOrElse(SessionHandle()) + var trinoContext: TrinoContext = _ private var clientSession: ClientSession = _ private var catalogName: String = null diff --git a/externals/kyuubi-trino-engine/src/main/scala/org/apache/kyuubi/engine/trino/session/TrinoSessionManager.scala b/externals/kyuubi-trino-engine/src/main/scala/org/apache/kyuubi/engine/trino/session/TrinoSessionManager.scala index 6d56d5c05..e18b8f758 100644 --- a/externals/kyuubi-trino-engine/src/main/scala/org/apache/kyuubi/engine/trino/session/TrinoSessionManager.scala +++ b/externals/kyuubi-trino-engine/src/main/scala/org/apache/kyuubi/engine/trino/session/TrinoSessionManager.scala @@ -20,6 +20,7 @@ package org.apache.kyuubi.engine.trino.session import org.apache.hive.service.rpc.thrift.TProtocolVersion import org.apache.kyuubi.config.KyuubiConf.ENGINE_SHARE_LEVEL +import org.apache.kyuubi.config.KyuubiReservedKeys.KYUUBI_SESSION_HANDLE_KEY import org.apache.kyuubi.engine.ShareLevel import org.apache.kyuubi.engine.trino.TrinoSqlEngine import org.apache.kyuubi.engine.trino.operation.TrinoOperationManager @@ -36,7 +37,10 @@ class TrinoSessionManager password: String, ipAddress: String, conf: Map[String, String]): Session = { - new TrinoSessionImpl(protocol, user, password, ipAddress, conf, this) + conf.get(KYUUBI_SESSION_HANDLE_KEY).map(SessionHandle.fromUUID).flatMap( + getSessionOption).getOrElse { + new TrinoSessionImpl(protocol, user, password, ipAddress, conf, this) + } } override def closeSession(sessionHandle: SessionHandle): Unit = { From d086d79fbb52463f1dfbb4872b88682d5e2d2047 Mon Sep 17 00:00:00 2001 From: Tianlin Liao Date: Tue, 28 Feb 2023 14:11:59 +0800 Subject: [PATCH 123/760] [KYUUBI #4404] Support to list/close sessions in AdminResource ### _Why are the changes needed?_ ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [ ] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4423 from lightning-L/kyuubi-4404. Closes #4404 1570303a1 [Tianlin Liao] add response message e5921c992 [Tianlin Liao] [KYUUBI #4404] Support to list/close sessions in AdminResource Authored-by: Tianlin Liao Signed-off-by: fwang12 --- .../kyuubi/server/api/v1/AdminResource.scala | 51 ++++++++++++++++++- .../server/api/v1/AdminResourceSuite.scala | 46 ++++++++++++++++- 2 files changed, 94 insertions(+), 3 deletions(-) diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/AdminResource.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/AdminResource.scala index 104dd1045..a85cef16d 100644 --- a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/AdminResource.scala +++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/AdminResource.scala @@ -24,12 +24,12 @@ import javax.ws.rs.core.{MediaType, Response} import scala.collection.JavaConverters._ import scala.collection.mutable.ListBuffer -import io.swagger.v3.oas.annotations.media.Content +import io.swagger.v3.oas.annotations.media.{ArraySchema, Content, Schema} import io.swagger.v3.oas.annotations.responses.ApiResponse import io.swagger.v3.oas.annotations.tags.Tag import org.apache.kyuubi.{KYUUBI_VERSION, Logging, Utils} -import org.apache.kyuubi.client.api.v1.dto.Engine +import org.apache.kyuubi.client.api.v1.dto.{Engine, SessionData} import org.apache.kyuubi.config.KyuubiConf import org.apache.kyuubi.config.KyuubiConf._ import org.apache.kyuubi.ha.HighAvailabilityConf.HA_NAMESPACE @@ -37,6 +37,7 @@ import org.apache.kyuubi.ha.client.{DiscoveryPaths, ServiceNodeInfo} import org.apache.kyuubi.ha.client.DiscoveryClientProvider.withDiscoveryClient import org.apache.kyuubi.server.KyuubiServer import org.apache.kyuubi.server.api.ApiRequestContext +import org.apache.kyuubi.session.SessionHandle @Tag(name = "Admin") @Produces(Array(MediaType.APPLICATION_JSON)) @@ -102,6 +103,52 @@ private[v1] class AdminResource extends ApiRequestContext with Logging { Response.ok(s"Refresh the unlimited users successfully.").build() } + @ApiResponse( + responseCode = "200", + content = Array(new Content( + mediaType = MediaType.APPLICATION_JSON, + array = new ArraySchema(schema = new Schema(implementation = classOf[SessionData])))), + description = "get the list of all live sessions") + @GET + @Path("sessions") + def sessions(): Seq[SessionData] = { + val userName = fe.getSessionUser(Map.empty[String, String]) + val ipAddress = fe.getIpAddress + info(s"Received listing all live sessions request from $userName/$ipAddress") + if (!isAdministrator(userName)) { + throw new NotAllowedException( + s"$userName is not allowed to list all live sessions") + } + fe.be.sessionManager.allSessions().map { session => + new SessionData( + session.handle.identifier.toString, + session.user, + session.ipAddress, + session.conf.asJava, + session.createTime, + session.lastAccessTime - session.createTime, + session.getNoOperationTime) + }.toSeq + } + + @ApiResponse( + responseCode = "200", + content = Array(new Content(mediaType = MediaType.APPLICATION_JSON)), + description = "Close a session") + @DELETE + @Path("sessions/{sessionHandle}") + def closeSession(@PathParam("sessionHandle") sessionHandleStr: String): Response = { + val userName = fe.getSessionUser(Map.empty[String, String]) + val ipAddress = fe.getIpAddress + info(s"Received closing a session request from $userName/$ipAddress") + if (!isAdministrator(userName)) { + throw new NotAllowedException( + s"$userName is not allowed to close the session $sessionHandleStr") + } + fe.be.closeSession(SessionHandle.fromUUID(sessionHandleStr)) + Response.ok(s"Session $sessionHandleStr is closed successfully.").build() + } + @ApiResponse( responseCode = "200", content = Array(new Content(mediaType = MediaType.APPLICATION_JSON)), diff --git a/kyuubi-server/src/test/scala/org/apache/kyuubi/server/api/v1/AdminResourceSuite.scala b/kyuubi-server/src/test/scala/org/apache/kyuubi/server/api/v1/AdminResourceSuite.scala index ffd4a9140..9c050ba31 100644 --- a/kyuubi-server/src/test/scala/org/apache/kyuubi/server/api/v1/AdminResourceSuite.scala +++ b/kyuubi-server/src/test/scala/org/apache/kyuubi/server/api/v1/AdminResourceSuite.scala @@ -18,13 +18,17 @@ package org.apache.kyuubi.server.api.v1 import java.util.{Base64, UUID} +import javax.ws.rs.client.Entity import javax.ws.rs.core.{GenericType, MediaType} +import scala.collection.JavaConverters._ + import org.scalatest.time.SpanSugar.convertIntToGrainOfTime import org.apache.kyuubi.{KYUUBI_VERSION, KyuubiFunSuite, RestFrontendTestHelper, Utils} -import org.apache.kyuubi.client.api.v1.dto.Engine +import org.apache.kyuubi.client.api.v1.dto.{Engine, SessionData, SessionHandle, SessionOpenRequest} import org.apache.kyuubi.config.KyuubiConf +import org.apache.kyuubi.config.KyuubiReservedKeys.KYUUBI_SESSION_CONNECTION_URL_KEY import org.apache.kyuubi.engine.{ApplicationState, EngineRef, KyuubiApplicationManager} import org.apache.kyuubi.engine.EngineType.SPARK_SQL import org.apache.kyuubi.engine.ShareLevel.{CONNECTION, USER} @@ -123,6 +127,46 @@ class AdminResourceSuite extends KyuubiFunSuite with RestFrontendTestHelper { assert(200 == response.getStatus) } + test("list/close sessions") { + val requestObj = new SessionOpenRequest( + 1, + Map("testConfig" -> "testValue").asJava) + + var response = webTarget.path("api/v1/sessions") + .request(MediaType.APPLICATION_JSON_TYPE) + .post(Entity.entity(requestObj, MediaType.APPLICATION_JSON_TYPE)) + + val adminUser = Utils.currentUser + val encodeAuthorization = new String( + Base64.getEncoder.encode( + s"$adminUser:".getBytes()), + "UTF-8") + + // get session list + var response2 = webTarget.path("api/v1/admin/sessions").request() + .header(AUTHORIZATION_HEADER, s"BASIC $encodeAuthorization") + .get() + assert(200 == response2.getStatus) + val sessions1 = response2.readEntity(new GenericType[Seq[SessionData]]() {}) + assert(sessions1.nonEmpty) + assert(sessions1.head.getConf.get(KYUUBI_SESSION_CONNECTION_URL_KEY) === fe.connectionUrl) + + // close an opened session + val sessionHandle = response.readEntity(classOf[SessionHandle]).getIdentifier + response = webTarget.path(s"api/v1/admin/sessions/$sessionHandle").request() + .header(AUTHORIZATION_HEADER, s"BASIC $encodeAuthorization") + .delete() + assert(200 == response.getStatus) + + // get session list again + response2 = webTarget.path("api/v1/admin/sessions").request() + .header(AUTHORIZATION_HEADER, s"BASIC $encodeAuthorization") + .get() + assert(200 == response2.getStatus) + val sessions2 = response2.readEntity(classOf[Seq[SessionData]]) + assert(sessions2.isEmpty) + } + test("delete engine - user share level") { val id = UUID.randomUUID().toString conf.set(KyuubiConf.ENGINE_SHARE_LEVEL, USER.toString) From 5b3ab9ca924b789b4f10f91600ffed684265e958 Mon Sep 17 00:00:00 2001 From: zwangsheng <2213335496@qq.com> Date: Tue, 28 Feb 2023 08:54:33 +0000 Subject: [PATCH 124/760] [KYUUBI #4424][REST] Catch No Node Exception, when list kyuubi engines ### _Why are the changes needed?_ Close #4424 Catch No Node Exception, when list kyuubi engines ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [x] Add screenshots for manual tests if appropriate - [ ] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request ![WX20230227-184957](https://user-images.githubusercontent.com/52876270/221544376-2f0d6b4b-0bc4-446e-abb1-d0c79211aea4.png) Closes #4425 from zwangsheng/kyuubi-4424. Closes #4424 3052b157 [zwangsheng] [Kyuubi #4424] Fix scala style 74825cf8 [zwangsheng] [Kyuubi #4424] Throw User Friendly Exception 7e8363cc [zwangsheng] [Kyuubi #4424] Remove usless file & catch subException 4a3c469a [zwangsheng] [Kyuubi #4424] Catch cacth No Node Exception, when list kyuubi engines Authored-by: zwangsheng <2213335496@qq.com> Signed-off-by: ulyssesyou --- .../kyuubi/server/api/v1/AdminResource.scala | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/AdminResource.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/AdminResource.scala index a85cef16d..2aae7076c 100644 --- a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/AdminResource.scala +++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/AdminResource.scala @@ -27,6 +27,7 @@ import scala.collection.mutable.ListBuffer import io.swagger.v3.oas.annotations.media.{ArraySchema, Content, Schema} import io.swagger.v3.oas.annotations.responses.ApiResponse import io.swagger.v3.oas.annotations.tags.Tag +import org.apache.zookeeper.KeeperException.NoNodeException import org.apache.kyuubi.{KYUUBI_VERSION, Logging, Utils} import org.apache.kyuubi.client.api.v1.dto.{Engine, SessionData} @@ -207,9 +208,19 @@ private[v1] class AdminResource extends ApiRequestContext with Logging { } case None => withDiscoveryClient(fe.getConf) { discoveryClient => - discoveryClient.getChildren(engineSpace).map { child => - info(s"Listing engine nodes for $engineSpace/$child") - engineNodes ++= discoveryClient.getServiceNodesInfo(s"$engineSpace/$child") + try { + discoveryClient.getChildren(engineSpace).map { child => + info(s"Listing engine nodes for $engineSpace/$child") + engineNodes ++= discoveryClient.getServiceNodesInfo(s"$engineSpace/$child") + } + } catch { + case nne: NoNodeException => + error( + s"No such engine for user: $userName, " + + s"engine type: $engineType, share level: $shareLevel, subdomain: $subdomain", + nne) + throw new NotFoundException(s"No such engine for user: $userName, " + + s"engine type: $engineType, share level: $shareLevel, subdomain: $subdomain") } } } From 835454de630c3b791623287d96829870b8052cc2 Mon Sep 17 00:00:00 2001 From: Cheng Pan Date: Tue, 28 Feb 2023 21:14:36 +0800 Subject: [PATCH 125/760] [KYUUBI #4418] Allow disable metadata operation async retry and fail fast on unrecoverable DB error ### _Why are the changes needed?_ The key changes are 1. allow disabling metadata operation retry 2. always fail fast on "duplicated key on unique index" error Currently, when metadata operations failed, we always do async retry, to tolerate long-time metadata store outages w/o blocking the submission request, but it can not guarantee metadata consistency eventually, e.g. when inserting data violates the unique key restriction, it will never succeed, and block any following update request for the batch job, in such cases, the client gets succeed response but the metadata can not be updated correctly. We should distinguish between recoverable and unrecoverable errors, for unrecoverable errors, we should fail fast, but the fact is it's hard to enumerate all recoverable nor unrecoverable errors, in this PR, we just enumerate the "duplicated key" as unrecoverable errors, and provide a switch to disable async retry so that the error can propagate to client correctly. Some configurations are renamed w/ the `async.` prefix(the original key still takes effect) because we may introduce the sync retry logic in the future. ### _How was this patch tested?_ - [x] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [x] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4418 from pan3793/sync-retry. Closes #4418 ce58ac58c [Cheng Pan] revert migration-guide.md c2d8377a4 [Cheng Pan] simplify Authored-by: Cheng Pan Signed-off-by: Cheng Pan --- docs/deployment/settings.md | 5 +- .../org/apache/kyuubi/config/KyuubiConf.scala | 37 ++- .../server/metadata/MetadataManager.scala | 139 +++++----- .../server/metadata/MetadataRequest.scala | 2 +- .../metadata/MetadataManagerSuite.scala | 241 ++++++++++-------- 5 files changed, 234 insertions(+), 190 deletions(-) diff --git a/docs/deployment/settings.md b/docs/deployment/settings.md index 1b593bde1..cd8f5b770 100644 --- a/docs/deployment/settings.md +++ b/docs/deployment/settings.md @@ -302,9 +302,10 @@ You can configure the Kyuubi properties in `$KYUUBI_HOME/conf/kyuubi-defaults.co | kyuubi.metadata.cleaner.interval | PT30M | The interval to check and clean expired metadata. | duration | 1.6.0 | | kyuubi.metadata.max.age | PT72H | The maximum age of metadata, the metadata exceeding the age will be cleaned. | duration | 1.6.0 | | kyuubi.metadata.recovery.threads | 10 | The number of threads for recovery from the metadata store when the Kyuubi server restarts. | int | 1.6.0 | +| kyuubi.metadata.request.async.retry.enabled | true | Whether to retry in async when metadata request failed. When true, return success response immediately even the metadata request failed, and schedule it in background until success, to tolerate long-time metadata store outages w/o blocking the submission request. | boolean | 1.7.0 | +| kyuubi.metadata.request.async.retry.queue.size | 65536 | The maximum queue size for buffering metadata requests in memory when the external metadata storage is down. Requests will be dropped if the queue exceeds. Only take affect when kyuubi.metadata.request.async.retry.enabled is `true`. | int | 1.6.0 | +| kyuubi.metadata.request.async.retry.threads | 10 | Number of threads in the metadata request async retry manager thread pool. Only take affect when kyuubi.metadata.request.async.retry.enabled is `true`. | int | 1.6.0 | | kyuubi.metadata.request.retry.interval | PT5S | The interval to check and trigger the metadata request retry tasks. | duration | 1.6.0 | -| kyuubi.metadata.request.retry.queue.size | 65536 | The maximum queue size for buffering metadata requests in memory when the external metadata storage is down. Requests will be dropped if the queue exceeds. | int | 1.6.0 | -| kyuubi.metadata.request.retry.threads | 10 | Number of threads in the metadata request retry manager thread pool. The metadata store might be unavailable sometimes and the requests will fail, tolerant for this case and unblock the main thread, we support retrying the failed requests in an async way. | int | 1.6.0 | | kyuubi.metadata.store.class | org.apache.kyuubi.server.metadata.jdbc.JDBCMetadataStore | Fully qualified class name for server metadata store. | string | 1.6.0 | | kyuubi.metadata.store.jdbc.database.schema.init | true | Whether to init the JDBC metadata store database schema. | boolean | 1.6.0 | | kyuubi.metadata.store.jdbc.database.type | DERBY | The database type for server jdbc metadata store.
    • DERBY: Apache Derby, JDBC driver `org.apache.derby.jdbc.AutoloadedDriver`.
    • MYSQL: MySQL, JDBC driver `com.mysql.jdbc.Driver`.
    • CUSTOM: User-defined database type, need to specify corresponding JDBC driver.
    • Note that: The JDBC datasource is powered by HiKariCP, for datasource properties, please specify them with the prefix: kyuubi.metadata.store.jdbc.datasource. For example, kyuubi.metadata.store.jdbc.datasource.connectionTimeout=10000. | string | 1.6.0 | diff --git a/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala b/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala index f59cb2d98..f61cfeaa7 100644 --- a/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala +++ b/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala @@ -1568,16 +1568,6 @@ object KyuubiConf { .intConf .createWithDefault(10) - val METADATA_REQUEST_RETRY_THREADS: ConfigEntry[Int] = - buildConf("kyuubi.metadata.request.retry.threads") - .doc("Number of threads in the metadata request retry manager thread pool. The metadata" + - " store might be unavailable sometimes and the requests will fail, tolerant for this" + - " case and unblock the main thread, we support retrying the failed requests" + - " in an async way.") - .version("1.6.0") - .intConf - .createWithDefault(10) - val METADATA_REQUEST_RETRY_INTERVAL: ConfigEntry[Long] = buildConf("kyuubi.metadata.request.retry.interval") .doc("The interval to check and trigger the metadata request retry tasks.") @@ -1585,10 +1575,31 @@ object KyuubiConf { .timeConf .createWithDefault(Duration.ofSeconds(5).toMillis) - val METADATA_REQUEST_RETRY_QUEUE_SIZE: ConfigEntry[Int] = - buildConf("kyuubi.metadata.request.retry.queue.size") + val METADATA_REQUEST_ASYNC_RETRY_ENABLED: ConfigEntry[Boolean] = + buildConf("kyuubi.metadata.request.async.retry.enabled") + .doc("Whether to retry in async when metadata request failed. When true, return " + + "success response immediately even the metadata request failed, and schedule " + + "it in background until success, to tolerate long-time metadata store outages " + + "w/o blocking the submission request.") + .version("1.7.0") + .booleanConf + .createWithDefault(true) + + val METADATA_REQUEST_ASYNC_RETRY_THREADS: ConfigEntry[Int] = + buildConf("kyuubi.metadata.request.async.retry.threads") + .withAlternative("kyuubi.metadata.request.retry.threads") + .doc("Number of threads in the metadata request async retry manager thread pool. Only " + + s"take affect when ${METADATA_REQUEST_ASYNC_RETRY_ENABLED.key} is `true`.") + .version("1.6.0") + .intConf + .createWithDefault(10) + + val METADATA_REQUEST_ASYNC_RETRY_QUEUE_SIZE: ConfigEntry[Int] = + buildConf("kyuubi.metadata.request.async.retry.queue.size") + .withAlternative("kyuubi.metadata.request.retry.queue.size") .doc("The maximum queue size for buffering metadata requests in memory when the external" + - " metadata storage is down. Requests will be dropped if the queue exceeds.") + " metadata storage is down. Requests will be dropped if the queue exceeds. Only" + + s" take affect when ${METADATA_REQUEST_ASYNC_RETRY_ENABLED.key} is `true`.") .version("1.6.0") .intConf .createWithDefault(65536) diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/metadata/MetadataManager.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/metadata/MetadataManager.scala index 5cecd2ab1..35abc1b30 100644 --- a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/metadata/MetadataManager.scala +++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/metadata/MetadataManager.scala @@ -37,44 +37,55 @@ class MetadataManager extends AbstractService("MetadataManager") { private var _metadataStore: MetadataStore = _ // Visible for testing. - private[metadata] val identifierRequestsRetryRefs = + private[metadata] val identifierRequestsAsyncRetryRefs = new ConcurrentHashMap[String, MetadataRequestsRetryRef]() // Visible for testing. - private[metadata] val identifierRequestsRetryingCounts = + private[metadata] val identifierRequestsAsyncRetryingCounts = new ConcurrentHashMap[String, AtomicInteger]() - private val requestsRetryTrigger = - ThreadUtils.newDaemonSingleThreadScheduledExecutor("metadata-requests-retry-trigger") + private lazy val requestsRetryInterval = + conf.get(KyuubiConf.METADATA_REQUEST_RETRY_INTERVAL) - private var requestsRetryExecutor: ThreadPoolExecutor = _ + private lazy val requestsAsyncRetryEnabled = + conf.get(KyuubiConf.METADATA_REQUEST_ASYNC_RETRY_ENABLED) - private var maxMetadataRequestsRetryRefs: Int = _ + private lazy val requestsAsyncRetryTrigger = + ThreadUtils.newDaemonSingleThreadScheduledExecutor("metadata-requests-async-retry-trigger") - private val metadataCleaner = + private lazy val requestsAsyncRetryExecutor: ThreadPoolExecutor = + ThreadUtils.newDaemonFixedThreadPool( + conf.get(KyuubiConf.METADATA_REQUEST_ASYNC_RETRY_THREADS), + "metadata-requests-async-retry") + + private lazy val cleanerEnabled = conf.get(KyuubiConf.METADATA_CLEANER_ENABLED) + + private lazy val metadataCleaner = ThreadUtils.newDaemonSingleThreadScheduledExecutor("metadata-cleaner") override def initialize(conf: KyuubiConf): Unit = { _metadataStore = MetadataManager.createMetadataStore(conf) - val retryExecutorNumThreads = - conf.get(KyuubiConf.METADATA_REQUEST_RETRY_THREADS) - requestsRetryExecutor = ThreadUtils.newDaemonFixedThreadPool( - retryExecutorNumThreads, - "metadata-requests-retry-executor") - maxMetadataRequestsRetryRefs = conf.get(KyuubiConf.METADATA_REQUEST_RETRY_QUEUE_SIZE) super.initialize(conf) } override def start(): Unit = { super.start() - startMetadataRequestsRetryTrigger() - startMetadataCleaner() + if (requestsAsyncRetryEnabled) { + startMetadataRequestsAsyncRetryTrigger() + } + if (cleanerEnabled) { + startMetadataCleaner() + } } override def stop(): Unit = { - ThreadUtils.shutdown(requestsRetryTrigger) - ThreadUtils.shutdown(requestsRetryExecutor) - ThreadUtils.shutdown(metadataCleaner) + if (requestsAsyncRetryEnabled) { + ThreadUtils.shutdown(requestsAsyncRetryTrigger) + ThreadUtils.shutdown(requestsAsyncRetryExecutor) + } + if (cleanerEnabled) { + ThreadUtils.shutdown(metadataCleaner) + } _metadataStore.close() super.stop() } @@ -93,11 +104,22 @@ class MetadataManager extends AbstractService("MetadataManager") { } } - def insertMetadata(metadata: Metadata, retryOnError: Boolean = true): Unit = { + protected def unrecoverableDBErr(cause: Throwable): Boolean = { + val unrecoverableKeywords = Seq( + "duplicate key value in a unique or primary key constraint or unique index", // Derby + "Duplicate entry" // MySQL + ) + unrecoverableKeywords.exists(cause.getMessage.contains) + } + + def insertMetadata(metadata: Metadata, asyncRetryOnError: Boolean = true): Unit = { try { withMetadataRequestMetrics(_metadataStore.insertMetadata(metadata)) } catch { - case e: Throwable if retryOnError => + // stop to retry when encounter duplicated key error. + case rethrow: Throwable if unrecoverableDBErr(rethrow) => + throw rethrow + case e: Throwable if requestsAsyncRetryEnabled && asyncRetryOnError => error(s"Error inserting metadata for session ${metadata.identifier}", e) addMetadataRetryRequest(InsertMetadata(metadata)) } @@ -156,11 +178,11 @@ class MetadataManager extends AbstractService("MetadataManager") { withMetadataRequestMetrics(_metadataStore.getMetadataList(filter, from, size, true)) } - def updateMetadata(metadata: Metadata, retryOnError: Boolean = true): Unit = { + def updateMetadata(metadata: Metadata, asyncRetryOnError: Boolean = true): Unit = { try { withMetadataRequestMetrics(_metadataStore.updateMetadata(metadata)) } catch { - case e: Throwable if retryOnError => + case e: Throwable if requestsAsyncRetryEnabled && asyncRetryOnError => error(s"Error updating metadata for session ${metadata.identifier}", e) addMetadataRetryRequest(UpdateMetadata(metadata)) } @@ -171,35 +193,33 @@ class MetadataManager extends AbstractService("MetadataManager") { } private def startMetadataCleaner(): Unit = { - val cleanerEnabled = conf.get(KyuubiConf.METADATA_CLEANER_ENABLED) val stateMaxAge = conf.get(METADATA_MAX_AGE) - - if (cleanerEnabled) { - val interval = conf.get(KyuubiConf.METADATA_CLEANER_INTERVAL) - val cleanerTask: Runnable = () => { - try { - withMetadataRequestMetrics(_metadataStore.cleanupMetadataByAge(stateMaxAge)) - } catch { - case e: Throwable => error("Error cleaning up the metadata by age", e) - } + val interval = conf.get(KyuubiConf.METADATA_CLEANER_INTERVAL) + val cleanerTask: Runnable = () => { + try { + withMetadataRequestMetrics(_metadataStore.cleanupMetadataByAge(stateMaxAge)) + } catch { + case e: Throwable => error("Error cleaning up the metadata by age", e) } - - metadataCleaner.scheduleWithFixedDelay( - cleanerTask, - interval, - interval, - TimeUnit.MILLISECONDS) } + + metadataCleaner.scheduleWithFixedDelay( + cleanerTask, + interval, + interval, + TimeUnit.MILLISECONDS) } def addMetadataRetryRequest(request: MetadataRequest): Unit = { - if (identifierRequestsRetryRefs.size() > maxMetadataRequestsRetryRefs) { + val maxRequestsAsyncRetryRefs: Int = + conf.get(KyuubiConf.METADATA_REQUEST_ASYNC_RETRY_QUEUE_SIZE) + if (identifierRequestsAsyncRetryRefs.size() > maxRequestsAsyncRetryRefs) { throw new KyuubiException( "The number of metadata requests retry instances exceeds the limitation:" + - maxMetadataRequestsRetryRefs) + maxRequestsAsyncRetryRefs) } val identifier = request.metadata.identifier - val ref = identifierRequestsRetryRefs.computeIfAbsent( + val ref = identifierRequestsAsyncRetryRefs.computeIfAbsent( identifier, identifier => { val ref = new MetadataRequestsRetryRef @@ -207,30 +227,29 @@ class MetadataManager extends AbstractService("MetadataManager") { ref }) ref.addRetryingMetadataRequest(request) - identifierRequestsRetryRefs.putIfAbsent(identifier, ref) + identifierRequestsAsyncRetryRefs.putIfAbsent(identifier, ref) MetricsSystem.tracing(_.markMeter(MetricsConstants.METADATA_REQUEST_RETRYING)) } def getMetadataRequestsRetryRef(identifier: String): MetadataRequestsRetryRef = { - identifierRequestsRetryRefs.get(identifier) + identifierRequestsAsyncRetryRefs.get(identifier) } def deRegisterRequestsRetryRef(identifier: String): Unit = { - identifierRequestsRetryRefs.remove(identifier) - identifierRequestsRetryingCounts.remove(identifier) + identifierRequestsAsyncRetryRefs.remove(identifier) + identifierRequestsAsyncRetryingCounts.remove(identifier) } - private def startMetadataRequestsRetryTrigger(): Unit = { - val interval = conf.get(KyuubiConf.METADATA_REQUEST_RETRY_INTERVAL) + private def startMetadataRequestsAsyncRetryTrigger(): Unit = { val triggerTask = new Runnable { override def run(): Unit = { - identifierRequestsRetryRefs.forEach { (id, ref) => + identifierRequestsAsyncRetryRefs.forEach { (id, ref) => if (!ref.hasRemainingRequests()) { - identifierRequestsRetryRefs.remove(id) - identifierRequestsRetryingCounts.remove(id) + identifierRequestsAsyncRetryRefs.remove(id) + identifierRequestsAsyncRetryingCounts.remove(id) } else { - val retryingCount = - identifierRequestsRetryingCounts.computeIfAbsent(id, _ => new AtomicInteger(0)) + val retryingCount = identifierRequestsAsyncRetryingCounts + .computeIfAbsent(id, _ => new AtomicInteger(0)) if (retryingCount.get() == 0) { val retryTask = new Runnable { @@ -241,12 +260,9 @@ class MetadataManager extends AbstractService("MetadataManager") { while (request != null) { request match { case insert: InsertMetadata => - insertMetadata(insert.metadata, retryOnError = false) - + insertMetadata(insert.metadata, asyncRetryOnError = false) case update: UpdateMetadata => - updateMetadata(update.metadata, retryOnError = false) - - case _ => + updateMetadata(update.metadata, asyncRetryOnError = false) } ref.metadataRequests.remove(request) MetricsSystem.tracing(_.markMeter( @@ -265,22 +281,21 @@ class MetadataManager extends AbstractService("MetadataManager") { try { retryingCount.incrementAndGet() - requestsRetryExecutor.submit(retryTask) + requestsAsyncRetryExecutor.submit(retryTask) } catch { case e: Throwable => error(s"Error submitting metadata retry requests for $id", e) retryingCount.decrementAndGet() } } - } } } } - requestsRetryTrigger.scheduleWithFixedDelay( + requestsAsyncRetryTrigger.scheduleWithFixedDelay( triggerTask, - interval, - interval, + requestsRetryInterval, + requestsRetryInterval, TimeUnit.MILLISECONDS) } } diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/metadata/MetadataRequest.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/metadata/MetadataRequest.scala index dcee6466b..2c121edfe 100644 --- a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/metadata/MetadataRequest.scala +++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/metadata/MetadataRequest.scala @@ -19,7 +19,7 @@ package org.apache.kyuubi.server.metadata import org.apache.kyuubi.server.metadata.api.Metadata -trait MetadataRequest { +sealed trait MetadataRequest { def metadata: Metadata } diff --git a/kyuubi-server/src/test/scala/org/apache/kyuubi/server/metadata/MetadataManagerSuite.scala b/kyuubi-server/src/test/scala/org/apache/kyuubi/server/metadata/MetadataManagerSuite.scala index d8a8af202..75c935a3d 100644 --- a/kyuubi-server/src/test/scala/org/apache/kyuubi/server/metadata/MetadataManagerSuite.scala +++ b/kyuubi-server/src/test/scala/org/apache/kyuubi/server/metadata/MetadataManagerSuite.scala @@ -25,103 +25,152 @@ import org.scalatest.time.SpanSugar.convertIntToGrainOfTime import org.apache.kyuubi.{KyuubiException, KyuubiFunSuite} import org.apache.kyuubi.config.KyuubiConf +import org.apache.kyuubi.config.KyuubiConf._ import org.apache.kyuubi.metrics.{MetricsConstants, MetricsSystem} +import org.apache.kyuubi.metrics.MetricsConstants._ import org.apache.kyuubi.server.metadata.api.Metadata import org.apache.kyuubi.session.SessionType class MetadataManagerSuite extends KyuubiFunSuite { - val metadataManager = new MetadataManager() - val metricsSystem = new MetricsSystem() - val conf = KyuubiConf().set(KyuubiConf.METADATA_REQUEST_RETRY_INTERVAL, 100L) - - override def beforeAll(): Unit = { - super.beforeAll() - metricsSystem.initialize(conf) - metricsSystem.start() - metadataManager.initialize(conf) - metadataManager.start() - } - override def afterAll(): Unit = { - metadataManager.getBatches(null, null, null, 0, 0, 0, Int.MaxValue).foreach { batch => - metadataManager.cleanupMetadataById(batch.getId) + test("fail fast on duplicated key") { + Seq("true", "false").foreach { enableAsyncRetry => + withMetadataManager(Map( + METADATA_REQUEST_ASYNC_RETRY_ENABLED.key -> enableAsyncRetry, + METADATA_REQUEST_RETRY_INTERVAL.key -> "100")) { metadataManager => + val metadata = newMetadata() + metadataManager.insertMetadata(metadata) + Seq(true, false).foreach { asyncRetryOnError => + intercept[KyuubiException] { + metadataManager.insertMetadata(metadata, asyncRetryOnError) + } + } + } } - metadataManager.stop() - metricsSystem.stop() - super.afterAll() } - override protected def afterEach(): Unit = { - eventually(timeout(5.seconds), interval(200.milliseconds)) { - assert(MetricsSystem.counterValue( - MetricsConstants.METADATA_REQUEST_OPENED).getOrElse(0L) === 0) + test("async retry the metadata store requests") { + withMetadataManager( + Map( + METADATA_REQUEST_ASYNC_RETRY_ENABLED.key -> "true", + METADATA_REQUEST_RETRY_INTERVAL.key -> "100"), + () => + new MetadataManager { + override protected def unrecoverableDBErr(cause: Throwable): Boolean = false + }) { metadataManager => + val metadata = newMetadata() + metadataManager.insertMetadata(metadata) + metadataManager.insertMetadata(metadata, asyncRetryOnError = true) + val retryRef = metadataManager.getMetadataRequestsRetryRef(metadata.identifier) + val metadataToUpdate = metadata.copy(state = "RUNNING") + retryRef.addRetryingMetadataRequest(UpdateMetadata(metadataToUpdate)) + eventually(timeout(3.seconds)) { + assert(retryRef.hasRemainingRequests()) + assert(metadataManager.getBatch(metadata.identifier).getState === "PENDING") + } + + val metadata2 = metadata.copy(identifier = UUID.randomUUID().toString) + val metadata2ToUpdate = metadata2.copy( + engineId = "app_id", + engineName = "app_name", + engineUrl = "app_url", + engineState = "app_state", + state = "RUNNING") + + metadataManager.addMetadataRetryRequest(InsertMetadata(metadata2)) + metadataManager.addMetadataRetryRequest(UpdateMetadata(metadata2ToUpdate)) + + val retryRef2 = metadataManager.getMetadataRequestsRetryRef(metadata2.identifier) + + eventually(timeout(3.seconds)) { + assert(!retryRef2.hasRemainingRequests()) + assert(metadataManager.getBatch(metadata2.identifier).getState === "RUNNING") + } + + metadataManager.identifierRequestsAsyncRetryRefs.clear() + eventually(timeout(3.seconds)) { + metadataManager.identifierRequestsAsyncRetryingCounts.asScala.forall(_._2.get() == 0) + } + metadataManager.identifierRequestsAsyncRetryingCounts.clear() } } - test("retry the metadata store requests") { - val metadata = Metadata( - identifier = UUID.randomUUID().toString, - sessionType = SessionType.BATCH, - realUser = "kyuubi", - username = "kyuubi", - ipAddress = "127.0.0.1", - kyuubiInstance = "localhost:10009", - state = "PENDING", - resource = "intern", - className = "org.apache.kyuubi.SparkWC", - requestName = "kyuubi_batch", - requestConf = Map("spark.master" -> "local"), - requestArgs = Seq("100"), - createTime = System.currentTimeMillis(), - engineType = "spark", - clusterManager = Some("local")) - metadataManager.insertMetadata(metadata) - intercept[KyuubiException] { - metadataManager.insertMetadata(metadata, retryOnError = false) - } - metadataManager.insertMetadata(metadata, retryOnError = true) - val retryRef = metadataManager.getMetadataRequestsRetryRef(metadata.identifier) - val metadataToUpdate = metadata.copy(state = "RUNNING") - retryRef.addRetryingMetadataRequest(UpdateMetadata(metadataToUpdate)) - eventually(timeout(3.seconds)) { - assert(retryRef.hasRemainingRequests()) - assert(metadataManager.getBatch(metadata.identifier).getState === "PENDING") - } - - val metadata2 = metadata.copy(identifier = UUID.randomUUID().toString) - val metadata2ToUpdate = metadata2.copy( - engineId = "app_id", - engineName = "app_name", - engineUrl = "app_url", - engineState = "app_state", - state = "RUNNING") - - metadataManager.addMetadataRetryRequest(InsertMetadata(metadata2)) - metadataManager.addMetadataRetryRequest(UpdateMetadata(metadata2ToUpdate)) - - val retryRef2 = metadataManager.getMetadataRequestsRetryRef(metadata2.identifier) - - eventually(timeout(3.seconds)) { - assert(!retryRef2.hasRemainingRequests()) - assert(metadataManager.getBatch(metadata2.identifier).getState === "RUNNING") + test("async metadata request metrics") { + withMetadataManager(Map( + METADATA_REQUEST_ASYNC_RETRY_ENABLED.key -> "true", + METADATA_REQUEST_RETRY_INTERVAL.key -> "100")) { metadataManager => + val totalRequests = MetricsSystem.meterValue(METADATA_REQUEST_TOTAL).getOrElse(0L) + val failedRequests = MetricsSystem.meterValue(METADATA_REQUEST_FAIL).getOrElse(0L) + val retryingRequests = MetricsSystem.meterValue(METADATA_REQUEST_RETRYING).getOrElse(0L) + + val metadata = newMetadata() + metadataManager.insertMetadata(metadata) + + assert( + MetricsSystem.meterValue(MetricsConstants.METADATA_REQUEST_TOTAL) + .getOrElse(0L) - totalRequests === 1) + assert( + MetricsSystem.meterValue(MetricsConstants.METADATA_REQUEST_FAIL) + .getOrElse(0L) - failedRequests === 0) + assert( + MetricsSystem.meterValue(MetricsConstants.METADATA_REQUEST_RETRYING) + .getOrElse(0L) - retryingRequests === 0) + + val invalidMetadata = metadata.copy(kyuubiInstance = null) + intercept[Exception](metadataManager.insertMetadata(invalidMetadata, false)) + assert( + MetricsSystem.meterValue(MetricsConstants.METADATA_REQUEST_TOTAL) + .getOrElse(0L) - totalRequests === 2) + assert( + MetricsSystem.meterValue(MetricsConstants.METADATA_REQUEST_FAIL) + .getOrElse(0L) - failedRequests === 1) + assert( + MetricsSystem.meterValue(MetricsConstants.METADATA_REQUEST_RETRYING) + .getOrElse(0L) - retryingRequests === 0) + + metadataManager.insertMetadata(invalidMetadata, true) + + assert( + MetricsSystem.meterValue(MetricsConstants.METADATA_REQUEST_TOTAL) + .getOrElse(0L) - totalRequests === 3) + assert( + MetricsSystem.meterValue(MetricsConstants.METADATA_REQUEST_FAIL) + .getOrElse(0L) - failedRequests === 2) + assert( + MetricsSystem.meterValue(MetricsConstants.METADATA_REQUEST_RETRYING) + .getOrElse(0L) - retryingRequests === 1) } + } - metadataManager.identifierRequestsRetryRefs.clear() - eventually(timeout(3.seconds)) { - metadataManager.identifierRequestsRetryingCounts.asScala.forall(_._2.get() == 0) + private def withMetadataManager( + confOverlay: Map[String, String], + newMetadataMgr: () => MetadataManager = () => new MetadataManager())( + f: MetadataManager => Unit): Unit = { + val metricsSystem = new MetricsSystem() + val metadataManager = newMetadataMgr() + val conf = KyuubiConf() + confOverlay.foreach { case (k, v) => conf.set(k, v) } + try { + metricsSystem.initialize(conf) + metricsSystem.start() + metadataManager.initialize(conf) + metadataManager.start() + f(metadataManager) + } finally { + metadataManager.getBatches(null, null, null, 0, 0, 0, Int.MaxValue).foreach { batch => + metadataManager.cleanupMetadataById(batch.getId) + } + // ensure no metadata request leak + eventually(timeout(5.seconds), interval(200.milliseconds)) { + assert(MetricsSystem.counterValue(METADATA_REQUEST_OPENED).getOrElse(0L) === 0) + } + metadataManager.stop() + metricsSystem.stop() } - metadataManager.identifierRequestsRetryingCounts.clear() } - test("metadata request metrics") { - val totalRequests = - MetricsSystem.meterValue(MetricsConstants.METADATA_REQUEST_TOTAL).getOrElse(0L) - val failedRequests = - MetricsSystem.meterValue(MetricsConstants.METADATA_REQUEST_FAIL).getOrElse(0L) - val retryingRequests = - MetricsSystem.meterValue(MetricsConstants.METADATA_REQUEST_RETRYING).getOrElse(0L) - - val metadata = Metadata( + private def newMetadata(): Metadata = { + Metadata( identifier = UUID.randomUUID().toString, sessionType = SessionType.BATCH, realUser = "kyuubi", @@ -137,37 +186,5 @@ class MetadataManagerSuite extends KyuubiFunSuite { createTime = System.currentTimeMillis(), engineType = "spark", clusterManager = Some("local")) - metadataManager.insertMetadata(metadata) - - assert( - MetricsSystem.meterValue(MetricsConstants.METADATA_REQUEST_TOTAL).getOrElse( - 0L) - totalRequests === 1) - assert( - MetricsSystem.meterValue(MetricsConstants.METADATA_REQUEST_FAIL).getOrElse( - 0L) - failedRequests === 0) - assert(MetricsSystem.meterValue( - MetricsConstants.METADATA_REQUEST_RETRYING).getOrElse(0L) - retryingRequests === 0) - - val invalidMetadata = metadata.copy(kyuubiInstance = null) - intercept[Exception](metadataManager.insertMetadata(invalidMetadata, false)) - assert( - MetricsSystem.meterValue(MetricsConstants.METADATA_REQUEST_TOTAL).getOrElse( - 0L) - totalRequests === 2) - assert( - MetricsSystem.meterValue(MetricsConstants.METADATA_REQUEST_FAIL).getOrElse( - 0L) - failedRequests === 1) - assert(MetricsSystem.meterValue( - MetricsConstants.METADATA_REQUEST_RETRYING).getOrElse(0L) - retryingRequests === 0) - - metadataManager.insertMetadata(invalidMetadata, true) - - assert( - MetricsSystem.meterValue(MetricsConstants.METADATA_REQUEST_TOTAL).getOrElse( - 0L) - totalRequests === 3) - assert( - MetricsSystem.meterValue(MetricsConstants.METADATA_REQUEST_FAIL).getOrElse( - 0L) - failedRequests === 2) - assert(MetricsSystem.meterValue( - MetricsConstants.METADATA_REQUEST_RETRYING).getOrElse(0L) - retryingRequests === 1) } } From abc0a1d9f0973a2d9b5bdcbf92a814d9b2d6b3b3 Mon Sep 17 00:00:00 2001 From: odone Date: Wed, 1 Mar 2023 10:05:14 +0800 Subject: [PATCH 126/760] [KYUUBI #4429] Add support for `OneRelation` ### _Why are the changes needed?_ The `LogicalPlan` of SQL with `OneRowRelation`: ``` select 1,2,(select count(distinct" + " ifnull(get_json_object(a, '$.b.imei'), get_json_object(a, '$.b.android_id'))) from t2) ``` ### _How was this patch tested?_ - [x] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [x] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4429 from iodone/kyuubi-count. Closes #4429 c3759239 [odone] add OneRowRelation Authored-by: odone Signed-off-by: ulyssesyou --- .../helper/SparkSQLLineageParseHelper.scala | 12 ++++++++++++ .../SparkSQLLineageParserHelperSuite.scala | 17 +++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/extensions/spark/kyuubi-spark-lineage/src/main/scala/org/apache/kyuubi/plugin/lineage/helper/SparkSQLLineageParseHelper.scala b/extensions/spark/kyuubi-spark-lineage/src/main/scala/org/apache/kyuubi/plugin/lineage/helper/SparkSQLLineageParseHelper.scala index a58653113..cfd155527 100644 --- a/extensions/spark/kyuubi-spark-lineage/src/main/scala/org/apache/kyuubi/plugin/lineage/helper/SparkSQLLineageParseHelper.scala +++ b/extensions/spark/kyuubi-spark-lineage/src/main/scala/org/apache/kyuubi/plugin/lineage/helper/SparkSQLLineageParseHelper.scala @@ -402,6 +402,18 @@ trait LineageParser { case p: LocalRelation => joinRelationColumnLineage(parentColumnsLineage, p.output, Seq(LOCAL_TABLE_IDENTIFIER)) + case _: OneRowRelation => + parentColumnsLineage.map { + case (k, attrs) => + k -> AttributeSet(attrs.map { + case attr + if attr.qualifier.nonEmpty && attr.qualifier.last.equalsIgnoreCase( + SUBQUERY_COLUMN_IDENTIFIER) => + attr.withQualifier(attr.qualifier.init) + case attr => attr + }) + } + case p: InMemoryRelation => // get logical plan from cachedPlan val cachedTableLogical = findSparkPlanLogicalLink(Seq(p.cacheBuilder.cachedPlan)) diff --git a/extensions/spark/kyuubi-spark-lineage/src/test/scala/org/apache/kyuubi/plugin/lineage/helper/SparkSQLLineageParserHelperSuite.scala b/extensions/spark/kyuubi-spark-lineage/src/test/scala/org/apache/kyuubi/plugin/lineage/helper/SparkSQLLineageParserHelperSuite.scala index 050f3ddc9..4e1edc5c1 100644 --- a/extensions/spark/kyuubi-spark-lineage/src/test/scala/org/apache/kyuubi/plugin/lineage/helper/SparkSQLLineageParserHelperSuite.scala +++ b/extensions/spark/kyuubi-spark-lineage/src/test/scala/org/apache/kyuubi/plugin/lineage/helper/SparkSQLLineageParserHelperSuite.scala @@ -1213,6 +1213,23 @@ class SparkSQLLineageParserHelperSuite extends KyuubiFunSuite } } + test("test count()") { + withTable("t1", "t2") { _ => + spark.sql("CREATE TABLE t1 (a string, b string, c string) USING hive") + spark.sql("CREATE TABLE t2 (a string, b string, c string) USING hive") + val ret0 = exectractLineage("insert into t1 select 1,2,(select count(distinct" + + " ifnull(get_json_object(a, '$.b.imei'), get_json_object(a, '$.b.android_id'))) from t2)") + + assert(ret0 == Lineage( + List("default.t2"), + List("default.t1"), + List( + ("default.t1.a", Set()), + ("default.t1.b", Set()), + ("default.t1.c", Set("default.t2.a"))))) + } + } + private def exectractLineageWithoutExecuting(sql: String): Lineage = { val parsed = spark.sessionState.sqlParser.parsePlan(sql) val analyzed = spark.sessionState.analyzer.execute(parsed) From 19b4b0a3fd73fb5ecb3f39264fe55d46ffe7489a Mon Sep 17 00:00:00 2001 From: Yikf Date: Wed, 1 Mar 2023 16:16:55 +0800 Subject: [PATCH 127/760] [KYUUBI #4432] jobId across tasks should be consistent to meet the contract expected by Hadoop committers ### _Why are the changes needed?_ jobId across tasks should be consistent to meet the contract expected by Hadoop committers ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [ ] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4432 from Yikf/jobid. Closes #4432 4e7401c91 [Yikf] jobId across tasks should be consistent Authored-by: Yikf Signed-off-by: Cheng Pan --- .../hive/write/FileWriterFactory.scala | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/extensions/spark/kyuubi-spark-connector-hive/src/main/scala/org/apache/kyuubi/spark/connector/hive/write/FileWriterFactory.scala b/extensions/spark/kyuubi-spark-connector-hive/src/main/scala/org/apache/kyuubi/spark/connector/hive/write/FileWriterFactory.scala index 6ebb55f14..3b86d43c7 100644 --- a/extensions/spark/kyuubi-spark-connector-hive/src/main/scala/org/apache/kyuubi/spark/connector/hive/write/FileWriterFactory.scala +++ b/extensions/spark/kyuubi-spark-connector-hive/src/main/scala/org/apache/kyuubi/spark/connector/hive/write/FileWriterFactory.scala @@ -19,6 +19,7 @@ package org.apache.kyuubi.spark.connector.hive.write import java.util.Date +import org.apache.hadoop.mapred.JobID import org.apache.hadoop.mapreduce.{TaskAttemptID, TaskID, TaskType} import org.apache.hadoop.mapreduce.task.TaskAttemptContextImpl import org.apache.spark.internal.io.FileCommitProtocol @@ -34,7 +35,12 @@ case class FileWriterFactory( description: WriteJobDescription, committer: FileCommitProtocol) extends DataWriterFactory { - @transient private lazy val jobId = sparkHadoopWriterUtils.createJobID(new Date, 0) + // SPARK-42478: jobId across tasks should be consistent to meet the contract + // expected by Hadoop committers, but `JobId` cannot be serialized. + // thus, persist the serializable jobTrackerID in the class and make jobId a + // transient lazy val which recreates it each time to ensure jobId is unique. + private[this] val jobTrackerID = sparkHadoopWriterUtils.createJobTrackerID(new Date) + @transient private lazy val jobId = createJobID(jobTrackerID, 0) override def createWriter(partitionId: Int, realTaskId: Long): DataWriter[InternalRow] = { val taskAttemptContext = createTaskAttemptContext(partitionId) @@ -59,4 +65,18 @@ case class FileWriterFactory( new TaskAttemptContextImpl(hadoopConf, taskAttemptId) } + + /** + * Create a job ID. + * + * @param jobTrackerID unique job track id + * @param id job number + * @return a job ID + */ + def createJobID(jobTrackerID: String, id: Int): JobID = { + if (id < 0) { + throw new IllegalArgumentException("Job number is negative") + } + new JobID(jobTrackerID, id) + } } From 885d0c7bbc17ea93f652a84344e0bbc6047fd6da Mon Sep 17 00:00:00 2001 From: wxmimperio Date: Thu, 2 Mar 2023 11:28:22 +0800 Subject: [PATCH 128/760] [KYUUBI #4435] Change kyuubi.operation.result.format log level to debug ### _Why are the changes needed?_ There is no need to log kyuubi.operation.result.format information every time a statement is executed. ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [ ] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4435 from imperio-wxm/master. Closes #4435 2dd4fb744 [Cheng Pan] Update kyuubi-hive-jdbc/src/main/java/org/apache/kyuubi/jdbc/hive/KyuubiStatement.java e32c6fea6 [Cheng Pan] Update kyuubi-hive-jdbc/src/main/java/org/apache/kyuubi/jdbc/hive/KyuubiStatement.java 1d9c5ed2b [wxmimperio] Change kyuubi.operation.result.format log level to debug Lead-authored-by: wxmimperio Co-authored-by: Cheng Pan Signed-off-by: Cheng Pan --- .../java/org/apache/kyuubi/jdbc/hive/KyuubiStatement.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kyuubi-hive-jdbc/src/main/java/org/apache/kyuubi/jdbc/hive/KyuubiStatement.java b/kyuubi-hive-jdbc/src/main/java/org/apache/kyuubi/jdbc/hive/KyuubiStatement.java index b452ca6aa..cbe32eca6 100644 --- a/kyuubi-hive-jdbc/src/main/java/org/apache/kyuubi/jdbc/hive/KyuubiStatement.java +++ b/kyuubi-hive-jdbc/src/main/java/org/apache/kyuubi/jdbc/hive/KyuubiStatement.java @@ -212,7 +212,7 @@ private boolean executeWithConfOverlay(String sql, Map confOverl String resultFormat = properties.getOrDefault("__kyuubi_operation_result_format__", DEFAULT_RESULT_FORMAT); - LOG.info("kyuubi.operation.result.format: " + resultFormat); + LOG.debug("kyuubi.operation.result.format: {}", resultFormat); switch (resultFormat) { case "arrow": boolean timestampAsString = @@ -275,7 +275,7 @@ public boolean executeAsync(String sql) throws SQLException { String resultFormat = properties.getOrDefault("__kyuubi_operation_result_format__", DEFAULT_RESULT_FORMAT); - LOG.info("kyuubi.operation.result.format: " + resultFormat); + LOG.debug("kyuubi.operation.result.format: {}", resultFormat); switch (resultFormat) { case "arrow": boolean timestampAsString = From efbaaff6fbe0a11a38e0cb13421175f92bb0bc45 Mon Sep 17 00:00:00 2001 From: Cheng Pan Date: Thu, 2 Mar 2023 17:42:52 +0800 Subject: [PATCH 129/760] [KYUUBI #4390] Allow user to provide batch id on submitting batch job ### _Why are the changes needed?_ This PR proposes to allow the user to provide a batch id on submitting a batch job. If the batch id already existed in metastore, Kyuubi ignores this submission and just returns the existing one, w/ a marker in response, this could avoid duplicated batch job submission. Talking about the implementation, the key things are How does the user set the custom batch id? - User can optionally set the `kyuubi.batch.id` in `conf: Map[String, String]`, and the value must be a UUID, for Java users, it can be generated by `UUID.randomUUID().toString()` How does the Kyuubi Server detect the duplication? - It's simple in single Kyuubi Server instance case, Kyuubi just needs to look up the metastore before creating a batch job - In HA mode, suppose the user requests to create the batch jobs w/ the same batch id concurrently, multiple Kyuubi Servers may process the request and try to insert to metastore DB at the same time, but only the first insertion success, others will fail w/ "duplicated key", Kyuubi Server needs to catch this error and return the existing batch job information instead of creating a new one. How does the user know if the returned batch job is new created or duplicated? - a new field `batchInfo: Map[String, String]` is added to the response, and for duplicated batch job, `"kyuubi.batch.duplicated": "true"` will be contained. ### _How was this patch tested?_ - [x] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [x] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4390 from pan3793/batch-id. Closes #4390 b6917babf [Cheng Pan] move constant to rest client 79ef1b5d8 [Cheng Pan] flaky test f82228506 [Cheng Pan] it 88bdfa50a [Cheng Pan] ut fd8bc222a [Cheng Pan] ut c820f5e43 [Cheng Pan] Support user provided batch id on batch job submission Authored-by: Cheng Pan Signed-off-by: Cheng Pan --- .../spark/SparkOnKubernetesTestsSuite.scala | 11 ++- .../org/apache/kyuubi/util/JdbcUtils.scala | 8 ++ .../kyuubi/client/api/v1/dto/Batch.java | 18 ++++- .../client/api/v1/dto/BatchRequest.java | 6 +- .../apache/kyuubi/client/util/BatchUtils.java | 9 +++ .../kyuubi/client/RestClientTestUtils.java | 53 ++++++------- .../server/api/v1/BatchesResource.scala | 76 ++++++++++++++----- .../server/metadata/MetadataManager.scala | 14 ++-- .../session/KyuubiBatchSessionImpl.scala | 13 ++-- .../kyuubi/WithKyuubiServerOnYarn.scala | 15 +++- .../ServerJsonLoggingEventHandlerSuite.scala | 24 +++--- .../server/api/v1/BatchesResourceSuite.scala | 34 ++++++++- .../server/rest/client/BatchCliSuite.scala | 8 +- 13 files changed, 200 insertions(+), 89 deletions(-) diff --git a/integration-tests/kyuubi-kubernetes-it/src/test/scala/org/apache/kyuubi/kubernetes/test/spark/SparkOnKubernetesTestsSuite.scala b/integration-tests/kyuubi-kubernetes-it/src/test/scala/org/apache/kyuubi/kubernetes/test/spark/SparkOnKubernetesTestsSuite.scala index 019de840d..14db8b408 100644 --- a/integration-tests/kyuubi-kubernetes-it/src/test/scala/org/apache/kyuubi/kubernetes/test/spark/SparkOnKubernetesTestsSuite.scala +++ b/integration-tests/kyuubi-kubernetes-it/src/test/scala/org/apache/kyuubi/kubernetes/test/spark/SparkOnKubernetesTestsSuite.scala @@ -17,13 +17,16 @@ package org.apache.kyuubi.kubernetes.test.spark +import java.util.UUID + import scala.collection.JavaConverters._ import scala.concurrent.duration._ import org.apache.hadoop.conf.Configuration import org.apache.hadoop.net.NetUtils -import org.apache.kyuubi.{BatchTestHelper, KyuubiException, Logging, Utils, WithKyuubiServer, WithSimpleDFSService} +import org.apache.kyuubi._ +import org.apache.kyuubi.client.util.BatchUtils._ import org.apache.kyuubi.config.KyuubiConf import org.apache.kyuubi.config.KyuubiConf.FRONTEND_THRIFT_BINARY_BIND_HOST import org.apache.kyuubi.engine.{ApplicationInfo, ApplicationOperation, KubernetesApplicationOperation} @@ -134,7 +137,8 @@ class KyuubiOperationKubernetesClusterClientModeSuite server.backendService.sessionManager.asInstanceOf[KyuubiSessionManager] test("Spark Client Mode On Kubernetes Kyuubi KubernetesApplicationOperation Suite") { - val batchRequest = newSparkBatchRequest(conf.getAll) + val batchRequest = newSparkBatchRequest(conf.getAll ++ Map( + KYUUBI_BATCH_ID_KEY -> UUID.randomUUID().toString)) val sessionHandle = sessionManager.openBatchSession( "kyuubi", @@ -193,7 +197,8 @@ class KyuubiOperationKubernetesClusterClusterModeSuite "spark.kubernetes.driver.pod.name", driverPodNamePrefix + "-" + System.currentTimeMillis()) - val batchRequest = newSparkBatchRequest(conf.getAll) + val batchRequest = newSparkBatchRequest(conf.getAll ++ Map( + KYUUBI_BATCH_ID_KEY -> UUID.randomUUID().toString)) val sessionHandle = sessionManager.openBatchSession( "runner", diff --git a/kyuubi-common/src/main/scala/org/apache/kyuubi/util/JdbcUtils.scala b/kyuubi-common/src/main/scala/org/apache/kyuubi/util/JdbcUtils.scala index df72ee339..b89580f4c 100644 --- a/kyuubi-common/src/main/scala/org/apache/kyuubi/util/JdbcUtils.scala +++ b/kyuubi-common/src/main/scala/org/apache/kyuubi/util/JdbcUtils.scala @@ -104,4 +104,12 @@ object JdbcUtils extends Logging { case _ => "(empty)" } } + + def isDuplicatedKeyDBErr(cause: Throwable): Boolean = { + val duplicatedKeyKeywords = Seq( + "duplicate key value in a unique or primary key constraint or unique index", // Derby + "Duplicate entry" // MySQL + ) + duplicatedKeyKeywords.exists(cause.getMessage.contains) + } } diff --git a/kyuubi-rest-client/src/main/java/org/apache/kyuubi/client/api/v1/dto/Batch.java b/kyuubi-rest-client/src/main/java/org/apache/kyuubi/client/api/v1/dto/Batch.java index 43fbf10af..b318b709d 100644 --- a/kyuubi-rest-client/src/main/java/org/apache/kyuubi/client/api/v1/dto/Batch.java +++ b/kyuubi-rest-client/src/main/java/org/apache/kyuubi/client/api/v1/dto/Batch.java @@ -17,6 +17,8 @@ package org.apache.kyuubi.client.api.v1.dto; +import java.util.Collections; +import java.util.Map; import java.util.Objects; import org.apache.commons.lang3.builder.ReflectionToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; @@ -35,6 +37,7 @@ public class Batch { private String state; private long createTime; private long endTime; + private Map batchInfo = Collections.emptyMap(); public Batch() {} @@ -51,7 +54,8 @@ public Batch( String kyuubiInstance, String state, long createTime, - long endTime) { + long endTime, + Map batchInfo) { this.id = id; this.user = user; this.batchType = batchType; @@ -65,6 +69,7 @@ public Batch( this.state = state; this.createTime = createTime; this.endTime = endTime; + this.batchInfo = batchInfo; } public String getId() { @@ -171,6 +176,17 @@ public void setEndTime(long endTime) { this.endTime = endTime; } + public Map getBatchInfo() { + if (batchInfo == null) { + return Collections.emptyMap(); + } + return batchInfo; + } + + public void setBatchInfo(Map batchInfo) { + this.batchInfo = batchInfo; + } + @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/kyuubi-rest-client/src/main/java/org/apache/kyuubi/client/api/v1/dto/BatchRequest.java b/kyuubi-rest-client/src/main/java/org/apache/kyuubi/client/api/v1/dto/BatchRequest.java index f10a8fdb5..f45821fc2 100644 --- a/kyuubi-rest-client/src/main/java/org/apache/kyuubi/client/api/v1/dto/BatchRequest.java +++ b/kyuubi-rest-client/src/main/java/org/apache/kyuubi/client/api/v1/dto/BatchRequest.java @@ -29,8 +29,8 @@ public class BatchRequest { private String resource; private String className; private String name; - private Map conf; - private List args; + private Map conf = Collections.emptyMap(); + private List args = Collections.emptyList(); public BatchRequest() {} @@ -54,8 +54,6 @@ public BatchRequest(String batchType, String resource, String className, String this.resource = resource; this.className = className; this.name = name; - this.conf = Collections.emptyMap(); - this.args = Collections.emptyList(); } public String getBatchType() { diff --git a/kyuubi-rest-client/src/main/java/org/apache/kyuubi/client/util/BatchUtils.java b/kyuubi-rest-client/src/main/java/org/apache/kyuubi/client/util/BatchUtils.java index 59f5967a0..f7efaad9d 100644 --- a/kyuubi-rest-client/src/main/java/org/apache/kyuubi/client/util/BatchUtils.java +++ b/kyuubi-rest-client/src/main/java/org/apache/kyuubi/client/util/BatchUtils.java @@ -20,6 +20,7 @@ import java.util.Arrays; import java.util.List; import java.util.Locale; +import org.apache.kyuubi.client.api.v1.dto.Batch; public final class BatchUtils { /** The batch has not been submitted to resource manager yet. */ @@ -40,6 +41,10 @@ public final class BatchUtils { public static List terminalBatchStates = Arrays.asList(FINISHED_STATE, ERROR_STATE, CANCELED_STATE); + public static String KYUUBI_BATCH_ID_KEY = "kyuubi.batch.id"; + + public static String KYUUBI_BATCH_DUPLICATED_KEY = "kyuubi.batch.duplicated"; + public static boolean isPendingState(String state) { return PENDING_STATE.equalsIgnoreCase(state); } @@ -55,4 +60,8 @@ public static boolean isFinishedState(String state) { public static boolean isTerminalState(String state) { return state != null && terminalBatchStates.contains(state.toUpperCase(Locale.ROOT)); } + + public static boolean isDuplicatedSubmission(Batch batch) { + return "true".equalsIgnoreCase(batch.getBatchInfo().get(KYUUBI_BATCH_DUPLICATED_KEY)); + } } diff --git a/kyuubi-rest-client/src/test/java/org/apache/kyuubi/client/RestClientTestUtils.java b/kyuubi-rest-client/src/test/java/org/apache/kyuubi/client/RestClientTestUtils.java index 82413e2a4..1ac0278bf 100644 --- a/kyuubi-rest-client/src/test/java/org/apache/kyuubi/client/RestClientTestUtils.java +++ b/kyuubi-rest-client/src/test/java/org/apache/kyuubi/client/RestClientTestUtils.java @@ -45,35 +45,31 @@ public static CloseBatchResponse generateTestCloseBatchResp() { } public static Batch generateTestBatch(String id) { - Batch batch = - new Batch( - id, - TEST_USERNAME, - "spark", - "batch_name", - 0, - id, - null, - "RUNNING", - null, - "192.168.31.130:64573", - "RUNNING", - BATCH_CREATE_TIME, - 0); - - return batch; + return new Batch( + id, + TEST_USERNAME, + "spark", + "batch_name", + 0, + id, + null, + "RUNNING", + null, + "192.168.31.130:64573", + "RUNNING", + BATCH_CREATE_TIME, + 0, + Collections.emptyMap()); } public static BatchRequest generateTestBatchRequest() { - BatchRequest batchRequest = - new BatchRequest( - "spark", - "/MySpace/kyuubi-spark-sql-engine_2.12-1.6.0-SNAPSHOT.jar", - "org.apache.kyuubi.engine.spark.SparkSQLEngine", - "test_batch", - Collections.singletonMap("spark.driver.memory", "16m"), - Collections.emptyList()); - return batchRequest; + return new BatchRequest( + "spark", + "/MySpace/kyuubi-spark-sql-engine_2.12-1.6.0-SNAPSHOT.jar", + "org.apache.kyuubi.engine.spark.SparkSQLEngine", + "test_batch", + Collections.singletonMap("spark.driver.memory", "16m"), + Collections.emptyList()); } public static GetBatchesResponse generateTestBatchesResponse() { @@ -87,9 +83,8 @@ public static GetBatchesResponse generateTestBatchesResponse() { public static OperationLog generateTestOperationLog() { List logs = Arrays.asList( - "13:15:13.523 INFO org.apache.curator.framework.state." - + "ConnectionStateManager: State change: CONNECTED", - "13:15:13.528 INFO org.apache.kyuubi." + "engine.EngineRef: Launching engine:"); + "13:15:13.523 INFO ConnectionStateManager: State change: CONNECTED", + "13:15:13.528 INFO EngineRef: Launching engine:"); return new OperationLog(logs, 2); } } diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/BatchesResource.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/BatchesResource.scala index 924e7b89c..edfc05616 100644 --- a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/BatchesResource.scala +++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/BatchesResource.scala @@ -19,13 +19,14 @@ package org.apache.kyuubi.server.api.v1 import java.io.InputStream import java.util -import java.util.{Collections, Locale} +import java.util.{Collections, Locale, UUID} import java.util.concurrent.ConcurrentHashMap import javax.ws.rs._ import javax.ws.rs.core.MediaType import javax.ws.rs.core.Response.Status import scala.collection.JavaConverters._ +import scala.util.{Failure, Success, Try} import scala.util.control.NonFatal import io.swagger.v3.oas.annotations.media.{Content, Schema} @@ -36,6 +37,7 @@ import org.glassfish.jersey.media.multipart.{FormDataContentDisposition, FormDat import org.apache.kyuubi.{Logging, Utils} import org.apache.kyuubi.client.api.v1.dto._ import org.apache.kyuubi.client.exception.KyuubiRestException +import org.apache.kyuubi.client.util.BatchUtils._ import org.apache.kyuubi.config.KyuubiConf import org.apache.kyuubi.config.KyuubiReservedKeys._ import org.apache.kyuubi.engine.{ApplicationInfo, KyuubiApplicationManager} @@ -45,6 +47,7 @@ import org.apache.kyuubi.server.api.v1.BatchesResource._ import org.apache.kyuubi.server.metadata.MetadataManager import org.apache.kyuubi.server.metadata.api.Metadata import org.apache.kyuubi.session.{KyuubiBatchSessionImpl, KyuubiSessionManager, SessionHandle} +import org.apache.kyuubi.util.JdbcUtils @Tag(name = "Batch") @Produces(Array(MediaType.APPLICATION_JSON)) @@ -105,7 +108,8 @@ private[v1] class BatchesResource extends ApiRequestContext with Logging { session.connectionUrl, batchOpStatus.state.toString, session.createTime, - batchOpStatus.completed) + batchOpStatus.completed, + Map.empty[String, String].asJava) } private def buildBatch( @@ -142,7 +146,8 @@ private[v1] class BatchesResource extends ApiRequestContext with Logging { metadata.kyuubiInstance, currentBatchState, metadata.createTime, - metadata.endTime) + metadata.endTime, + Map.empty[String, String].asJava) }.getOrElse(MetadataManager.buildBatch(metadata)) } @@ -210,22 +215,55 @@ private[v1] class BatchesResource extends ApiRequestContext with Logging { } request.setBatchType(request.getBatchType.toUpperCase(Locale.ROOT)) - val userName = fe.getSessionUser(request.getConf.asScala.toMap) - val ipAddress = fe.getIpAddress - request.setConf( - (request.getConf.asScala ++ Map( - KYUUBI_BATCH_RESOURCE_UPLOADED_KEY -> isResourceFromUpload.toString, - KYUUBI_CLIENT_IP_KEY -> ipAddress, - KYUUBI_SERVER_IP_KEY -> fe.host, - KYUUBI_SESSION_CONNECTION_URL_KEY -> fe.connectionUrl, - KYUUBI_SESSION_REAL_USER_KEY -> fe.getRealUser())).asJava) - val sessionHandle = sessionManager.openBatchSession( - userName, - "anonymous", - ipAddress, - request.getConf.asScala.toMap, - request) - buildBatch(sessionManager.getBatchSessionImpl(sessionHandle)) + val userProvidedBatchId = request.getConf.asScala.get(KYUUBI_BATCH_ID_KEY) + userProvidedBatchId.foreach { batchId => + try UUID.fromString(batchId) + catch { + case NonFatal(e) => + throw new IllegalArgumentException(s"$KYUUBI_BATCH_ID_KEY=$batchId must be an UUID", e) + } + } + + userProvidedBatchId.flatMap { batchId => + Option(sessionManager.getBatchFromMetadataStore(batchId)) + } match { + case Some(batch) => + markDuplicated(batch) + case None => + val userName = fe.getSessionUser(request.getConf.asScala.toMap) + val ipAddress = fe.getIpAddress + val batchId = userProvidedBatchId.getOrElse(UUID.randomUUID().toString) + request.setConf( + (request.getConf.asScala ++ Map( + KYUUBI_BATCH_ID_KEY -> batchId, + KYUUBI_BATCH_RESOURCE_UPLOADED_KEY -> isResourceFromUpload.toString, + KYUUBI_CLIENT_IP_KEY -> ipAddress, + KYUUBI_SERVER_IP_KEY -> fe.host, + KYUUBI_SESSION_CONNECTION_URL_KEY -> fe.connectionUrl, + KYUUBI_SESSION_REAL_USER_KEY -> fe.getRealUser())).asJava) + + Try { + sessionManager.openBatchSession( + userName, + "anonymous", + ipAddress, + request.getConf.asScala.toMap, + request) + } match { + case Success(sessionHandle) => + buildBatch(sessionManager.getBatchSessionImpl(sessionHandle)) + case Failure(cause) if JdbcUtils.isDuplicatedKeyDBErr(cause) => + val batch = sessionManager.getBatchFromMetadataStore(batchId) + assert(batch != null, s"can not find duplicated batch $batchId from metadata store") + markDuplicated(batch) + } + } + } + + private def markDuplicated(batch: Batch): Batch = { + warn(s"duplicated submission: ${batch.getId}, ignore and return the existing batch.") + batch.setBatchInfo(Map(KYUUBI_BATCH_DUPLICATED_KEY -> "true").asJava) + batch } @ApiResponse( diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/metadata/MetadataManager.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/metadata/MetadataManager.scala index 35abc1b30..88a7f4e4e 100644 --- a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/metadata/MetadataManager.scala +++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/metadata/MetadataManager.scala @@ -20,6 +20,8 @@ package org.apache.kyuubi.server.metadata import java.util.concurrent.{ConcurrentHashMap, ThreadPoolExecutor, TimeUnit} import java.util.concurrent.atomic.AtomicInteger +import scala.collection.JavaConverters._ + import org.apache.kyuubi.{KyuubiException, Logging} import org.apache.kyuubi.client.api.v1.dto.Batch import org.apache.kyuubi.config.KyuubiConf @@ -29,7 +31,7 @@ import org.apache.kyuubi.operation.OperationState import org.apache.kyuubi.server.metadata.api.{Metadata, MetadataFilter} import org.apache.kyuubi.service.AbstractService import org.apache.kyuubi.session.SessionType -import org.apache.kyuubi.util.{ClassUtils, ThreadUtils} +import org.apache.kyuubi.util.{ClassUtils, JdbcUtils, ThreadUtils} class MetadataManager extends AbstractService("MetadataManager") { import MetadataManager._ @@ -105,11 +107,8 @@ class MetadataManager extends AbstractService("MetadataManager") { } protected def unrecoverableDBErr(cause: Throwable): Boolean = { - val unrecoverableKeywords = Seq( - "duplicate key value in a unique or primary key constraint or unique index", // Derby - "Duplicate entry" // MySQL - ) - unrecoverableKeywords.exists(cause.getMessage.contains) + // cover other cases in the future + JdbcUtils.isDuplicatedKeyDBErr(cause) } def insertMetadata(metadata: Metadata, asyncRetryOnError: Boolean = true): Unit = { @@ -334,6 +333,7 @@ object MetadataManager extends Logging { batchMetadata.kyuubiInstance, batchState, batchMetadata.createTime, - batchMetadata.endTime) + batchMetadata.endTime, + Map.empty[String, String].asJava) } } diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/session/KyuubiBatchSessionImpl.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/session/KyuubiBatchSessionImpl.scala index 7864d61f3..228890a1e 100644 --- a/kyuubi-server/src/main/scala/org/apache/kyuubi/session/KyuubiBatchSessionImpl.scala +++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/session/KyuubiBatchSessionImpl.scala @@ -17,13 +17,12 @@ package org.apache.kyuubi.session -import java.util.UUID - import scala.collection.JavaConverters._ import org.apache.hive.service.rpc.thrift.TProtocolVersion import org.apache.kyuubi.client.api.v1.dto.BatchRequest +import org.apache.kyuubi.client.util.BatchUtils._ import org.apache.kyuubi.config.{KyuubiConf, KyuubiReservedKeys} import org.apache.kyuubi.engine.KyuubiApplicationManager import org.apache.kyuubi.engine.spark.SparkProcessBuilder @@ -50,9 +49,10 @@ class KyuubiBatchSessionImpl( sessionManager) { override val sessionType: SessionType = SessionType.BATCH - override val handle: SessionHandle = recoveryMetadata.map { metadata => - SessionHandle(UUID.fromString(metadata.identifier)) - }.getOrElse(SessionHandle()) + override val handle: SessionHandle = { + val batchId = recoveryMetadata.map(_.identifier).getOrElse(conf(KYUUBI_BATCH_ID_KEY)) + SessionHandle.fromUUID(batchId) + } override def createTime: Long = recoveryMetadata.map(_.createTime).getOrElse(super.createTime) @@ -105,7 +105,7 @@ class KyuubiBatchSessionImpl( } private val sessionEvent = KyuubiSessionEvent(this) - recoveryMetadata.map(metadata => sessionEvent.engineId = metadata.engineId) + recoveryMetadata.foreach(metadata => sessionEvent.engineId = metadata.engineId) EventBus.post(sessionEvent) override def getSessionEvent: Option[KyuubiSessionEvent] = { @@ -146,6 +146,7 @@ class KyuubiBatchSessionImpl( engineType = batchRequest.getBatchType, clusterManager = batchJobSubmissionOp.builder.clusterManager()) + // there is a chance that operation failed w/ duplicated key error sessionManager.insertMetadata(metaData) } diff --git a/kyuubi-server/src/test/scala/org/apache/kyuubi/WithKyuubiServerOnYarn.scala b/kyuubi-server/src/test/scala/org/apache/kyuubi/WithKyuubiServerOnYarn.scala index 27e769d29..3bc6bb1c5 100644 --- a/kyuubi-server/src/test/scala/org/apache/kyuubi/WithKyuubiServerOnYarn.scala +++ b/kyuubi-server/src/test/scala/org/apache/kyuubi/WithKyuubiServerOnYarn.scala @@ -17,9 +17,12 @@ package org.apache.kyuubi +import java.util.UUID + import scala.collection.JavaConverters._ import scala.concurrent.duration._ +import org.apache.kyuubi.client.util.BatchUtils._ import org.apache.kyuubi.config.KyuubiConf import org.apache.kyuubi.config.KyuubiConf._ import org.apache.kyuubi.config.KyuubiConf.FrontendProtocols.FrontendProtocol @@ -104,7 +107,10 @@ class KyuubiOperationYarnClusterSuite extends WithKyuubiServerOnYarn with HiveJD test("open batch session") { val batchRequest = - newSparkBatchRequest(Map("spark.master" -> "local", "spark.executor.instances" -> "1")) + newSparkBatchRequest(Map( + "spark.master" -> "local", + "spark.executor.instances" -> "1", + KYUUBI_BATCH_ID_KEY -> UUID.randomUUID().toString)) val sessionHandle = sessionManager.openBatchSession( "kyuubi", @@ -162,7 +168,9 @@ class KyuubiOperationYarnClusterSuite extends WithKyuubiServerOnYarn with HiveJD } test("prevent dead loop if the batch job submission process it not alive") { - val batchRequest = newSparkBatchRequest(Map("spark.submit.deployMode" -> "invalid")) + val batchRequest = newSparkBatchRequest(Map( + "spark.submit.deployMode" -> "invalid", + KYUUBI_BATCH_ID_KEY -> UUID.randomUUID().toString)) val sessionHandle = sessionManager.openBatchSession( "kyuubi", @@ -188,7 +196,8 @@ class KyuubiOperationYarnClusterSuite extends WithKyuubiServerOnYarn with HiveJD "spark.submit.deployMode" -> "cluster", "spark.sql.defaultCatalog=spark_catalog" -> "spark_catalog", "spark.sql.catalog.spark_catalog.type" -> "invalid_type", - "kyuubi.session.engine.initialize.timeout" -> "PT10m"))(Map.empty) { + "kyuubi.session.engine.initialize.timeout" -> "PT10M", + KYUUBI_BATCH_ID_KEY -> UUID.randomUUID().toString))(Map.empty) { val startTime = System.currentTimeMillis() val exception = intercept[Exception] { withJdbcStatement() { _ => } diff --git a/kyuubi-server/src/test/scala/org/apache/kyuubi/events/handler/ServerJsonLoggingEventHandlerSuite.scala b/kyuubi-server/src/test/scala/org/apache/kyuubi/events/handler/ServerJsonLoggingEventHandlerSuite.scala index df8ea1083..3bdc9cd38 100644 --- a/kyuubi-server/src/test/scala/org/apache/kyuubi/events/handler/ServerJsonLoggingEventHandlerSuite.scala +++ b/kyuubi-server/src/test/scala/org/apache/kyuubi/events/handler/ServerJsonLoggingEventHandlerSuite.scala @@ -28,8 +28,10 @@ import com.fasterxml.jackson.databind.ObjectMapper import org.apache.hadoop.conf.Configuration import org.apache.hadoop.fs.{FileSystem, Path} import org.apache.hive.service.rpc.thrift.{TOpenSessionReq, TStatusCode} +import org.scalatest.time.SpanSugar.convertIntToGrainOfTime import org.apache.kyuubi._ +import org.apache.kyuubi.client.util.BatchUtils._ import org.apache.kyuubi.config.KyuubiConf import org.apache.kyuubi.operation.HiveJDBCTestHelper import org.apache.kyuubi.operation.OperationState._ @@ -138,7 +140,7 @@ class ServerJsonLoggingEventHandlerSuite extends WithKyuubiServer with HiveJDBCT Utils.currentUser, "kyuubi", "127.0.0.1", - Map.empty, + Map(KYUUBI_BATCH_ID_KEY -> UUID.randomUUID().toString), batchRequest) withSessionConf()(Map.empty)(Map("spark.sql.shuffle.partitions" -> "2")) { withJdbcStatement() { statement => @@ -277,15 +279,17 @@ class ServerJsonLoggingEventHandlerSuite extends WithKyuubiServer with HiveJDBCT } } - val serverSessionEventPath = - Paths.get(serverLogRoot, "kyuubi_session", s"day=$currentDate") - withJdbcStatement() { statement => - val res = statement.executeQuery( - s"SELECT * FROM `json`.`$serverSessionEventPath` " + - s"where sessionName = '$name' and exception is not null limit 1") - assert(res.next()) - val exception = res.getObject("exception") - assert(exception.toString.contains("Invalid maximum heap size: -Xmxabc")) + eventually(timeout(2.minutes), interval(10.seconds)) { + val serverSessionEventPath = + Paths.get(serverLogRoot, "kyuubi_session", s"day=$currentDate") + withJdbcStatement() { statement => + val res = statement.executeQuery( + s"SELECT * FROM `json`.`$serverSessionEventPath` " + + s"where sessionName = '$name' and exception is not null limit 1") + assert(res.next()) + val exception = res.getObject("exception") + assert(exception.toString.contains("Invalid maximum heap size: -Xmxabc")) + } } } } diff --git a/kyuubi-server/src/test/scala/org/apache/kyuubi/server/api/v1/BatchesResourceSuite.scala b/kyuubi-server/src/test/scala/org/apache/kyuubi/server/api/v1/BatchesResourceSuite.scala index ce05cbd6b..055496ff3 100644 --- a/kyuubi-server/src/test/scala/org/apache/kyuubi/server/api/v1/BatchesResourceSuite.scala +++ b/kyuubi-server/src/test/scala/org/apache/kyuubi/server/api/v1/BatchesResourceSuite.scala @@ -33,6 +33,8 @@ import org.glassfish.jersey.media.multipart.file.FileDataBodyPart import org.apache.kyuubi.{BatchTestHelper, KyuubiFunSuite, RestFrontendTestHelper} import org.apache.kyuubi.client.api.v1.dto._ +import org.apache.kyuubi.client.util.BatchUtils +import org.apache.kyuubi.client.util.BatchUtils._ import org.apache.kyuubi.config.KyuubiConf import org.apache.kyuubi.config.KyuubiConf._ import org.apache.kyuubi.engine.{ApplicationInfo, KyuubiApplicationManager} @@ -226,6 +228,30 @@ class BatchesResourceSuite extends KyuubiFunSuite with RestFrontendTestHelper wi } } + test("open batch session w/ batch id") { + val batchId = UUID.randomUUID().toString + val reqObj = newSparkBatchRequest(Map( + "spark.master" -> "local", + KYUUBI_BATCH_ID_KEY -> batchId)) + + val resp1 = webTarget.path("api/v1/batches") + .request(MediaType.APPLICATION_JSON_TYPE) + .post(Entity.entity(reqObj, MediaType.APPLICATION_JSON_TYPE)) + assert(200 == resp1.getStatus) + val batch1 = resp1.readEntity(classOf[Batch]) + assert(batch1.getId === batchId) + + val resp2 = webTarget.path("api/v1/batches") + .request(MediaType.APPLICATION_JSON_TYPE) + .post(Entity.entity(reqObj, MediaType.APPLICATION_JSON_TYPE)) + assert(200 == resp2.getStatus) + val batch2 = resp2.readEntity(classOf[Batch]) + assert(batch2.getId === batchId) + + assert(batch1.getCreateTime === batch2.getCreateTime) + assert(BatchUtils.isDuplicatedSubmission(batch2)) + } + test("get batch session list") { val sessionManager = server.frontendServices.head .be.sessionManager.asInstanceOf[KyuubiSessionManager] @@ -250,7 +276,7 @@ class BatchesResourceSuite extends KyuubiFunSuite with RestFrontendTestHelper wi "kyuubi", "kyuubi", InetAddress.getLocalHost.getCanonicalHostName, - Map.empty, + Map(KYUUBI_BATCH_ID_KEY -> UUID.randomUUID().toString), newBatchRequest( "spark", sparkBatchTestResource.get, @@ -272,7 +298,7 @@ class BatchesResourceSuite extends KyuubiFunSuite with RestFrontendTestHelper wi "kyuubi", "kyuubi", InetAddress.getLocalHost.getCanonicalHostName, - Map.empty, + Map(KYUUBI_BATCH_ID_KEY -> UUID.randomUUID().toString), newBatchRequest( "spark", sparkBatchTestResource.get, @@ -282,7 +308,7 @@ class BatchesResourceSuite extends KyuubiFunSuite with RestFrontendTestHelper wi "kyuubi", "kyuubi", InetAddress.getLocalHost.getCanonicalHostName, - Map.empty, + Map(KYUUBI_BATCH_ID_KEY -> UUID.randomUUID().toString), newBatchRequest( "spark", sparkBatchTestResource.get, @@ -672,7 +698,7 @@ class BatchesResourceSuite extends KyuubiFunSuite with RestFrontendTestHelper wi "kyuubi", "kyuubi", InetAddress.getLocalHost.getCanonicalHostName, - Map.empty, + Map(KYUUBI_BATCH_ID_KEY -> UUID.randomUUID().toString), newSparkBatchRequest(Map("spark.jars" -> "disAllowPath"))) } val sessionHandleRegex = "\\[[\\S]*\\]".r diff --git a/kyuubi-server/src/test/scala/org/apache/kyuubi/server/rest/client/BatchCliSuite.scala b/kyuubi-server/src/test/scala/org/apache/kyuubi/server/rest/client/BatchCliSuite.scala index 9d0a9b15a..ff807ef02 100644 --- a/kyuubi-server/src/test/scala/org/apache/kyuubi/server/rest/client/BatchCliSuite.scala +++ b/kyuubi-server/src/test/scala/org/apache/kyuubi/server/rest/client/BatchCliSuite.scala @@ -21,6 +21,7 @@ import java.io.File import java.net.InetAddress import java.nio.charset.StandardCharsets import java.nio.file.{Files, Paths} +import java.util.UUID import org.apache.hadoop.security.UserGroupInformation import org.apache.hadoop.shaded.com.nimbusds.jose.util.StandardCharset @@ -28,6 +29,7 @@ import org.apache.hive.service.rpc.thrift.TProtocolVersion import org.scalatest.time.SpanSugar.convertIntToGrainOfTime import org.apache.kyuubi.{BatchTestHelper, RestClientTestHelper, Utils} +import org.apache.kyuubi.client.util.BatchUtils._ import org.apache.kyuubi.config.KyuubiConf import org.apache.kyuubi.ctl.{CtlConf, TestPrematureExit} import org.apache.kyuubi.metrics.{MetricsConstants, MetricsSystem} @@ -256,7 +258,7 @@ class BatchCliSuite extends RestClientTestHelper with TestPrematureExit with Bat "kyuubi", "kyuubi", InetAddress.getLocalHost.getCanonicalHostName, - Map.empty, + Map(KYUUBI_BATCH_ID_KEY -> UUID.randomUUID().toString), newBatchRequest( "spark", "", @@ -278,7 +280,7 @@ class BatchCliSuite extends RestClientTestHelper with TestPrematureExit with Bat "kyuubi", "kyuubi", InetAddress.getLocalHost.getCanonicalHostName, - Map.empty, + Map(KYUUBI_BATCH_ID_KEY -> UUID.randomUUID().toString), newBatchRequest( "spark", "", @@ -288,7 +290,7 @@ class BatchCliSuite extends RestClientTestHelper with TestPrematureExit with Bat "kyuubi", "kyuubi", InetAddress.getLocalHost.getCanonicalHostName, - Map.empty, + Map(KYUUBI_BATCH_ID_KEY -> UUID.randomUUID().toString), newBatchRequest( "spark", "", From 5fab9b710ae048f165d4467d0368eb5d41ed50c1 Mon Sep 17 00:00:00 2001 From: liangbowen Date: Thu, 2 Mar 2023 18:00:19 +0800 Subject: [PATCH 130/760] [KYUUBI #4274] [FOLLOWUP] Increase maximum degree of concurrency for mvnd in CI jobs ### _Why are the changes needed?_ - concurrent execution and smart builder is shipped with `mvnd`, but the CI log shows they are not activated - increase maximum degree of concurrency and utilize `SmartBuilder` feature in `mvnd`. `-Dmvnd.minThreads` is set as fallback and it will be ignored if `--threads` or `-Dmvnd.threads` is used. #### Before: (https://github.com/apache/kyuubi/actions/runs/4276652363/jobs/7444896450#step:6:64) ``` [INFO] Build maximum degree of concurrency is 1 ``` #### After: (https://github.com/apache/kyuubi/actions/runs/4279322563/jobs/7449912132#step:8:64) ``` [INFO] Using the SmartBuilder implementation with a thread count of 4 [INFO] Build maximum degree of concurrency is 4 ``` (https://github.com/apache/kyuubi/actions/runs/4279322563/jobs/7449912132#step:8:553) ``` [INFO] Segment walltime 13 s, segment projects service time 36 s, effective/maximum degree of concurrency 2.63/4 ``` ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [x] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4420 from bowenliang123/mvnd-smart. Closes #4274 64f0cc53e [liangbowen] update 391da26d3 [liangbowen] quite in dep and style jobs c776356a5 [liangbowen] use -Dmvnd.minThreads d10bfb70c [liangbowen] use -Dmvnd.minThreads f793d2b6b [liangbowen] warn bbb929fe3 [liangbowen] quiet a5e6d4914 [liangbowen] logging warn level 3cfbaad45 [liangbowen] enable quite option and cancel redirect stderr 09442ff69 [liangbowen] update ca5f855fa [liangbowen] try to redirect stderr 0ebdd18d4 [liangbowen] set completion and print MAVEN_CLI_OPTS d3b2c9656 [liangbowen] revert explicitly setting `mvnd.noBuffering` e2eca0140 [liangbowen] revert explicitly setting `mvnd.noBuffering` dba25e327 [liangbowen] explicitly set `mvnd.noBuffering` to false prevent displaying events continuously b7583f936 [liangbowen] shell d60ebd047 [liangbowen] change mvnd cache key 2cc576ef8 [liangbowen] dep bbd6414c9 [liangbowen] move multithread setting to mvnd's MAVEN_CLI_OPTS 347b58c8c [liangbowen] increase concurrency for spotless check 15c5519e4 [liangbowen] increase concurrency Authored-by: liangbowen Signed-off-by: liangbowen --- .github/actions/setup-mvnd/action.yaml | 6 +++++- .github/workflows/dep.yml | 2 +- .github/workflows/style.yml | 6 +++--- build/mvnd | 7 ++++++- 4 files changed, 15 insertions(+), 6 deletions(-) diff --git a/.github/actions/setup-mvnd/action.yaml b/.github/actions/setup-mvnd/action.yaml index d7497e332..55c8139ff 100644 --- a/.github/actions/setup-mvnd/action.yaml +++ b/.github/actions/setup-mvnd/action.yaml @@ -17,6 +17,7 @@ name: 'setup-mvnd' description: 'Setup the maven daemon' +continue-on-error: true runs: using: composite steps: @@ -26,7 +27,10 @@ runs: path: | build/maven-mvnd-* build/apache-maven-* - key: setup-mvnd-${{ runner.os }}-mvnd + key: setup-mvnd-${{ runner.os }} + - name: Check Maven + run: build/mvn -v + shell: bash - name: Check Mvnd run: build/mvnd -v shell: bash diff --git a/.github/workflows/dep.yml b/.github/workflows/dep.yml index ebda6b47e..72f5c915d 100644 --- a/.github/workflows/dep.yml +++ b/.github/workflows/dep.yml @@ -50,7 +50,7 @@ jobs: - name: Check kyuubi modules available id: modules-check run: >- - build/mvnd dependency:resolve validate + build/mvnd dependency:resolve validate -q -DincludeGroupIds="org.apache.kyuubi" -DincludeScope="compile" -Pfast -Denforcer.skip=false -pl kyuubi-ctl,kyuubi-server,kyuubi-assembly -am diff --git a/.github/workflows/style.yml b/.github/workflows/style.yml index 9ff484824..93fce5c4e 100644 --- a/.github/workflows/style.yml +++ b/.github/workflows/style.yml @@ -54,7 +54,7 @@ jobs: cache: 'pip' - name: Check kyuubi modules available id: modules-check - run: build/mvnd dependency:resolve -DincludeGroupIds="org.apache.kyuubi" -DincludeScope="compile" -DexcludeTransitive=true ${{ matrix.profiles }} + run: build/mvnd dependency:resolve -DincludeGroupIds="org.apache.kyuubi" -DincludeScope="compile" -DexcludeTransitive=true -q ${{ matrix.profiles }} continue-on-error: true - name: Install @@ -69,7 +69,7 @@ jobs: - name: Scalastyle with maven id: scalastyle-check - run: build/mvnd scalastyle:check ${{ matrix.profiles }} + run: build/mvnd scalastyle:check -q ${{ matrix.profiles }} - name: Print scalastyle error report if: failure() && steps.scalastyle-check.outcome != 'success' run: >- @@ -83,7 +83,7 @@ jobs: run: | SPOTLESS_BLACK_VERSION=$(build/mvn help:evaluate -Dexpression=spotless.python.black.version -q -DforceStdout) pip install black==$SPOTLESS_BLACK_VERSION - build/mvnd spotless:check ${{ matrix.profiles }} -Pspotless-python + build/mvnd spotless:check -q ${{ matrix.profiles }} -Pspotless-python - name: setup npm uses: actions/setup-node@v3 with: diff --git a/build/mvnd b/build/mvnd index 493ee43ad..9af3429f3 100755 --- a/build/mvnd +++ b/build/mvnd @@ -25,7 +25,7 @@ _CALLING_DIR="$(pwd)" _COMPILE_JVM_OPTS="-Xms2g -Xmx2g -XX:ReservedCodeCacheSize=1g -Xss128m" if [ "$CI" ]; then - export MAVEN_CLI_OPTS="--no-transfer-progress --errors --fail-fast" + export MAVEN_CLI_OPTS="-Dmvnd.minThreads=8 --no-transfer-progress --errors --fail-fast -Dstyle.color=always" fi # Installs any application tarball given a URL, the expected tarball name, @@ -131,4 +131,9 @@ cd "${_CALLING_DIR}" export MAVEN_OPTS=${MAVEN_OPTS:-"$_COMPILE_JVM_OPTS"} echo "Using \`mvnd\` from path: $MVND_BIN" 1>&2 + +if [ "$MAVEN_CLI_OPTS" != "" ]; then + echo "MAVEN_CLI_OPTS=$MAVEN_CLI_OPTS" +fi + ${MVND_BIN} $MAVEN_CLI_OPTS "$@" From 355ed803b6d8b55c7b076640d48fcf01b951782f Mon Sep 17 00:00:00 2001 From: liangbowen Date: Fri, 3 Mar 2023 11:16:10 +0800 Subject: [PATCH 131/760] [KYUUBI #4437] [Authz] Fix dependencies conflict by replacing `jersey-bundle` with `jersey-client` ### _Why are the changes needed?_ - `jerysey-bundle` 1.x , which is depended by `ranger-plugins-common`, contains `javax.ws.rs.*` (like `javax.ws.rs.core.*`) packages that conflicted to `jarkarta.ws.rs-api-2.1.6.jar` in Spark 3.0+ jars - exclude `jerysey-bundle` 1.x and include `jersey-client` 1.19.4 to align with `ranger-plugins-common` Diff in Authz dependency jar list: ```diff gethostname4j-1.0.0.jar jackson-jaxrs-1.9.13.jar - jersey-bundle-1.19.3.jar + jersey-client-1.19.4.jar + jersey-core-1.19.4.jar jetty-client-9.4.50.v20221201.jar jetty-http-9.4.50.v20221201.jar jetty-io-9.4.50.v20221201.jar jetty-util-9.4.50.v20221201.jar jna-5.7.0.jar jna-platform-5.7.0.jar ranger-plugins-audit-2.3.0.jar ranger-plugins-common-2.3.0.jar ranger-plugins-cred-2.3.0.jar ``` ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [ ] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4437 from bowenliang123/authz-jerseyclient. Closes #4437 23e7eed5a [liangbowen] fixing conflicts in `javax.ws.rs.*`packages by excluding `jersey-bundle` and including `jersey-client` with `jsr311-api` excluded Authored-by: liangbowen Signed-off-by: Cheng Pan --- extensions/spark/kyuubi-spark-authz/pom.xml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/extensions/spark/kyuubi-spark-authz/pom.xml b/extensions/spark/kyuubi-spark-authz/pom.xml index 84f76e49e..8df1b9465 100644 --- a/extensions/spark/kyuubi-spark-authz/pom.xml +++ b/extensions/spark/kyuubi-spark-authz/pom.xml @@ -32,7 +32,9 @@ https://kyuubi.apache.org/ + 1.0.0 + 1.19.4 5.7.0 @@ -42,6 +44,10 @@ ranger-plugins-common ${ranger.version} + + com.sun.jersey + jersey-bundle + org.apache.ranger ranger-plugin-classloader @@ -101,6 +107,18 @@ + + com.sun.jersey + jersey-client + ${jersey.client.version} + + + javax.ws.rs + jsr311-api + + + + com.kstruct gethostname4j From b7496d2db04c42060843b7eb7aafc75c08382532 Mon Sep 17 00:00:00 2001 From: Tianlin Liao Date: Fri, 3 Mar 2023 22:09:07 +0800 Subject: [PATCH 132/760] [KYUUBI #4439] add list/close operation method for AdminResouce ### _Why are the changes needed?_ close #4439 ### _How was this patch tested?_ - [x] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [ ] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4440 from lightning-L/kyuubi-4439. Closes #4439 14d3ebd80 [Tianlin Liao] [KYUUBI #4439] add list/close operation method for AdminResouce Authored-by: Tianlin Liao Signed-off-by: fwang12 --- .../kyuubi/operation/OperationManager.scala | 4 ++ .../kyuubi/server/api/v1/AdminResource.scala | 43 +++++++++++++++++++ .../server/api/v1/AdminResourceSuite.scala | 40 +++++++++++++++++ 3 files changed, 87 insertions(+) diff --git a/kyuubi-common/src/main/scala/org/apache/kyuubi/operation/OperationManager.scala b/kyuubi-common/src/main/scala/org/apache/kyuubi/operation/OperationManager.scala index fe38263db..df45e6dee 100644 --- a/kyuubi-common/src/main/scala/org/apache/kyuubi/operation/OperationManager.scala +++ b/kyuubi-common/src/main/scala/org/apache/kyuubi/operation/OperationManager.scala @@ -17,6 +17,8 @@ package org.apache.kyuubi.operation +import scala.collection.JavaConverters._ + import org.apache.hive.service.rpc.thrift._ import org.apache.kyuubi.KyuubiSQLException @@ -41,6 +43,8 @@ abstract class OperationManager(name: String) extends AbstractService(name) { def getOperationCount: Int = handleToOperation.size() + def allOperations(): Iterable[Operation] = handleToOperation.values().asScala + override def initialize(conf: KyuubiConf): Unit = { LogDivertAppender.initialize(skipOperationLog) super.initialize(conf) diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/AdminResource.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/AdminResource.scala index 2aae7076c..ceb7179b8 100644 --- a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/AdminResource.scala +++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/AdminResource.scala @@ -33,9 +33,11 @@ import org.apache.kyuubi.{KYUUBI_VERSION, Logging, Utils} import org.apache.kyuubi.client.api.v1.dto.{Engine, SessionData} import org.apache.kyuubi.config.KyuubiConf import org.apache.kyuubi.config.KyuubiConf._ +import org.apache.kyuubi.events.KyuubiOperationEvent import org.apache.kyuubi.ha.HighAvailabilityConf.HA_NAMESPACE import org.apache.kyuubi.ha.client.{DiscoveryPaths, ServiceNodeInfo} import org.apache.kyuubi.ha.client.DiscoveryClientProvider.withDiscoveryClient +import org.apache.kyuubi.operation.{KyuubiOperation, OperationHandle} import org.apache.kyuubi.server.KyuubiServer import org.apache.kyuubi.server.api.ApiRequestContext import org.apache.kyuubi.session.SessionHandle @@ -150,6 +152,47 @@ private[v1] class AdminResource extends ApiRequestContext with Logging { Response.ok(s"Session $sessionHandleStr is closed successfully.").build() } + @ApiResponse( + responseCode = "200", + content = Array(new Content( + mediaType = MediaType.APPLICATION_JSON, + array = new ArraySchema(schema = new Schema(implementation = + classOf[KyuubiOperationEvent])))), + description = + "get the list of all active operation events") + @GET + @Path("operations") + def listOperations(): Seq[KyuubiOperationEvent] = { + val userName = fe.getSessionUser(Map.empty[String, String]) + val ipAddress = fe.getIpAddress + info(s"Received listing all of the active operations request from $userName/$ipAddress") + if (!isAdministrator(userName)) { + throw new NotAllowedException( + s"$userName is not allowed to list all the operations") + } + fe.be.sessionManager.operationManager.allOperations() + .map(operation => KyuubiOperationEvent(operation.asInstanceOf[KyuubiOperation])).toSeq + } + + @ApiResponse( + responseCode = "200", + content = Array(new Content(mediaType = MediaType.APPLICATION_JSON)), + description = "close an operation") + @DELETE + @Path("operations/{operationHandle}") + def closeOperation(@PathParam("operationHandle") operationHandleStr: String): Response = { + val userName = fe.getSessionUser(Map.empty[String, String]) + val ipAddress = fe.getIpAddress + info(s"Received close an operation request from $userName/$ipAddress") + if (!isAdministrator(userName)) { + throw new NotAllowedException( + s"$userName is not allowed to close the operation $operationHandleStr") + } + val operationHandle = OperationHandle(operationHandleStr) + fe.be.closeOperation(operationHandle) + Response.ok(s"Operation $operationHandleStr is closed successfully.").build() + } + @ApiResponse( responseCode = "200", content = Array(new Content(mediaType = MediaType.APPLICATION_JSON)), diff --git a/kyuubi-server/src/test/scala/org/apache/kyuubi/server/api/v1/AdminResourceSuite.scala b/kyuubi-server/src/test/scala/org/apache/kyuubi/server/api/v1/AdminResourceSuite.scala index 9c050ba31..8aaf6c512 100644 --- a/kyuubi-server/src/test/scala/org/apache/kyuubi/server/api/v1/AdminResourceSuite.scala +++ b/kyuubi-server/src/test/scala/org/apache/kyuubi/server/api/v1/AdminResourceSuite.scala @@ -23,6 +23,7 @@ import javax.ws.rs.core.{GenericType, MediaType} import scala.collection.JavaConverters._ +import org.apache.hive.service.rpc.thrift.TProtocolVersion.HIVE_CLI_SERVICE_PROTOCOL_V2 import org.scalatest.time.SpanSugar.convertIntToGrainOfTime import org.apache.kyuubi.{KYUUBI_VERSION, KyuubiFunSuite, RestFrontendTestHelper, Utils} @@ -32,6 +33,7 @@ import org.apache.kyuubi.config.KyuubiReservedKeys.KYUUBI_SESSION_CONNECTION_URL import org.apache.kyuubi.engine.{ApplicationState, EngineRef, KyuubiApplicationManager} import org.apache.kyuubi.engine.EngineType.SPARK_SQL import org.apache.kyuubi.engine.ShareLevel.{CONNECTION, USER} +import org.apache.kyuubi.events.KyuubiOperationEvent import org.apache.kyuubi.ha.HighAvailabilityConf import org.apache.kyuubi.ha.client.DiscoveryClientProvider.withDiscoveryClient import org.apache.kyuubi.ha.client.DiscoveryPaths @@ -167,6 +169,44 @@ class AdminResourceSuite extends KyuubiFunSuite with RestFrontendTestHelper { assert(sessions2.isEmpty) } + test("list/close operations") { + val sessionHandle = fe.be.openSession( + HIVE_CLI_SERVICE_PROTOCOL_V2, + "admin", + "123456", + "localhost", + Map("testConfig" -> "testValue")) + val operation = fe.be.getCatalogs(sessionHandle) + + val adminUser = Utils.currentUser + val encodeAuthorization = new String( + Base64.getEncoder.encode( + s"$adminUser:".getBytes()), + "UTF-8") + + // list operations + var response = webTarget.path("api/v1/admin/operations").request() + .header(AUTHORIZATION_HEADER, s"BASIC $encodeAuthorization") + .get() + assert(200 == response.getStatus) + var operations = response.readEntity(new GenericType[Seq[KyuubiOperationEvent]]() {}) + assert(operations.nonEmpty) + assert(operations.map(op => op.statementId).contains(operation.identifier.toString)) + + // close operation + response = webTarget.path(s"api/v1/admin/operations/${operation.identifier}").request() + .header(AUTHORIZATION_HEADER, s"BASIC $encodeAuthorization") + .delete() + assert(200 == response.getStatus) + + // list again + response = webTarget.path("api/v1/admin/operations").request() + .header(AUTHORIZATION_HEADER, s"BASIC $encodeAuthorization") + .get() + operations = response.readEntity(new GenericType[Seq[KyuubiOperationEvent]]() {}) + assert(!operations.map(op => op.statementId).contains(operation.identifier.toString)) + } + test("delete engine - user share level") { val id = UUID.randomUUID().toString conf.set(KyuubiConf.ENGINE_SHARE_LEVEL, USER.toString) From 3d65f2711faa5dc9173130557e2d33adab04b5c7 Mon Sep 17 00:00:00 2001 From: fwang12 Date: Sat, 4 Mar 2023 18:41:34 +0800 Subject: [PATCH 133/760] [KYUUBI #4443] Do not set engine session init sql for alive probe session ### _Why are the changes needed?_ Prevent duplicate queries on UI. ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [x] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4443 from turboFei/alive_session_conf. Closes #4443 1ec571643 [fwang12] do not init for alive probe Authored-by: fwang12 Signed-off-by: fwang12 --- .../org/apache/kyuubi/client/KyuubiSyncThriftClient.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/client/KyuubiSyncThriftClient.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/client/KyuubiSyncThriftClient.scala index e3e66960b..12a4c824c 100644 --- a/kyuubi-server/src/main/scala/org/apache/kyuubi/client/KyuubiSyncThriftClient.scala +++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/client/KyuubiSyncThriftClient.scala @@ -186,7 +186,8 @@ class KyuubiSyncThriftClient private ( Utils.tryLogNonFatalError { req.setConfiguration((configs ++ Map( KyuubiConf.SESSION_NAME.key -> sessionName, - KYUUBI_SESSION_HANDLE_KEY -> UUID.randomUUID().toString)).asJava) + KYUUBI_SESSION_HANDLE_KEY -> UUID.randomUUID().toString, + KyuubiConf.ENGINE_SESSION_INITIALIZE_SQL.key -> "")).asJava) val resp = aliveProbeClient.OpenSession(req) ThriftUtils.verifyTStatus(resp.getStatus) _aliveProbeSessionHandle = resp.getSessionHandle From b40fea20e99332c7e46640d53beb6103c476c7ef Mon Sep 17 00:00:00 2001 From: yeatsliao Date: Mon, 6 Mar 2023 10:38:53 +0800 Subject: [PATCH 134/760] [KYUUBI #4171] Support skip retrieving table's properties to speed up GetTables operation ### _Why are the changes needed?_ `GetTables` operation is too slow because it queries table details info one by one, but then only a table comment is used to construct a result row, which i think could be optional. This PR add an optional config which can control this operation. By default, `GetTables` operation queries all message. Otherwise, `GetTables` operation just return table identifiers. ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [x] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4444 from liaoyt/master. Closes #4171 af5e60e36 [yeatsliao] rename config 0c9985e32 [yeatsliao] add doc 5e8687cb3 [yeatsliao] Supports ignore table comment when list all tables. Authored-by: yeatsliao Signed-off-by: Cheng Pan --- docs/deployment/settings.md | 35 ++++++++++--------- .../engine/spark/operation/GetTables.scala | 15 +++++++- .../engine/spark/shim/CatalogShim_v2_4.scala | 3 +- .../engine/spark/shim/CatalogShim_v3_0.scala | 10 +++--- .../engine/spark/shim/SparkCatalogShim.scala | 3 +- .../org/apache/kyuubi/config/KyuubiConf.scala | 7 ++++ 6 files changed, 49 insertions(+), 24 deletions(-) diff --git a/docs/deployment/settings.md b/docs/deployment/settings.md index cd8f5b770..1ba684f27 100644 --- a/docs/deployment/settings.md +++ b/docs/deployment/settings.md @@ -329,23 +329,24 @@ You can configure the Kyuubi properties in `$KYUUBI_HOME/conf/kyuubi-defaults.co ### Operation -| Key | Default | Meaning | Type | Since | -|-------------------------------------------------|---------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------|-------| -| kyuubi.operation.idle.timeout | PT3H | Operation will be closed when it's not accessed for this duration of time | duration | 1.0.0 | -| kyuubi.operation.interrupt.on.cancel | true | When true, all running tasks will be interrupted if one cancels a query. When false, all running tasks will remain until finished. | boolean | 1.2.0 | -| kyuubi.operation.language | SQL | Choose a programing language for the following inputs
      • SQL: (Default) Run all following statements as SQL queries.
      • SCALA: Run all following input as scala codes
      • PYTHON: (Experimental) Run all following input as Python codes with Spark engine
      | string | 1.5.0 | -| kyuubi.operation.log.dir.root | server_operation_logs | Root directory for query operation log at server-side. | string | 1.4.0 | -| kyuubi.operation.plan.only.excludes | ResetCommand,SetCommand,SetNamespaceCommand,UseStatement,SetCatalogAndNamespace | Comma-separated list of query plan names, in the form of simple class names, i.e, for `SET abc=xyz`, the value will be `SetCommand`. For those auxiliary plans, such as `switch databases`, `set properties`, or `create temporary view` etc., which are used for setup evaluating environments for analyzing actual queries, we can use this config to exclude them and let them take effect. See also kyuubi.operation.plan.only.mode. | seq | 1.5.0 | -| kyuubi.operation.plan.only.mode | none | Configures the statement performed mode, The value can be 'parse', 'analyze', 'optimize', 'optimize_with_stats', 'physical', 'execution', or 'none', when it is 'none', indicate to the statement will be fully executed, otherwise only way without executing the query. different engines currently support different modes, the Spark engine supports all modes, and the Flink engine supports 'parse', 'physical', and 'execution', other engines do not support planOnly currently. | string | 1.4.0 | -| kyuubi.operation.plan.only.output.style | plain | Configures the planOnly output style. The value can be 'plain' or 'json', and the default value is 'plain'. This configuration supports only the output styles of the Spark engine | string | 1.7.0 | -| kyuubi.operation.progress.enabled | false | Whether to enable the operation progress. When true, the operation progress will be returned in `GetOperationStatus`. | boolean | 1.6.0 | -| kyuubi.operation.query.timeout | <undefined> | Timeout for query executions at server-side, take effect with client-side timeout(`java.sql.Statement.setQueryTimeout`) together, a running query will be cancelled automatically if timeout. It's off by default, which means only client-side take full control of whether the query should timeout or not. If set, client-side timeout is capped at this point. To cancel the queries right away without waiting for task to finish, consider enabling kyuubi.operation.interrupt.on.cancel together. | duration | 1.2.0 | -| kyuubi.operation.result.arrow.timestampAsString | false | When true, arrow-based rowsets will convert columns of type timestamp to strings for transmission. | boolean | 1.7.0 | -| kyuubi.operation.result.format | thrift | Specify the result format, available configs are:
      • THRIFT: the result will convert to TRow at the engine driver side.
      • ARROW: the result will be encoded as Arrow at the executor side before collecting by the driver, and deserialized at the client side. note that it only takes effect for kyuubi-hive-jdbc clients now.
      | string | 1.7.0 | -| kyuubi.operation.result.max.rows | 0 | Max rows of Spark query results. Rows exceeding the limit would be ignored. By setting this value to 0 to disable the max rows limit. | int | 1.6.0 | -| kyuubi.operation.scheduler.pool | <undefined> | The scheduler pool of job. Note that, this config should be used after changing Spark config spark.scheduler.mode=FAIR. | string | 1.1.1 | -| kyuubi.operation.spark.listener.enabled | true | When set to true, Spark engine registers an SQLOperationListener before executing the statement, logging a few summary statistics when each stage completes. | boolean | 1.6.0 | -| kyuubi.operation.status.polling.timeout | PT5S | Timeout(ms) for long polling asynchronous running sql query's status | duration | 1.0.0 | +| Key | Default | Meaning | Type | Since | +|--------------------------------------------------|---------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------|-------| +| kyuubi.operation.getTables.ignoreTableProperties | false | Speed up the `GetTables` operation by returning table identities only. | boolean | 1.8.0 | +| kyuubi.operation.idle.timeout | PT3H | Operation will be closed when it's not accessed for this duration of time | duration | 1.0.0 | +| kyuubi.operation.interrupt.on.cancel | true | When true, all running tasks will be interrupted if one cancels a query. When false, all running tasks will remain until finished. | boolean | 1.2.0 | +| kyuubi.operation.language | SQL | Choose a programing language for the following inputs
      • SQL: (Default) Run all following statements as SQL queries.
      • SCALA: Run all following input as scala codes
      • PYTHON: (Experimental) Run all following input as Python codes with Spark engine
      | string | 1.5.0 | +| kyuubi.operation.log.dir.root | server_operation_logs | Root directory for query operation log at server-side. | string | 1.4.0 | +| kyuubi.operation.plan.only.excludes | ResetCommand,SetCommand,SetNamespaceCommand,UseStatement,SetCatalogAndNamespace | Comma-separated list of query plan names, in the form of simple class names, i.e, for `SET abc=xyz`, the value will be `SetCommand`. For those auxiliary plans, such as `switch databases`, `set properties`, or `create temporary view` etc., which are used for setup evaluating environments for analyzing actual queries, we can use this config to exclude them and let them take effect. See also kyuubi.operation.plan.only.mode. | seq | 1.5.0 | +| kyuubi.operation.plan.only.mode | none | Configures the statement performed mode, The value can be 'parse', 'analyze', 'optimize', 'optimize_with_stats', 'physical', 'execution', or 'none', when it is 'none', indicate to the statement will be fully executed, otherwise only way without executing the query. different engines currently support different modes, the Spark engine supports all modes, and the Flink engine supports 'parse', 'physical', and 'execution', other engines do not support planOnly currently. | string | 1.4.0 | +| kyuubi.operation.plan.only.output.style | plain | Configures the planOnly output style. The value can be 'plain' or 'json', and the default value is 'plain'. This configuration supports only the output styles of the Spark engine | string | 1.7.0 | +| kyuubi.operation.progress.enabled | false | Whether to enable the operation progress. When true, the operation progress will be returned in `GetOperationStatus`. | boolean | 1.6.0 | +| kyuubi.operation.query.timeout | <undefined> | Timeout for query executions at server-side, take effect with client-side timeout(`java.sql.Statement.setQueryTimeout`) together, a running query will be cancelled automatically if timeout. It's off by default, which means only client-side take full control of whether the query should timeout or not. If set, client-side timeout is capped at this point. To cancel the queries right away without waiting for task to finish, consider enabling kyuubi.operation.interrupt.on.cancel together. | duration | 1.2.0 | +| kyuubi.operation.result.arrow.timestampAsString | false | When true, arrow-based rowsets will convert columns of type timestamp to strings for transmission. | boolean | 1.7.0 | +| kyuubi.operation.result.format | thrift | Specify the result format, available configs are:
      • THRIFT: the result will convert to TRow at the engine driver side.
      • ARROW: the result will be encoded as Arrow at the executor side before collecting by the driver, and deserialized at the client side. note that it only takes effect for kyuubi-hive-jdbc clients now.
      | string | 1.7.0 | +| kyuubi.operation.result.max.rows | 0 | Max rows of Spark query results. Rows exceeding the limit would be ignored. By setting this value to 0 to disable the max rows limit. | int | 1.6.0 | +| kyuubi.operation.scheduler.pool | <undefined> | The scheduler pool of job. Note that, this config should be used after changing Spark config spark.scheduler.mode=FAIR. | string | 1.1.1 | +| kyuubi.operation.spark.listener.enabled | true | When set to true, Spark engine registers an SQLOperationListener before executing the statement, logging a few summary statistics when each stage completes. | boolean | 1.6.0 | +| kyuubi.operation.status.polling.timeout | PT5S | Timeout(ms) for long polling asynchronous running sql query's status | duration | 1.0.0 | ### Server diff --git a/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/operation/GetTables.scala b/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/operation/GetTables.scala index 4093c61c1..40642b825 100644 --- a/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/operation/GetTables.scala +++ b/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/operation/GetTables.scala @@ -19,6 +19,7 @@ package org.apache.kyuubi.engine.spark.operation import org.apache.spark.sql.types.StructType +import org.apache.kyuubi.config.KyuubiConf.OPERATION_GET_TABLES_IGNORE_TABLE_PROPERTIES import org.apache.kyuubi.engine.spark.shim.SparkCatalogShim import org.apache.kyuubi.operation.IterableFetchIterator import org.apache.kyuubi.operation.meta.ResultSetSchemaConstant._ @@ -32,6 +33,12 @@ class GetTables( tableTypes: Set[String]) extends SparkOperation(session) { + protected val ignoreTableProperties = + spark.conf.getOption(OPERATION_GET_TABLES_IGNORE_TABLE_PROPERTIES.key) match { + case Some(s) => s.toBoolean + case _ => session.sessionManager.getConf.get(OPERATION_GET_TABLES_IGNORE_TABLE_PROPERTIES) + } + override def statement: String = { super.statement + s" [catalog: $catalog," + @@ -68,7 +75,13 @@ class GetTables( val tablePattern = toJavaRegex(tableName) val sparkShim = SparkCatalogShim() val catalogTablesAndViews = - sparkShim.getCatalogTablesOrViews(spark, catalog, schemaPattern, tablePattern, tableTypes) + sparkShim.getCatalogTablesOrViews( + spark, + catalog, + schemaPattern, + tablePattern, + tableTypes, + ignoreTableProperties) val allTableAndViews = if (tableTypes.exists("VIEW".equalsIgnoreCase)) { diff --git a/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/shim/CatalogShim_v2_4.scala b/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/shim/CatalogShim_v2_4.scala index 0f6195acf..ea72dd156 100644 --- a/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/shim/CatalogShim_v2_4.scala +++ b/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/shim/CatalogShim_v2_4.scala @@ -64,7 +64,8 @@ class CatalogShim_v2_4 extends SparkCatalogShim { catalogName: String, schemaPattern: String, tablePattern: String, - tableTypes: Set[String]): Seq[Row] = { + tableTypes: Set[String], + ignoreTableProperties: Boolean): Seq[Row] = { val catalog = spark.sessionState.catalog val databases = catalog.listDatabases(schemaPattern) diff --git a/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/shim/CatalogShim_v3_0.scala b/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/shim/CatalogShim_v3_0.scala index a663ba636..27c524f30 100644 --- a/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/shim/CatalogShim_v3_0.scala +++ b/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/shim/CatalogShim_v3_0.scala @@ -150,7 +150,8 @@ class CatalogShim_v3_0 extends CatalogShim_v2_4 { catalogName: String, schemaPattern: String, tablePattern: String, - tableTypes: Set[String]): Seq[Row] = { + tableTypes: Set[String], + ignoreTableProperties: Boolean = false): Seq[Row] = { val catalog = getCatalog(spark, catalogName) val namespaces = listNamespacesWithPattern(catalog, schemaPattern) catalog match { @@ -160,16 +161,17 @@ class CatalogShim_v3_0 extends CatalogShim_v2_4 { SESSION_CATALOG, schemaPattern, tablePattern, - tableTypes) + tableTypes, + ignoreTableProperties) case tc: TableCatalog => val tp = tablePattern.r.pattern val identifiers = namespaces.flatMap { ns => tc.listTables(ns).filter(i => tp.matcher(quoteIfNeeded(i.name())).matches()) } identifiers.map { ident => - val table = tc.loadTable(ident) // TODO: restore view type for session catalog - val comment = table.properties().getOrDefault(TableCatalog.PROP_COMMENT, "") + val comment = if (ignoreTableProperties) "" + else tc.loadTable(ident).properties().getOrDefault(TableCatalog.PROP_COMMENT, "") val schema = ident.namespace().map(quoteIfNeeded).mkString(".") val tableName = quoteIfNeeded(ident.name()) Row(catalog.name(), schema, tableName, "TABLE", comment, null, null, null, null, null) diff --git a/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/shim/SparkCatalogShim.scala b/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/shim/SparkCatalogShim.scala index bc5792823..83c806523 100644 --- a/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/shim/SparkCatalogShim.scala +++ b/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/shim/SparkCatalogShim.scala @@ -69,7 +69,8 @@ trait SparkCatalogShim extends Logging { catalogName: String, schemaPattern: String, tablePattern: String, - tableTypes: Set[String]): Seq[Row] + tableTypes: Set[String], + ignoreTableProperties: Boolean): Seq[Row] def getTempViews( spark: SparkSession, diff --git a/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala b/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala index f61cfeaa7..c836b0165 100644 --- a/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala +++ b/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala @@ -2713,4 +2713,11 @@ object KyuubiConf { .version("1.7.0") .timeConf .createWithDefault(Duration.ofSeconds(60).toMillis) + + val OPERATION_GET_TABLES_IGNORE_TABLE_PROPERTIES: ConfigEntry[Boolean] = + buildConf("kyuubi.operation.getTables.ignoreTableProperties") + .doc("Speed up the `GetTables` operation by returning table identities only.") + .version("1.8.0") + .booleanConf + .createWithDefault(false) } From 489d8a3c664d48a253162fd63a67d502c49473be Mon Sep 17 00:00:00 2001 From: liangbowen Date: Mon, 6 Mar 2023 11:20:16 +0800 Subject: [PATCH 135/760] [KYUUBI #4447] Bump log4j from 2.19.0 to 2.20.0 ### _Why are the changes needed?_ - log4j 2.20.0 release notes: https://logging.apache.org/log4j/2.x/release-notes/2.20.0.html ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [x] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4447 from bowenliang123/log4j-2.20.0. Closes #4447 c9bc9aa95 [liangbowen] bump log4j from 2.19.0 to 2.20.0 Authored-by: liangbowen Signed-off-by: liangbowen --- dev/dependencyList | 8 ++++---- pom.xml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/dev/dependencyList b/dev/dependencyList index 7932c5cdf..e2e3e7ecf 100644 --- a/dev/dependencyList +++ b/dev/dependencyList @@ -127,10 +127,10 @@ kubernetes-model-scheduling/5.12.1//kubernetes-model-scheduling-5.12.1.jar kubernetes-model-storageclass/5.12.1//kubernetes-model-storageclass-5.12.1.jar libfb303/0.9.3//libfb303-0.9.3.jar libthrift/0.9.3//libthrift-0.9.3.jar -log4j-1.2-api/2.19.0//log4j-1.2-api-2.19.0.jar -log4j-api/2.19.0//log4j-api-2.19.0.jar -log4j-core/2.19.0//log4j-core-2.19.0.jar -log4j-slf4j-impl/2.19.0//log4j-slf4j-impl-2.19.0.jar +log4j-1.2-api/2.20.0//log4j-1.2-api-2.20.0.jar +log4j-api/2.20.0//log4j-api-2.20.0.jar +log4j-core/2.20.0//log4j-core-2.20.0.jar +log4j-slf4j-impl/2.20.0//log4j-slf4j-impl-2.20.0.jar logging-interceptor/3.12.12//logging-interceptor-3.12.12.jar metrics-core/4.2.8//metrics-core-4.2.8.jar metrics-jmx/4.2.8//metrics-jmx-4.2.8.jar diff --git a/pom.xml b/pom.xml index 2ebed48b3..e95b4904e 100644 --- a/pom.xml +++ b/pom.xml @@ -170,7 +170,7 @@ 5.12.1 1.15.0 6.0.5 - 2.19.0 + 2.20.0 8.0.32 4.1.87.Final 1.10.1 From cc4ec5a5d5c9ec5e9699acf8d6c0e195e439b7b6 Mon Sep 17 00:00:00 2001 From: liangbowen Date: Mon, 6 Mar 2023 13:05:46 +0800 Subject: [PATCH 136/760] [KYUUBI #4448] Bump Netty from 4.1.87.Final to 4.1.89.Final ### _Why are the changes needed?_ - Netty 4.1.89.Final: https://netty.io/news/2023/02/13/4-1-89-Final.html - Netty 4.1.88.Final: https://netty.io/news/2023/02/12/4-1-88-Final.html ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [x] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4448 from bowenliang123/netty-4.1.89. Closes #4448 8854bb3ea [liangbowen] bump netty to 4.1.89.Final Authored-by: liangbowen Signed-off-by: liangbowen --- dev/dependencyList | 34 +++++++++++++++++----------------- pom.xml | 2 +- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/dev/dependencyList b/dev/dependencyList index e2e3e7ecf..ff2550804 100644 --- a/dev/dependencyList +++ b/dev/dependencyList @@ -137,23 +137,23 @@ metrics-jmx/4.2.8//metrics-jmx-4.2.8.jar metrics-json/4.2.8//metrics-json-4.2.8.jar metrics-jvm/4.2.8//metrics-jvm-4.2.8.jar mimepull/1.9.15//mimepull-1.9.15.jar -netty-all/4.1.87.Final//netty-all-4.1.87.Final.jar -netty-buffer/4.1.87.Final//netty-buffer-4.1.87.Final.jar -netty-codec-dns/4.1.87.Final//netty-codec-dns-4.1.87.Final.jar -netty-codec-http/4.1.87.Final//netty-codec-http-4.1.87.Final.jar -netty-codec-http2/4.1.87.Final//netty-codec-http2-4.1.87.Final.jar -netty-codec-socks/4.1.87.Final//netty-codec-socks-4.1.87.Final.jar -netty-codec/4.1.87.Final//netty-codec-4.1.87.Final.jar -netty-common/4.1.87.Final//netty-common-4.1.87.Final.jar -netty-handler-proxy/4.1.87.Final//netty-handler-proxy-4.1.87.Final.jar -netty-handler/4.1.87.Final//netty-handler-4.1.87.Final.jar -netty-resolver-dns/4.1.87.Final//netty-resolver-dns-4.1.87.Final.jar -netty-resolver/4.1.87.Final//netty-resolver-4.1.87.Final.jar -netty-transport-classes-epoll/4.1.87.Final//netty-transport-classes-epoll-4.1.87.Final.jar -netty-transport-native-epoll/4.1.87.Final/linux-aarch_64/netty-transport-native-epoll-4.1.87.Final-linux-aarch_64.jar -netty-transport-native-epoll/4.1.87.Final/linux-x86_64/netty-transport-native-epoll-4.1.87.Final-linux-x86_64.jar -netty-transport-native-unix-common/4.1.87.Final//netty-transport-native-unix-common-4.1.87.Final.jar -netty-transport/4.1.87.Final//netty-transport-4.1.87.Final.jar +netty-all/4.1.89.Final//netty-all-4.1.89.Final.jar +netty-buffer/4.1.89.Final//netty-buffer-4.1.89.Final.jar +netty-codec-dns/4.1.89.Final//netty-codec-dns-4.1.89.Final.jar +netty-codec-http/4.1.89.Final//netty-codec-http-4.1.89.Final.jar +netty-codec-http2/4.1.89.Final//netty-codec-http2-4.1.89.Final.jar +netty-codec-socks/4.1.89.Final//netty-codec-socks-4.1.89.Final.jar +netty-codec/4.1.89.Final//netty-codec-4.1.89.Final.jar +netty-common/4.1.89.Final//netty-common-4.1.89.Final.jar +netty-handler-proxy/4.1.89.Final//netty-handler-proxy-4.1.89.Final.jar +netty-handler/4.1.89.Final//netty-handler-4.1.89.Final.jar +netty-resolver-dns/4.1.89.Final//netty-resolver-dns-4.1.89.Final.jar +netty-resolver/4.1.89.Final//netty-resolver-4.1.89.Final.jar +netty-transport-classes-epoll/4.1.89.Final//netty-transport-classes-epoll-4.1.89.Final.jar +netty-transport-native-epoll/4.1.89.Final/linux-aarch_64/netty-transport-native-epoll-4.1.89.Final-linux-aarch_64.jar +netty-transport-native-epoll/4.1.89.Final/linux-x86_64/netty-transport-native-epoll-4.1.89.Final-linux-x86_64.jar +netty-transport-native-unix-common/4.1.89.Final//netty-transport-native-unix-common-4.1.89.Final.jar +netty-transport/4.1.89.Final//netty-transport-4.1.89.Final.jar okhttp-urlconnection/3.14.9//okhttp-urlconnection-3.14.9.jar okhttp/3.12.12//okhttp-3.12.12.jar okio/1.15.0//okio-1.15.0.jar diff --git a/pom.xml b/pom.xml index e95b4904e..ed5eb48d6 100644 --- a/pom.xml +++ b/pom.xml @@ -172,7 +172,7 @@ 6.0.5 2.20.0 8.0.32 - 4.1.87.Final + 4.1.89.Final 1.10.1 6.0.0 0.16.0 From dee07f0c21c20354cc0667941dda5a007a6ac03b Mon Sep 17 00:00:00 2001 From: yuan Date: Tue, 7 Mar 2023 10:32:27 +0800 Subject: [PATCH 137/760] [KYUUBI #4078] [FOLLOWUP] fix shellcheck violations in scripts of /bin folder MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### _Why are the changes needed?_ - fix shellcheck violations in scripts of /bin folder - enable shellcheck rule checks ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [ ] [Run test](https://kyuubi.apache.org/docs/latest/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4162 from davidyuan1223/master. Closes #4078 c48ad38c7 [yuanfuyuan] remove the used blank lines 55a0a43c5 [xiaoyuandajian] Merge pull request #10 from xiaoyuandajian/fix-#4057 cb1193576 [yuan] Merge remote-tracking branch 'origin/fix-#4057' into fix-#4057 86e4e1ce0 [yuan] fix-#4057 info: modify the shellcheck errors file in ./bin 1. "$@" is a array, we want use string to compare. so update "$@" => "$*" 2. `tty` mean execute the command, we can use $(tty) replace it 3. param $# is a number, compare number should use -gt/-lt,not >/< 4. not sure the /bin/kyuubi line 63 'exit -1' need modify? so the directory bin only have a shellcheck note in /bin/kyuubi dd39efdeb [袁福元] fix-#4057 info: 1. "$@" is a array, we want use string to compare. so update "$@" => "$*" 2. `tty` mean execute the command, we can use $(tty) replace it 3. param $# is a number, compare number should use -gt/-lt,not >/< Lead-authored-by: yuan Co-authored-by: 袁福元 Co-authored-by: xiaoyuandajian <51512358+xiaoyuandajian@users.noreply.github.com> Co-authored-by: yuanfuyuan <1406957364@qq.com> Signed-off-by: liangbowen --- bin/docker-image-tool.sh | 3 +-- bin/kyuubi | 3 +-- bin/kyuubi-logo | 7 ++----- bin/stop-application.sh | 3 +-- 4 files changed, 5 insertions(+), 11 deletions(-) diff --git a/bin/docker-image-tool.sh b/bin/docker-image-tool.sh index f3efc8bf5..14d5fe7b0 100755 --- a/bin/docker-image-tool.sh +++ b/bin/docker-image-tool.sh @@ -200,8 +200,7 @@ Examples: EOF } -# shellcheck disable=SC2199 -if [[ "$@" = *--help ]] || [[ "$@" = *-h ]]; then +if [[ "$*" = *--help ]] || [[ "$*" = *-h ]]; then usage exit 0 fi diff --git a/bin/kyuubi b/bin/kyuubi index 09c8e9373..9bcca2c46 100755 --- a/bin/kyuubi +++ b/bin/kyuubi @@ -30,8 +30,7 @@ function usage() { echo " -h | --help - Show this help message" } -# shellcheck disable=SC2199 -if [[ "$@" = *--help ]] || [[ "$@" = *-h ]]; then +if [[ "$*" = *--help ]] || [[ "$*" = *-h ]]; then usage exit 0 fi diff --git a/bin/kyuubi-logo b/bin/kyuubi-logo index 15a45a4bb..1f95ca02e 100755 --- a/bin/kyuubi-logo +++ b/bin/kyuubi-logo @@ -15,18 +15,15 @@ # See the License for the specific language governing permissions and # limitations under the License. # - # Bugzilla 37848: When no TTY is available, don't output to console have_tty=0 -# shellcheck disable=SC2006 -if [[ "`tty`" != "not a tty" ]]; then +if [[ "$(tty)" != "not a tty" ]]; then have_tty=1 fi # Bugzilla 37848: When no TTY is available, don't output to console have_tty=0 -# shellcheck disable=SC2006 -if [[ "`tty`" != "not a tty" ]]; then +if [[ "$(tty)" != "not a tty" ]]; then have_tty=1 fi diff --git a/bin/stop-application.sh b/bin/stop-application.sh index b208ab505..000eb4cdd 100755 --- a/bin/stop-application.sh +++ b/bin/stop-application.sh @@ -16,8 +16,7 @@ # limitations under the License. # -# shellcheck disable=SC2071 -if [[ $# < 1 ]] ; then +if [[ $# -lt 1 ]] ; then echo "USAGE: $0 " exit 1 fi From a4a16ebda25c48df2d3f55717bb92255f67dce0c Mon Sep 17 00:00:00 2001 From: edddddy Date: Tue, 7 Mar 2023 11:49:36 +0800 Subject: [PATCH 138/760] [KYUUBI #4466] Use bitnami/minio docker image ### _Why are the changes needed?_ As the latest official minio docker image has already supported amd/arm64, it seems better to use that one. ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [ ] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4466 from edddddy/official-minio. Closes #4466 d409d6a05 [edddddy] use official minio docker image Authored-by: edddddy Signed-off-by: Cheng Pan --- docker/playground/compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/playground/compose.yml b/docker/playground/compose.yml index 069624ee2..14658d894 100644 --- a/docker/playground/compose.yml +++ b/docker/playground/compose.yml @@ -17,7 +17,7 @@ services: minio: - image: alekcander/bitnami-minio-multiarch:RELEASE.2022-05-26T05-48-41Z + image: bitnami/minio:2023-debian-11 environment: MINIO_ROOT_USER: minio MINIO_ROOT_PASSWORD: minio_minio From 8af07fa47d7fca926a97267fe95b52b4eba42ea5 Mon Sep 17 00:00:00 2001 From: Yikf Date: Tue, 7 Mar 2023 13:58:43 +0800 Subject: [PATCH 139/760] [KYUUBI #4462] Fix variable usage issue in `SessionManager#stop` ### _Why are the changes needed?_ Fix variable usage issue in `SessionManager#stop` ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [ ] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4462 from Yikf/sessionmanager. Closes #4462 d4340d4ec [Yikf] fix variable usage issue Authored-by: Yikf Signed-off-by: Cheng Pan --- .../main/scala/org/apache/kyuubi/session/SessionManager.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kyuubi-common/src/main/scala/org/apache/kyuubi/session/SessionManager.scala b/kyuubi-common/src/main/scala/org/apache/kyuubi/session/SessionManager.scala index f8e77dd63..aa46b8d6f 100644 --- a/kyuubi-common/src/main/scala/org/apache/kyuubi/session/SessionManager.scala +++ b/kyuubi-common/src/main/scala/org/apache/kyuubi/session/SessionManager.scala @@ -288,9 +288,9 @@ abstract class SessionManager(name: String) extends CompositeService(name) { shutdown = true val shutdownTimeout: Long = if (isServer) { - conf.get(ENGINE_EXEC_POOL_SHUTDOWN_TIMEOUT) - } else { conf.get(SERVER_EXEC_POOL_SHUTDOWN_TIMEOUT) + } else { + conf.get(ENGINE_EXEC_POOL_SHUTDOWN_TIMEOUT) } ThreadUtils.shutdown(timeoutChecker, Duration(shutdownTimeout, TimeUnit.MILLISECONDS)) From ab52c9d4ee36a224cd02e798cb838036d093d544 Mon Sep 17 00:00:00 2001 From: zwangsheng <2213335496@qq.com> Date: Tue, 7 Mar 2023 14:00:49 +0800 Subject: [PATCH 140/760] [KYUUBI #4461] [CI][K8S]Add `Kyuubi Kubernetes IT` Module to CI style check ### _Why are the changes needed?_ Add `Kyuubi Kubernetes IT` Module to CI style check ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [ ] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request - [X] Run CI Closes #4461 from zwangsheng/ci/add_kubernetes. Closes #4461 7ebac75b0 [zwangsheng] fix style for kubernetes-it c1754d452 [zwangsheng] Add Kyuubi Kubernetes IT Module to CI style check Authored-by: zwangsheng <2213335496@qq.com> Signed-off-by: Cheng Pan --- .github/workflows/style.yml | 2 +- integration-tests/kyuubi-kubernetes-it/pom.xml | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/.github/workflows/style.yml b/.github/workflows/style.yml index 93fce5c4e..717cbbc70 100644 --- a/.github/workflows/style.yml +++ b/.github/workflows/style.yml @@ -34,7 +34,7 @@ jobs: strategy: matrix: profiles: - - '-Pflink-provided,hive-provided,spark-provided,spark-block-cleaner,spark-3.3,spark-3.2,spark-3.1,tpcds' + - '-Pflink-provided,hive-provided,spark-provided,spark-block-cleaner,spark-3.3,spark-3.2,spark-3.1,tpcds,kubernetes-it' steps: - uses: actions/checkout@v3 diff --git a/integration-tests/kyuubi-kubernetes-it/pom.xml b/integration-tests/kyuubi-kubernetes-it/pom.xml index ef56a770f..9d92a1f4b 100644 --- a/integration-tests/kyuubi-kubernetes-it/pom.xml +++ b/integration-tests/kyuubi-kubernetes-it/pom.xml @@ -15,17 +15,15 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - - + 4.0.0 org.apache.kyuubi integration-tests 1.8.0-SNAPSHOT ../pom.xml - 4.0.0 kubernetes-integration-tests_2.12 Kyuubi Test Kubernetes IT From 889dbc6bf0533ff3b599b4551d4b3ae0544203ca Mon Sep 17 00:00:00 2001 From: liangbowen Date: Tue, 7 Mar 2023 14:01:37 +0800 Subject: [PATCH 141/760] [KYUUBI #4450] Ignore unknown fields `policyPriority` when reading policy json file ### _Why are the changes needed?_ To fix #4450. - allow ignoring unknown fields in policy file for testing which brought by Ranger version changes ``` com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "policyPriority" (class org.apache.ranger.plugin.model.RangerPolicy), not marked as ignorable (21 known properties: "denyPolicyItems", "resources", "guid", "resourceSignature", "name", "policyType", "allowExceptions", "policyItems", "isAuditEnabled", "updatedBy", "service", "updateTime", "isEnabled", "version", "id", "description", "createdBy", "createTime", "denyExceptions", "dataMaskPolicyItems", "rowFilterPolicyItems"]) ``` ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [x] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4455 from bowenliang123/authz-unknown-fields. Closes #4450 592a9545d [liangbowen] ignore unknown fields in json mapper Authored-by: liangbowen Signed-off-by: Cheng Pan --- .../kyuubi/plugin/spark/authz/ranger/RangerLocalClient.scala | 2 ++ 1 file changed, 2 insertions(+) diff --git a/extensions/spark/kyuubi-spark-authz/src/test/scala/org/apache/kyuubi/plugin/spark/authz/ranger/RangerLocalClient.scala b/extensions/spark/kyuubi-spark-authz/src/test/scala/org/apache/kyuubi/plugin/spark/authz/ranger/RangerLocalClient.scala index d25ea716a..d7473a580 100644 --- a/extensions/spark/kyuubi-spark-authz/src/test/scala/org/apache/kyuubi/plugin/spark/authz/ranger/RangerLocalClient.scala +++ b/extensions/spark/kyuubi-spark-authz/src/test/scala/org/apache/kyuubi/plugin/spark/authz/ranger/RangerLocalClient.scala @@ -19,6 +19,7 @@ package org.apache.kyuubi.plugin.spark.authz.ranger import java.text.SimpleDateFormat +import com.fasterxml.jackson.databind.DeserializationFeature import com.fasterxml.jackson.databind.json.JsonMapper import org.apache.ranger.admin.client.RangerAdminRESTClient import org.apache.ranger.plugin.util.ServicePolicies @@ -27,6 +28,7 @@ class RangerLocalClient extends RangerAdminRESTClient with RangerClientHelper { private val mapper = new JsonMapper() .setDateFormat(new SimpleDateFormat("yyyyMMdd-HH:mm:ss.SSS-Z")) + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) private val policies: ServicePolicies = { val loader = Thread.currentThread().getContextClassLoader From 1bc05e5e45838cbff206db7438ba7f357bf127a7 Mon Sep 17 00:00:00 2001 From: zwangsheng <2213335496@qq.com> Date: Tue, 7 Mar 2023 14:02:26 +0800 Subject: [PATCH 142/760] [KYUUBI #4453][Improvement][K8S] Bump Kubernetes Client Version to 6.4.1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### _Why are the changes needed?_ Close #4453 kubernetes client [compare](https://github.com/fabric8io/kubernetes-client/compare/v5.12.1...v6.4.1) version | K8s 1.26.0 | K8s 1.25.3 | K8s 1.24.7 | K8s 1.23.13 | K8s 1.22.1 | K8s 1.21.1 | K8s 1.20.2 | K8s 1.19.1 | K8s 1.18.0 | K8s 1.17.0 | K8s 1.16.0 | K8s 1.15.3 | K8s 1.14.2 | K8s 1.12.0 | K8s 1.11.0 | K8s 1.10.0 | K8s 1.9.0 --|-- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- kubernetes-client 6.4.1 |   | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | - kubernetes-client 5.12.1 |   |   |   | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | - ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [ ] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request - [x] [Run ci test] Closes #4456 from zwangsheng/bump/kubernetes-client-6.4.1. Closes #4453 39039f0f9 [zwangsheng] [KYUUBI #4453] Remove dup dependence 4a5b27ce8 [zwangsheng] [KYUUBI #4453] IT test with same logic 95a292f99 [zwangsheng] [KYUUBI #4453] Fix Dependency e4bf0107e [zwangsheng] [KYUUBI #4453] Init Bump Authored-by: zwangsheng <2213335496@qq.com> Signed-off-by: Cheng Pan --- dev/dependencyList | 47 ++++++++++--------- .../kyuubi-kubernetes-it/pom.xml | 6 --- .../kyuubi/kubernetes/test/MiniKube.scala | 21 +++++++-- .../test/WithKyuubiServerOnKubernetes.scala | 4 +- kyuubi-server/pom.xml | 5 ++ .../KubernetesApplicationOperation.scala | 28 +++++------ .../apache/kyuubi/util/KubernetesUtils.scala | 7 ++- pom.xml | 12 ++++- 8 files changed, 79 insertions(+), 51 deletions(-) diff --git a/dev/dependencyList b/dev/dependencyList index ff2550804..509e28405 100644 --- a/dev/dependencyList +++ b/dev/dependencyList @@ -22,7 +22,6 @@ annotations/4.1.1.4//annotations-4.1.1.4.jar antlr-runtime/3.5.3//antlr-runtime-3.5.3.jar antlr4-runtime/4.9.3//antlr4-runtime-4.9.3.jar aopalliance-repackaged/2.6.1//aopalliance-repackaged-2.6.1.jar -automaton/1.11-8//automaton-1.11-8.jar classgraph/4.8.138//classgraph-4.8.138.jar commons-codec/1.15//commons-codec-1.15.jar commons-collections/3.2.2//commons-collections-3.2.2.jar @@ -37,7 +36,6 @@ error_prone_annotations/2.14.0//error_prone_annotations-2.14.0.jar failsafe/2.4.4//failsafe-2.4.4.jar failureaccess/1.0.1//failureaccess-1.0.1.jar fliptables/1.0.2//fliptables-1.0.2.jar -generex/1.0.2//generex-1.0.2.jar grpc-api/1.48.0//grpc-api-1.48.0.jar grpc-context/1.48.0//grpc-context-1.48.0.jar grpc-core/1.48.0//grpc-core-1.48.0.jar @@ -104,27 +102,30 @@ jetty-util-ajax/9.4.50.v20221201//jetty-util-ajax-9.4.50.v20221201.jar jetty-util/9.4.50.v20221201//jetty-util-9.4.50.v20221201.jar jline/0.9.94//jline-0.9.94.jar jul-to-slf4j/1.7.36//jul-to-slf4j-1.7.36.jar -kubernetes-client/5.12.1//kubernetes-client-5.12.1.jar -kubernetes-model-admissionregistration/5.12.1//kubernetes-model-admissionregistration-5.12.1.jar -kubernetes-model-apiextensions/5.12.1//kubernetes-model-apiextensions-5.12.1.jar -kubernetes-model-apps/5.12.1//kubernetes-model-apps-5.12.1.jar -kubernetes-model-autoscaling/5.12.1//kubernetes-model-autoscaling-5.12.1.jar -kubernetes-model-batch/5.12.1//kubernetes-model-batch-5.12.1.jar -kubernetes-model-certificates/5.12.1//kubernetes-model-certificates-5.12.1.jar -kubernetes-model-common/5.12.1//kubernetes-model-common-5.12.1.jar -kubernetes-model-coordination/5.12.1//kubernetes-model-coordination-5.12.1.jar -kubernetes-model-core/5.12.1//kubernetes-model-core-5.12.1.jar -kubernetes-model-discovery/5.12.1//kubernetes-model-discovery-5.12.1.jar -kubernetes-model-events/5.12.1//kubernetes-model-events-5.12.1.jar -kubernetes-model-extensions/5.12.1//kubernetes-model-extensions-5.12.1.jar -kubernetes-model-flowcontrol/5.12.1//kubernetes-model-flowcontrol-5.12.1.jar -kubernetes-model-metrics/5.12.1//kubernetes-model-metrics-5.12.1.jar -kubernetes-model-networking/5.12.1//kubernetes-model-networking-5.12.1.jar -kubernetes-model-node/5.12.1//kubernetes-model-node-5.12.1.jar -kubernetes-model-policy/5.12.1//kubernetes-model-policy-5.12.1.jar -kubernetes-model-rbac/5.12.1//kubernetes-model-rbac-5.12.1.jar -kubernetes-model-scheduling/5.12.1//kubernetes-model-scheduling-5.12.1.jar -kubernetes-model-storageclass/5.12.1//kubernetes-model-storageclass-5.12.1.jar +kubernetes-client-api/6.4.1//kubernetes-client-api-6.4.1.jar +kubernetes-client/6.4.1//kubernetes-client-6.4.1.jar +kubernetes-httpclient-okhttp/6.4.1//kubernetes-httpclient-okhttp-6.4.1.jar +kubernetes-model-admissionregistration/6.4.1//kubernetes-model-admissionregistration-6.4.1.jar +kubernetes-model-apiextensions/6.4.1//kubernetes-model-apiextensions-6.4.1.jar +kubernetes-model-apps/6.4.1//kubernetes-model-apps-6.4.1.jar +kubernetes-model-autoscaling/6.4.1//kubernetes-model-autoscaling-6.4.1.jar +kubernetes-model-batch/6.4.1//kubernetes-model-batch-6.4.1.jar +kubernetes-model-certificates/6.4.1//kubernetes-model-certificates-6.4.1.jar +kubernetes-model-common/6.4.1//kubernetes-model-common-6.4.1.jar +kubernetes-model-coordination/6.4.1//kubernetes-model-coordination-6.4.1.jar +kubernetes-model-core/6.4.1//kubernetes-model-core-6.4.1.jar +kubernetes-model-discovery/6.4.1//kubernetes-model-discovery-6.4.1.jar +kubernetes-model-events/6.4.1//kubernetes-model-events-6.4.1.jar +kubernetes-model-extensions/6.4.1//kubernetes-model-extensions-6.4.1.jar +kubernetes-model-flowcontrol/6.4.1//kubernetes-model-flowcontrol-6.4.1.jar +kubernetes-model-gatewayapi/6.4.1//kubernetes-model-gatewayapi-6.4.1.jar +kubernetes-model-metrics/6.4.1//kubernetes-model-metrics-6.4.1.jar +kubernetes-model-networking/6.4.1//kubernetes-model-networking-6.4.1.jar +kubernetes-model-node/6.4.1//kubernetes-model-node-6.4.1.jar +kubernetes-model-policy/6.4.1//kubernetes-model-policy-6.4.1.jar +kubernetes-model-rbac/6.4.1//kubernetes-model-rbac-6.4.1.jar +kubernetes-model-scheduling/6.4.1//kubernetes-model-scheduling-6.4.1.jar +kubernetes-model-storageclass/6.4.1//kubernetes-model-storageclass-6.4.1.jar libfb303/0.9.3//libfb303-0.9.3.jar libthrift/0.9.3//libthrift-0.9.3.jar log4j-1.2-api/2.20.0//log4j-1.2-api-2.20.0.jar diff --git a/integration-tests/kyuubi-kubernetes-it/pom.xml b/integration-tests/kyuubi-kubernetes-it/pom.xml index 9d92a1f4b..a796ccab5 100644 --- a/integration-tests/kyuubi-kubernetes-it/pom.xml +++ b/integration-tests/kyuubi-kubernetes-it/pom.xml @@ -60,12 +60,6 @@ test
      - - io.fabric8 - kubernetes-client - test - - org.apache.hadoop hadoop-client-minicluster diff --git a/integration-tests/kyuubi-kubernetes-it/src/test/scala/org/apache/kyuubi/kubernetes/test/MiniKube.scala b/integration-tests/kyuubi-kubernetes-it/src/test/scala/org/apache/kyuubi/kubernetes/test/MiniKube.scala index cd373873a..f4cd557bb 100644 --- a/integration-tests/kyuubi-kubernetes-it/src/test/scala/org/apache/kyuubi/kubernetes/test/MiniKube.scala +++ b/integration-tests/kyuubi-kubernetes-it/src/test/scala/org/apache/kyuubi/kubernetes/test/MiniKube.scala @@ -17,7 +17,11 @@ package org.apache.kyuubi.kubernetes.test -import io.fabric8.kubernetes.client.{Config, DefaultKubernetesClient} +import io.fabric8.kubernetes.client.{Config, KubernetesClient, KubernetesClientBuilder} +import io.fabric8.kubernetes.client.okhttp.OkHttpClientFactory +import okhttp3.{Dispatcher, OkHttpClient} + +import org.apache.kyuubi.util.ThreadUtils /** * This code copied from Aapache Spark @@ -44,7 +48,7 @@ object MiniKube { executeMinikube(true, "ip").head } - def getKubernetesClient: DefaultKubernetesClient = { + def getKubernetesClient: KubernetesClient = { // only the three-part version number is matched (the optional suffix like "-beta.0" is dropped) val versionArrayOpt = "\\d+\\.\\d+\\.\\d+".r .findFirstIn(minikubeVersionString.split(VERSION_PREFIX)(1)) @@ -65,7 +69,18 @@ object MiniKube { "For minikube version a three-part version number is expected (the optional " + "non-numeric suffix is intentionally dropped)") } + // https://github.com/fabric8io/kubernetes-client/issues/3547 + val dispatcher = new Dispatcher( + ThreadUtils.newDaemonCachedThreadPool("kubernetes-dispatcher")) + val factoryWithCustomDispatcher = new OkHttpClientFactory() { + override protected def additionalConfig(builder: OkHttpClient.Builder): Unit = { + builder.dispatcher(dispatcher) + } + } - new DefaultKubernetesClient(Config.autoConfigure("minikube")) + new KubernetesClientBuilder() + .withConfig(Config.autoConfigure("minikube")) + .withHttpClientFactory(factoryWithCustomDispatcher) + .build() } } diff --git a/integration-tests/kyuubi-kubernetes-it/src/test/scala/org/apache/kyuubi/kubernetes/test/WithKyuubiServerOnKubernetes.scala b/integration-tests/kyuubi-kubernetes-it/src/test/scala/org/apache/kyuubi/kubernetes/test/WithKyuubiServerOnKubernetes.scala index ed9cbce09..595fdd431 100644 --- a/integration-tests/kyuubi-kubernetes-it/src/test/scala/org/apache/kyuubi/kubernetes/test/WithKyuubiServerOnKubernetes.scala +++ b/integration-tests/kyuubi-kubernetes-it/src/test/scala/org/apache/kyuubi/kubernetes/test/WithKyuubiServerOnKubernetes.scala @@ -18,14 +18,14 @@ package org.apache.kyuubi.kubernetes.test import io.fabric8.kubernetes.api.model.Pod -import io.fabric8.kubernetes.client.DefaultKubernetesClient +import io.fabric8.kubernetes.client.KubernetesClient import org.apache.kyuubi.KyuubiFunSuite trait WithKyuubiServerOnKubernetes extends KyuubiFunSuite { protected def connectionConf: Map[String, String] = Map.empty - lazy val miniKubernetesClient: DefaultKubernetesClient = MiniKube.getKubernetesClient + lazy val miniKubernetesClient: KubernetesClient = MiniKube.getKubernetesClient lazy val kyuubiPod: Pod = miniKubernetesClient.pods().withName("kyuubi-test").get() lazy val kyuubiServerIp: String = kyuubiPod.getStatus.getPodIP lazy val miniKubeIp: String = MiniKube.getIp diff --git a/kyuubi-server/pom.xml b/kyuubi-server/pom.xml index aebce5dda..83698fa5a 100644 --- a/kyuubi-server/pom.xml +++ b/kyuubi-server/pom.xml @@ -92,6 +92,11 @@ kubernetes-client + + io.fabric8 + kubernetes-httpclient-okhttp + + org.apache.hive hive-metastore diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/engine/KubernetesApplicationOperation.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/engine/KubernetesApplicationOperation.scala index bee69b117..1dd5ff83f 100644 --- a/kyuubi-server/src/main/scala/org/apache/kyuubi/engine/KubernetesApplicationOperation.scala +++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/engine/KubernetesApplicationOperation.scala @@ -17,9 +17,10 @@ package org.apache.kyuubi.engine -import io.fabric8.kubernetes.api.model.{Pod, PodList} +import java.util + +import io.fabric8.kubernetes.api.model.Pod import io.fabric8.kubernetes.client.KubernetesClient -import io.fabric8.kubernetes.client.dsl.FilterWatchListDeletable import org.apache.kyuubi.Logging import org.apache.kyuubi.config.KyuubiConf @@ -58,17 +59,17 @@ class KubernetesApplicationOperation extends ApplicationOperation with Logging { debug(s"Deleting application info from Kubernetes cluster by $tag tag") try { // Need driver only - val operation = findDriverPodByTag(tag) - val podList = operation.list().getItems + val podList = findDriverPodByTag(tag) if (podList.size() != 0) { - toApplicationState(podList.get(0).getStatus.getPhase) match { + val targetPod = podList.get(0) + toApplicationState(targetPod.getStatus.getPhase) match { case FAILED | UNKNOWN => ( false, - s"Target Pod ${podList.get(0).getMetadata.getName} is in FAILED or UNKNOWN status") + s"Target Pod ${targetPod.getMetadata.getName} is in FAILED or UNKNOWN status") case _ => ( - operation.delete(), + !kubernetesClient.pods.withName(targetPod.getMetadata.getName).delete().isEmpty, s"Operation of deleted appId: ${podList.get(0).getMetadata.getName} is completed") } } else { @@ -88,8 +89,7 @@ class KubernetesApplicationOperation extends ApplicationOperation with Logging { if (kubernetesClient != null) { debug(s"Getting application info from Kubernetes cluster by $tag tag") try { - val operation = findDriverPodByTag(tag) - val podList = operation.list().getItems + val podList = findDriverPodByTag(tag) if (podList.size() != 0) { val pod = podList.get(0) val info = ApplicationInfo( @@ -114,14 +114,14 @@ class KubernetesApplicationOperation extends ApplicationOperation with Logging { } } - private def findDriverPodByTag(tag: String): FilterWatchListDeletable[Pod, PodList] = { - val operation = kubernetesClient.pods() - .withLabel(KubernetesApplicationOperation.LABEL_KYUUBI_UNIQUE_KEY, tag) - val size = operation.list().getItems.size() + private def findDriverPodByTag(tag: String): util.List[Pod] = { + val podList = kubernetesClient.pods() + .withLabel(KubernetesApplicationOperation.LABEL_KYUUBI_UNIQUE_KEY, tag).list().getItems + val size = podList.size() if (size != 1) { warn(s"Get Tag: ${tag} Driver Pod In Kubernetes size: ${size}, we expect 1") } - operation + podList } override def stop(): Unit = { diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/util/KubernetesUtils.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/util/KubernetesUtils.scala index 921aa04ae..0c934b51d 100644 --- a/kyuubi-server/src/main/scala/org/apache/kyuubi/util/KubernetesUtils.scala +++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/util/KubernetesUtils.scala @@ -22,7 +22,7 @@ import java.io.File import com.fasterxml.jackson.databind.ObjectMapper import com.google.common.base.Charsets import com.google.common.io.Files -import io.fabric8.kubernetes.client.{Config, ConfigBuilder, DefaultKubernetesClient, KubernetesClient} +import io.fabric8.kubernetes.client.{Config, ConfigBuilder, KubernetesClient, KubernetesClientBuilder} import io.fabric8.kubernetes.client.Config.autoConfigure import io.fabric8.kubernetes.client.okhttp.OkHttpClientFactory import okhttp3.{Dispatcher, OkHttpClient} @@ -93,7 +93,10 @@ object KubernetesUtils extends Logging { debug("Kubernetes client config: " + new ObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(config)) - Some(new DefaultKubernetesClient(factoryWithCustomDispatcher.createHttpClient(config), config)) + Some(new KubernetesClientBuilder() + .withHttpClientFactory(factoryWithCustomDispatcher) + .withConfig(config) + .build()) } implicit private class OptionConfigurableConfigBuilder(val configBuilder: ConfigBuilder) diff --git a/pom.xml b/pom.xml index ed5eb48d6..2082499dc 100644 --- a/pom.xml +++ b/pom.xml @@ -167,7 +167,7 @@ 9.4.50.v20221201 0.9.94 4.13.2 - 5.12.1 + 6.4.1 1.15.0 6.0.5 2.20.0 @@ -595,6 +595,16 @@ kubernetes-client ${kubernetes-client.version} + + + io.fabric8 + kubernetes-httpclient-okhttp + ${kubernetes-client.version} + + + 4.0.0 + + org.apache.kyuubi + kyuubi-parent + 1.8.0-SNAPSHOT + ../../pom.xml + + + kyuubi-chat-engine_2.12 + jar + Kyuubi Project Engine Chat + https://kyuubi.apache.org/ + + + + + org.apache.kyuubi + kyuubi-common_${scala.binary.version} + ${project.version} + + + + org.apache.kyuubi + kyuubi-ha_${scala.binary.version} + ${project.version} + + + + org.apache.httpcomponents + httpclient + + + + org.apache.kyuubi + kyuubi-common_${scala.binary.version} + ${project.version} + test-jar + test + + + + org.apache.kyuubi + ${hive.jdbc.artifact} + ${project.version} + test + + + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + prepare-test-jar + + test-jar + + test-compile + + + + + target/scala-${scala.binary.version}/classes + target/scala-${scala.binary.version}/test-classes + + + diff --git a/externals/kyuubi-chat-engine/src/main/scala/org/apache/kyuubi/engine/chat/ChatBackendService.scala b/externals/kyuubi-chat-engine/src/main/scala/org/apache/kyuubi/engine/chat/ChatBackendService.scala new file mode 100644 index 000000000..fdc710e2c --- /dev/null +++ b/externals/kyuubi-chat-engine/src/main/scala/org/apache/kyuubi/engine/chat/ChatBackendService.scala @@ -0,0 +1,28 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.kyuubi.engine.chat + +import org.apache.kyuubi.engine.chat.session.ChatSessionManager +import org.apache.kyuubi.service.AbstractBackendService +import org.apache.kyuubi.session.SessionManager + +class ChatBackendService + extends AbstractBackendService("ChatBackendService") { + + override val sessionManager: SessionManager = new ChatSessionManager() + +} diff --git a/externals/kyuubi-chat-engine/src/main/scala/org/apache/kyuubi/engine/chat/ChatEngine.scala b/externals/kyuubi-chat-engine/src/main/scala/org/apache/kyuubi/engine/chat/ChatEngine.scala new file mode 100644 index 000000000..c1fdea953 --- /dev/null +++ b/externals/kyuubi-chat-engine/src/main/scala/org/apache/kyuubi/engine/chat/ChatEngine.scala @@ -0,0 +1,86 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.kyuubi.engine.chat + +import ChatEngine.currentEngine + +import org.apache.kyuubi.{Logging, Utils} +import org.apache.kyuubi.Utils.{addShutdownHook, JDBC_ENGINE_SHUTDOWN_PRIORITY} +import org.apache.kyuubi.config.KyuubiConf +import org.apache.kyuubi.ha.HighAvailabilityConf.HA_ZK_CONN_RETRY_POLICY +import org.apache.kyuubi.ha.client.RetryPolicies +import org.apache.kyuubi.service.Serverable +import org.apache.kyuubi.util.SignalRegister + +class ChatEngine extends Serverable("ChatEngine") { + + override val backendService = new ChatBackendService() + override val frontendServices = Seq(new ChatTBinaryFrontendService(this)) + + override def start(): Unit = { + super.start() + // Start engine self-terminating checker after all services are ready and it can be reached by + // all servers in engine spaces. + backendService.sessionManager.startTerminatingChecker(() => { + currentEngine.foreach(_.stop()) + }) + } + + override protected def stopServer(): Unit = {} +} + +object ChatEngine extends Logging { + + val kyuubiConf: KyuubiConf = KyuubiConf() + + var currentEngine: Option[ChatEngine] = None + + def startEngine(): Unit = { + currentEngine = Some(new ChatEngine()) + currentEngine.foreach { engine => + engine.initialize(kyuubiConf) + engine.start() + addShutdownHook( + () => { + engine.stop() + }, + JDBC_ENGINE_SHUTDOWN_PRIORITY + 1) + } + } + + def main(args: Array[String]): Unit = { + SignalRegister.registerLogger(logger) + + try { + Utils.fromCommandLineArgs(args, kyuubiConf) + kyuubiConf.setIfMissing(KyuubiConf.FRONTEND_THRIFT_BINARY_BIND_PORT, 0) + kyuubiConf.setIfMissing(HA_ZK_CONN_RETRY_POLICY, RetryPolicies.N_TIME.toString) + + startEngine() + } catch { + case t: Throwable if currentEngine.isDefined => + currentEngine.foreach { engine => + engine.stop() + } + error("Failed to create Chat Engine", t) + throw t + case t: Throwable => + error("Failed to create Chat Engine.", t) + throw t + } + } +} diff --git a/externals/kyuubi-chat-engine/src/main/scala/org/apache/kyuubi/engine/chat/ChatTBinaryFrontendService.scala b/externals/kyuubi-chat-engine/src/main/scala/org/apache/kyuubi/engine/chat/ChatTBinaryFrontendService.scala new file mode 100644 index 000000000..80702c97c --- /dev/null +++ b/externals/kyuubi-chat-engine/src/main/scala/org/apache/kyuubi/engine/chat/ChatTBinaryFrontendService.scala @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.kyuubi.engine.chat + +import org.apache.kyuubi.ha.client.{EngineServiceDiscovery, ServiceDiscovery} +import org.apache.kyuubi.service.{Serverable, Service, TBinaryFrontendService} + +class ChatTBinaryFrontendService(override val serverable: Serverable) + extends TBinaryFrontendService("ChatTBinaryFrontend") { + + /** + * An optional `ServiceDiscovery` for [[FrontendService]] to expose itself + */ + override lazy val discoveryService: Option[Service] = + if (ServiceDiscovery.supportServiceDiscovery(conf)) { + Some(new EngineServiceDiscovery(this)) + } else { + None + } +} diff --git a/externals/kyuubi-chat-engine/src/main/scala/org/apache/kyuubi/engine/chat/operation/ChatOperation.scala b/externals/kyuubi-chat-engine/src/main/scala/org/apache/kyuubi/engine/chat/operation/ChatOperation.scala new file mode 100644 index 000000000..38527cbf1 --- /dev/null +++ b/externals/kyuubi-chat-engine/src/main/scala/org/apache/kyuubi/engine/chat/operation/ChatOperation.scala @@ -0,0 +1,100 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.kyuubi.engine.chat.operation + +import org.apache.hive.service.rpc.thrift._ + +import org.apache.kyuubi.{KyuubiSQLException, Utils} +import org.apache.kyuubi.config.KyuubiConf +import org.apache.kyuubi.engine.chat.schema.{RowSet, SchemaHelper} +import org.apache.kyuubi.operation.{AbstractOperation, FetchIterator, OperationState} +import org.apache.kyuubi.operation.FetchOrientation.{FETCH_FIRST, FETCH_NEXT, FETCH_PRIOR, FetchOrientation} +import org.apache.kyuubi.session.Session + +abstract class ChatOperation(session: Session) extends AbstractOperation(session) { + + protected var iter: FetchIterator[Array[String]] = _ + + protected lazy val conf: KyuubiConf = session.sessionManager.getConf + + override def getNextRowSet(order: FetchOrientation, rowSetSize: Int): TRowSet = { + validateDefaultFetchOrientation(order) + assertState(OperationState.FINISHED) + setHasResultSet(true) + order match { + case FETCH_NEXT => + iter.fetchNext() + case FETCH_PRIOR => + iter.fetchPrior(rowSetSize) + case FETCH_FIRST => + iter.fetchAbsolute(0) + } + + val taken = iter.take(rowSetSize) + val resultRowSet = RowSet.toTRowSet(taken.toSeq, 1, getProtocolVersion) + resultRowSet.setStartRowOffset(iter.getPosition) + resultRowSet + } + + override def cancel(): Unit = { + cleanup(OperationState.CANCELED) + } + + override def close(): Unit = { + cleanup(OperationState.CLOSED) + } + + protected def onError(cancel: Boolean = false): PartialFunction[Throwable, Unit] = { + // We should use Throwable instead of Exception since `java.lang.NoClassDefFoundError` + // could be thrown. + case e: Throwable => + state.synchronized { + val errMsg = Utils.stringifyException(e) + if (state == OperationState.TIMEOUT) { + val ke = KyuubiSQLException(s"Timeout operating $opType: $errMsg") + setOperationException(ke) + throw ke + } else if (isTerminalState(state)) { + setOperationException(KyuubiSQLException(errMsg)) + warn(s"Ignore exception in terminal state with $statementId: $errMsg") + } else { + error(s"Error operating $opType: $errMsg", e) + val ke = KyuubiSQLException(s"Error operating $opType: $errMsg", e) + setOperationException(ke) + setState(OperationState.ERROR) + throw ke + } + } + } + + override protected def beforeRun(): Unit = { + setState(OperationState.PENDING) + setHasResultSet(true) + } + + override protected def afterRun(): Unit = {} + + override def getResultSetMetadata: TGetResultSetMetadataResp = { + val tTableSchema = SchemaHelper.stringTTableSchema("reply") + val resp = new TGetResultSetMetadataResp + resp.setSchema(tTableSchema) + resp.setStatus(OK_STATUS) + resp + } + + override def shouldRunAsync: Boolean = false +} diff --git a/externals/kyuubi-chat-engine/src/main/scala/org/apache/kyuubi/engine/chat/operation/ChatOperationManager.scala b/externals/kyuubi-chat-engine/src/main/scala/org/apache/kyuubi/engine/chat/operation/ChatOperationManager.scala new file mode 100644 index 000000000..1e8916517 --- /dev/null +++ b/externals/kyuubi-chat-engine/src/main/scala/org/apache/kyuubi/engine/chat/operation/ChatOperationManager.scala @@ -0,0 +1,130 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.kyuubi.engine.chat.operation + +import java.util + +import org.apache.kyuubi.KyuubiSQLException +import org.apache.kyuubi.config.KyuubiConf +import org.apache.kyuubi.engine.chat.provider.ChatProvider +import org.apache.kyuubi.operation.{Operation, OperationManager} +import org.apache.kyuubi.session.Session + +class ChatOperationManager( + conf: KyuubiConf, + chatProvider: ChatProvider) extends OperationManager("ChatOperationManager") { + + override def newExecuteStatementOperation( + session: Session, + statement: String, + confOverlay: Map[String, String], + runAsync: Boolean, + queryTimeout: Long): Operation = { + val executeStatement = + new ExecuteStatement( + session, + statement, + runAsync, + queryTimeout, + chatProvider) + addOperation(executeStatement) + } + + override def newGetTypeInfoOperation(session: Session): Operation = { + throw KyuubiSQLException.featureNotSupported() + } + + override def newGetCatalogsOperation(session: Session): Operation = { + throw KyuubiSQLException.featureNotSupported() + } + + override def newGetSchemasOperation( + session: Session, + catalog: String, + schema: String): Operation = { + throw KyuubiSQLException.featureNotSupported() + } + + override def newGetTablesOperation( + session: Session, + catalogName: String, + schemaName: String, + tableName: String, + tableTypes: util.List[String]): Operation = { + throw KyuubiSQLException.featureNotSupported() + } + + override def newGetTableTypesOperation(session: Session): Operation = { + throw KyuubiSQLException.featureNotSupported() + } + + override def newGetColumnsOperation( + session: Session, + catalogName: String, + schemaName: String, + tableName: String, + columnName: String): Operation = { + throw KyuubiSQLException.featureNotSupported() + } + + override def newGetFunctionsOperation( + session: Session, + catalogName: String, + schemaName: String, + functionName: String): Operation = { + throw KyuubiSQLException.featureNotSupported() + } + + override def newGetPrimaryKeysOperation( + session: Session, + catalogName: String, + schemaName: String, + tableName: String): Operation = { + throw KyuubiSQLException.featureNotSupported() + } + + override def newGetCrossReferenceOperation( + session: Session, + primaryCatalog: String, + primarySchema: String, + primaryTable: String, + foreignCatalog: String, + foreignSchema: String, + foreignTable: String): Operation = { + throw KyuubiSQLException.featureNotSupported() + } + + override def getQueryId(operation: Operation): String = { + throw KyuubiSQLException.featureNotSupported() + } + + override def newSetCurrentCatalogOperation(session: Session, catalog: String): Operation = { + throw KyuubiSQLException.featureNotSupported() + } + + override def newGetCurrentCatalogOperation(session: Session): Operation = { + throw KyuubiSQLException.featureNotSupported() + } + + override def newSetCurrentDatabaseOperation(session: Session, database: String): Operation = { + throw KyuubiSQLException.featureNotSupported() + } + + override def newGetCurrentDatabaseOperation(session: Session): Operation = { + throw KyuubiSQLException.featureNotSupported() + } +} diff --git a/externals/kyuubi-chat-engine/src/main/scala/org/apache/kyuubi/engine/chat/operation/ExecuteStatement.scala b/externals/kyuubi-chat-engine/src/main/scala/org/apache/kyuubi/engine/chat/operation/ExecuteStatement.scala new file mode 100644 index 000000000..754a51932 --- /dev/null +++ b/externals/kyuubi-chat-engine/src/main/scala/org/apache/kyuubi/engine/chat/operation/ExecuteStatement.scala @@ -0,0 +1,66 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.kyuubi.engine.chat.operation + +import org.apache.kyuubi.Logging +import org.apache.kyuubi.engine.chat.provider.ChatProvider +import org.apache.kyuubi.operation.{ArrayFetchIterator, OperationState} +import org.apache.kyuubi.operation.log.OperationLog +import org.apache.kyuubi.session.Session + +class ExecuteStatement( + session: Session, + override val statement: String, + override val shouldRunAsync: Boolean, + queryTimeout: Long, + chatProvider: ChatProvider) + extends ChatOperation(session) with Logging { + + private val operationLog: OperationLog = OperationLog.createOperationLog(session, getHandle) + override def getOperationLog: Option[OperationLog] = Option(operationLog) + + override protected def runInternal(): Unit = { + addTimeoutMonitor(queryTimeout) + if (shouldRunAsync) { + val asyncOperation = new Runnable { + override def run(): Unit = { + executeStatement() + } + } + val chatSessionManager = session.sessionManager + val backgroundHandle = chatSessionManager.submitBackgroundOperation(asyncOperation) + setBackgroundHandle(backgroundHandle) + } else { + executeStatement() + } + } + + private def executeStatement(): Unit = { + setState(OperationState.RUNNING) + + try { + val reply = chatProvider.ask(session.handle.identifier.toString, statement) + iter = new ArrayFetchIterator(Array(Array(reply))) + + setState(OperationState.FINISHED) + } catch { + onError(true) + } finally { + shutdownTimeoutMonitor() + } + } +} diff --git a/externals/kyuubi-chat-engine/src/main/scala/org/apache/kyuubi/engine/chat/provider/ChatGPTProvider.scala b/externals/kyuubi-chat-engine/src/main/scala/org/apache/kyuubi/engine/chat/provider/ChatGPTProvider.scala new file mode 100644 index 000000000..f948eb154 --- /dev/null +++ b/externals/kyuubi-chat-engine/src/main/scala/org/apache/kyuubi/engine/chat/provider/ChatGPTProvider.scala @@ -0,0 +1,84 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.kyuubi.engine.chat.provider + +import java.util +import java.util.concurrent.TimeUnit + +import com.google.common.cache.{CacheBuilder, CacheLoader, LoadingCache} +import org.apache.http.HttpStatus +import org.apache.http.client.methods.HttpPost +import org.apache.http.entity.StringEntity +import org.apache.http.impl.client.{CloseableHttpClient, HttpClientBuilder} +import org.apache.http.util.EntityUtils + +import org.apache.kyuubi.config.KyuubiConf +import org.apache.kyuubi.engine.chat.provider.ChatProvider.mapper + +class ChatGPTProvider(conf: KyuubiConf) extends ChatProvider { + + val token = conf.get(KyuubiConf.ENGINE_CHAT_GPT_API_KEY).get + + val httpClient: CloseableHttpClient = HttpClientBuilder.create().build() + + private val chatHistory: LoadingCache[String, util.ArrayDeque[Message]] = + CacheBuilder.newBuilder() + .expireAfterWrite(10, TimeUnit.MINUTES) + .build(new CacheLoader[String, util.ArrayDeque[Message]] { + override def load(sessionId: String): util.ArrayDeque[Message] = + new util.ArrayDeque[Message] + }) + + override def open(sessionId: String): Unit = { + chatHistory.getIfPresent(sessionId) + } + + override def ask(sessionId: String, q: String): String = { + val messages = chatHistory.get(sessionId) + messages.addLast(Message("user", q)) + + val request = new HttpPost("https://api.openai.com/v1/chat/completions") + request.addHeader("Content-Type", "application/json") + request.addHeader("Authorization", "Bearer " + token) + + val req = Map( + "messages" -> messages, + "model" -> "gpt-3.5-turbo", + "max_tokens" -> 200, + "temperature" -> 0.5, + "top_p" -> 1) + + request.setEntity(new StringEntity(mapper.writeValueAsString(req))) + val responseEntity = httpClient.execute(request) + val respJson = mapper.readTree(EntityUtils.toString(responseEntity.getEntity)) + val statusCode = responseEntity.getStatusLine.getStatusCode + if (responseEntity.getStatusLine.getStatusCode == HttpStatus.SC_OK) { + val replyMessage = mapper.treeToValue[Message]( + respJson.get("choices").get(0).get("message")) + messages.addLast(replyMessage) + replyMessage.content + } else { + messages.removeLast() + s"Chat failed. Status: $statusCode. ${respJson.get("error").get("message").asText}" + } + } + + override def close(sessionId: String): Unit = { + chatHistory.invalidate(sessionId) + } +} diff --git a/externals/kyuubi-chat-engine/src/main/scala/org/apache/kyuubi/engine/chat/provider/ChatProvider.scala b/externals/kyuubi-chat-engine/src/main/scala/org/apache/kyuubi/engine/chat/provider/ChatProvider.scala new file mode 100644 index 000000000..af1ba434b --- /dev/null +++ b/externals/kyuubi-chat-engine/src/main/scala/org/apache/kyuubi/engine/chat/provider/ChatProvider.scala @@ -0,0 +1,59 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.kyuubi.engine.chat.provider + +import scala.util.control.NonFatal + +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.module.scala.{ClassTagExtensions, DefaultScalaModule} + +import org.apache.kyuubi.{KyuubiException, Logging} +import org.apache.kyuubi.config.KyuubiConf +import org.apache.kyuubi.reflection.DynConstructors + +trait ChatProvider { + + def open(sessionId: String): Unit + + def ask(sessionId: String, q: String): String + + def close(sessionId: String): Unit +} + +object ChatProvider extends Logging { + + val mapper: ObjectMapper with ClassTagExtensions = + new ObjectMapper().registerModule(DefaultScalaModule) :: ClassTagExtensions + + def load(conf: KyuubiConf): ChatProvider = { + val groupProviderClass = conf.get(KyuubiConf.ENGINE_CHAT_PROVIDER) + try { + DynConstructors.builder(classOf[ChatProvider]) + .impl(groupProviderClass, classOf[KyuubiConf]) + .impl(groupProviderClass) + .buildChecked + .newInstanceChecked(conf) + } catch { + case _: ClassCastException => + throw new KyuubiException( + s"Class $groupProviderClass is not a child of '${classOf[ChatProvider].getName}'.") + case NonFatal(e) => + throw new IllegalArgumentException(s"Error while instantiating '$groupProviderClass': ", e) + } + } +} diff --git a/externals/kyuubi-chat-engine/src/main/scala/org/apache/kyuubi/engine/chat/provider/EchoProvider.scala b/externals/kyuubi-chat-engine/src/main/scala/org/apache/kyuubi/engine/chat/provider/EchoProvider.scala new file mode 100644 index 000000000..31ad3b8e3 --- /dev/null +++ b/externals/kyuubi-chat-engine/src/main/scala/org/apache/kyuubi/engine/chat/provider/EchoProvider.scala @@ -0,0 +1,28 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.kyuubi.engine.chat.provider + +class EchoProvider extends ChatProvider { + + override def open(sessionId: String): Unit = {} + + override def ask(sessionId: String, q: String): String = + "This is ChatKyuubi, nice to meet you!" + + override def close(sessionId: String): Unit = {} +} diff --git a/externals/kyuubi-chat-engine/src/main/scala/org/apache/kyuubi/engine/chat/provider/Message.scala b/externals/kyuubi-chat-engine/src/main/scala/org/apache/kyuubi/engine/chat/provider/Message.scala new file mode 100644 index 000000000..e2162be9f --- /dev/null +++ b/externals/kyuubi-chat-engine/src/main/scala/org/apache/kyuubi/engine/chat/provider/Message.scala @@ -0,0 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.kyuubi.engine.chat.provider + +case class Message(role: String, content: String) diff --git a/externals/kyuubi-chat-engine/src/main/scala/org/apache/kyuubi/engine/chat/schema/RowSet.scala b/externals/kyuubi-chat-engine/src/main/scala/org/apache/kyuubi/engine/chat/schema/RowSet.scala new file mode 100644 index 000000000..3bb4ba7df --- /dev/null +++ b/externals/kyuubi-chat-engine/src/main/scala/org/apache/kyuubi/engine/chat/schema/RowSet.scala @@ -0,0 +1,108 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.kyuubi.engine.chat.schema + +import java.util + +import org.apache.hive.service.rpc.thrift._ + +import org.apache.kyuubi.util.RowSetUtils._ + +object RowSet { + + def emptyTRowSet(): TRowSet = { + new TRowSet(0, new java.util.ArrayList[TRow](0)) + } + + def toTRowSet( + rows: Seq[Array[String]], + columnSize: Int, + protocolVersion: TProtocolVersion): TRowSet = { + if (protocolVersion.getValue < TProtocolVersion.HIVE_CLI_SERVICE_PROTOCOL_V6.getValue) { + toRowBasedSet(rows, columnSize) + } else { + toColumnBasedSet(rows, columnSize) + } + } + + def toRowBasedSet(rows: Seq[Array[String]], columnSize: Int): TRowSet = { + val rowSize = rows.length + val tRows = new java.util.ArrayList[TRow](rowSize) + var i = 0 + while (i < rowSize) { + val row = rows(i) + val tRow = new TRow() + var j = 0 + val columnSize = row.length + while (j < columnSize) { + val columnValue = stringTColumnValue(j, row) + tRow.addToColVals(columnValue) + j += 1 + } + i += 1 + tRows.add(tRow) + } + new TRowSet(0, tRows) + } + + def toColumnBasedSet(rows: Seq[Array[String]], columnSize: Int): TRowSet = { + val rowSize = rows.length + val tRowSet = new TRowSet(0, new util.ArrayList[TRow](rowSize)) + var i = 0 + while (i < columnSize) { + val tColumn = toTColumn(rows, i) + tRowSet.addToColumns(tColumn) + i += 1 + } + tRowSet + } + + private def toTColumn(rows: Seq[Array[String]], ordinal: Int): TColumn = { + val nulls = new java.util.BitSet() + val values = getOrSetAsNull[String](rows, ordinal, nulls, "") + TColumn.stringVal(new TStringColumn(values, nulls)) + } + + private def getOrSetAsNull[String]( + rows: Seq[Array[String]], + ordinal: Int, + nulls: util.BitSet, + defaultVal: String): util.List[String] = { + val size = rows.length + val ret = new util.ArrayList[String](size) + var idx = 0 + while (idx < size) { + val row = rows(idx) + val isNull = row(ordinal) == null + if (isNull) { + nulls.set(idx, true) + ret.add(idx, defaultVal) + } else { + ret.add(idx, row(ordinal)) + } + idx += 1 + } + ret + } + + private def stringTColumnValue(ordinal: Int, row: Array[String]): TColumnValue = { + val tStringValue = new TStringValue + if (row(ordinal) != null) tStringValue.setValue(row(ordinal)) + TColumnValue.stringVal(tStringValue) + } +} diff --git a/externals/kyuubi-chat-engine/src/main/scala/org/apache/kyuubi/engine/chat/schema/SchemaHelper.scala b/externals/kyuubi-chat-engine/src/main/scala/org/apache/kyuubi/engine/chat/schema/SchemaHelper.scala new file mode 100644 index 000000000..8ccfdda2f --- /dev/null +++ b/externals/kyuubi-chat-engine/src/main/scala/org/apache/kyuubi/engine/chat/schema/SchemaHelper.scala @@ -0,0 +1,56 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.kyuubi.engine.chat.schema + +import java.util.Collections + +import org.apache.hive.service.rpc.thrift._ + +object SchemaHelper { + + def stringTTypeQualifiers: TTypeQualifiers = { + val ret = new TTypeQualifiers() + val qualifiers = Collections.emptyMap[String, TTypeQualifierValue]() + ret.setQualifiers(qualifiers) + ret + } + + def stringTTypeDesc: TTypeDesc = { + val typeEntry = new TPrimitiveTypeEntry(TTypeId.STRING_TYPE) + typeEntry.setTypeQualifiers(stringTTypeQualifiers) + val tTypeDesc = new TTypeDesc() + tTypeDesc.addToTypes(TTypeEntry.primitiveEntry(typeEntry)) + tTypeDesc + } + + def stringTColumnDesc(fieldName: String, pos: Int): TColumnDesc = { + val tColumnDesc = new TColumnDesc() + tColumnDesc.setColumnName(fieldName) + tColumnDesc.setTypeDesc(stringTTypeDesc) + tColumnDesc.setPosition(pos) + tColumnDesc + } + + def stringTTableSchema(fieldsName: String*): TTableSchema = { + val tTableSchema = new TTableSchema() + fieldsName.zipWithIndex.foreach { case (f, i) => + tTableSchema.addToColumns(stringTColumnDesc(f, i)) + } + tTableSchema + } +} diff --git a/externals/kyuubi-chat-engine/src/main/scala/org/apache/kyuubi/engine/chat/session/ChatSessionImpl.scala b/externals/kyuubi-chat-engine/src/main/scala/org/apache/kyuubi/engine/chat/session/ChatSessionImpl.scala new file mode 100644 index 000000000..29f420768 --- /dev/null +++ b/externals/kyuubi-chat-engine/src/main/scala/org/apache/kyuubi/engine/chat/session/ChatSessionImpl.scala @@ -0,0 +1,68 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.kyuubi.engine.chat.session + +import org.apache.hive.service.rpc.thrift.{TGetInfoType, TGetInfoValue, TProtocolVersion} + +import org.apache.kyuubi.{KYUUBI_VERSION, KyuubiSQLException} +import org.apache.kyuubi.config.KyuubiReservedKeys.KYUUBI_SESSION_HANDLE_KEY +import org.apache.kyuubi.session.{AbstractSession, SessionHandle, SessionManager} + +class ChatSessionImpl( + protocol: TProtocolVersion, + user: String, + password: String, + ipAddress: String, + conf: Map[String, String], + sessionManager: SessionManager) + extends AbstractSession(protocol, user, password, ipAddress, conf, sessionManager) { + + override val handle: SessionHandle = + conf.get(KYUUBI_SESSION_HANDLE_KEY).map(SessionHandle.fromUUID).getOrElse(SessionHandle()) + + private val chatProvider = sessionManager.asInstanceOf[ChatSessionManager].chatProvider + + override def open(): Unit = { + info(s"Starting to open chat session.") + chatProvider.open(handle.identifier.toString) + super.open() + info(s"The chat session is started.") + } + + override def getInfo(infoType: TGetInfoType): TGetInfoValue = withAcquireRelease() { + infoType match { + case TGetInfoType.CLI_SERVER_NAME | TGetInfoType.CLI_DBMS_NAME => + TGetInfoValue.stringValue("Kyuubi Chat Engine") + case TGetInfoType.CLI_DBMS_VER => + TGetInfoValue.stringValue(KYUUBI_VERSION) + case TGetInfoType.CLI_ODBC_KEYWORDS => TGetInfoValue.stringValue("Unimplemented") + case TGetInfoType.CLI_MAX_COLUMN_NAME_LEN => + TGetInfoValue.lenValue(128) + case TGetInfoType.CLI_MAX_SCHEMA_NAME_LEN => + TGetInfoValue.lenValue(128) + case TGetInfoType.CLI_MAX_TABLE_NAME_LEN => + TGetInfoValue.lenValue(128) + case _ => throw KyuubiSQLException(s"Unrecognized GetInfoType value: $infoType") + } + } + + override def close(): Unit = { + chatProvider.close(handle.identifier.toString) + super.close() + } + +} diff --git a/externals/kyuubi-chat-engine/src/main/scala/org/apache/kyuubi/engine/chat/session/ChatSessionManager.scala b/externals/kyuubi-chat-engine/src/main/scala/org/apache/kyuubi/engine/chat/session/ChatSessionManager.scala new file mode 100644 index 000000000..33a9dd450 --- /dev/null +++ b/externals/kyuubi-chat-engine/src/main/scala/org/apache/kyuubi/engine/chat/session/ChatSessionManager.scala @@ -0,0 +1,71 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.kyuubi.engine.chat.session + +import org.apache.hive.service.rpc.thrift.TProtocolVersion + +import org.apache.kyuubi.config.KyuubiConf +import org.apache.kyuubi.config.KyuubiConf.ENGINE_SHARE_LEVEL +import org.apache.kyuubi.config.KyuubiReservedKeys.KYUUBI_SESSION_HANDLE_KEY +import org.apache.kyuubi.engine.ShareLevel +import org.apache.kyuubi.engine.chat.ChatEngine +import org.apache.kyuubi.engine.chat.operation.ChatOperationManager +import org.apache.kyuubi.engine.chat.provider.ChatProvider +import org.apache.kyuubi.operation.OperationManager +import org.apache.kyuubi.session.{Session, SessionHandle, SessionManager} + +class ChatSessionManager(name: String) + extends SessionManager(name) { + + def this() = this(classOf[ChatSessionManager].getSimpleName) + + override protected def isServer: Boolean = false + + lazy val chatProvider: ChatProvider = ChatProvider.load(conf) + + override lazy val operationManager: OperationManager = + new ChatOperationManager(conf, chatProvider) + + override def initialize(conf: KyuubiConf): Unit = { + this.conf = conf + super.initialize(conf) + } + + override protected def createSession( + protocol: TProtocolVersion, + user: String, + password: String, + ipAddress: String, + conf: Map[String, String]): Session = { + conf.get(KYUUBI_SESSION_HANDLE_KEY).map(SessionHandle.fromUUID) + .flatMap(getSessionOption).getOrElse { + new ChatSessionImpl(protocol, user, password, ipAddress, conf, this) + } + } + + override def closeSession(sessionHandle: SessionHandle): Unit = { + super.closeSession(sessionHandle) + if (conf.get(ENGINE_SHARE_LEVEL) == ShareLevel.CONNECTION.toString) { + info("Session stopped due to shared level is Connection.") + stopSession() + } + } + + private def stopSession(): Unit = { + ChatEngine.currentEngine.foreach(_.stop()) + } +} diff --git a/externals/kyuubi-chat-engine/src/test/resources/log4j2-test.xml b/externals/kyuubi-chat-engine/src/test/resources/log4j2-test.xml new file mode 100644 index 000000000..585a12c6f --- /dev/null +++ b/externals/kyuubi-chat-engine/src/test/resources/log4j2-test.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/externals/kyuubi-chat-engine/src/test/scala/org/apache/kyuubi/engine/chat/WithChatEngine.scala b/externals/kyuubi-chat-engine/src/test/scala/org/apache/kyuubi/engine/chat/WithChatEngine.scala new file mode 100644 index 000000000..287fdde2f --- /dev/null +++ b/externals/kyuubi-chat-engine/src/test/scala/org/apache/kyuubi/engine/chat/WithChatEngine.scala @@ -0,0 +1,60 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.kyuubi.engine.chat + +import org.apache.kyuubi.KyuubiFunSuite +import org.apache.kyuubi.config.KyuubiConf + +trait WithChatEngine extends KyuubiFunSuite { + + protected var engine: ChatEngine = _ + protected var connectionUrl: String = _ + + protected val kyuubiConf: KyuubiConf = ChatEngine.kyuubiConf + + def withKyuubiConf: Map[String, String] + + override def beforeAll(): Unit = { + super.beforeAll() + startChatEngine() + } + + override def afterAll(): Unit = { + stopChatEngine() + super.afterAll() + } + + def stopChatEngine(): Unit = { + if (engine != null) { + engine.stop() + engine = null + } + } + + def startChatEngine(): Unit = { + withKyuubiConf.foreach { case (k, v) => + System.setProperty(k, v) + kyuubiConf.set(k, v) + } + ChatEngine.startEngine() + engine = ChatEngine.currentEngine.get + connectionUrl = engine.frontendServices.head.connectionUrl + } + + protected def jdbcConnectionUrl: String = s"jdbc:hive2://$connectionUrl/;" + +} diff --git a/externals/kyuubi-chat-engine/src/test/scala/org/apache/kyuubi/engine/chat/operation/ChatOperationSuite.scala b/externals/kyuubi-chat-engine/src/test/scala/org/apache/kyuubi/engine/chat/operation/ChatOperationSuite.scala new file mode 100644 index 000000000..b14407a26 --- /dev/null +++ b/externals/kyuubi-chat-engine/src/test/scala/org/apache/kyuubi/engine/chat/operation/ChatOperationSuite.scala @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.kyuubi.engine.chat.operation + +import org.apache.kyuubi.config.KyuubiConf._ +import org.apache.kyuubi.engine.chat.WithChatEngine +import org.apache.kyuubi.operation.HiveJDBCTestHelper + +class ChatOperationSuite extends HiveJDBCTestHelper with WithChatEngine { + + override def withKyuubiConf: Map[String, String] = Map( + ENGINE_CHAT_PROVIDER.key -> "echo") + + override protected def jdbcUrl: String = jdbcConnectionUrl + + test("test echo chat provider") { + withJdbcStatement() { stmt => + val result = stmt.executeQuery("Hello, Kyuubi") + assert(result.next()) + val expected = "This is ChatKyuubi, nice to meet you!" + assert(result.getString("reply") === expected) + assert(!result.next()) + } + } +} diff --git a/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala b/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala index 1b7737344..b39bee307 100644 --- a/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala +++ b/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala @@ -1817,6 +1817,7 @@ object KyuubiConf { " all the capacity of the Hive Server2." + "
    • JDBC: specify this engine type will launch a JDBC engine which can provide" + " a MySQL protocol connector, for now we only support Doris dialect.
    • " + + "
    • CHAT: specify this engine type will launch a Chat engine.
    • " + "
    ") .version("1.4.0") .stringConf @@ -2621,6 +2622,51 @@ object KyuubiConf { Map(configs.map { cfg => cfg.key -> cfg }: _*) } + val ENGINE_CHAT_MEMORY: ConfigEntry[String] = + buildConf("kyuubi.engine.chat.memory") + .doc("The heap memory for the Chat engine") + .version("1.8.0") + .stringConf + .createWithDefault("1g") + + val ENGINE_CHAT_JAVA_OPTIONS: OptionalConfigEntry[String] = + buildConf("kyuubi.engine.chat.java.options") + .doc("The extra Java options for the Chat engine") + .version("1.8.0") + .stringConf + .createOptional + + val ENGINE_CHAT_PROVIDER: ConfigEntry[String] = + buildConf("kyuubi.engine.chat.provider") + .doc("The provider for the Chat engine. Candidates:
      " + + "
    • ECHO: simply replies a welcome message.
    • " + + "
    • GPT: a.k.a ChatGPT, powered by OpenAI.
    • " + + "
    ") + .version("1.8.0") + .stringConf + .transform { + case "ECHO" | "echo" => "org.apache.kyuubi.engine.chat.provider.EchoProvider" + case "GPT" | "gpt" | "ChatGPT" => "org.apache.kyuubi.engine.chat.provider.ChatGPTProvider" + case other => other + } + .createWithDefault("ECHO") + + val ENGINE_CHAT_GPT_API_KEY: OptionalConfigEntry[String] = + buildConf("kyuubi.engine.chat.gpt.apiKey") + .doc("The key to access OpenAI open API, which could be got at " + + "https://platform.openai.com/account/api-keys") + .version("1.8.0") + .stringConf + .createOptional + + val ENGINE_CHAT_EXTRA_CLASSPATH: OptionalConfigEntry[String] = + buildConf("kyuubi.engine.chat.extra.classpath") + .doc("The extra classpath for the Chat engine, for configuring the location " + + "of the SDK and etc.") + .version("1.8.0") + .stringConf + .createOptional + val ENGINE_JDBC_MEMORY: ConfigEntry[String] = buildConf("kyuubi.engine.jdbc.memory") .doc("The heap memory for the JDBC query engine") diff --git a/kyuubi-common/src/main/scala/org/apache/kyuubi/engine/EngineType.scala b/kyuubi-common/src/main/scala/org/apache/kyuubi/engine/EngineType.scala index 88680a8c7..3d850ba14 100644 --- a/kyuubi-common/src/main/scala/org/apache/kyuubi/engine/EngineType.scala +++ b/kyuubi-common/src/main/scala/org/apache/kyuubi/engine/EngineType.scala @@ -23,5 +23,5 @@ package org.apache.kyuubi.engine object EngineType extends Enumeration { type EngineType = Value - val SPARK_SQL, FLINK_SQL, TRINO, HIVE_SQL, JDBC = Value + val SPARK_SQL, FLINK_SQL, CHAT, TRINO, HIVE_SQL, JDBC = Value } diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/engine/EngineRef.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/engine/EngineRef.scala index e2ddb4221..5f3af28f8 100644 --- a/kyuubi-server/src/main/scala/org/apache/kyuubi/engine/EngineRef.scala +++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/engine/EngineRef.scala @@ -29,8 +29,9 @@ import org.apache.kyuubi.{KYUUBI_VERSION, KyuubiSQLException, Logging, Utils} import org.apache.kyuubi.config.KyuubiConf import org.apache.kyuubi.config.KyuubiConf._ import org.apache.kyuubi.config.KyuubiReservedKeys.KYUUBI_ENGINE_SUBMIT_TIME_KEY -import org.apache.kyuubi.engine.EngineType.{EngineType, FLINK_SQL, HIVE_SQL, JDBC, SPARK_SQL, TRINO} +import org.apache.kyuubi.engine.EngineType._ import org.apache.kyuubi.engine.ShareLevel.{CONNECTION, GROUP, SERVER, ShareLevel} +import org.apache.kyuubi.engine.chat.ChatProcessBuilder import org.apache.kyuubi.engine.flink.FlinkProcessBuilder import org.apache.kyuubi.engine.hive.HiveProcessBuilder import org.apache.kyuubi.engine.jdbc.JdbcProcessBuilder @@ -192,6 +193,8 @@ private[kyuubi] class EngineRef( new HiveProcessBuilder(appUser, conf, engineRefId, extraEngineLog) case JDBC => new JdbcProcessBuilder(appUser, conf, engineRefId, extraEngineLog) + case CHAT => + new ChatProcessBuilder(appUser, conf, engineRefId, extraEngineLog) } MetricsSystem.tracing(_.incCount(ENGINE_TOTAL)) diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/engine/chat/ChatProcessBuilder.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/engine/chat/ChatProcessBuilder.scala new file mode 100644 index 000000000..532c5dddf --- /dev/null +++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/engine/chat/ChatProcessBuilder.scala @@ -0,0 +1,115 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.kyuubi.engine.chat + +import java.io.File +import java.nio.file.{Files, Paths} +import java.util + +import scala.collection.JavaConverters._ +import scala.collection.mutable.ArrayBuffer + +import com.google.common.annotations.VisibleForTesting + +import org.apache.kyuubi.{Logging, SCALA_COMPILE_VERSION, Utils} +import org.apache.kyuubi.Utils.REDACTION_REPLACEMENT_TEXT +import org.apache.kyuubi.config.KyuubiConf +import org.apache.kyuubi.config.KyuubiConf._ +import org.apache.kyuubi.config.KyuubiReservedKeys.KYUUBI_SESSION_USER_KEY +import org.apache.kyuubi.engine.ProcBuilder +import org.apache.kyuubi.operation.log.OperationLog + +class ChatProcessBuilder( + override val proxyUser: String, + override val conf: KyuubiConf, + val engineRefId: String, + val extraEngineLog: Option[OperationLog] = None) + extends ProcBuilder with Logging { + + @VisibleForTesting + def this(proxyUser: String, conf: KyuubiConf) { + this(proxyUser, conf, "") + } + + /** + * The short name of the engine process builder, we use this for form the engine jar paths now + * see `mainResource` + */ + override def shortName: String = "chat" + + override protected def module: String = "kyuubi-chat-engine" + + /** + * The class containing the main method + */ + override protected def mainClass: String = "org.apache.kyuubi.engine.chat.ChatEngine" + + override protected val commands: Array[String] = { + val buffer = new ArrayBuffer[String]() + buffer += executable + + val memory = conf.get(ENGINE_CHAT_MEMORY) + buffer += s"-Xmx$memory" + + val javaOptions = conf.get(ENGINE_CHAT_JAVA_OPTIONS) + javaOptions.foreach(buffer += _) + + buffer += "-cp" + val classpathEntries = new util.LinkedHashSet[String] + mainResource.foreach(classpathEntries.add) + mainResource.foreach { path => + val parent = Paths.get(path).getParent + val chatDevDepDir = parent + .resolve(s"scala-$SCALA_COMPILE_VERSION") + .resolve("jars") + if (Files.exists(chatDevDepDir)) { + // add dev classpath + classpathEntries.add(s"$chatDevDepDir${File.separator}*") + } else { + // add prod classpath + classpathEntries.add(s"$parent${File.separator}*") + } + } + + val extraCp = conf.get(ENGINE_CHAT_EXTRA_CLASSPATH) + extraCp.foreach(classpathEntries.add) + buffer += classpathEntries.asScala.mkString(File.pathSeparator) + buffer += mainClass + + buffer += "--conf" + buffer += s"$KYUUBI_SESSION_USER_KEY=$proxyUser" + + for ((k, v) <- conf.getAll) { + buffer += "--conf" + buffer += s"$k=$v" + } + buffer.toArray + } + + override def toString: String = { + if (commands == null) { + super.toString() + } else { + Utils.redactCommandLineArgs(conf, commands).map { + case arg if arg.contains(ENGINE_CHAT_GPT_API_KEY.key) => + s"${ENGINE_CHAT_GPT_API_KEY.key}=$REDACTION_REPLACEMENT_TEXT" + case arg => arg + }.mkString("\n") + } + } +} diff --git a/pom.xml b/pom.xml index 2082499dc..a2de5d29d 100644 --- a/pom.xml +++ b/pom.xml @@ -60,10 +60,11 @@ extensions/spark/kyuubi-spark-connector-tpcds extensions/spark/kyuubi-spark-connector-tpch extensions/spark/kyuubi-spark-lineage + externals/kyuubi-chat-engine externals/kyuubi-download externals/kyuubi-flink-sql-engine - externals/kyuubi-jdbc-engine externals/kyuubi-hive-sql-engine + externals/kyuubi-jdbc-engine externals/kyuubi-spark-sql-engine externals/kyuubi-trino-engine integration-tests From 6ded07974eebcbd8cbba4167a25c80e4549014d6 Mon Sep 17 00:00:00 2001 From: sychen Date: Fri, 17 Mar 2023 21:48:58 +0800 Subject: [PATCH 185/760] [KYUUBI #4548] Kyuubi Chat Engine supports Chinese questions and HTTP proxy ### _Why are the changes needed?_ - Support Chinese question - Support proxy settings - Support setting timeout image ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [x] Add screenshots for manual tests if appropriate - [x] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4548 from cxzl25/chatgpt_followup. Closes #4548 1d5715442 [Cheng Pan] Update externals/kyuubi-chat-engine/src/main/scala/org/apache/kyuubi/engine/chat/provider/ChatGPTProvider.scala 7add6a733 [Cheng Pan] Update externals/kyuubi-chat-engine/src/main/scala/org/apache/kyuubi/engine/chat/provider/ChatGPTProvider.scala 55974f298 [sychen] fix 2d360e102 [sychen] typo 19b5d0814 [sychen] doc bdf8e29b6 [sychen] 1.utf8;2.proxy;timeout Lead-authored-by: sychen Co-authored-by: Cheng Pan Signed-off-by: Cheng Pan --- docs/deployment/settings.md | 3 ++ .../chat/provider/ChatGPTProvider.scala | 33 ++++++++++++++----- .../org/apache/kyuubi/config/KyuubiConf.scala | 25 ++++++++++++++ 3 files changed, 53 insertions(+), 8 deletions(-) diff --git a/docs/deployment/settings.md b/docs/deployment/settings.md index 9c5f76712..db6e3d246 100644 --- a/docs/deployment/settings.md +++ b/docs/deployment/settings.md @@ -122,6 +122,9 @@ You can configure the Kyuubi properties in `$KYUUBI_HOME/conf/kyuubi-defaults.co |----------------------------------------------------------|---------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------|-------| | kyuubi.engine.chat.extra.classpath | <undefined> | The extra classpath for the Chat engine, for configuring the location of the SDK and etc. | string | 1.8.0 | | kyuubi.engine.chat.gpt.apiKey | <undefined> | The key to access OpenAI open API, which could be got at https://platform.openai.com/account/api-keys | string | 1.8.0 | +| kyuubi.engine.chat.gpt.http.connect.timeout | PT2M | The timeout[ms] for establishing the connection with the Chat GPT server. A timeout value of zero is interpreted as an infinite timeout. | duration | 1.8.0 | +| kyuubi.engine.chat.gpt.http.proxy | <undefined> | HTTP proxy url for API calling in Chat GPT engine. e.g. http://127.0.0.1:1087 | string | 1.8.0 | +| kyuubi.engine.chat.gpt.http.socket.timeout | PT2M | The timeout[ms] for waiting for data packets after Chat GPT server connection is established. A timeout value of zero is interpreted as an infinite timeout. | duration | 1.8.0 | | kyuubi.engine.chat.java.options | <undefined> | The extra Java options for the Chat engine | string | 1.8.0 | | kyuubi.engine.chat.memory | 1g | The heap memory for the Chat engine | string | 1.8.0 | | kyuubi.engine.chat.provider | ECHO | The provider for the Chat engine. Candidates:
    • ECHO: simply replies a welcome message.
    • GPT: a.k.a ChatGPT, powered by OpenAI.
    | string | 1.8.0 | diff --git a/externals/kyuubi-chat-engine/src/main/scala/org/apache/kyuubi/engine/chat/provider/ChatGPTProvider.scala b/externals/kyuubi-chat-engine/src/main/scala/org/apache/kyuubi/engine/chat/provider/ChatGPTProvider.scala index f948eb154..28ef36bb4 100644 --- a/externals/kyuubi-chat-engine/src/main/scala/org/apache/kyuubi/engine/chat/provider/ChatGPTProvider.scala +++ b/externals/kyuubi-chat-engine/src/main/scala/org/apache/kyuubi/engine/chat/provider/ChatGPTProvider.scala @@ -21,9 +21,10 @@ import java.util import java.util.concurrent.TimeUnit import com.google.common.cache.{CacheBuilder, CacheLoader, LoadingCache} -import org.apache.http.HttpStatus +import org.apache.http.{HttpHost, HttpStatus} +import org.apache.http.client.config.RequestConfig import org.apache.http.client.methods.HttpPost -import org.apache.http.entity.StringEntity +import org.apache.http.entity.{ContentType, StringEntity} import org.apache.http.impl.client.{CloseableHttpClient, HttpClientBuilder} import org.apache.http.util.EntityUtils @@ -32,9 +33,25 @@ import org.apache.kyuubi.engine.chat.provider.ChatProvider.mapper class ChatGPTProvider(conf: KyuubiConf) extends ChatProvider { - val token = conf.get(KyuubiConf.ENGINE_CHAT_GPT_API_KEY).get + private val gptApiKey = conf.get(KyuubiConf.ENGINE_CHAT_GPT_API_KEY).getOrElse { + throw new IllegalArgumentException( + s"'${KyuubiConf.ENGINE_CHAT_GPT_API_KEY.key}' must be configured, " + + s"which could be got at https://platform.openai.com/account/api-keys") + } - val httpClient: CloseableHttpClient = HttpClientBuilder.create().build() + private val httpClient: CloseableHttpClient = HttpClientBuilder.create().build() + + private val requestConfig = { + val connectTimeout = conf.get(KyuubiConf.ENGINE_CHAT_GPT_HTTP_CONNECT_TIMEOUT).asInstanceOf[Int] + val socketTimeout = conf.get(KyuubiConf.ENGINE_CHAT_GPT_HTTP_SOCKET_TIMEOUT).asInstanceOf[Int] + val builder: RequestConfig.Builder = RequestConfig.custom() + .setConnectTimeout(connectTimeout) + .setSocketTimeout(socketTimeout) + conf.get(KyuubiConf.ENGINE_CHAT_GPT_HTTP_PROXY).foreach { url => + builder.setProxy(HttpHost.create(url)) + } + builder.build() + } private val chatHistory: LoadingCache[String, util.ArrayDeque[Message]] = CacheBuilder.newBuilder() @@ -53,8 +70,7 @@ class ChatGPTProvider(conf: KyuubiConf) extends ChatProvider { messages.addLast(Message("user", q)) val request = new HttpPost("https://api.openai.com/v1/chat/completions") - request.addHeader("Content-Type", "application/json") - request.addHeader("Authorization", "Bearer " + token) + request.addHeader("Authorization", "Bearer " + gptApiKey) val req = Map( "messages" -> messages, @@ -62,8 +78,9 @@ class ChatGPTProvider(conf: KyuubiConf) extends ChatProvider { "max_tokens" -> 200, "temperature" -> 0.5, "top_p" -> 1) - - request.setEntity(new StringEntity(mapper.writeValueAsString(req))) + val entity = new StringEntity(mapper.writeValueAsString(req), ContentType.APPLICATION_JSON) + request.setEntity(entity) + request.setConfig(requestConfig) val responseEntity = httpClient.execute(request) val respJson = mapper.readTree(EntityUtils.toString(responseEntity.getEntity)) val statusCode = responseEntity.getStatusLine.getStatusCode diff --git a/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala b/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala index b39bee307..35626eea5 100644 --- a/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala +++ b/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala @@ -2667,6 +2667,31 @@ object KyuubiConf { .stringConf .createOptional + val ENGINE_CHAT_GPT_HTTP_PROXY: OptionalConfigEntry[String] = + buildConf("kyuubi.engine.chat.gpt.http.proxy") + .doc("HTTP proxy url for API calling in Chat GPT engine. e.g. http://127.0.0.1:1087") + .version("1.8.0") + .stringConf + .createOptional + + val ENGINE_CHAT_GPT_HTTP_CONNECT_TIMEOUT: ConfigEntry[Long] = + buildConf("kyuubi.engine.chat.gpt.http.connect.timeout") + .doc("The timeout[ms] for establishing the connection with the Chat GPT server. " + + "A timeout value of zero is interpreted as an infinite timeout.") + .version("1.8.0") + .timeConf + .checkValue(_ >= 0, "must be 0 or positive number") + .createWithDefault(Duration.ofSeconds(120).toMillis) + + val ENGINE_CHAT_GPT_HTTP_SOCKET_TIMEOUT: ConfigEntry[Long] = + buildConf("kyuubi.engine.chat.gpt.http.socket.timeout") + .doc("The timeout[ms] for waiting for data packets after Chat GPT server " + + "connection is established. A timeout value of zero is interpreted as an infinite timeout.") + .version("1.8.0") + .timeConf + .checkValue(_ >= 0, "must be 0 or positive number") + .createWithDefault(Duration.ofSeconds(120).toMillis) + val ENGINE_JDBC_MEMORY: ConfigEntry[String] = buildConf("kyuubi.engine.jdbc.memory") .doc("The heap memory for the JDBC query engine") From 067c6010a668c1ed9e83a991e589fb886304c959 Mon Sep 17 00:00:00 2001 From: liangbowen Date: Sat, 18 Mar 2023 15:34:16 +0800 Subject: [PATCH 186/760] [KYUUBI #4554] [CHAT] Code improvement in ChatGPTProvider ### _Why are the changes needed?_ - set authentication as default header in client construction instead of request construction - handle response's status code in scala style - transforming config's long value to int with `.intValue` instead of `asInstanceOf` casting - fix var name to `response` ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [ ] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4554 from bowenliang123/chatgpt-http. Closes #4554 114484a4d [liangbowen] httpclient improvement in ChatGPTProvider Authored-by: liangbowen Signed-off-by: Cheng Pan --- .../chat/provider/ChatGPTProvider.scala | 42 +++++++++++-------- 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/externals/kyuubi-chat-engine/src/main/scala/org/apache/kyuubi/engine/chat/provider/ChatGPTProvider.scala b/externals/kyuubi-chat-engine/src/main/scala/org/apache/kyuubi/engine/chat/provider/ChatGPTProvider.scala index 28ef36bb4..a4cdb7c94 100644 --- a/externals/kyuubi-chat-engine/src/main/scala/org/apache/kyuubi/engine/chat/provider/ChatGPTProvider.scala +++ b/externals/kyuubi-chat-engine/src/main/scala/org/apache/kyuubi/engine/chat/provider/ChatGPTProvider.scala @@ -20,12 +20,15 @@ package org.apache.kyuubi.engine.chat.provider import java.util import java.util.concurrent.TimeUnit +import scala.collection.JavaConverters._ + import com.google.common.cache.{CacheBuilder, CacheLoader, LoadingCache} -import org.apache.http.{HttpHost, HttpStatus} +import org.apache.http.{HttpHeaders, HttpHost, HttpStatus} import org.apache.http.client.config.RequestConfig import org.apache.http.client.methods.HttpPost import org.apache.http.entity.{ContentType, StringEntity} import org.apache.http.impl.client.{CloseableHttpClient, HttpClientBuilder} +import org.apache.http.message.BasicHeader import org.apache.http.util.EntityUtils import org.apache.kyuubi.config.KyuubiConf @@ -39,11 +42,16 @@ class ChatGPTProvider(conf: KyuubiConf) extends ChatProvider { s"which could be got at https://platform.openai.com/account/api-keys") } - private val httpClient: CloseableHttpClient = HttpClientBuilder.create().build() + private val httpClient: CloseableHttpClient = { + HttpClientBuilder.create() + .setDefaultHeaders(List( + new BasicHeader(HttpHeaders.AUTHORIZATION, s"Bearer $gptApiKey")).asJava) + .build() + } - private val requestConfig = { - val connectTimeout = conf.get(KyuubiConf.ENGINE_CHAT_GPT_HTTP_CONNECT_TIMEOUT).asInstanceOf[Int] - val socketTimeout = conf.get(KyuubiConf.ENGINE_CHAT_GPT_HTTP_SOCKET_TIMEOUT).asInstanceOf[Int] + private val requestConfig: RequestConfig = { + val connectTimeout = conf.get(KyuubiConf.ENGINE_CHAT_GPT_HTTP_CONNECT_TIMEOUT).intValue() + val socketTimeout = conf.get(KyuubiConf.ENGINE_CHAT_GPT_HTTP_SOCKET_TIMEOUT).intValue() val builder: RequestConfig.Builder = RequestConfig.custom() .setConnectTimeout(connectTimeout) .setSocketTimeout(socketTimeout) @@ -70,8 +78,6 @@ class ChatGPTProvider(conf: KyuubiConf) extends ChatProvider { messages.addLast(Message("user", q)) val request = new HttpPost("https://api.openai.com/v1/chat/completions") - request.addHeader("Authorization", "Bearer " + gptApiKey) - val req = Map( "messages" -> messages, "model" -> "gpt-3.5-turbo", @@ -81,17 +87,17 @@ class ChatGPTProvider(conf: KyuubiConf) extends ChatProvider { val entity = new StringEntity(mapper.writeValueAsString(req), ContentType.APPLICATION_JSON) request.setEntity(entity) request.setConfig(requestConfig) - val responseEntity = httpClient.execute(request) - val respJson = mapper.readTree(EntityUtils.toString(responseEntity.getEntity)) - val statusCode = responseEntity.getStatusLine.getStatusCode - if (responseEntity.getStatusLine.getStatusCode == HttpStatus.SC_OK) { - val replyMessage = mapper.treeToValue[Message]( - respJson.get("choices").get(0).get("message")) - messages.addLast(replyMessage) - replyMessage.content - } else { - messages.removeLast() - s"Chat failed. Status: $statusCode. ${respJson.get("error").get("message").asText}" + val response = httpClient.execute(request) + val respJson = mapper.readTree(EntityUtils.toString(response.getEntity)) + response.getStatusLine.getStatusCode match { + case HttpStatus.SC_OK => + val replyMessage = mapper.treeToValue[Message]( + respJson.get("choices").get(0).get("message")) + messages.addLast(replyMessage) + replyMessage.content + case errorStatusCode => + messages.removeLast() + s"Chat failed. Status: $errorStatusCode. ${respJson.get("error").get("message").asText}" } } From 06e69ddb2b9b522f9fabef61a59969159acb74b0 Mon Sep 17 00:00:00 2001 From: liangbowen Date: Sat, 18 Mar 2023 21:54:45 +0800 Subject: [PATCH 187/760] [KYUUBI #4295] Introduce `super-linter` action for linting JSON, XML, ENV files and bash_exec ### _Why are the changes needed?_ - introduce `action/super-linter` with multiple linters, (https://github.com/marketplace/actions/super-linter) - using the smaller size version of image `github/super-linter/slim` for faster image pulling - enable linting for `bash_exec` for checking executable of bash scripts, with fixed `tools/spark-block-cleaner/kubernetes/docker/entrypoint.sh` - enable integrity checks for JSON files in JSONC style - enable integrity checks for XML files - enable linting for ENV files ### _How was this patch tested?_ - [x] Pass CI jobs Closes #4295 from bowenliang123/superlinter-action. Closes #4295 321d24912 [liangbowen] update 497bf801c [liangbowen] use JSONC instead of JSON to support comments 43787e233 [liangbowen] use superlinter Authored-by: liangbowen Signed-off-by: liangbowen --- .github/workflows/style.yml | 26 ++++++++++++++++++- .../kubernetes/docker/entrypoint.sh | 0 2 files changed, 25 insertions(+), 1 deletion(-) mode change 100644 => 100755 tools/spark-block-cleaner/kubernetes/docker/entrypoint.sh diff --git a/.github/workflows/style.yml b/.github/workflows/style.yml index bfaf22e2e..040cdfb3e 100644 --- a/.github/workflows/style.yml +++ b/.github/workflows/style.yml @@ -38,6 +38,8 @@ jobs: steps: - uses: actions/checkout@v3 + with: + fetch-depth: 0 - name: Setup JDK 8 uses: actions/setup-java@v3 with: @@ -104,10 +106,32 @@ jobs: echo "---------------------------------------------------------------------------------" shellcheck: - name: Shellcheck + name: Super Linter and Shellcheck runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v3 + - name: Super Linter Checks + uses: github/super-linter/slim@v4 + env: + CREATE_LOG_FILE: true + ERROR_ON_MISSING_EXEC_BIT: true + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + IGNORE_GENERATED_FILES: true + IGNORE_GITIGNORED_FILES: true + LINTER_RULES_PATH: / + LOG_LEVEL: NOTICE + SUPPRESS_POSSUM: true + VALIDATE_BASH_EXEC: true + VALIDATE_ENV: true + VALIDATE_JSONC: true + VALIDATE_POWERSHELL: true + VALIDATE_XML: true + - name: Upload Super Linter logs + if: failure() + uses: actions/upload-artifact@v3 + with: + name: super-linter-log + path: super-linter.log - name: check bin directory uses: ludeeus/action-shellcheck@1.1.0 with: diff --git a/tools/spark-block-cleaner/kubernetes/docker/entrypoint.sh b/tools/spark-block-cleaner/kubernetes/docker/entrypoint.sh old mode 100644 new mode 100755 From c03664b8d8169f0d8b8b369e0bf96a5291e8361c Mon Sep 17 00:00:00 2001 From: Chao Chen Date: Sun, 19 Mar 2023 17:12:07 +0800 Subject: [PATCH 188/760] [KYUUBI #4517] [FLINK] Fix multiple executions lead to abnormal results on Flink 1.14 Fix bug in flink 1.14 version, multiple executions lead to abnormal results ### _Why are the changes needed?_ Fix bug in flink 1.14 version, multiple executions lead to abnormal results ### _How was this patch tested?_ - [X] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [X] Add screenshots for manual tests if appropriate - [X] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4517 from waywtdcc/flink1.14_result_ok_error. Closes #4517 96ce6129c [Cheng Pan] ut 1bd9d1e2f [Cheng Pan] nit 5e5bccc91 [Cheng Pan] Migrate Flink engine Java code to Scala 4afb02064 [chenchao4] Fix bug in flink 1.14 version, multiple executions lead to abnormal results 3d5dc64c5 [chenchao4] Fix bug in flink 1.14 version, multiple executions lead to abnormal results c084864bd [chenchao4] Fix bug in flink 1.14 version, multiple executions lead to abnormal results 954d76062 [chenchao4] Fix bug in flink 1.14 version, multiple executions lead to abnormal results d63ec55f2 [chenchao4] Fix bug in flink 1.14 version, multiple executions lead to abnormal results Lead-authored-by: Chao Chen Co-authored-by: chenchao4 Co-authored-by: Cheng Pan Signed-off-by: Cheng Pan --- .../kyuubi/engine/flink/result/Constants.java | 28 --- .../kyuubi/engine/flink/result/ResultSet.java | 178 ------------------ .../engine/flink/result/Constants.scala | 24 +++ .../engine/flink/result/ResultSet.scala | 139 ++++++++++++++ .../flink/operation/FlinkOperationSuite.scala | 19 +- 5 files changed, 170 insertions(+), 218 deletions(-) delete mode 100644 externals/kyuubi-flink-sql-engine/src/main/java/org/apache/kyuubi/engine/flink/result/Constants.java delete mode 100644 externals/kyuubi-flink-sql-engine/src/main/java/org/apache/kyuubi/engine/flink/result/ResultSet.java create mode 100644 externals/kyuubi-flink-sql-engine/src/main/scala/org/apache/kyuubi/engine/flink/result/Constants.scala create mode 100644 externals/kyuubi-flink-sql-engine/src/main/scala/org/apache/kyuubi/engine/flink/result/ResultSet.scala diff --git a/externals/kyuubi-flink-sql-engine/src/main/java/org/apache/kyuubi/engine/flink/result/Constants.java b/externals/kyuubi-flink-sql-engine/src/main/java/org/apache/kyuubi/engine/flink/result/Constants.java deleted file mode 100644 index b683eb76a..000000000 --- a/externals/kyuubi-flink-sql-engine/src/main/java/org/apache/kyuubi/engine/flink/result/Constants.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.kyuubi.engine.flink.result; - -/** Constant column names. */ -public class Constants { - - public static final String TABLE_TYPE = "TABLE"; - public static final String VIEW_TYPE = "VIEW"; - - public static final String[] SUPPORTED_TABLE_TYPES = new String[] {TABLE_TYPE, VIEW_TYPE}; -} diff --git a/externals/kyuubi-flink-sql-engine/src/main/java/org/apache/kyuubi/engine/flink/result/ResultSet.java b/externals/kyuubi-flink-sql-engine/src/main/java/org/apache/kyuubi/engine/flink/result/ResultSet.java deleted file mode 100644 index 66f03a159..000000000 --- a/externals/kyuubi-flink-sql-engine/src/main/java/org/apache/kyuubi/engine/flink/result/ResultSet.java +++ /dev/null @@ -1,178 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.kyuubi.engine.flink.result; - -import com.google.common.collect.Iterators; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Iterator; -import java.util.List; -import java.util.Objects; -import javax.annotation.Nullable; -import org.apache.flink.table.api.ResultKind; -import org.apache.flink.table.api.TableResult; -import org.apache.flink.table.catalog.Column; -import org.apache.flink.table.catalog.ResolvedSchema; -import org.apache.flink.types.Row; -import org.apache.flink.util.Preconditions; -import org.apache.kyuubi.operation.ArrayFetchIterator; -import org.apache.kyuubi.operation.FetchIterator; - -/** - * A set of one statement execution result containing result kind, columns, rows of data and change - * flags for streaming mode. - */ -public class ResultSet { - - private final ResultKind resultKind; - private final List columns; - private final FetchIterator data; - - // null in batch mode - // - // list of boolean in streaming mode, - // true if the corresponding row is an append row, false if its a retract row - private final List changeFlags; - - private ResultSet( - ResultKind resultKind, - List columns, - FetchIterator data, - @Nullable List changeFlags) { - this.resultKind = Preconditions.checkNotNull(resultKind, "resultKind must not be null"); - this.columns = Preconditions.checkNotNull(columns, "columns must not be null"); - this.data = Preconditions.checkNotNull(data, "data must not be null"); - this.changeFlags = changeFlags; - if (changeFlags != null) { - Preconditions.checkArgument( - Iterators.size((Iterator) data) == changeFlags.size(), - "the size of data and the size of changeFlags should be equal"); - } - } - - public List getColumns() { - return columns; - } - - public FetchIterator getData() { - return data; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - ResultSet resultSet = (ResultSet) o; - return resultKind.equals(resultSet.resultKind) - && columns.equals(resultSet.columns) - && data.equals(resultSet.data) - && Objects.equals(changeFlags, resultSet.changeFlags); - } - - @Override - public int hashCode() { - return Objects.hash(resultKind, columns, data, changeFlags); - } - - @Override - public String toString() { - return "ResultSet{" - + "resultKind=" - + resultKind - + ", columns=" - + columns - + ", data=" - + data - + ", changeFlags=" - + changeFlags - + '}'; - } - - public static ResultSet fromTableResult(TableResult tableResult) { - ResolvedSchema schema = tableResult.getResolvedSchema(); - // collect all rows from table result as list - // this is ok as TableResult contains limited rows - List rows = new ArrayList<>(); - tableResult.collect().forEachRemaining(rows::add); - return builder() - .resultKind(tableResult.getResultKind()) - .columns(schema.getColumns()) - .data(rows.toArray(new Row[0])) - .build(); - } - - public static Builder builder() { - return new Builder(); - } - - /** Builder for {@link ResultSet}. */ - public static class Builder { - private ResultKind resultKind = null; - private List columns = null; - private FetchIterator data = null; - private List changeFlags = null; - - private Builder() {} - - /** Set {@link ResultKind}. */ - public Builder resultKind(ResultKind resultKind) { - this.resultKind = resultKind; - return this; - } - - /** Set columns. */ - public Builder columns(Column... columns) { - this.columns = Arrays.asList(columns); - return this; - } - - /** Set columns. */ - public Builder columns(List columns) { - this.columns = columns; - return this; - } - - /** Set data. */ - public Builder data(FetchIterator data) { - this.data = data; - return this; - } - - /** Set data. */ - public Builder data(Row[] data) { - this.data = new ArrayFetchIterator<>(data); - return this; - } - - /** Set change flags. */ - public Builder changeFlags(List changeFlags) { - this.changeFlags = changeFlags; - return this; - } - - /** Returns a {@link ResultSet} instance. */ - public ResultSet build() { - return new ResultSet(resultKind, columns, data, changeFlags); - } - } -} diff --git a/externals/kyuubi-flink-sql-engine/src/main/scala/org/apache/kyuubi/engine/flink/result/Constants.scala b/externals/kyuubi-flink-sql-engine/src/main/scala/org/apache/kyuubi/engine/flink/result/Constants.scala new file mode 100644 index 000000000..ca582b2e3 --- /dev/null +++ b/externals/kyuubi-flink-sql-engine/src/main/scala/org/apache/kyuubi/engine/flink/result/Constants.scala @@ -0,0 +1,24 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.kyuubi.engine.flink.result + +object Constants { + val TABLE_TYPE: String = "TABLE" + val VIEW_TYPE: String = "VIEW" + val SUPPORTED_TABLE_TYPES: Array[String] = Array[String](TABLE_TYPE, VIEW_TYPE) +} diff --git a/externals/kyuubi-flink-sql-engine/src/main/scala/org/apache/kyuubi/engine/flink/result/ResultSet.scala b/externals/kyuubi-flink-sql-engine/src/main/scala/org/apache/kyuubi/engine/flink/result/ResultSet.scala new file mode 100644 index 000000000..323d157b1 --- /dev/null +++ b/externals/kyuubi-flink-sql-engine/src/main/scala/org/apache/kyuubi/engine/flink/result/ResultSet.scala @@ -0,0 +1,139 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.kyuubi.engine.flink.result + +import java.util +import java.util.Collections + +import scala.collection.JavaConverters._ + +import com.google.common.collect.Iterators +import org.apache.flink.table.api.{DataTypes, ResultKind, TableResult} +import org.apache.flink.table.api.internal.TableResultImpl +import org.apache.flink.table.catalog.{Column, ResolvedSchema} +import org.apache.flink.types.Row + +import org.apache.kyuubi.engine.flink.FlinkEngineUtils._ +import org.apache.kyuubi.operation.{ArrayFetchIterator, FetchIterator} +import org.apache.kyuubi.reflection.{DynFields, DynMethods} + +case class ResultSet( + resultKind: ResultKind, + columns: util.List[Column], + data: FetchIterator[Row], + // null in batch mode + // list of boolean in streaming mode, + // true if the corresponding row is an append row, false if its a retract row + changeFlags: Option[util.List[Boolean]]) { + + require(resultKind != null, "resultKind must not be null") + require(columns != null, "columns must not be null") + require(data != null, "data must not be null") + changeFlags.foreach { flags => + require( + Iterators.size(data.asInstanceOf[util.Iterator[_]]) == flags.size, + "the size of data and the size of changeFlags should be equal") + } + + def getColumns: util.List[Column] = columns + + def getData: FetchIterator[Row] = data +} + +/** + * A set of one statement execution result containing result kind, columns, rows of data and change + * flags for streaming mode. + */ +object ResultSet { + + private lazy val TABLE_RESULT_OK = DynFields.builder() + .hiddenImpl(classOf[TableResultImpl], "TABLE_RESULT_OK") // for Flink 1.14 + .buildStatic[TableResult] + .get + + def fromTableResult(tableResult: TableResult): ResultSet = { + // FLINK-25558, if execute multiple SQLs that return OK, the second and latter results + // would be empty, which affects Flink 1.14 + val fixedTableResult: TableResult = + if (isFlinkVersionAtMost("1.14") && tableResult == TABLE_RESULT_OK) { + // FLINK-24461 executeOperation method changes the return type + // from TableResult to TableResultInternal + val builder = TableResultImpl.builder + .resultKind(ResultKind.SUCCESS) + .schema(ResolvedSchema.of(Column.physical("result", DataTypes.STRING))) + .data(Collections.singletonList(Row.of("OK"))) + // FLINK-24461 the return type of TableResultImpl.Builder#build changed + // from TableResult to TableResultInternal + DynMethods.builder("build") + .impl(classOf[TableResultImpl.Builder]) + .build(builder) + .invoke[TableResult]() + } else { + tableResult + } + val schema = fixedTableResult.getResolvedSchema + // collect all rows from table result as list + // this is ok as TableResult contains limited rows + val rows = fixedTableResult.collect.asScala.toArray + builder.resultKind(fixedTableResult.getResultKind) + .columns(schema.getColumns) + .data(rows) + .build + } + + def builder: Builder = new ResultSet.Builder + + class Builder { + private var resultKind: ResultKind = _ + private var columns: util.List[Column] = _ + private var data: FetchIterator[Row] = _ + private var changeFlags: Option[util.List[Boolean]] = None + + def resultKind(resultKind: ResultKind): ResultSet.Builder = { + this.resultKind = resultKind + this + } + + def columns(columns: Column*): ResultSet.Builder = { + this.columns = columns.asJava + this + } + + def columns(columns: util.List[Column]): ResultSet.Builder = { + this.columns = columns + this + } + + def data(data: FetchIterator[Row]): ResultSet.Builder = { + this.data = data + this + } + + def data(data: Array[Row]): ResultSet.Builder = { + this.data = new ArrayFetchIterator[Row](data) + this + } + + def changeFlags(changeFlags: util.List[Boolean]): ResultSet.Builder = { + this.changeFlags = Some(changeFlags) + this + } + + def build: ResultSet = new ResultSet(resultKind, columns, data, changeFlags) + } +} diff --git a/externals/kyuubi-flink-sql-engine/src/test/scala/org/apache/kyuubi/engine/flink/operation/FlinkOperationSuite.scala b/externals/kyuubi-flink-sql-engine/src/test/scala/org/apache/kyuubi/engine/flink/operation/FlinkOperationSuite.scala index d0522d3ea..70eb84ddb 100644 --- a/externals/kyuubi-flink-sql-engine/src/test/scala/org/apache/kyuubi/engine/flink/operation/FlinkOperationSuite.scala +++ b/externals/kyuubi-flink-sql-engine/src/test/scala/org/apache/kyuubi/engine/flink/operation/FlinkOperationSuite.scala @@ -876,20 +876,15 @@ class FlinkOperationSuite extends WithFlinkSQLEngine with HiveJDBCTestHelper { } test("execute statement - create/drop catalog") { - withJdbcStatement()({ statement => - val createResult = { + withJdbcStatement() { statement => + val createResult = statement.executeQuery("create catalog cat_a with ('type'='generic_in_memory')") - } - if (isFlinkVersionAtLeast("1.15")) { - assert(createResult.next()) - assert(createResult.getString(1) === "OK") - } + assert(createResult.next()) + assert(createResult.getString(1) === "OK") val dropResult = statement.executeQuery("drop catalog cat_a") - if (isFlinkVersionAtLeast("1.15")) { - assert(dropResult.next()) - assert(dropResult.getString(1) === "OK") - } - }) + assert(dropResult.next()) + assert(dropResult.getString(1) === "OK") + } } test("execute statement - set/get catalog") { From 24e87ef21a4aeb3eaf2a951533b591b56d5f9a52 Mon Sep 17 00:00:00 2001 From: liangbowen Date: Sun, 19 Mar 2023 19:24:41 +0800 Subject: [PATCH 189/760] [KYUUBI #4556] [CHAT] Refactor ChatGPTProvider to use `openai-java` client ### _Why are the changes needed?_ - use Java SDK `openai-java` for ChatGPT which is popular and listed in official website, https://github.com/TheoKanning/openai-java - Focus on lifecycle in ChatGPTProvider, and prevent handling lower-level concepts in details, like POJO mapping, HTTP request handling. - follow the changes from upstream changes from OpenAI ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [x] Add screenshots for manual tests if appropriate - [x] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4556 from bowenliang123/chatgpt-third. Closes #4556 ecf1e2cf6 [liangbowen] manually add `openai-gpt3-java:*` and its dependency to LICENSE-binary 53b8375a5 [liangbowen] refactor ChatGPTProvider to use `openai-java` SDK Authored-by: liangbowen Signed-off-by: liangbowen --- LICENSE-binary | 5 ++ externals/kyuubi-chat-engine/pom.xml | 5 +- .../chat/provider/ChatGPTProvider.scala | 87 ++++++++----------- pom.xml | 7 ++ 4 files changed, 53 insertions(+), 51 deletions(-) diff --git a/LICENSE-binary b/LICENSE-binary index 92daf62ab..feab9965e 100644 --- a/LICENSE-binary +++ b/LICENSE-binary @@ -319,6 +319,8 @@ io.swagger.core.v3:swagger-models io.vertx:vertx-core io.vertx:vertx-grpc org.apache.zookeeper:zookeeper +com.squareup.retrofit2:retrofit +com.squareup.okhttp3:okhttp BSD ------------ @@ -356,6 +358,9 @@ org.codehaus.mojo:animal-sniffer-annotations org.slf4j:slf4j-api org.slf4j:jcl-over-slf4j org.slf4j:jul-over-slf4j +com.theokanning.openai-gpt3-java:api +com.theokanning.openai-gpt3-java:client +com.theokanning.openai-gpt3-java:service kyuubi-server/src/main/resources/org/apache/kyuubi/ui/static/assets/fonts/* kyuubi-server/src/main/resources/org/apache/kyuubi/ui/static/icon.min.css diff --git a/externals/kyuubi-chat-engine/pom.xml b/externals/kyuubi-chat-engine/pom.xml index 7e2178918..28779f450 100644 --- a/externals/kyuubi-chat-engine/pom.xml +++ b/externals/kyuubi-chat-engine/pom.xml @@ -45,8 +45,9 @@ - org.apache.httpcomponents - httpclient + com.theokanning.openai-gpt3-java + service + ${openai.java.version} diff --git a/externals/kyuubi-chat-engine/src/main/scala/org/apache/kyuubi/engine/chat/provider/ChatGPTProvider.scala b/externals/kyuubi-chat-engine/src/main/scala/org/apache/kyuubi/engine/chat/provider/ChatGPTProvider.scala index a4cdb7c94..2e4bf3f8d 100644 --- a/externals/kyuubi-chat-engine/src/main/scala/org/apache/kyuubi/engine/chat/provider/ChatGPTProvider.scala +++ b/externals/kyuubi-chat-engine/src/main/scala/org/apache/kyuubi/engine/chat/provider/ChatGPTProvider.scala @@ -17,22 +17,20 @@ package org.apache.kyuubi.engine.chat.provider +import java.net.{InetSocketAddress, Proxy, URL} +import java.time.Duration import java.util import java.util.concurrent.TimeUnit import scala.collection.JavaConverters._ import com.google.common.cache.{CacheBuilder, CacheLoader, LoadingCache} -import org.apache.http.{HttpHeaders, HttpHost, HttpStatus} -import org.apache.http.client.config.RequestConfig -import org.apache.http.client.methods.HttpPost -import org.apache.http.entity.{ContentType, StringEntity} -import org.apache.http.impl.client.{CloseableHttpClient, HttpClientBuilder} -import org.apache.http.message.BasicHeader -import org.apache.http.util.EntityUtils +import com.theokanning.openai.OpenAiApi +import com.theokanning.openai.completion.chat.{ChatCompletionRequest, ChatMessage} +import com.theokanning.openai.service.OpenAiService +import com.theokanning.openai.service.OpenAiService.{defaultClient, defaultObjectMapper, defaultRetrofit} import org.apache.kyuubi.config.KyuubiConf -import org.apache.kyuubi.engine.chat.provider.ChatProvider.mapper class ChatGPTProvider(conf: KyuubiConf) extends ChatProvider { @@ -42,31 +40,32 @@ class ChatGPTProvider(conf: KyuubiConf) extends ChatProvider { s"which could be got at https://platform.openai.com/account/api-keys") } - private val httpClient: CloseableHttpClient = { - HttpClientBuilder.create() - .setDefaultHeaders(List( - new BasicHeader(HttpHeaders.AUTHORIZATION, s"Bearer $gptApiKey")).asJava) - .build() - } + private val openAiService: OpenAiService = { + val builder = defaultClient( + gptApiKey, + Duration.ofMillis(conf.get(KyuubiConf.ENGINE_CHAT_GPT_HTTP_SOCKET_TIMEOUT))) + .newBuilder + .connectTimeout(Duration.ofMillis(conf.get(KyuubiConf.ENGINE_CHAT_GPT_HTTP_CONNECT_TIMEOUT))) - private val requestConfig: RequestConfig = { - val connectTimeout = conf.get(KyuubiConf.ENGINE_CHAT_GPT_HTTP_CONNECT_TIMEOUT).intValue() - val socketTimeout = conf.get(KyuubiConf.ENGINE_CHAT_GPT_HTTP_SOCKET_TIMEOUT).intValue() - val builder: RequestConfig.Builder = RequestConfig.custom() - .setConnectTimeout(connectTimeout) - .setSocketTimeout(socketTimeout) - conf.get(KyuubiConf.ENGINE_CHAT_GPT_HTTP_PROXY).foreach { url => - builder.setProxy(HttpHost.create(url)) + conf.get(KyuubiConf.ENGINE_CHAT_GPT_HTTP_PROXY) match { + case Some(httpProxyUrl) => + val url = new URL(httpProxyUrl) + val proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(url.getHost, url.getPort)) + builder.proxy(proxy) + case _ => } - builder.build() + + val retrofit = defaultRetrofit(builder.build(), defaultObjectMapper) + val api = retrofit.create(classOf[OpenAiApi]) + new OpenAiService(api) } - private val chatHistory: LoadingCache[String, util.ArrayDeque[Message]] = + private val chatHistory: LoadingCache[String, util.ArrayDeque[ChatMessage]] = CacheBuilder.newBuilder() .expireAfterWrite(10, TimeUnit.MINUTES) - .build(new CacheLoader[String, util.ArrayDeque[Message]] { - override def load(sessionId: String): util.ArrayDeque[Message] = - new util.ArrayDeque[Message] + .build(new CacheLoader[String, util.ArrayDeque[ChatMessage]] { + override def load(sessionId: String): util.ArrayDeque[ChatMessage] = + new util.ArrayDeque[ChatMessage] }) override def open(sessionId: String): Unit = { @@ -75,29 +74,19 @@ class ChatGPTProvider(conf: KyuubiConf) extends ChatProvider { override def ask(sessionId: String, q: String): String = { val messages = chatHistory.get(sessionId) - messages.addLast(Message("user", q)) - - val request = new HttpPost("https://api.openai.com/v1/chat/completions") - val req = Map( - "messages" -> messages, - "model" -> "gpt-3.5-turbo", - "max_tokens" -> 200, - "temperature" -> 0.5, - "top_p" -> 1) - val entity = new StringEntity(mapper.writeValueAsString(req), ContentType.APPLICATION_JSON) - request.setEntity(entity) - request.setConfig(requestConfig) - val response = httpClient.execute(request) - val respJson = mapper.readTree(EntityUtils.toString(response.getEntity)) - response.getStatusLine.getStatusCode match { - case HttpStatus.SC_OK => - val replyMessage = mapper.treeToValue[Message]( - respJson.get("choices").get(0).get("message")) - messages.addLast(replyMessage) - replyMessage.content - case errorStatusCode => + try { + messages.addLast(new ChatMessage("user", q)) + val completionRequest = ChatCompletionRequest.builder() + .messages(messages.asScala.toList.asJava) + .model("gpt-3.5-turbo") + .build() + val responseText = openAiService.createChatCompletion(completionRequest).getChoices.asScala + .map(c => c.getMessage.getContent).mkString + responseText + } catch { + case e: Throwable => messages.removeLast() - s"Chat failed. Status: $errorStatusCode. ${respJson.get("error").get("message").asText}" + s"Chat failed. Error: ${e.getMessage}" } } diff --git a/pom.xml b/pom.xml index a2de5d29d..7f6d1ed71 100644 --- a/pom.xml +++ b/pom.xml @@ -174,6 +174,7 @@ 2.20.0 8.0.32 4.1.89.Final + 0.11.1 1.10.1 6.0.0 0.16.0 @@ -1658,6 +1659,12 @@ py4j ${py4j.version} + + + com.theokanning.openai-gpt3-java + service + ${openai.java.version} + From 301a05af5918cbd0eb90c9ddd1a9390b9d93dbe6 Mon Sep 17 00:00:00 2001 From: liangbowen Date: Sun, 19 Mar 2023 22:27:58 +0800 Subject: [PATCH 190/760] [KYUUBI #4558] [CHAT] Make ChatGPT model ID configurable ### _Why are the changes needed?_ - Make ChatGPT model ID configurable ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [x] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4558 from bowenliang123/chatgpt-model. Closes #4558 63f8ee30d [liangbowen] nit 3012ccaaa [liangbowen] make chatgpt model configurable Authored-by: liangbowen Signed-off-by: liangbowen --- docs/deployment/settings.md | 1 + .../kyuubi/engine/chat/provider/ChatGPTProvider.scala | 2 +- .../main/scala/org/apache/kyuubi/config/KyuubiConf.scala | 8 ++++++++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/docs/deployment/settings.md b/docs/deployment/settings.md index db6e3d246..30f557704 100644 --- a/docs/deployment/settings.md +++ b/docs/deployment/settings.md @@ -125,6 +125,7 @@ You can configure the Kyuubi properties in `$KYUUBI_HOME/conf/kyuubi-defaults.co | kyuubi.engine.chat.gpt.http.connect.timeout | PT2M | The timeout[ms] for establishing the connection with the Chat GPT server. A timeout value of zero is interpreted as an infinite timeout. | duration | 1.8.0 | | kyuubi.engine.chat.gpt.http.proxy | <undefined> | HTTP proxy url for API calling in Chat GPT engine. e.g. http://127.0.0.1:1087 | string | 1.8.0 | | kyuubi.engine.chat.gpt.http.socket.timeout | PT2M | The timeout[ms] for waiting for data packets after Chat GPT server connection is established. A timeout value of zero is interpreted as an infinite timeout. | duration | 1.8.0 | +| kyuubi.engine.chat.gpt.model | gpt-3.5-turbo | ID of the model used in ChatGPT. Available models refer to OpenAI's [Model overview](https://platform.openai.com/docs/models/overview). | string | 1.8.0 | | kyuubi.engine.chat.java.options | <undefined> | The extra Java options for the Chat engine | string | 1.8.0 | | kyuubi.engine.chat.memory | 1g | The heap memory for the Chat engine | string | 1.8.0 | | kyuubi.engine.chat.provider | ECHO | The provider for the Chat engine. Candidates:
    • ECHO: simply replies a welcome message.
    • GPT: a.k.a ChatGPT, powered by OpenAI.
    | string | 1.8.0 | diff --git a/externals/kyuubi-chat-engine/src/main/scala/org/apache/kyuubi/engine/chat/provider/ChatGPTProvider.scala b/externals/kyuubi-chat-engine/src/main/scala/org/apache/kyuubi/engine/chat/provider/ChatGPTProvider.scala index 2e4bf3f8d..cdea89d2a 100644 --- a/externals/kyuubi-chat-engine/src/main/scala/org/apache/kyuubi/engine/chat/provider/ChatGPTProvider.scala +++ b/externals/kyuubi-chat-engine/src/main/scala/org/apache/kyuubi/engine/chat/provider/ChatGPTProvider.scala @@ -77,8 +77,8 @@ class ChatGPTProvider(conf: KyuubiConf) extends ChatProvider { try { messages.addLast(new ChatMessage("user", q)) val completionRequest = ChatCompletionRequest.builder() + .model(conf.get(KyuubiConf.ENGINE_CHAT_GPT_MODEL)) .messages(messages.asScala.toList.asJava) - .model("gpt-3.5-turbo") .build() val responseText = openAiService.createChatCompletion(completionRequest).getChoices.asScala .map(c => c.getMessage.getContent).mkString diff --git a/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala b/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala index 35626eea5..359ee1ba5 100644 --- a/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala +++ b/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala @@ -2659,6 +2659,14 @@ object KyuubiConf { .stringConf .createOptional + val ENGINE_CHAT_GPT_MODEL: ConfigEntry[String] = + buildConf("kyuubi.engine.chat.gpt.model") + .doc("ID of the model used in ChatGPT. Available models refer to OpenAI's " + + "[Model overview](https://platform.openai.com/docs/models/overview).") + .version("1.8.0") + .stringConf + .createWithDefault("gpt-3.5-turbo") + val ENGINE_CHAT_EXTRA_CLASSPATH: OptionalConfigEntry[String] = buildConf("kyuubi.engine.chat.extra.classpath") .doc("The extra classpath for the Chat engine, for configuring the location " + From 391be269c3dd3072ea9b27ced9fb895e89bf2bf1 Mon Sep 17 00:00:00 2001 From: Cheng Pan Date: Mon, 20 Mar 2023 17:06:54 +0800 Subject: [PATCH 191/760] [KYUUBI #4559] [CHAT] Pretty chat engine launch command ### _Why are the changes needed?_ ``` 2023-03-20 03:31:12.571 INFO org.apache.kyuubi.engine.EngineRef: Launching engine: /Users/chengpan/.sdkman/candidates/java/current/bin/java \ -Xmx1g \ -cp /Users/chengpan/Projects/apache-kyuubi/externals/kyuubi-chat-engine/target/kyuubi-chat-engine_2.12-1.8.0-SNAPSHOT.jar:/Users/chengpan/Projects/apache-kyuubi/externals/kyuubi-chat-engine/target/scala-2.12/jars/* \ org.apache.kyuubi.engine.chat.ChatEngine \ --conf kyuubi.session.user=chengpan \ --conf hive.server2.thrift.resultset.default.fetch.size=1000 \ --conf kyuubi.client.ipAddress=10.221.96.184 \ --conf kyuubi.engine.submit.time=1679254587454 \ --conf kyuubi.engine.type=CHAT \ --conf kyuubi.ha.addresses=10.221.96.184:2181 \ --conf kyuubi.ha.engine.ref.id=1a19e1e5-d8d3-49eb-bee0-3c806212134b \ --conf kyuubi.ha.namespace=/kyuubi_1.8.0-SNAPSHOT_USER_CHAT/chengpan/default \ --conf kyuubi.ha.zookeeper.auth.type=NONE \ --conf kyuubi.server.ipAddress=10.221.96.184 \ --conf kyuubi.session.connection.url=10.221.96.184:10009 \ --conf kyuubi.session.real.user=chengpan ``` ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [x] Add screenshots for manual tests if appropriate - [ ] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4559 from pan3793/gpt-cmd. Closes #4559 1ba70f02d [Cheng Pan] nit 34aecc1b0 [Cheng Pan] nit f4815866d [Cheng Pan] nit 526c79f15 [Cheng Pan] nit 28bd9d9fa [Cheng Pan] [CHAT] Pretty chat engine launch command Authored-by: Cheng Pan Signed-off-by: Cheng Pan --- .../org/apache/kyuubi/engine/chat/ChatProcessBuilder.scala | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/engine/chat/ChatProcessBuilder.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/engine/chat/ChatProcessBuilder.scala index 532c5dddf..3e4a20de3 100644 --- a/kyuubi-server/src/main/scala/org/apache/kyuubi/engine/chat/ChatProcessBuilder.scala +++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/engine/chat/ChatProcessBuilder.scala @@ -94,7 +94,7 @@ class ChatProcessBuilder( buffer += "--conf" buffer += s"$KYUUBI_SESSION_USER_KEY=$proxyUser" - for ((k, v) <- conf.getAll) { + conf.getAll.foreach { case (k, v) => buffer += "--conf" buffer += s"$k=$v" } @@ -106,10 +106,11 @@ class ChatProcessBuilder( super.toString() } else { Utils.redactCommandLineArgs(conf, commands).map { + case arg if arg.startsWith("-") || arg == mainClass => s"\\\n\t$arg" case arg if arg.contains(ENGINE_CHAT_GPT_API_KEY.key) => s"${ENGINE_CHAT_GPT_API_KEY.key}=$REDACTION_REPLACEMENT_TEXT" case arg => arg - }.mkString("\n") + }.mkString(" ") } } } From 465e23a0f67088f0f24467cc531abcce27896403 Mon Sep 17 00:00:00 2001 From: "xu.guo" Date: Mon, 20 Mar 2023 17:13:28 +0800 Subject: [PATCH 192/760] [KYUUBI #4508][BEELINE] Beeline should reset stdin after consuming init SQL file ### _Why are the changes needed?_ To fix #4508 . ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [ ] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4510 from thomasg19930417/master. Closes #4508 f8405eb93 [Cheng Pan] Update kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/KyuubiCommands.java 3ad46c1d6 [Cheng Pan] Update kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/KyuubiCommands.java 31fa80771 [xu.guo] Merge branch 'master' of https://github.com/thomasg19930417/kyuubi 41e090d66 [thomasgx] Merge branch 'apache:master' into master ba82229a8 [xu.guo] Move initializeConsoleReader to KyuubiCommands#connect e06927b78 [xu.guo] Replace create new consoleReader instance with call initializeConsoleReader 86f2e5078 [xu.guo] Fix code style b0c472229 [xu.guo] Fix Beeline with -i run sql faild 042987aa6 [xu.guo] Fix Beeline with -i run sql faild Lead-authored-by: xu.guo Co-authored-by: Cheng Pan Co-authored-by: thomasgx <570736711@qq.com> Signed-off-by: Cheng Pan --- .../src/main/java/org/apache/hive/beeline/KyuubiCommands.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/KyuubiCommands.java b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/KyuubiCommands.java index aaa32739a..bf368ce0d 100644 --- a/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/KyuubiCommands.java +++ b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/KyuubiCommands.java @@ -485,6 +485,9 @@ public boolean connect(Properties props) throws IOException { beeLine.updateOptsForCli(); } beeLine.runInit(); + if (beeLine.getOpts().getInitFiles() != null) { + beeLine.initializeConsoleReader(null); + } beeLine.setCompletions(); beeLine.getOpts().setLastConnectedUrl(url); From 7727a6267de6583833c9bd5cfaa6c8ebe0982354 Mon Sep 17 00:00:00 2001 From: Binjie Yang <52876270+zwangsheng@users.noreply.github.com> Date: Tue, 21 Mar 2023 20:49:50 +0800 Subject: [PATCH 193/760] [KYUUBI #4565] [UI] Fix out-date `README.md` about `Development Project` ### _Why are the changes needed?_ Fix out-date `README.md` about `Development Project` ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [ ] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4565 from zwangsheng/doc/fix_web_ui_readme. Closes #4565 a08698845 [Binjie Yang] Update kyuubi-server/web-ui/README.md 5b3e25313 [Binjie Yang] Update kyuubi-server/web-ui/README.md 10adf17fb [Binjie Yang] Update kyuubi-server/web-ui/README.md a7ee5a4fe [zwangsheng] [UI] Fix out-date README.md about run dev b4d4f6076 [zwangsheng] [UI] Fix out-date README.md about run dev Lead-authored-by: Binjie Yang <52876270+zwangsheng@users.noreply.github.com> Co-authored-by: zwangsheng <2213335496@qq.com> Signed-off-by: liangbowen --- kyuubi-server/web-ui/README.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/kyuubi-server/web-ui/README.md b/kyuubi-server/web-ui/README.md index f93373414..b892a6902 100644 --- a/kyuubi-server/web-ui/README.md +++ b/kyuubi-server/web-ui/README.md @@ -15,7 +15,15 @@ npm install ### Development Project -To do this you can change the VITE_APP_DEV_WEB_URL parameter variable as the service url in `.env.development` in the project root directory, such as http://127.0.0.1:8090 +Notice: + +Before you start the Web UI project, please make sure the Kyuubi server has been started. + +Kyuubi Web UI will proxy the requests to Kyuubi server, with the default endpoint path to`http://localhost:10099`. Modify `VITE_APP_DEV_WEB_URL` in `.env.development` for customizing targeted endpoint path. + +#### Why proxy to http://localhost:10099 + +Currently kyuubi server binds on `http://0.0.0.0:10099` in case your are running kyuubi server in MacOS or Windows(If in linux, you should config kyuubi server `kyuubi.frontend.rest.bind.host=0.0.0.0`, or change `VITE_APP_DEV_WEB_URL` in `.env.development`). ```shell npm run dev From e00f6b5bf45a461cb8a887c4944c205f2c16902e Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 21 Mar 2023 20:58:01 +0800 Subject: [PATCH 194/760] [KYUUBI #4568] Make kyuubi-ctl doc enable variable 'release' automatic substitution ### _Why are the changes needed?_ Kyuubi-ctl doc kyuubi version not convert automatic. The version of doc is 1.8.0-SNAPSHOT, but kyuubi version is 1.6.0-SNAPSHOT. image ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [x] Add screenshots for manual tests if appropriate image - [ ] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request Closes #4568 from Kiss736921/optimize_ctl_doc. Closes #4568 eb10a9e3b [Alex] Update docs/tools/kyuubi-ctl.rst 2006a59b3 [Alex] Update docs/tools/kyuubi-ctl.rst 2501e119f [Alex] convert kyuubi-ctl.md to kyuubi-ctl.rst to enable variable 'release' automatic substitution Lead-authored-by: Alex Co-authored-by: Alex <43542750+Kiss736921@users.noreply.github.com> Signed-off-by: Cheng Pan --- docs/tools/kyuubi-ctl.md | 183 -------------------------------- docs/tools/kyuubi-ctl.rst | 213 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 213 insertions(+), 183 deletions(-) delete mode 100644 docs/tools/kyuubi-ctl.md create mode 100644 docs/tools/kyuubi-ctl.rst diff --git a/docs/tools/kyuubi-ctl.md b/docs/tools/kyuubi-ctl.md deleted file mode 100644 index aae67584e..000000000 --- a/docs/tools/kyuubi-ctl.md +++ /dev/null @@ -1,183 +0,0 @@ - - -# Managing kyuubi servers and engines Tool - -## Usage - -```shell -bin/kyuubi-ctl --help -``` - -Output - -```shell -kyuubi 1.6.0-SNAPSHOT -Usage: kyuubi-ctl [create|get|delete|list] [options] - - -zk, --zk-quorum - The connection string for the zookeeper ensemble, using zk quorum manually. - -n, --namespace The namespace, using kyuubi-defaults/conf if absent. - -s, --host Hostname or IP address of a service. - -p, --port Listening port of a service. - -v, --version Using the compiled KYUUBI_VERSION default, change it if the active service is running in another. - -b, --verbose Print additional debug output. - -Command: create [server] - -Command: create server - Expose Kyuubi server instance to another domain. - -Command: get [server|engine] [options] - Get the service/engine node info, host and port needed. -Command: get server - Get Kyuubi server info of domain -Command: get engine - Get Kyuubi engine info belong to a user. - -u, --user The user name this engine belong to. - -et, --engine-type - The engine type this engine belong to. - -es, --engine-subdomain - The engine subdomain this engine belong to. - -esl, --engine-share-level - The engine share level this engine belong to. - -Command: delete [server|engine] [options] - Delete the specified service/engine node, host and port needed. -Command: delete server - Delete the specified service node for a domain -Command: delete engine - Delete the specified engine node for user. - -u, --user The user name this engine belong to. - -et, --engine-type - The engine type this engine belong to. - -es, --engine-subdomain - The engine subdomain this engine belong to. - -esl, --engine-share-level - The engine share level this engine belong to. - -Command: list [server|engine] [options] - List all the service/engine nodes for a particular domain. -Command: list server - List all the service nodes for a particular domain -Command: list engine - List all the engine nodes for a user - -u, --user The user name this engine belong to. - -et, --engine-type - The engine type this engine belong to. - -es, --engine-subdomain - The engine subdomain this engine belong to. - -esl, --engine-share-level - The engine share level this engine belong to. - - -h, --help Show help message and exit. -``` - -## Manage kyuubi servers - -You can specify the zookeeper address(`--zk-quorum`) and namespace(`--namespace`), version(`--version`) parameters to query a specific kyuubi server cluster. - -### List server - -List all the service nodes for a particular domain. - -```shell -bin/kyuubi-ctl list server -``` - -### Create server - -Expose Kyuubi server instance to another domain. - -First read `kyuubi.ha.zookeeper.namespace` in `conf/kyuubi-defaults.conf`, if there are server instances under this namespace, register them in the new namespace specified by the `--namespace` parameter. - -```shell -bin/kyuubi-ctl create server --namespace XXX -``` - -### Get server - -Get Kyuubi server info of domain. - -```shell -bin/kyuubi-ctl get server --host XXX --port YYY -``` - -### Delete server - -Delete the specified service node for a domain. - -After the server node is deleted, the kyuubi server stops opening new sessions and waits for all currently open sessions to be closed before the process exits. - -```shell -bin/kyuubi-ctl delete server --host XXX --port YYY -``` - -## Manage kyuubi engines - -You can also specify the engine type(`--engine-type`), engine share level subdomain(`--engine-subdomain`) and engine share level(`--engine-share-level`). - -If not specified, the configuration item `kyuubi.engine.type` of `kyuubi-defaults.conf` read, the default value is `SPARK_SQL`, `kyuubi.engine.share.level.subdomain`, the default value is `default`, `kyuubi.engine.share.level`, the default value is `USER`. - -If the engine pool mode is enabled through `kyuubi.engine.pool.size`, the subdomain consists of `kyuubi.engine.pool.name` and a number below size, e.g. `engine-pool-0` . - -`--engine-share-level` supports the following enum values. -* CONNECTION - -The engine Ref Id (UUID) must be specified via `--engine-subdomain`. -* USER: - -Default Value. -* GROUP: - -The `--user` parameter is the group name corresponding to the user. -* SERVER: - -The `--user` parameter is the user who started the kyuubi server. - -### List engine - -List all the engine nodes for a user. - -```shell -bin/kyuubi-ctl list engine --user AAA -``` - -The management share level is SERVER, the user who starts the kyuubi server is A, the engine is TRINO, and the subdomain is adhoc. - -```shell -bin/kyuubi-ctl list engine --user A --engine-type TRINO --engine-subdomain adhoc --engine-share-level SERVER -``` - -### Get engine - -Get Kyuubi engine info belong to a user. - -```shell -bin/kyuubi-ctl get engine --user AAA --host XXX --port YYY -``` - -### Delete engine - -Delete the specified engine node for user. - -After the engine node is deleted, the kyuubi engine stops opening new sessions and waits for all currently open sessions to be closed before the process exits. - -```shell -bin/kyuubi-ctl delete engine --user AAA --host XXX --port YYY -``` - diff --git a/docs/tools/kyuubi-ctl.rst b/docs/tools/kyuubi-ctl.rst new file mode 100644 index 000000000..4a9308fed --- /dev/null +++ b/docs/tools/kyuubi-ctl.rst @@ -0,0 +1,213 @@ +.. Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + +.. http://www.apache.org/licenses/LICENSE-2.0 + +.. Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +Administrator CLI +================= + +.. _usage: + +Usage +----- +.. code-block:: bash + + bin/kyuubi-ctl --help + +Output + +.. parsed-literal:: + + kyuubi |release| + Usage: kyuubi-ctl [create|get|delete|list] [options] + + -zk, --zk-quorum + The connection string for the zookeeper ensemble, using zk quorum manually. + -n, --namespace The namespace, using kyuubi-defaults/conf if absent. + -s, --host Hostname or IP address of a service. + -p, --port Listening port of a service. + -v, --version Using the compiled KYUUBI_VERSION default, change it if the active service is running in another. + -b, --verbose Print additional debug output. + + Command: create [server] + + Command: create server + Expose Kyuubi server instance to another domain. + + Command: get [server|engine] [options] + Get the service/engine node info, host and port needed. + Command: get server + Get Kyuubi server info of domain + Command: get engine + Get Kyuubi engine info belong to a user. + -u, --user The user name this engine belong to. + -et, --engine-type + The engine type this engine belong to. + -es, --engine-subdomain + The engine subdomain this engine belong to. + -esl, --engine-share-level + The engine share level this engine belong to. + + Command: delete [server|engine] [options] + Delete the specified service/engine node, host and port needed. + Command: delete server + Delete the specified service node for a domain + Command: delete engine + Delete the specified engine node for user. + -u, --user The user name this engine belong to. + -et, --engine-type + The engine type this engine belong to. + -es, --engine-subdomain + The engine subdomain this engine belong to. + -esl, --engine-share-level + The engine share level this engine belong to. + + Command: list [server|engine] [options] + List all the service/engine nodes for a particular domain. + Command: list server + List all the service nodes for a particular domain + Command: list engine + List all the engine nodes for a user + -u, --user The user name this engine belong to. + -et, --engine-type + The engine type this engine belong to. + -es, --engine-subdomain + The engine subdomain this engine belong to. + -esl, --engine-share-level + The engine share level this engine belong to. + + -h, --help Show help message and exit. + +.. _manage_kyuubi_servers: + +Manage kyuubi servers +--------------------- + +You can specify the zookeeper address(``--zk-quorum``) and namespace(``--namespace``), version(``--version``) parameters to query a specific kyuubi server cluster. + +.. _list_servers: + +List server +*********** + +List all the service nodes for a particular domain. + +.. code-block:: bash + + bin/kyuubi-ctl list server + +.. _create_servers: + +Create server +*********** +Expose Kyuubi server instance to another domain. + +First read ``kyuubi.ha.zookeeper.namespace`` in ``conf/kyuubi-defaults.conf``, if there are server instances under this namespace, register them in the new namespace specified by the ``--namespace`` parameter. + +.. code-block:: bash + + bin/kyuubi-ctl create server --namespace XXX + +.. _get_servers: + +Get server +*********** + +Get Kyuubi server info of domain. + +.. code-block:: bash + + bin/kyuubi-ctl get server --host XXX --port YYY + +.. _delete_servers: + +Delete server +*********** + +Delete the specified service node for a domain. + +After the server node is deleted, the kyuubi server stops opening new sessions and waits for all currently open sessions to be closed before the process exits. + +.. code-block:: bash + + bin/kyuubi-ctl delete server --host XXX --port YYY + +.. _manage_kyuubi_engines: + +Manage kyuubi engines +--------------------- + +You can also specify the engine type(``--engine-type``), engine share level subdomain(``--engine-subdomain``) and engine share level(``--engine-share-level``). + +If not specified, the configuration item ``kyuubi.engine.type`` of ``kyuubi-defaults.conf`` read, the default value is ``SPARK_SQL``, ``kyuubi.engine.share.level.subdomain``, the default value is ``default``, ``kyuubi.engine.share.level``, the default value is ``USER``. + +If the engine pool mode is enabled through ``kyuubi.engine.pool.size``, the subdomain consists of ``kyuubi.engine.pool.name`` and a number below size, e.g. ``engine-pool-0`` . + +``--engine-share-level`` supports the following enum values. + +- CONNECTION + +The engine Ref Id (UUID) must be specified via ``--engine-subdomain``. + +- USER: + +Default Value. + +- GROUP: + +The ``--user`` parameter is the group name corresponding to the user. + +- SERVER: + +The ``--user`` parameter is the user who started the kyuubi server. + +.. _list_engines: + +List engine +*********** + +List all the engine nodes for a user. + +.. code-block:: bash + + bin/kyuubi-ctl list engine --user AAA + +The management share level is SERVER, the user who starts the kyuubi server is A, the engine is TRINO, and the subdomain is adhoc. + +.. code-block:: bash + + bin/kyuubi-ctl list engine --user A --engine-type TRINO --engine-subdomain adhoc --engine-share-level SERVER + +.. _get_engines: + +Get engine +*********** + +Get Kyuubi engine info belong to a user. + +.. code-block:: bash + + bin/kyuubi-ctl get engine --user AAA --host XXX --port YYY + +.. _delete_engines: + +Delete engine +************* + +Delete the specified engine node for user. + +After the engine node is deleted, the kyuubi engine stops opening new sessions and waits for all currently open sessions to be closed before the process exits. + +.. code-block:: bash + + bin/kyuubi-ctl delete engine --user AAA --host XXX --port YYY \ No newline at end of file From 824aba60708c0973ee638eeb97d296e2f8dc737e Mon Sep 17 00:00:00 2001 From: zwangsheng <2213335496@qq.com> Date: Tue, 21 Mar 2023 21:05:57 +0800 Subject: [PATCH 195/760] [KYUUBI #3646][Improvement][UI] Front-end style should bracket same line ### _Why are the changes needed?_ Should bracket same line. According to https://github.com/npetruzzelli/prettier-config-standard, should set true to bracketSameLine and also drop jsxBracketSameLine, which been deprecated. Close #3646 ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [ ] Add screenshots for manual tests if appropriate - [ ] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request - [x] CI Closes #4571 from zwangsheng/KYUUBI_4570. Closes #3646 2b61d1173 [zwangsheng] [KYUUBI #3646] bracket same line Authored-by: zwangsheng <2213335496@qq.com> Signed-off-by: Cheng Pan --- kyuubi-server/web-ui/.prettierrc | 2 +- kyuubi-server/web-ui/src/components/menu/index.vue | 9 +++------ .../web-ui/src/views/layout/components/header/index.vue | 3 +-- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/kyuubi-server/web-ui/.prettierrc b/kyuubi-server/web-ui/.prettierrc index 1fceefb98..01db7f49b 100644 --- a/kyuubi-server/web-ui/.prettierrc +++ b/kyuubi-server/web-ui/.prettierrc @@ -4,7 +4,7 @@ "vueIndentScriptAndStyle": true, "singleQuote": true, "quoteProps": "as-needed", - "jsxBracketSameLine": false, + "bracketSameLine": true, "jsxSingleQuote": true, "arrowParens": "always", "htmlWhitespaceSensitivity": "strict", diff --git a/kyuubi-server/web-ui/src/components/menu/index.vue b/kyuubi-server/web-ui/src/components/menu/index.vue index b563b491e..d6d4d1b56 100644 --- a/kyuubi-server/web-ui/src/components/menu/index.vue +++ b/kyuubi-server/web-ui/src/components/menu/index.vue @@ -21,14 +21,12 @@ class="el-menu-container" :collapse="isCollapse" :default-active="activePath" - :router="true" - > + :router="true"> - +

    5CYfgVecl=fIuX0H4`+X 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