Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,15 @@

package org.apache.texera.service

import com.fasterxml.jackson.module.scala.DefaultScalaModule
import com.typesafe.scalalogging.LazyLogging
import io.dropwizard.configuration.{EnvironmentVariableSubstitutor, SubstitutingSourceProvider}
import io.dropwizard.core.Application
import io.dropwizard.core.setup.{Bootstrap, Environment}
import org.apache.texera.common.config.StorageConfig
import org.apache.texera.auth.{AuthFeatures, RequestLoggingFilter, RoleAnnotationEnforcer}
import org.apache.texera.dao.SqlServer
import org.apache.texera.auth.{
AuthFeatures,
RequestLoggingFilter,
RoleAnnotationEnforcer,
ServiceBootstrap
}
import org.apache.texera.service.activity.UserActivityEventListener
import org.apache.texera.service.resource.{
AccessControlResource,
Expand All @@ -33,25 +34,11 @@ import org.apache.texera.service.resource.{
LiteLLMProxyResource
}
import org.eclipse.jetty.server.session.SessionHandler
import java.nio.file.Path

class AccessControlService extends Application[AccessControlServiceConfiguration] with LazyLogging {
override def initialize(bootstrap: Bootstrap[AccessControlServiceConfiguration]): Unit = {
// enable environment variable substitution in YAML config
bootstrap.setConfigurationSourceProvider(
new SubstitutingSourceProvider(
bootstrap.getConfigurationSourceProvider,
new EnvironmentVariableSubstitutor(false)
)
)
// Register Scala module to Dropwizard default object mapper
bootstrap.getObjectMapper.registerModule(DefaultScalaModule)

SqlServer.initConnection(
StorageConfig.jdbcUrl,
StorageConfig.jdbcUsername,
StorageConfig.jdbcPassword
)
ServiceBootstrap.configure(bootstrap)
ServiceBootstrap.initDatabase()
}

override def run(
Expand Down Expand Up @@ -85,15 +72,10 @@ class AccessControlService extends Application[AccessControlServiceConfiguration
}
object AccessControlService {
def main(args: Array[String]): Unit = {
val accessControlPath = Path
.of(sys.env.getOrElse("TEXERA_HOME", "."))
.resolve("access-control-service")
.resolve("src")
.resolve("main")
.resolve("resources")
.resolve("access-control-service-web-config.yaml")
.toAbsolutePath
.toString
val accessControlPath = ServiceBootstrap.configFilePath(
"access-control-service",
"access-control-service-web-config.yaml"
)

// Start the Dropwizard application
new AccessControlService().run("server", accessControlPath)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@

package org.apache.texera.service

import io.dropwizard.core.setup.Environment
import com.fasterxml.jackson.databind.ObjectMapper
import io.dropwizard.configuration.ConfigurationSourceProvider
import io.dropwizard.core.setup.{Bootstrap, Environment}
import io.dropwizard.jersey.DropwizardResourceConfig
import io.dropwizard.jersey.setup.JerseyEnvironment
import io.dropwizard.jetty.MutableServletContextHandler
Expand All @@ -33,13 +35,29 @@ import org.apache.texera.service.resource.{
LiteLLMProxyResource
}
import org.glassfish.jersey.server.filter.RolesAllowedDynamicFeature
import org.mockito.ArgumentMatchers.isA
import org.mockito.ArgumentMatchers.{any, isA}
import org.mockito.Mockito.{mock, verify, when}
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers

import scala.util.control.NonFatal

class AccessControlServiceRunSpec extends AnyFlatSpec with Matchers {

"AccessControlService.initialize" should "run the shared bootstrap configuration and database setup" in {
val bootstrap = mock(classOf[Bootstrap[AccessControlServiceConfiguration]])
when(bootstrap.getObjectMapper).thenReturn(mock(classOf[ObjectMapper]))
when(bootstrap.getConfigurationSourceProvider)
.thenReturn(mock(classOf[ConfigurationSourceProvider]))

// initialize() also opens the SQL pool, which needs a live database that a bare unit
// run lacks, so tolerate a failure after the config wiring asserted below.
try new AccessControlService().initialize(bootstrap)
catch { case NonFatal(_) => }

verify(bootstrap).setConfigurationSourceProvider(any(classOf[ConfigurationSourceProvider]))
}

"AccessControlService.run" should "register UserActivityEventListener on the Jersey environment" in {
val jersey = mock(classOf[JerseyEnvironment])
val servlets = mock(classOf[ServletEnvironment])
Expand Down
19 changes: 17 additions & 2 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,12 @@
lazy val Resource = (project in file("common/resource")).settings(asfLicensingSettings)
lazy val Auth = (project in file("common/auth"))
.settings(asfLicensingSettings)
.settings(
// ServiceBootstrapSpec exercises ServiceBootstrap.initDatabase, which mutates the
// JVM-wide SqlServer singleton that ComputingUnitAccessSpec also relies on. Run this
// module's suites serially so the two can't clobber each other's connection.
Test / parallelExecution := false
)
.configs(Test)
.dependsOn(DAO, Config)
.dependsOn(DAO % "test->test") // reuse MockTexeraDB embedded Postgres in tests
Expand All @@ -86,7 +92,11 @@
dependencyOverrides ++= Seq(
// override it as io.dropwizard 4 require 2.16.1 or higher
"com.fasterxml.jackson.module" %% "jackson-module-scala" % jacksonVersion
)
),
// AccessControlServiceRunSpec's initialize test opens the JVM-wide SqlServer
// singleton that the DB-backed AccessControlResourceSpec also relies on; run
// the suites serially so they can't clobber each other's connection.
Test / parallelExecution := false
)
.configs(Test)
.dependsOn(DAO % "test->test", Auth % "test->test")
Expand Down Expand Up @@ -122,7 +132,7 @@
// override it as io.dropwizard 4 require 2.16.1 or higher
"com.fasterxml.jackson.module" %% "jackson-module-scala" % jacksonVersion,
"com.fasterxml.jackson.core" % "jackson-databind" % jacksonVersion,
"org.glassfish.jersey.core" % "jersey-common" % "3.0.12"

Check warning on line 135 in build.sbt

View workflow job for this annotation

GitHub Actions / Bench

The evaluation of `/` inside an anonymous function is prohibited.
),
// Each testcontainers-based suite starts its own LakeFS/MinIO/Postgres stack
// and mutates JVM-wide singletons (StorageConfig endpoints, LakeFS client),
Expand All @@ -132,7 +142,7 @@
Test / forkOptions := (Test / forkOptions).value
.withWorkingDirectory((ThisBuild / baseDirectory).value),
Test / testGrouping := (Test / definedTests).value.map { suite =>
Tests.Group(suite.name, Seq(suite), Tests.SubProcess((Test / forkOptions).value))

Check warning on line 145 in build.sbt

View workflow job for this annotation

GitHub Actions / Bench

The evaluation of `/` inside an anonymous function is prohibited.

Check warning on line 145 in build.sbt

View workflow job for this annotation

GitHub Actions / build / amber (ubuntu-latest, 17)

The evaluation of `/` inside an anonymous function is prohibited.

Check warning on line 145 in build.sbt

View workflow job for this annotation

GitHub Actions / build / amber-integration (ubuntu-latest, 17)

The evaluation of `/` inside an anonymous function is prohibited.

Check warning on line 145 in build.sbt

View workflow job for this annotation

GitHub Actions / build / amber-integration (macos-latest, 17)

The evaluation of `/` inside an anonymous function is prohibited.

Check warning on line 145 in build.sbt

View workflow job for this annotation

GitHub Actions / build / platform (notebook-migration-service)

The evaluation of `/` inside an anonymous function is prohibited.

Check warning on line 145 in build.sbt

View workflow job for this annotation

GitHub Actions / build / platform (config-service)

The evaluation of `/` inside an anonymous function is prohibited.

Check warning on line 145 in build.sbt

View workflow job for this annotation

GitHub Actions / build / platform (computing-unit-managing-service)

The evaluation of `/` inside an anonymous function is prohibited.

Check warning on line 145 in build.sbt

View workflow job for this annotation

GitHub Actions / build / platform (access-control-service)

The evaluation of `/` inside an anonymous function is prohibited.

Check warning on line 145 in build.sbt

View workflow job for this annotation

GitHub Actions / build / platform (file-service)

The evaluation of `/` inside an anonymous function is prohibited.

Check warning on line 145 in build.sbt

View workflow job for this annotation

GitHub Actions / build / platform (workflow-compiling-service)

The evaluation of `/` inside an anonymous function is prohibited.
}
)

Expand Down Expand Up @@ -187,8 +197,13 @@
dependencyOverrides ++= Seq(
// override it as io.dropwizard 4 require 2.16.1 or higher
"com.fasterxml.jackson.module" %% "jackson-module-scala" % jacksonVersion
)
),
// NotebookMigrationServiceRunSpec's initialize test opens the JVM-wide SqlServer
// singleton that the DB-backed NotebookMigrationResourceSpec also relies on; run
// the suites serially so they can't clobber each other's connection.
Test / parallelExecution := false
)
.configs(Test)
.dependsOn(DAO % "test->test") // test scope dependency

// root project definition
Expand Down
1 change: 1 addition & 0 deletions common/auth/build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ libraryDependencies ++= Seq(
"org.glassfish.jersey.core" % "jersey-server" % "3.0.12" % "provided", // for RoleAnnotationEnforcer's ResourceConfig overload and AuthFeatures' RolesAllowedDynamicFeature
"io.dropwizard" % "dropwizard-core" % "4.0.7" % "provided", // for AuthFeatures' Environment
"io.dropwizard" % "dropwizard-auth" % "4.0.7" % "provided", // for AuthFeatures' AuthDynamicFeature/AuthValueFactoryProvider
"com.fasterxml.jackson.module" %% "jackson-module-scala" % "2.18.6" % "provided", // for ServiceBootstrap's DefaultScalaModule
"org.scalatest" %% "scalatest" % "3.2.17" % Test,
"org.mockito" % "mockito-core" % "5.4.0" % Test // for mocking the Jersey environment in AuthFeaturesSpec
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* 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.texera.auth

import com.fasterxml.jackson.module.scala.DefaultScalaModule
import io.dropwizard.configuration.{EnvironmentVariableSubstitutor, SubstitutingSourceProvider}
import io.dropwizard.core.Configuration
import io.dropwizard.core.setup.Bootstrap
import org.apache.texera.common.config.StorageConfig
import org.apache.texera.dao.SqlServer

import java.nio.file.Path

/** Shared Dropwizard service bootstrap steps, identical across every Texera
* service. Kept here so the services don't drift apart.
*/
object ServiceBootstrap {

/** Enable `${ENV_VAR}` substitution in the YAML config and register the Scala
* module on Dropwizard's default object mapper.
*/
def configure[T <: Configuration](bootstrap: Bootstrap[T]): Unit = {
// enable environment variable substitution in YAML config
bootstrap.setConfigurationSourceProvider(
new SubstitutingSourceProvider(
bootstrap.getConfigurationSourceProvider,
new EnvironmentVariableSubstitutor(false)
)
)
// register Scala module to Dropwizard default object mapper
bootstrap.getObjectMapper.registerModule(DefaultScalaModule)
}

/** Open the shared SQL connection pool using the storage configuration. */
def initDatabase(): Unit =
SqlServer.initConnection(
StorageConfig.jdbcUrl,
StorageConfig.jdbcUsername,
StorageConfig.jdbcPassword
)

/** Resolve `$TEXERA_HOME/<serviceDir>/src/main/resources/<configFileName>` to
* an absolute path string, the convention every service `main` uses.
*/
def configFilePath(serviceDir: String, configFileName: String): String =
Path
.of(sys.env.getOrElse("TEXERA_HOME", "."))
.resolve(serviceDir)
.resolve("src")
.resolve("main")
.resolve("resources")
.resolve(configFileName)
.toAbsolutePath
.toString
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* 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.texera.auth

import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.scala.DefaultScalaModule
import io.dropwizard.configuration.ConfigurationSourceProvider
import io.dropwizard.core.Configuration
import io.dropwizard.core.setup.Bootstrap
import org.apache.texera.dao.SqlServer
import org.mockito.ArgumentMatchers.{any, isA}
import org.mockito.Mockito.{mock, verify, when}
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers

import java.nio.file.Paths
import scala.util.control.NonFatal

class ServiceBootstrapSpec extends AnyFlatSpec with Matchers {

// Every service shares this bootstrap helper, so its behavior is verified once here
// rather than per service.
"ServiceBootstrap.configure" should "wrap the config source provider and register the Scala module" in {
val bootstrap = mock(classOf[Bootstrap[Configuration]])
val objectMapper = mock(classOf[ObjectMapper])
val sourceProvider = mock(classOf[ConfigurationSourceProvider])
when(bootstrap.getObjectMapper).thenReturn(objectMapper)
when(bootstrap.getConfigurationSourceProvider).thenReturn(sourceProvider)

ServiceBootstrap.configure(bootstrap)

verify(bootstrap).setConfigurationSourceProvider(any(classOf[ConfigurationSourceProvider]))
verify(objectMapper).registerModule(isA(classOf[DefaultScalaModule]))
}

"ServiceBootstrap.configFilePath" should "resolve the conventional resources path under the service dir" in {
val result = ServiceBootstrap.configFilePath("file-service", "file-service-web-config.yaml")

val expectedSuffix = Paths
.get("file-service", "src", "main", "resources", "file-service-web-config.yaml")
.toString
result should endWith(expectedSuffix)
Paths.get(result).isAbsolute shouldBe true
}

"ServiceBootstrap.initDatabase" should "run the shared SQL connection-pool setup from storage config" in {
// A unit run may or may not have a reachable Postgres (CI provides one on
// localhost:5432; a bare checkout does not). Either way this exercises the shared
// init path: on success the SqlServer singleton is populated, on a connection
// failure it throws fast. We only require the path to run, not the DB to be up.
try {
ServiceBootstrap.initDatabase()
SqlServer.getInstance() should not be null
} catch {
case NonFatal(_) => succeed
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,34 +19,26 @@

package org.apache.texera.service

import com.fasterxml.jackson.module.scala.DefaultScalaModule
import io.dropwizard.configuration.{EnvironmentVariableSubstitutor, SubstitutingSourceProvider}
import io.dropwizard.core.Application
import io.dropwizard.core.setup.{Bootstrap, Environment}
import org.apache.texera.common.config.StorageConfig
import org.apache.texera.auth.{AuthFeatures, RequestLoggingFilter, RoleAnnotationEnforcer}
import org.apache.texera.dao.SqlServer
import org.apache.texera.auth.{
AuthFeatures,
RequestLoggingFilter,
RoleAnnotationEnforcer,
ServiceBootstrap
}
import org.apache.texera.service.resource.{
ComputingUnitAccessResource,
ComputingUnitManagingResource,
HealthCheckResource
}
import java.nio.file.Path

class ComputingUnitManagingService extends Application[ComputingUnitManagingServiceConfiguration] {

override def initialize(
bootstrap: Bootstrap[ComputingUnitManagingServiceConfiguration]
): Unit = {
// enable environment variable substitution in YAML config
bootstrap.setConfigurationSourceProvider(
new SubstitutingSourceProvider(
bootstrap.getConfigurationSourceProvider,
new EnvironmentVariableSubstitutor(false)
)
)
// register scala module to dropwizard default object mapper
bootstrap.getObjectMapper.registerModule(DefaultScalaModule)
ServiceBootstrap.configure(bootstrap)
}
override def run(
configuration: ComputingUnitManagingServiceConfiguration,
Expand All @@ -58,11 +50,7 @@ class ComputingUnitManagingService extends Application[ComputingUnitManagingServ

AuthFeatures.register(environment)

SqlServer.initConnection(
StorageConfig.jdbcUrl,
StorageConfig.jdbcUsername,
StorageConfig.jdbcPassword
)
ServiceBootstrap.initDatabase()

environment.jersey().register(new ComputingUnitManagingResource)
environment.jersey().register(new ComputingUnitAccessResource)
Expand All @@ -79,15 +67,10 @@ class ComputingUnitManagingService extends Application[ComputingUnitManagingServ

object ComputingUnitManagingService {
def main(args: Array[String]): Unit = {
val configFilePath = Path
.of(sys.env.getOrElse("TEXERA_HOME", "."))
.resolve("computing-unit-managing-service")
.resolve("src")
.resolve("main")
.resolve("resources")
.resolve("computing-unit-managing-service-config.yaml")
.toAbsolutePath
.toString
val configFilePath = ServiceBootstrap.configFilePath(
"computing-unit-managing-service",
"computing-unit-managing-service-config.yaml"
)

new ComputingUnitManagingService().run("server", configFilePath)
}
Expand Down
Loading
Loading