next.proxy.state.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of otoroshi_2.12 Show documentation
Show all versions of otoroshi_2.12 Show documentation
Lightweight api management on top of a modern http reverse proxy
The newest version!
package otoroshi.next.proxy
import com.github.blemale.scaffeine.Scaffeine
import otoroshi.auth.AuthModuleConfig
import otoroshi.env.Env
import otoroshi.models._
import otoroshi.next.models._
import otoroshi.next.plugins.api.NgPluginHelper.pluginId
import otoroshi.next.plugins.api.{NgPluginCategory, NgPluginHelper}
import otoroshi.next.plugins._
import otoroshi.script._
import otoroshi.ssl.{Cert, DynamicSSLEngineProvider}
import otoroshi.tcp.TcpService
import otoroshi.utils.TypedMap
import otoroshi.utils.cache.types.UnboundedTrieMap
import otoroshi.utils.syntax.implicits._
import play.api.Logger
import play.api.libs.json._
import play.api.mvc.RequestHeader
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.{AtomicBoolean, AtomicReference}
import scala.collection.concurrent.TrieMap
import scala.concurrent.duration.{DurationInt, DurationLong, FiniteDuration}
import scala.concurrent.{ExecutionContext, Future}
import scala.util.Failure
class NgProxyState(env: Env) {
private val logger = Logger("otoroshi-proxy-state")
private val routes = TrieMap
.newBuilder[String, NgRoute]
.result()
private val globalConfigRef = new AtomicReference[GlobalConfig]()
private val raw_routes = new UnboundedTrieMap[String, NgRoute]()
private val apikeys = new UnboundedTrieMap[String, ApiKey]()
private val backends = new UnboundedTrieMap[String, NgBackend]()
private val ngroutecompositions = new UnboundedTrieMap[String, NgRouteComposition]()
private val ngbackends = new UnboundedTrieMap[String, StoredNgBackend]()
private val jwtVerifiers = new UnboundedTrieMap[String, GlobalJwtVerifier]()
private val certificates = new UnboundedTrieMap[String, Cert]()
private val authModules = new UnboundedTrieMap[String, AuthModuleConfig]()
private val errorTemplates = new UnboundedTrieMap[String, ErrorTemplate]()
private val services = new UnboundedTrieMap[String, ServiceDescriptor]()
private val teams = new UnboundedTrieMap[String, Team]()
private val tenants = new UnboundedTrieMap[String, Tenant]()
private val serviceGroups = new UnboundedTrieMap[String, ServiceGroup]()
private val dataExporters = new UnboundedTrieMap[String, DataExporterConfig]()
private val otoroshiAdmins = new UnboundedTrieMap[String, OtoroshiAdmin]()
private val backofficeSessions = new UnboundedTrieMap[String, BackOfficeUser]()
private val privateAppsSessions = new UnboundedTrieMap[String, PrivateAppsUser]()
private val tcpServices = new UnboundedTrieMap[String, TcpService]()
private val scripts = new UnboundedTrieMap[String, Script]()
private val wasmPlugins = new UnboundedTrieMap[String, WasmPlugin]()
private val drafts = new UnboundedTrieMap[String, Draft]()
private val tryItEnabledReports = Scaffeine()
.expireAfterWrite(5.minutes)
.maximumSize(100)
.build[String, Unit]()
private val tryItReports = Scaffeine()
.expireAfterWrite(5.minutes)
.maximumSize(100)
.build[String, NgExecutionReport]()
private val routesByDomain = new UnboundedTrieMap[String, Seq[NgRoute]]()
private val domainPathTreeRef = new AtomicReference[NgTreeRouter](NgTreeRouter.empty)
def enableReportFor(id: String): Unit = {
tryItEnabledReports.put(id, ())
}
def isReportEnabledFor(id: String): Boolean = {
tryItEnabledReports.getIfPresent(id) match {
case Some(_) => {
tryItEnabledReports.invalidate(id)
true
}
case None => false
}
}
def addReport(id: String, report: NgExecutionReport): Unit = {
tryItReports.put(id, report)
}
def report(id: String): Option[NgExecutionReport] = {
tryItReports.getIfPresent(id) match {
case Some(report) => {
tryItReports.invalidate(id)
report.some
}
case None => None
}
}
def findRoutes(domain: String, path: String): Option[Seq[NgRoute]] =
domainPathTreeRef.get().find(domain, path).map(_.routes)
def findRoute(request: RequestHeader, attrs: TypedMap): Option[NgMatchedRoute] =
domainPathTreeRef.get().findRoute(request, attrs)(env)
def getDomainRoutes(domain: String): Option[Seq[NgRoute]] = routesByDomain.get(domain) match {
case s @ Some(_) => s
case None => domainPathTreeRef.get().findWildcard(domain).map(_.routes)
}
def globalConfig(): Option[GlobalConfig] = Option(globalConfigRef.get())
def wasmPlugin(id: String): Option[WasmPlugin] = wasmPlugins.get(id)
def draft(id: String): Option[Draft] = drafts.get(id)
def script(id: String): Option[Script] = scripts.get(id)
def backend(id: String): Option[NgBackend] = backends.get(id)
def storedBackend(id: String): Option[StoredNgBackend] = ngbackends.get(id)
def errorTemplate(id: String): Option[ErrorTemplate] = errorTemplates.get(id)
def route(id: String): Option[NgRoute] = routes.get(id)
def routeComposition(id: String): Option[NgRouteComposition] = ngroutecompositions.get(id)
def apikey(id: String): Option[ApiKey] = apikeys.get(id)
def jwtVerifier(id: String): Option[GlobalJwtVerifier] = jwtVerifiers.get(id)
def certificate(id: String): Option[Cert] = certificates.get(id)
def authModule(id: String): Option[AuthModuleConfig] = authModules.get(id)
def authModuleAsync(id: String): Future[Option[AuthModuleConfig]] = authModules.get(id).vfuture
def service(id: String): Option[ServiceDescriptor] = services.get(id)
def team(id: String): Option[Team] = teams.get(id)
def tenant(id: String): Option[Tenant] = tenants.get(id)
def serviceGroup(id: String): Option[ServiceGroup] = serviceGroups.get(id)
def dataExporter(id: String): Option[DataExporterConfig] = dataExporters.get(id)
def otoroshiAdmin(id: String): Option[OtoroshiAdmin] = otoroshiAdmins.get(id)
def backofficeSession(id: String): Option[BackOfficeUser] = backofficeSessions.get(id)
def privateAppsSession(id: String): Option[PrivateAppsUser] = privateAppsSessions.get(id)
def tcpService(id: String): Option[TcpService] = tcpServices.get(id)
def rawRoute(id: String): Option[NgRoute] = raw_routes.get(id)
def allWasmPlugins(): Seq[WasmPlugin] = wasmPlugins.values.toSeq
def allDrafts(): Seq[Draft] = drafts.values.toSeq
def allScripts(): Seq[Script] = scripts.values.toSeq
def allRawRoutes(): Seq[NgRoute] = raw_routes.values.toSeq
def allRoutes(): Seq[NgRoute] = routes.values.toSeq
def allRouteCompositions(): Seq[NgRouteComposition] = ngroutecompositions.values.toSeq
def allApikeys(): Seq[ApiKey] = apikeys.values.toSeq
def allJwtVerifiers(): Seq[GlobalJwtVerifier] = jwtVerifiers.values.toSeq
def allCertificates(): Seq[Cert] = certificates.values.toSeq
def allCertificatesMap(): TrieMap[String, Cert] = certificates
def allAuthModules(): Seq[AuthModuleConfig] = authModules.values.toSeq
def allServices(): Seq[ServiceDescriptor] = services.values.toSeq
def allTeams(): Seq[Team] = teams.values.toSeq
def allTenants(): Seq[Tenant] = tenants.values.toSeq
def allServiceGroups(): Seq[ServiceGroup] = serviceGroups.values.toSeq
def allDataExporters(): Seq[DataExporterConfig] = dataExporters.values.toSeq
def allOtoroshiAdmins(): Seq[OtoroshiAdmin] = otoroshiAdmins.values.toSeq
def allBackofficeSessions(): Seq[BackOfficeUser] = backofficeSessions.values.toSeq
def allPrivateAppsSessions(): Seq[PrivateAppsUser] = privateAppsSessions.values.toSeq
def allTcpServices(): Seq[TcpService] = tcpServices.values.toSeq
def allNgServices(): Seq[NgRouteComposition] = ngroutecompositions.values.toSeq
def allStoredBackends(): Seq[StoredNgBackend] = ngbackends.values.toSeq
def allBackends(): Seq[NgBackend] = backends.values.toSeq
def hasCert(id: String): Boolean = certificates.contains(id)
def addCert(id: String, cert: Cert): Unit = {
certificates.put(id, cert)
}
def removeCert(id: String): Unit = {
certificates.remove(id)
}
def updateRawRoutes(values: Seq[NgRoute]): Unit = {
raw_routes
.addAll(values.map(v => (v.cacheableId, v)))
.remAll(raw_routes.keySet.toSeq.diff(values.map(_.cacheableId)))
}
def updateRoutes(values: Seq[NgRoute]): Unit = {
routes.addAll(values.map(v => (v.cacheableId, v))).remAll(routes.keySet.toSeq.diff(values.map(_.cacheableId)))
val routesByDomainRaw: Map[String, Seq[NgRoute]] = values
.flatMap(r => r.frontend.domains.map(d => NgRouteDomainAndPathWrapper(r, d.domainLowerCase, d.path)))
.filterNot(_.domain.contains("*"))
.groupBy(_.domain)
.mapValues(_.sortWith((r1, r2) => r1.path.length.compareTo(r2.path.length) > 0).map(_.route))
routesByDomain.addAll(routesByDomainRaw).remAll(routesByDomain.keySet.toSeq.diff(routesByDomainRaw.keySet.toSeq))
val s = System.currentTimeMillis()
domainPathTreeRef.set(NgTreeRouter.build(values))
val d = System.currentTimeMillis() - s
if (logger.isDebugEnabled) logger.debug(s"built TreeRouter(${values.size} routes) in ${d} ms.")
// java.nio.file.Files.writeString(new java.io.File("./tree-router-config.json").toPath, domainPathTreeRef.get().json.prettify)
}
def updateServices(values: Seq[ServiceDescriptor]): Unit = {
services.addAll(values.map(v => (v.id, v))).remAll(services.keySet.toSeq.diff(values.map(_.id)))
}
def updateTeams(values: Seq[Team]): Unit = {
teams.addAll(values.map(v => (v.id.value, v))).remAll(teams.keySet.toSeq.diff(values.map(_.id.value)))
}
def updateWasmPlugins(values: Seq[WasmPlugin]): Unit = {
wasmPlugins.addAll(values.map(v => (v.id, v))).remAll(wasmPlugins.keySet.toSeq.diff(values.map(_.id)))
}
def updateDrafts(values: Seq[Draft]): Unit = {
drafts.addAll(values.map(v => (v.id, v))).remAll(drafts.keySet.toSeq.diff(values.map(_.id)))
}
def updateTenants(values: Seq[Tenant]): Unit = {
tenants.addAll(values.map(v => (v.id.value, v))).remAll(tenants.keySet.toSeq.diff(values.map(_.id.value)))
}
def updateServiceGroups(values: Seq[ServiceGroup]): Unit = {
serviceGroups.addAll(values.map(v => (v.id, v))).remAll(serviceGroups.keySet.toSeq.diff(values.map(_.id)))
}
def updateDataExporters(values: Seq[DataExporterConfig]): Unit = {
dataExporters.addAll(values.map(v => (v.id, v))).remAll(dataExporters.keySet.toSeq.diff(values.map(_.id)))
}
def updateOtoroshiAdmins(values: Seq[OtoroshiAdmin]): Unit = {
otoroshiAdmins
.addAll(values.map(v => (v.username, v)))
.remAll(otoroshiAdmins.keySet.toSeq.diff(values.map(_.username)))
}
def updateBackofficeSessions(values: Seq[BackOfficeUser]): Unit = {
backofficeSessions
.addAll(values.map(v => (v.randomId, v)))
.remAll(backofficeSessions.keySet.toSeq.diff(values.map(_.randomId)))
}
def updatePrivateAppsSessions(values: Seq[PrivateAppsUser]): Unit = {
privateAppsSessions
.addAll(values.map(v => (v.randomId, v)))
.remAll(privateAppsSessions.keySet.toSeq.diff(values.map(_.randomId)))
}
def updateTcpServices(values: Seq[TcpService]): Unit = {
tcpServices.addAll(values.map(v => (v.id, v))).remAll(tcpServices.keySet.toSeq.diff(values.map(_.id)))
}
def updateBackends(values: Seq[StoredNgBackend]): Unit = {
backends.addAll(values.map(v => (v.id, v.backend))).remAll(backends.keySet.toSeq.diff(values.map(_.id)))
}
def updateApikeys(values: Seq[ApiKey]): Unit = {
apikeys.addAll(values.map(v => (v.clientId, v))).remAll(apikeys.keySet.toSeq.diff(values.map(_.clientId)))
}
def updateJwtVerifiers(values: Seq[GlobalJwtVerifier]): Unit = {
jwtVerifiers.addAll(values.map(v => (v.id, v))).remAll(jwtVerifiers.keySet.toSeq.diff(values.map(_.id)))
}
def updateCertificates(values: Seq[Cert]): Unit = {
certificates.addAll(values.map(v => (v.id, v))).remAll(certificates.keySet.toSeq.diff(values.map(_.id)))
}
def updateAuthModules(values: Seq[AuthModuleConfig]): Unit = {
authModules.addAll(values.map(v => (v.id, v))).remAll(authModules.keySet.toSeq.diff(values.map(_.id)))
}
def updateErrorTemplates(values: Seq[ErrorTemplate]): Unit = {
errorTemplates
.addAll(values.map(v => (v.serviceId, v)))
.remAll(errorTemplates.keySet.toSeq.diff(values.map(_.serviceId)))
}
def updateScripts(values: Seq[Script]): Unit = {
scripts.addAll(values.map(v => (v.id, v))).remAll(scripts.keySet.toSeq.diff(values.map(_.id)))
}
def updateNgBackends(values: Seq[StoredNgBackend]): Unit = {
ngbackends.addAll(values.map(v => (v.id, v))).remAll(ngbackends.keySet.toSeq.diff(values.map(_.id)))
}
def updateNgSRouteCompositions(values: Seq[NgRouteComposition]): Unit = {
ngroutecompositions
.addAll(values.map(v => (v.id, v)))
.remAll(ngroutecompositions.keySet.toSeq.diff(values.map(_.id)))
}
def updateGlobalConfig(gc: GlobalConfig): Unit = {
globalConfigRef.set(gc)
}
private val fakeRoutesCount = 10 //10000 // 300000
private val dev = false && env.isDev
private def generateRoutesByDomain(env: Env): Future[Seq[NgRoute]] = {
import NgPluginHelper.pluginId
if (dev) {
(0 until fakeRoutesCount).map { idx =>
NgRoute(
location = EntityLocation.default,
id = s"route_generated-domain-${idx}",
name = s"generated_fake_route_domain_${idx}",
description = s"generated_fake_route_domain_${idx}",
tags = Seq.empty,
metadata = Map.empty,
enabled = true,
debugFlow = true,
capture = false,
exportReporting = false,
frontend = NgFrontend(
domains = Seq(NgDomainAndPath(s"${idx}-generated-next-gen.oto.tools")),
headers = Map.empty,
query = Map.empty,
methods = Seq.empty,
stripPath = true,
exact = false
),
backend = NgBackend(
targets = Seq(
NgTarget(
id = "mirror-1",
hostname = "request.otoroshi.io",
port = 443,
tls = true
)
),
root = s"/gen-${idx}",
rewrite = false,
loadBalancing = RoundRobin,
client = NgClientConfig.default
),
plugins = NgPlugins(
Seq(
NgPluginInstance(
plugin = pluginId[OverrideHost],
enabled = true,
include = Seq.empty,
exclude = Seq.empty,
config = NgPluginInstanceConfig(Json.obj())
),
NgPluginInstance(
plugin = pluginId[AdditionalHeadersOut],
enabled = true,
include = Seq.empty,
exclude = Seq.empty,
config = NgPluginInstanceConfig(
Json.obj(
"headers" -> Json.obj(
"bar" -> "foo"
)
)
)
)
)
)
)
}.vfuture
} else {
Seq.empty.vfuture
}
}
private def generateRoutesByName(env: Env): Future[Seq[NgRoute]] = {
import NgPluginHelper.pluginId
if (dev) {
(0 until fakeRoutesCount).map { idx =>
NgRoute(
location = EntityLocation.default,
id = s"route_generated-path-${idx}",
name = s"generated_fake_route_path_${idx}",
description = s"generated_fake_route_path_${idx}",
tags = Seq.empty,
metadata = Map.empty,
enabled = true,
capture = false,
debugFlow = true,
exportReporting = false,
frontend = NgFrontend(
domains = Seq(NgDomainAndPath(s"path-generated-next-gen.oto.tools/api/${idx}")),
headers = Map.empty,
query = Map.empty,
methods = Seq.empty,
stripPath = true,
exact = false
),
backend = NgBackend(
targets = Seq(
NgTarget(
id = "mirror-1",
hostname = "request.otoroshi.io",
port = 443,
tls = true
)
),
root = s"/path-${idx}",
rewrite = false,
loadBalancing = RoundRobin,
client = NgClientConfig.default
),
plugins = NgPlugins(
Seq(
NgPluginInstance(
plugin = pluginId[OverrideHost],
config = NgPluginInstanceConfig(Json.obj())
),
NgPluginInstance(
plugin = pluginId[AdditionalHeadersOut],
config = NgPluginInstanceConfig(
Json.obj(
"headers" -> Json.obj(
"bar" -> "foo"
)
)
)
)
)
)
)
}.vfuture
} else {
Seq.empty.vfuture
}
}
private def generateRandomRoutes(env: Env): Future[Seq[NgRoute]] = {
import NgPluginHelper.pluginId
if (dev) {
(0 until ((Math.random() * 50) + 10).toInt).map { idx =>
NgRoute(
location = EntityLocation.default,
id = s"route_generated-random-${idx}",
name = s"generated_fake_route_random_${idx}",
description = s"generated_fake_route_random_${idx}",
tags = Seq.empty,
metadata = Map.empty,
enabled = true,
capture = false,
debugFlow = true,
exportReporting = false,
frontend = NgFrontend(
domains = Seq(NgDomainAndPath(s"random-generated-next-gen.oto.tools/api/${idx}")),
headers = Map.empty,
query = Map.empty,
methods = Seq.empty,
stripPath = true,
exact = false
),
backend = NgBackend(
targets = Seq(
NgTarget(
id = "mirror-1",
hostname = "request.otoroshi.io",
port = 443,
tls = true
)
),
root = s"/path-${idx}",
rewrite = false,
loadBalancing = RoundRobin,
client = NgClientConfig.default
),
plugins = NgPlugins(
Seq(
NgPluginInstance(
plugin = pluginId[OverrideHost],
config = NgPluginInstanceConfig(Json.obj())
),
NgPluginInstance(
plugin = pluginId[AdditionalHeadersOut],
config = NgPluginInstanceConfig(
Json.obj(
"headers" -> Json.obj(
"bar" -> "foo"
)
)
)
)
)
)
)
}.vfuture
} else {
Seq.empty.vfuture
}
}
private def soapRoute(env: Env): Seq[NgRoute] = {
if (dev) {
Seq(
NgRoute(
location = EntityLocation.default,
id = s"route_soap",
name = s"route_soap",
description = s"route_soap",
tags = Seq.empty,
metadata = Map.empty,
enabled = true,
capture = false,
debugFlow = true,
exportReporting = false,
frontend = NgFrontend(
domains = Seq(NgDomainAndPath(s"soap-next-gen.oto.tools/text/from/number/:number")),
headers = Map.empty,
query = Map.empty,
methods = Seq("GET"),
stripPath = true,
exact = false
),
backend = NgBackend(
targets = Seq(
NgTarget(
id = "www.dataaccess.com",
hostname = "www.dataaccess.com",
port = 443,
tls = true
)
),
root = s"/webservicesserver/numberconversion.wso",
rewrite = true,
loadBalancing = RoundRobin,
client = NgClientConfig.default
),
plugins = NgPlugins(
Seq(
NgPluginInstance(
plugin = pluginId[SOAPAction],
config = NgPluginInstanceConfig(
SOAPActionConfig(
// url = "https://www.dataaccess.com/webservicesserver/numberconversion.wso",
envelope = s"""
|
|
|
| $${req.pathparams.number}
|
|
| """.stripMargin,
jqResponseFilter =
"""{number_str: .["soap:Envelope"] | .["soap:Body"] | .["m:NumberToWordsResponse"] | .["m:NumberToWordsResult"] }""".some
).json.asObject
)
)
)
)
)
)
} else {
Seq.empty
}
}
def sync()(implicit ec: ExecutionContext): Future[Unit] = {
implicit val ev = env
val start = System.currentTimeMillis()
val gc = env.datastores.globalConfigDataStore.latest()
val config = gc.plugins.config
.select(ProxyEngine.configRoot)
.asOpt[JsObject]
.map(v => ProxyEngineConfig.parse(v, env))
.getOrElse(ProxyEngineConfig.default)
val debug = config.debug
val debugHeaders = config.debugHeaders
for {
_ <- env.vaults.renewSecretsInCache()
routes <- env.datastores.routeDataStore.findAllAndFillSecrets() // secrets OK
routescomp <- env.datastores.routeCompositionDataStore.findAllAndFillSecrets() // secrets OK
genRoutesDomain <- generateRoutesByDomain(env)
genRoutesPath <- generateRoutesByName(env)
genRandom <- generateRandomRoutes(env)
descriptors <- env.datastores.serviceDescriptorDataStore.findAllAndFillSecrets() // secrets OK
fakeRoutes = if (dev) Seq(NgRoute.fake) else Seq.empty
newRoutes = (genRoutesDomain ++ genRoutesPath ++ genRandom ++ descriptors.map(d =>
NgRoute.fromServiceDescriptor(d, debug || debugHeaders).seffectOn(_.serviceDescriptor)
) ++ routes ++ routescomp.flatMap(_.toRoutes) ++ fakeRoutes ++ soapRoute(env)).filter(_.enabled)
apikeys <- env.datastores.apiKeyDataStore.findAllAndFillSecrets() // secrets OK
certs <- env.datastores.certificatesDataStore.findAllAndFillSecrets() // secrets OK
verifiers <- env.datastores.globalJwtVerifierDataStore.findAllAndFillSecrets() // secrets OK
modules <- env.datastores.authConfigsDataStore.findAllAndFillSecrets() // secrets OK
backends <- env.datastores.backendsDataStore.findAllAndFillSecrets() // secrets OK
errorTemplates <- env.datastores.errorTemplateDataStore.findAll() // no need for secrets
teams <- env.datastores.teamDataStore.findAll() // no need for secrets
tenants <- env.datastores.tenantDataStore.findAll() // no need for secrets
serviceGroups <- env.datastores.serviceGroupDataStore.findAll() // no need for secrets
dataExporters <- env.datastores.dataExporterConfigDataStore.findAllAndFillSecrets() // secrets OK
simpleAdmins <- env.datastores.simpleAdminDataStore.findAll() // no need for secrets
webauthnAdmins <- env.datastores.webAuthnAdminDataStore.findAll() // no need for secrets
backofficeSessions <- env.datastores.backOfficeUserDataStore.findAll() // no need for secrets
privateAppsSessions <- env.datastores.privateAppsUserDataStore.findAll() // no need for secrets
tcpServices <- env.datastores.tcpServiceDataStore.findAllAndFillSecrets() // secrets OK
scripts <- env.datastores.scriptDataStore.findAll() // no need for secrets
wasmPlugins <- env.datastores.wasmPluginsDataStore.findAllAndFillSecrets()
drafts <- env.datastores.draftsDataStore.findAll()
croutes <- if (dev) {
NgRouteComposition
.fromOpenApi(
"oto-api-next-gen.oto.tools",
"https://raw.githubusercontent.com/MAIF/otoroshi/master/otoroshi/public/openapi.json"
)
.map(route => {
// java.nio.file.Files.writeString(new java.io.File("./service.json").toPath(), route.json.prettify)
route.toRoutes.map(r =>
r.copy(
backend =
r.backend.copy(targets = r.backend.targets.map(t => t.copy(port = 9999, tls = false))),
plugins = NgPlugins(
Seq(
NgPluginInstance(
plugin = NgPluginHelper.pluginId[OverrideHost]
),
NgPluginInstance(
plugin = NgPluginHelper.pluginId[AdditionalHeadersIn],
config = NgPluginInstanceConfig(
Json.obj(
"headers" -> Json.obj(
"Otoroshi-Client-Id" -> "admin-api-apikey-id",
"Otoroshi-Client-Secret" -> "admin-api-apikey-secret"
)
)
)
)
)
)
)
)
})
} else Seq.empty[NgRoute].vfuture
_ <- env.adminExtensions.syncStates()
} yield {
env.proxyState.updateGlobalConfig(gc)
env.proxyState.updateRawRoutes(routes)
env.proxyState.updateRoutes(newRoutes ++ croutes)
env.proxyState.updateBackends(backends)
env.proxyState.updateApikeys(apikeys)
env.proxyState.updateCertificates(certs)
env.proxyState.updateAuthModules(modules)
env.proxyState.updateJwtVerifiers(verifiers)
env.proxyState.updateErrorTemplates(errorTemplates)
env.proxyState.updateServices(descriptors)
env.proxyState.updateTeams(teams)
env.proxyState.updateTenants(tenants)
env.proxyState.updateServiceGroups(serviceGroups)
env.proxyState.updateDataExporters(dataExporters)
env.proxyState.updateOtoroshiAdmins(simpleAdmins ++ webauthnAdmins)
env.proxyState.updateBackofficeSessions(backofficeSessions)
env.proxyState.updatePrivateAppsSessions(privateAppsSessions)
env.proxyState.updateTcpServices(tcpServices)
env.proxyState.updateScripts(scripts)
env.proxyState.updateWasmPlugins(wasmPlugins)
env.proxyState.updateDrafts(drafts)
env.proxyState.updateNgBackends(backends)
env.proxyState.updateNgSRouteCompositions(routescomp)
DynamicSSLEngineProvider.setCertificates(env)
NgProxyStateLoaderJob.firstSync.compareAndSet(false, true)
env.metrics.timerUpdate("ng-proxy-state-refresh", System.currentTimeMillis() - start, TimeUnit.MILLISECONDS)
}
}.andThen { case Failure(e) =>
e.printStackTrace()
}.map(_ => ())
}
object NgProxyStateLoaderJob {
val firstSync = new AtomicBoolean(false)
}
class NgProxyStateLoaderJob extends Job {
override def categories: Seq[NgPluginCategory] = Seq.empty
override def uniqueId: JobId = JobId("io.otoroshi.next.core.jobs.NgProxyStateLoaderJob")
override def name: String = "proxy state loader job"
override def jobVisibility: JobVisibility = JobVisibility.Internal
override def kind: JobKind = JobKind.ScheduledEvery
override def initialDelay(ctx: JobContext, env: Env): Option[FiniteDuration] = 1.millisecond.some
override def interval(ctx: JobContext, env: Env): Option[FiniteDuration] = {
env.configuration.getOptional[Long]("otoroshi.next.state-sync-interval").getOrElse(10000L).milliseconds.some
}
override def starting: JobStarting = JobStarting.Automatically
override def instantiation(ctx: JobContext, env: Env): JobInstantiation =
JobInstantiation.OneInstancePerOtoroshiInstance
override def predicate(ctx: JobContext, env: Env): Option[Boolean] = None
override def jobRun(ctx: JobContext)(implicit env: Env, ec: ExecutionContext): Future[Unit] = {
env.proxyState.sync()
}
}
class NgInternalStateMonitor extends Job {
import squants.information._
import squants.time._
private val logger = Logger("otoroshi-internal-state-monitor")
override def categories: Seq[NgPluginCategory] = Seq.empty
override def uniqueId: JobId = JobId("io.otoroshi.next.core.jobs.NgInternalStateMonitor")
override def name: String = "internal state size monitor"
override def jobVisibility: JobVisibility = JobVisibility.Internal
override def kind: JobKind = JobKind.ScheduledEvery
override def initialDelay(ctx: JobContext, env: Env): Option[FiniteDuration] = 1.second.some
override def interval(ctx: JobContext, env: Env): Option[FiniteDuration] = 10.seconds.some
override def starting: JobStarting = JobStarting.Automatically
override def instantiation(ctx: JobContext, env: Env): JobInstantiation =
JobInstantiation.OneInstancePerOtoroshiInstance
override def predicate(ctx: JobContext, env: Env): Option[Boolean] = None
def monitorProxyState(env: Env): Unit = {
val start = System.currentTimeMillis()
val total = Bytes(org.openjdk.jol.info.GraphLayout.parseInstance(env.proxyState).totalSize())
val duration = Milliseconds(System.currentTimeMillis() - start)
env.metrics.markDouble("ng-proxy-state-size-monitoring", total.value)
if (logger.isDebugEnabled) logger.debug(s"proxy-state: ${total.toMegabytes} mb, in ${duration}")
}
def monitorDataStoreState(env: Env): Unit = {
val start = System.currentTimeMillis()
val total = Bytes(org.openjdk.jol.info.GraphLayout.parseInstance(env.datastores).totalSize())
val duration = Milliseconds(System.currentTimeMillis() - start)
env.metrics.markDouble("ng-datastore-size-monitoring", total.value)
if (logger.isDebugEnabled) logger.debug(s"datastore: ${total.toMegabytes} mb, in ${duration}")
}
override def jobRun(ctx: JobContext)(implicit env: Env, ec: ExecutionContext): Future[Unit] = {
val monitorState = env.configuration.getOptional[Boolean]("otoroshi.next.monitor-proxy-state-size").getOrElse(false)
val monitorDatastore =
env.configuration.getOptional[Boolean]("otoroshi.next.monitor-datastore-size").getOrElse(false)
if (monitorState) monitorProxyState(env)
if (monitorDatastore) monitorDataStoreState(env)
().vfuture
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy