mirror of
https://github.com/MarginaliaSearch/MarginaliaSearch.git
synced 2025-02-23 21:18:58 +00:00
Embryo of new control process
* New events and heartbeat tables in mariadb * Refactored to a cleaner Service interface
This commit is contained in:
parent
42375f0e53
commit
62cc9df206
@ -0,0 +1,17 @@
|
|||||||
|
CREATE TABLE PROC_SERVICE_HEARTBEAT(
|
||||||
|
SERVICE_NAME VARCHAR(255) PRIMARY KEY COMMENT 'Full name of the service, including node id if applicable, e.g. search-service:0',
|
||||||
|
SERVICE_BASE VARCHAR(255) NOT NULL COMMENT 'Base name of the service, e.g. search-service',
|
||||||
|
INSTANCE VARCHAR(255) NOT NULL COMMENT 'UUID of the service instance',
|
||||||
|
ALIVE BOOLEAN NOT NULL DEFAULT TRUE COMMENT 'Set to false when the service is doing an orderly shutdown',
|
||||||
|
HEARTBEAT_TIME TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT 'Service was last seen at this point'
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE PROC_SERVICE_EVENTLOG(
|
||||||
|
ID BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT 'Unique id',
|
||||||
|
SERVICE_NAME VARCHAR(255) NOT NULL COMMENT 'Full name of the service, including node id if applicable, e.g. search-service:0',
|
||||||
|
SERVICE_BASE VARCHAR(255) NOT NULL COMMENT 'Base name of the service, e.g. search-service',
|
||||||
|
INSTANCE VARCHAR(255) NOT NULL COMMENT 'UUID of the service instance',
|
||||||
|
EVENT_TIME TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT 'Event time',
|
||||||
|
EVENT_TYPE VARCHAR(255) NOT NULL COMMENT 'Event type',
|
||||||
|
EVENT_MESSAGE VARCHAR(255) NOT NULL COMMENT 'Event message'
|
||||||
|
);
|
@ -0,0 +1,17 @@
|
|||||||
|
CREATE TABLE PROC_SERVICE_HEARTBEAT(
|
||||||
|
SERVICE_NAME VARCHAR(255) PRIMARY KEY COMMENT "Full name of the service, including node id if applicable, e.g. search-service:0",
|
||||||
|
SERVICE_BASE VARCHAR(255) NOT NULL COMMENT "Base name of the service, e.g. search-service",
|
||||||
|
INSTANCE VARCHAR(255) NOT NULL COMMENT "UUID of the service instance",
|
||||||
|
ALIVE BOOLEAN NOT NULL DEFAULT TRUE COMMENT "Set to false when the service is doing an orderly shutdown",
|
||||||
|
HEARTBEAT_TIME TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT "Service was last seen at this point"
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE PROC_SERVICE_EVENTLOG(
|
||||||
|
ID BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT "Unique id",
|
||||||
|
SERVICE_NAME VARCHAR(255) NOT NULL COMMENT "Full name of the service, including node id if applicable, e.g. search-service:0",
|
||||||
|
SERVICE_BASE VARCHAR(255) NOT NULL COMMENT "Base name of the service, e.g. search-service",
|
||||||
|
INSTANCE VARCHAR(255) NOT NULL COMMENT "UUID of the service instance",
|
||||||
|
EVENT_TIME TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT "Event time",
|
||||||
|
EVENT_TYPE VARCHAR(255) NOT NULL COMMENT "Event type",
|
||||||
|
EVENT_MESSAGE VARCHAR(255) NOT NULL COMMENT "Event message"
|
||||||
|
);
|
@ -13,7 +13,6 @@ import java.util.concurrent.Executors;
|
|||||||
import java.util.concurrent.ThreadFactory;
|
import java.util.concurrent.ThreadFactory;
|
||||||
|
|
||||||
public class AbortingScheduler {
|
public class AbortingScheduler {
|
||||||
private final String name;
|
|
||||||
private final ThreadFactory threadFactory;
|
private final ThreadFactory threadFactory;
|
||||||
|
|
||||||
private final Logger logger = LoggerFactory.getLogger(getClass());
|
private final Logger logger = LoggerFactory.getLogger(getClass());
|
||||||
@ -22,7 +21,6 @@ public class AbortingScheduler {
|
|||||||
private ExecutorService executorService;
|
private ExecutorService executorService;
|
||||||
|
|
||||||
public AbortingScheduler(String name) {
|
public AbortingScheduler(String name) {
|
||||||
this.name = name;
|
|
||||||
threadFactory = new ThreadFactoryBuilder()
|
threadFactory = new ThreadFactoryBuilder()
|
||||||
.setNameFormat(name+"client--%d")
|
.setNameFormat(name+"client--%d")
|
||||||
.setUncaughtExceptionHandler(this::handleException)
|
.setUncaughtExceptionHandler(this::handleException)
|
||||||
|
@ -0,0 +1,133 @@
|
|||||||
|
package nu.marginalia.client;
|
||||||
|
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
import com.google.inject.Singleton;
|
||||||
|
import com.zaxxer.hikari.HikariDataSource;
|
||||||
|
import nu.marginalia.service.id.ServiceId;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
public class ServiceMonitors {
|
||||||
|
private final HikariDataSource dataSource;
|
||||||
|
private final Logger logger = LoggerFactory.getLogger(getClass());
|
||||||
|
|
||||||
|
private final Set<String> runningServices = new HashSet<>();
|
||||||
|
private final Set<Runnable> callbacks = new HashSet<>();
|
||||||
|
|
||||||
|
|
||||||
|
private final int heartbeatInterval = Integer.getInteger("mcp.heartbeat.interval", 5);
|
||||||
|
|
||||||
|
private volatile boolean running;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public ServiceMonitors(HikariDataSource dataSource) {
|
||||||
|
this.dataSource = dataSource;
|
||||||
|
|
||||||
|
var runThread = new Thread(this::run, "service monitor");
|
||||||
|
runThread.setDaemon(true);
|
||||||
|
runThread.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void subscribe(Runnable callback) {
|
||||||
|
synchronized (callbacks) {
|
||||||
|
callbacks.add(callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public void unsubscribe(Runnable callback) {
|
||||||
|
synchronized (callbacks) {
|
||||||
|
callbacks.remove(callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void run() {
|
||||||
|
if (running) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
running = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (running) {
|
||||||
|
if (updateRunningServices()) {
|
||||||
|
runCallbacks();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
TimeUnit.SECONDS.sleep(heartbeatInterval);
|
||||||
|
}
|
||||||
|
catch (InterruptedException ex) {
|
||||||
|
logger.warn("ServiceMonitors interrupted", ex);
|
||||||
|
running = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void runCallbacks() {
|
||||||
|
synchronized (callbacks) {
|
||||||
|
for (var callback : callbacks) {
|
||||||
|
synchronized (runningServices) {
|
||||||
|
callback.run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean updateRunningServices() {
|
||||||
|
try (var conn = dataSource.getConnection();
|
||||||
|
var stmt = conn.prepareStatement("""
|
||||||
|
SELECT SERVICE_BASE, TIMESTAMPDIFF(SECOND, HEARTBEAT_TIME, CURRENT_TIMESTAMP(6))
|
||||||
|
FROM PROC_SERVICE_HEARTBEAT
|
||||||
|
WHERE ALIVE=1
|
||||||
|
""")) {
|
||||||
|
try (var rs = stmt.executeQuery()) {
|
||||||
|
Set<String> newRunningServices = new HashSet<>(10);
|
||||||
|
while (rs.next()) {
|
||||||
|
String svc = rs.getString(1);
|
||||||
|
int dtime = rs.getInt(2);
|
||||||
|
if (dtime < 2.5 * heartbeatInterval) {
|
||||||
|
newRunningServices.add(svc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean changed;
|
||||||
|
|
||||||
|
synchronized (runningServices) {
|
||||||
|
changed = !Objects.equals(runningServices, newRunningServices);
|
||||||
|
|
||||||
|
runningServices.clear();
|
||||||
|
runningServices.addAll(newRunningServices);
|
||||||
|
}
|
||||||
|
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (SQLException ex) {
|
||||||
|
logger.warn("Failed to update running services", ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isServiceUp(ServiceId serviceId) {
|
||||||
|
synchronized (runningServices) {
|
||||||
|
return runningServices.contains(serviceId.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ServiceId> getRunningServices() {
|
||||||
|
List<ServiceId> ret = new ArrayList<>(ServiceId.values().length);
|
||||||
|
|
||||||
|
synchronized (runningServices) {
|
||||||
|
for (var runningService : runningServices) {
|
||||||
|
ret.add(ServiceId.byName(runningService));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
@ -13,5 +13,7 @@ public class SearchServiceDescriptors {
|
|||||||
new ServiceDescriptor(ServiceId.Search, 5023),
|
new ServiceDescriptor(ServiceId.Search, 5023),
|
||||||
new ServiceDescriptor(ServiceId.Assistant, 5025),
|
new ServiceDescriptor(ServiceId.Assistant, 5025),
|
||||||
new ServiceDescriptor(ServiceId.Dating, 5070),
|
new ServiceDescriptor(ServiceId.Dating, 5070),
|
||||||
new ServiceDescriptor(ServiceId.Explorer, 5071)));
|
new ServiceDescriptor(ServiceId.Explorer, 5071),
|
||||||
|
new ServiceDescriptor(ServiceId.Control, 5090)
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
@ -7,19 +7,22 @@ public enum ServiceId {
|
|||||||
Search("search-service"),
|
Search("search-service"),
|
||||||
Index("index-service"),
|
Index("index-service"),
|
||||||
|
|
||||||
|
Control("control-service"),
|
||||||
|
|
||||||
Dating("dating-service"),
|
Dating("dating-service"),
|
||||||
Explorer("explorer-service"),
|
Explorer("explorer-service");
|
||||||
|
|
||||||
Other_Auth("auth"),
|
|
||||||
Other_Memex("memex"),
|
|
||||||
|
|
||||||
|
|
||||||
Other_ResourceStore("resource-store"),
|
|
||||||
Other_Renderer("renderer"),
|
|
||||||
Other_PodcastScraper("podcast-scraper");
|
|
||||||
|
|
||||||
public final String name;
|
public final String name;
|
||||||
ServiceId(String name) {
|
ServiceId(String name) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static ServiceId byName(String name) {
|
||||||
|
for (ServiceId id : values()) {
|
||||||
|
if (id.name.equals(name)) {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ java {
|
|||||||
dependencies {
|
dependencies {
|
||||||
implementation project(':code:common:service-client')
|
implementation project(':code:common:service-client')
|
||||||
implementation project(':code:common:service-discovery')
|
implementation project(':code:common:service-discovery')
|
||||||
|
implementation project(':code:common:db')
|
||||||
|
|
||||||
implementation libs.lombok
|
implementation libs.lombok
|
||||||
annotationProcessor libs.lombok
|
annotationProcessor libs.lombok
|
||||||
|
@ -3,6 +3,52 @@
|
|||||||
Contains the base classes for the services. This is where port configuration,
|
Contains the base classes for the services. This is where port configuration,
|
||||||
and common endpoints are set up.
|
and common endpoints are set up.
|
||||||
|
|
||||||
|
## Creating a new Service
|
||||||
|
|
||||||
|
The minimal service needs a `MainClass` and a `Service` class.
|
||||||
|
|
||||||
|
For proper initiation, the main class should look like this:
|
||||||
|
|
||||||
|
```java
|
||||||
|
public class FoobarMain extends MainClass {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public FoobarMain(FoobarService service) {}
|
||||||
|
|
||||||
|
public static void main(String... args) {
|
||||||
|
init(ServiceId.Foobar, args);
|
||||||
|
|
||||||
|
Injector injector = Guice.createInjector(
|
||||||
|
new FoobarModule(), /* optional custom bindings go here */
|
||||||
|
new DatabaseModule(),
|
||||||
|
new ConfigurationModule(SearchServiceDescriptors.descriptors,
|
||||||
|
ServiceId.Foobar));
|
||||||
|
|
||||||
|
injector.getInstance(FoobarMain.class);
|
||||||
|
|
||||||
|
// set the service as ready so that delayed tasks can be started
|
||||||
|
injector.getInstance(Initialization.class).setReady();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
A service class has a boilerplate set-up that looks like this:
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Singleton
|
||||||
|
public class FoobarService extends Service {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public FoobarService(BaseServiceParams params) {
|
||||||
|
super(params);
|
||||||
|
|
||||||
|
// set up Spark endpoints here
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Further the new service needs to be added to the `ServiceId` enum in [service-discovery](../service-discovery).
|
||||||
|
|
||||||
## Central Classes
|
## Central Classes
|
||||||
|
|
||||||
* [MainClass](src/main/java/nu/marginalia/service/MainClass.java) bootstraps all executables
|
* [MainClass](src/main/java/nu/marginalia/service/MainClass.java) bootstraps all executables
|
||||||
|
@ -11,6 +11,9 @@ import org.slf4j.LoggerFactory;
|
|||||||
import java.net.SocketTimeoutException;
|
import java.net.SocketTimeoutException;
|
||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
|
|
||||||
|
/** Each main class of a service should extend this class.
|
||||||
|
* They must also invoke init() in their main method.
|
||||||
|
*/
|
||||||
public abstract class MainClass {
|
public abstract class MainClass {
|
||||||
private final Logger logger = LoggerFactory.getLogger(getClass());
|
private final Logger logger = LoggerFactory.getLogger(getClass());
|
||||||
|
|
||||||
|
@ -0,0 +1,58 @@
|
|||||||
|
package nu.marginalia.service.control;
|
||||||
|
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
import com.google.inject.Singleton;
|
||||||
|
import com.zaxxer.hikari.HikariDataSource;
|
||||||
|
import nu.marginalia.service.module.ServiceConfiguration;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
public class ServiceEventLog {
|
||||||
|
private final HikariDataSource dataSource;
|
||||||
|
|
||||||
|
private final Logger logger = LoggerFactory.getLogger(ServiceEventLog.class);
|
||||||
|
|
||||||
|
private final String serviceName;
|
||||||
|
private final UUID instanceUuid;
|
||||||
|
private final String serviceBase;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public ServiceEventLog(HikariDataSource dataSource,
|
||||||
|
ServiceConfiguration configuration
|
||||||
|
) {
|
||||||
|
this.dataSource = dataSource;
|
||||||
|
|
||||||
|
this.serviceName = configuration.serviceName() + ":" + configuration.node();
|
||||||
|
this.instanceUuid = configuration.instanceUuid();
|
||||||
|
this.serviceBase = configuration.serviceName();
|
||||||
|
|
||||||
|
logger.info("Starting service {} instance {}", serviceName, instanceUuid);
|
||||||
|
|
||||||
|
logEvent("START", "Service starting");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void logEvent(String type, String message) {
|
||||||
|
|
||||||
|
try (var conn = dataSource.getConnection();
|
||||||
|
var stmt = conn.prepareStatement("""
|
||||||
|
INSERT INTO PROC_SERVICE_EVENTLOG(SERVICE_NAME, SERVICE_BASE, INSTANCE, EVENT_TYPE, EVENT_MESSAGE)
|
||||||
|
VALUES (?, ?, ?, ?, ?)
|
||||||
|
""")) {
|
||||||
|
stmt.setString(1, serviceName);
|
||||||
|
stmt.setString(2, serviceBase);
|
||||||
|
stmt.setString(3, instanceUuid.toString());
|
||||||
|
stmt.setString(4, type);
|
||||||
|
stmt.setString(5, Objects.requireNonNull(message, ""));
|
||||||
|
|
||||||
|
stmt.executeUpdate();
|
||||||
|
}
|
||||||
|
catch (SQLException ex) {
|
||||||
|
logger.error("Failed to log event {}:{}", type, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,145 @@
|
|||||||
|
package nu.marginalia.service.control;
|
||||||
|
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
import com.google.inject.Singleton;
|
||||||
|
import com.zaxxer.hikari.HikariDataSource;
|
||||||
|
import nu.marginalia.service.module.ServiceConfiguration;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/** This service sends a heartbeat to the database every 5 seconds.
|
||||||
|
*/
|
||||||
|
@Singleton
|
||||||
|
public class ServiceHeartbeat {
|
||||||
|
private final Logger logger = LoggerFactory.getLogger(ServiceHeartbeat.class);
|
||||||
|
private final String serviceName;
|
||||||
|
private final String serviceBase;
|
||||||
|
private final String instanceUUID;
|
||||||
|
private final HikariDataSource dataSource;
|
||||||
|
|
||||||
|
|
||||||
|
private final Thread runnerThread;
|
||||||
|
private final int heartbeatInterval = Integer.getInteger("mcp.heartbeat.interval", 5);
|
||||||
|
|
||||||
|
private volatile boolean running = false;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public ServiceHeartbeat(ServiceConfiguration configuration,
|
||||||
|
HikariDataSource dataSource)
|
||||||
|
{
|
||||||
|
this.serviceName = configuration.serviceName() + ":" + configuration.node();
|
||||||
|
this.serviceBase = configuration.serviceName();
|
||||||
|
this.dataSource = dataSource;
|
||||||
|
|
||||||
|
this.instanceUUID = configuration.instanceUuid().toString();
|
||||||
|
|
||||||
|
runnerThread = new Thread(this::run);
|
||||||
|
|
||||||
|
Runtime.getRuntime().addShutdownHook(new Thread(this::shutDown));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void start() {
|
||||||
|
if (!running) {
|
||||||
|
runnerThread.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void shutDown() {
|
||||||
|
if (!running)
|
||||||
|
return;
|
||||||
|
|
||||||
|
running = false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
runnerThread.join();
|
||||||
|
heartbeatStop();
|
||||||
|
}
|
||||||
|
catch (InterruptedException|SQLException ex) {
|
||||||
|
logger.warn("ServiceHeartbeat shutdown failed", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void run() {
|
||||||
|
if (!running)
|
||||||
|
running = true;
|
||||||
|
else
|
||||||
|
return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
heartbeatInit();
|
||||||
|
|
||||||
|
while (running) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
heartbeatUpdate();
|
||||||
|
}
|
||||||
|
catch (SQLException ex) {
|
||||||
|
logger.warn("ServiceHeartbeat failed to update", ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
TimeUnit.SECONDS.sleep(heartbeatInterval);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (InterruptedException|SQLException ex) {
|
||||||
|
logger.error("ServiceHeartbeat caught irrecoverable exception, killing service", ex);
|
||||||
|
System.exit(255);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void heartbeatInit() throws SQLException {
|
||||||
|
try (var connection = dataSource.getConnection()) {
|
||||||
|
try (var stmt = connection.prepareStatement(
|
||||||
|
"""
|
||||||
|
INSERT INTO PROC_SERVICE_HEARTBEAT (SERVICE_NAME, SERVICE_BASE, INSTANCE, HEARTBEAT_TIME, ALIVE)
|
||||||
|
VALUES (?, ?, ?, CURRENT_TIMESTAMP(6), 1)
|
||||||
|
ON DUPLICATE KEY UPDATE
|
||||||
|
INSTANCE = ?,
|
||||||
|
HEARTBEAT_TIME = CURRENT_TIMESTAMP(6),
|
||||||
|
ALIVE = 1
|
||||||
|
"""
|
||||||
|
))
|
||||||
|
{
|
||||||
|
stmt.setString(1, serviceName);
|
||||||
|
stmt.setString(2, serviceBase);
|
||||||
|
stmt.setString(3, instanceUUID);
|
||||||
|
stmt.setString(4, instanceUUID);
|
||||||
|
stmt.executeUpdate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void heartbeatUpdate() throws SQLException {
|
||||||
|
try (var connection = dataSource.getConnection()) {
|
||||||
|
try (var stmt = connection.prepareStatement(
|
||||||
|
"""
|
||||||
|
UPDATE PROC_SERVICE_HEARTBEAT
|
||||||
|
SET HEARTBEAT_TIME = CURRENT_TIMESTAMP(6)
|
||||||
|
WHERE INSTANCE = ? AND ALIVE = 1
|
||||||
|
""")
|
||||||
|
)
|
||||||
|
{
|
||||||
|
stmt.setString(1, instanceUUID);
|
||||||
|
stmt.executeUpdate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void heartbeatStop() throws SQLException {
|
||||||
|
try (var connection = dataSource.getConnection()) {
|
||||||
|
try (var stmt = connection.prepareStatement(
|
||||||
|
"""
|
||||||
|
UPDATE PROC_SERVICE_HEARTBEAT
|
||||||
|
SET HEARTBEAT_TIME = CURRENT_TIMESTAMP(6), ALIVE = 0
|
||||||
|
WHERE INSTANCE = ?
|
||||||
|
""")
|
||||||
|
)
|
||||||
|
{
|
||||||
|
stmt.setString(1, instanceUUID);
|
||||||
|
stmt.executeUpdate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -8,9 +8,9 @@ import nu.marginalia.service.descriptor.ServiceDescriptors;
|
|||||||
import nu.marginalia.service.id.ServiceId;
|
import nu.marginalia.service.id.ServiceId;
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
public class ConfigurationModule extends AbstractModule {
|
public class ConfigurationModule extends AbstractModule {
|
||||||
private static final String SERVICE_NAME = System.getProperty("service-name");
|
|
||||||
private final ServiceDescriptors descriptors;
|
private final ServiceDescriptors descriptors;
|
||||||
private final ServiceId id;
|
private final ServiceId id;
|
||||||
|
|
||||||
@ -21,15 +21,13 @@ public class ConfigurationModule extends AbstractModule {
|
|||||||
|
|
||||||
public void configure() {
|
public void configure() {
|
||||||
bind(ServiceDescriptors.class).toInstance(descriptors);
|
bind(ServiceDescriptors.class).toInstance(descriptors);
|
||||||
bind(String.class).annotatedWith(Names.named("service-name")).toInstance(Objects.requireNonNull(SERVICE_NAME));
|
|
||||||
bind(String.class).annotatedWith(Names.named("service-host")).toInstance(System.getProperty("service-host", "127.0.0.1"));
|
|
||||||
bind(Integer.class).annotatedWith(Names.named("service-port")).toInstance(descriptors.forId(id).port);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
int basePort = descriptors.forId(id).port;
|
||||||
@Named("metrics-server-port")
|
int prometheusPort = basePort + 1000;
|
||||||
public Integer provideMetricsServerPort(@Named("service-port") Integer servicePort) {
|
String host = Objects.requireNonNull(System.getProperty("service-host", "127.0.0.1"));
|
||||||
return servicePort + 1000;
|
var configObject = new ServiceConfiguration(id, 0, host, basePort, prometheusPort, UUID.randomUUID());
|
||||||
|
|
||||||
|
bind(ServiceConfiguration.class).toInstance(configObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,27 @@
|
|||||||
|
package nu.marginalia.service.module;
|
||||||
|
|
||||||
|
import nu.marginalia.service.id.ServiceId;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configuration object for a service. This is a guice-injectable object
|
||||||
|
* intended to keep down the amount of named bindings.
|
||||||
|
*
|
||||||
|
* @param serviceId - service descriptor
|
||||||
|
* @param node - always 0 for now, for future service partitioning
|
||||||
|
* @param host - the bind address of the service
|
||||||
|
* @param port - main port of the service
|
||||||
|
* @param metricsPort - prometheus metrics server port
|
||||||
|
* @param instanceUuid - unique identifier for this instance of the service
|
||||||
|
*/
|
||||||
|
public record ServiceConfiguration(ServiceId serviceId,
|
||||||
|
int node,
|
||||||
|
String host,
|
||||||
|
int port,
|
||||||
|
int metricsPort,
|
||||||
|
UUID instanceUuid) {
|
||||||
|
public String serviceName() {
|
||||||
|
return serviceId.name;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
package nu.marginalia.service.server;
|
||||||
|
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
import com.google.inject.Singleton;
|
||||||
|
import nu.marginalia.service.control.ServiceEventLog;
|
||||||
|
import nu.marginalia.service.control.ServiceHeartbeat;
|
||||||
|
import nu.marginalia.service.module.ServiceConfiguration;
|
||||||
|
|
||||||
|
/** This class exists to reduce Service boilerplate */
|
||||||
|
@Singleton
|
||||||
|
public class BaseServiceParams {
|
||||||
|
public final ServiceConfiguration configuration;
|
||||||
|
public final Initialization initialization;
|
||||||
|
public final MetricsServer metricsServer;
|
||||||
|
public final ServiceHeartbeat heartbeat;
|
||||||
|
public final ServiceEventLog eventLog;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public BaseServiceParams(ServiceConfiguration configuration,
|
||||||
|
Initialization initialization,
|
||||||
|
MetricsServer metricsServer,
|
||||||
|
ServiceHeartbeat heartbeat,
|
||||||
|
ServiceEventLog eventLog) {
|
||||||
|
this.configuration = configuration;
|
||||||
|
this.initialization = initialization;
|
||||||
|
this.metricsServer = metricsServer;
|
||||||
|
this.heartbeat = heartbeat;
|
||||||
|
this.eventLog = eventLog;
|
||||||
|
}
|
||||||
|
}
|
@ -5,10 +5,14 @@ import lombok.SneakyThrows;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
public class Initialization {
|
public class Initialization {
|
||||||
boolean initialized;
|
boolean initialized;
|
||||||
private final Logger logger = LoggerFactory.getLogger(getClass());
|
private final Logger logger = LoggerFactory.getLogger(getClass());
|
||||||
|
private final List<Runnable> callbacks = new ArrayList<>();
|
||||||
|
|
||||||
public static Initialization already() {
|
public static Initialization already() {
|
||||||
Initialization init = new Initialization();
|
Initialization init = new Initialization();
|
||||||
@ -21,6 +25,27 @@ public class Initialization {
|
|||||||
logger.info("Initialized");
|
logger.info("Initialized");
|
||||||
initialized = true;
|
initialized = true;
|
||||||
notifyAll();
|
notifyAll();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
callbacks.forEach(Runnable::run);
|
||||||
|
callbacks.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addCallback(Runnable callback) {
|
||||||
|
boolean runNow;
|
||||||
|
|
||||||
|
synchronized (this) {
|
||||||
|
if (!initialized) {
|
||||||
|
callbacks.add(callback);
|
||||||
|
runNow = false;
|
||||||
|
} else {
|
||||||
|
runNow = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (runNow) {
|
||||||
|
callback.run();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
package nu.marginalia.service.server;
|
package nu.marginalia.service.server;
|
||||||
|
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import com.google.inject.name.Named;
|
|
||||||
import io.prometheus.client.exporter.MetricsServlet;
|
import io.prometheus.client.exporter.MetricsServlet;
|
||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
|
import nu.marginalia.service.module.ServiceConfiguration;
|
||||||
import org.eclipse.jetty.server.Server;
|
import org.eclipse.jetty.server.Server;
|
||||||
import org.eclipse.jetty.servlet.ServletContextHandler;
|
import org.eclipse.jetty.servlet.ServletContextHandler;
|
||||||
import org.eclipse.jetty.servlet.ServletHolder;
|
import org.eclipse.jetty.servlet.ServletHolder;
|
||||||
@ -12,8 +12,8 @@ public class MetricsServer {
|
|||||||
|
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
@Inject
|
@Inject
|
||||||
public MetricsServer(@Named("metrics-server-port") int port) {
|
public MetricsServer(ServiceConfiguration configuration) {
|
||||||
Server server = new Server(port);
|
Server server = new Server(configuration.metricsPort());
|
||||||
ServletContextHandler context = new ServletContextHandler();
|
ServletContextHandler context = new ServletContextHandler();
|
||||||
context.setContextPath("/");
|
context.setContextPath("/");
|
||||||
server.setHandler(context);
|
server.setHandler(context);
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package nu.marginalia.service.server;
|
package nu.marginalia.service.server;
|
||||||
|
|
||||||
import com.google.common.base.Strings;
|
|
||||||
import io.prometheus.client.Counter;
|
import io.prometheus.client.Counter;
|
||||||
import nu.marginalia.client.Context;
|
import nu.marginalia.client.Context;
|
||||||
import nu.marginalia.client.exception.MessagingException;
|
import nu.marginalia.client.exception.MessagingException;
|
||||||
@ -35,22 +34,28 @@ public class Service {
|
|||||||
.labelNames("service")
|
.labelNames("service")
|
||||||
.register();
|
.register();
|
||||||
private final String serviceName;
|
private final String serviceName;
|
||||||
|
|
||||||
private static volatile boolean initialized = false;
|
private static volatile boolean initialized = false;
|
||||||
|
|
||||||
public Service(String ip, int port, Initialization initialization, MetricsServer metricsServer, Runnable configureStaticFiles) {
|
public Service(BaseServiceParams params,
|
||||||
this.initialization = initialization;
|
Runnable configureStaticFiles
|
||||||
|
) {
|
||||||
|
this.initialization = params.initialization;
|
||||||
|
|
||||||
serviceName = System.getProperty("service-name");
|
serviceName = System.getProperty("service-name");
|
||||||
|
|
||||||
|
initialization.addCallback(params.heartbeat::start);
|
||||||
|
initialization.addCallback(() -> params.eventLog.logEvent("SVC-INIT", ""));
|
||||||
|
|
||||||
if (!initialization.isReady() && ! initialized ) {
|
if (!initialization.isReady() && ! initialized ) {
|
||||||
initialized = true;
|
initialized = true;
|
||||||
|
|
||||||
Spark.threadPool(32, 4, 60_000);
|
Spark.threadPool(32, 4, 60_000);
|
||||||
Spark.ipAddress(ip);
|
Spark.ipAddress(params.configuration.host());
|
||||||
Spark.port(port);
|
Spark.port(params.configuration.port());
|
||||||
|
|
||||||
logger.info("{} Listening to {}:{}", getClass().getSimpleName(), ip == null ? "" : ip, port);
|
logger.info("{} Listening to {}:{}", getClass().getSimpleName(),
|
||||||
|
params.configuration.host(),
|
||||||
|
params.configuration.port());
|
||||||
|
|
||||||
configureStaticFiles.run();
|
configureStaticFiles.run();
|
||||||
|
|
||||||
@ -66,8 +71,8 @@ public class Service {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Service(String ip, int port, Initialization initialization, MetricsServer metricsServer) {
|
public Service(BaseServiceParams params) {
|
||||||
this(ip, port, initialization, metricsServer, () -> {
|
this(params, () -> {
|
||||||
// configureStaticFiles can't be an overridable method in Service because it may
|
// configureStaticFiles can't be an overridable method in Service because it may
|
||||||
// need to depend on parameters to the constructor, and super-constructors
|
// need to depend on parameters to the constructor, and super-constructors
|
||||||
// must run first
|
// must run first
|
||||||
|
@ -2,7 +2,6 @@ package nu.marginalia.assistant;
|
|||||||
|
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import com.google.inject.name.Named;
|
|
||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
import nu.marginalia.assistant.eval.Units;
|
import nu.marginalia.assistant.eval.Units;
|
||||||
import nu.marginalia.assistant.suggest.Suggestions;
|
import nu.marginalia.assistant.suggest.Suggestions;
|
||||||
@ -10,9 +9,7 @@ import nu.marginalia.assistant.eval.MathParser;
|
|||||||
import nu.marginalia.model.gson.GsonFactory;
|
import nu.marginalia.model.gson.GsonFactory;
|
||||||
import nu.marginalia.screenshot.ScreenshotService;
|
import nu.marginalia.screenshot.ScreenshotService;
|
||||||
import nu.marginalia.assistant.dict.DictionaryService;
|
import nu.marginalia.assistant.dict.DictionaryService;
|
||||||
import nu.marginalia.service.server.Initialization;
|
import nu.marginalia.service.server.*;
|
||||||
import nu.marginalia.service.server.MetricsServer;
|
|
||||||
import nu.marginalia.service.server.Service;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import spark.Request;
|
import spark.Request;
|
||||||
@ -28,18 +25,15 @@ public class AssistantService extends Service {
|
|||||||
|
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
@Inject
|
@Inject
|
||||||
public AssistantService(@Named("service-host") String ip,
|
public AssistantService(BaseServiceParams params,
|
||||||
@Named("service-port") Integer port,
|
|
||||||
Initialization initialization,
|
|
||||||
MetricsServer metricsServer,
|
|
||||||
DictionaryService dictionaryService,
|
DictionaryService dictionaryService,
|
||||||
MathParser mathParser,
|
MathParser mathParser,
|
||||||
Units units,
|
Units units,
|
||||||
ScreenshotService screenshotService,
|
ScreenshotService screenshotService,
|
||||||
Suggestions suggestions
|
Suggestions suggestions)
|
||||||
)
|
|
||||||
{
|
{
|
||||||
super(ip, port, initialization, metricsServer);
|
super(params);
|
||||||
|
|
||||||
this.mathParser = mathParser;
|
this.mathParser = mathParser;
|
||||||
this.units = units;
|
this.units = units;
|
||||||
this.suggestions = suggestions;
|
this.suggestions = suggestions;
|
||||||
|
@ -8,6 +8,7 @@ import nu.marginalia.WmsaHome;
|
|||||||
import nu.marginalia.lexicon.KeywordLexicon;
|
import nu.marginalia.lexicon.KeywordLexicon;
|
||||||
import nu.marginalia.lexicon.KeywordLexiconReadOnlyView;
|
import nu.marginalia.lexicon.KeywordLexiconReadOnlyView;
|
||||||
import nu.marginalia.lexicon.journal.KeywordLexiconJournal;
|
import nu.marginalia.lexicon.journal.KeywordLexiconJournal;
|
||||||
|
import nu.marginalia.service.control.ServiceEventLog;
|
||||||
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
|
||||||
@ -20,13 +21,20 @@ public class IndexModule extends AbstractModule {
|
|||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
private KeywordLexiconReadOnlyView createLexicon() {
|
private KeywordLexiconReadOnlyView createLexicon(ServiceEventLog eventLog) {
|
||||||
return new KeywordLexiconReadOnlyView(
|
try {
|
||||||
new KeywordLexicon(
|
eventLog.logEvent("INDEX-LEXICON-LOAD-BEGIN", "");
|
||||||
new KeywordLexiconJournal(WmsaHome.getDisk("index-write").resolve("dictionary.dat").toFile()
|
|
||||||
|
return new KeywordLexiconReadOnlyView(
|
||||||
|
new KeywordLexicon(
|
||||||
|
new KeywordLexiconJournal(WmsaHome.getDisk("index-write").resolve("dictionary.dat").toFile()
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
);
|
||||||
);
|
}
|
||||||
|
finally {
|
||||||
|
eventLog.logEvent("INDEX-LEXICON-LOAD-OK", "");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
|
@ -2,16 +2,14 @@ package nu.marginalia.index;
|
|||||||
|
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import com.google.inject.name.Named;
|
|
||||||
import io.reactivex.rxjava3.schedulers.Schedulers;
|
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||||
import nu.marginalia.index.index.SearchIndex;
|
import nu.marginalia.index.index.SearchIndex;
|
||||||
import nu.marginalia.index.svc.IndexOpsService;
|
import nu.marginalia.index.svc.IndexOpsService;
|
||||||
import nu.marginalia.index.svc.IndexQueryService;
|
import nu.marginalia.index.svc.IndexQueryService;
|
||||||
import nu.marginalia.index.svc.IndexSearchSetsService;
|
import nu.marginalia.index.svc.IndexSearchSetsService;
|
||||||
import nu.marginalia.model.gson.GsonFactory;
|
import nu.marginalia.model.gson.GsonFactory;
|
||||||
import nu.marginalia.service.server.Initialization;
|
import nu.marginalia.service.control.ServiceEventLog;
|
||||||
import nu.marginalia.service.server.MetricsServer;
|
import nu.marginalia.service.server.*;
|
||||||
import nu.marginalia.service.server.Service;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@ -34,28 +32,29 @@ public class IndexService extends Service {
|
|||||||
|
|
||||||
private final IndexServicesFactory servicesFactory;
|
private final IndexServicesFactory servicesFactory;
|
||||||
private final IndexSearchSetsService searchSetsService;
|
private final IndexSearchSetsService searchSetsService;
|
||||||
|
private final ServiceEventLog eventLog;
|
||||||
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public IndexService(@Named("service-host") String ip,
|
public IndexService(BaseServiceParams params,
|
||||||
@Named("service-port") Integer port,
|
|
||||||
Initialization init,
|
|
||||||
MetricsServer metricsServer,
|
|
||||||
IndexOpsService opsService,
|
IndexOpsService opsService,
|
||||||
IndexQueryService indexQueryService,
|
IndexQueryService indexQueryService,
|
||||||
SearchIndex searchIndex,
|
SearchIndex searchIndex,
|
||||||
IndexServicesFactory servicesFactory,
|
IndexServicesFactory servicesFactory,
|
||||||
IndexSearchSetsService searchSetsService)
|
IndexSearchSetsService searchSetsService,
|
||||||
|
ServiceEventLog eventLog)
|
||||||
{
|
{
|
||||||
super(ip, port, init, metricsServer);
|
super(params);
|
||||||
|
|
||||||
this.opsService = opsService;
|
this.opsService = opsService;
|
||||||
this.searchIndex = searchIndex;
|
this.searchIndex = searchIndex;
|
||||||
this.servicesFactory = servicesFactory;
|
this.servicesFactory = servicesFactory;
|
||||||
this.searchSetsService = searchSetsService;
|
this.searchSetsService = searchSetsService;
|
||||||
|
this.eventLog = eventLog;
|
||||||
|
|
||||||
final Gson gson = GsonFactory.get();
|
final Gson gson = GsonFactory.get();
|
||||||
|
|
||||||
this.init = init;
|
this.init = params.initialization;
|
||||||
|
|
||||||
Spark.post("/search/", indexQueryService::search, gson::toJson);
|
Spark.post("/search/", indexQueryService::search, gson::toJson);
|
||||||
|
|
||||||
@ -94,9 +93,11 @@ public class IndexService extends Service {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
eventLog.logEvent("INDEX-AUTO-CONVERT-BEGIN", "");
|
||||||
logger.info("Auto-converting");
|
logger.info("Auto-converting");
|
||||||
searchSetsService.recalculateAll();
|
searchSetsService.recalculateAll();
|
||||||
searchIndex.switchIndex();
|
searchIndex.switchIndex();
|
||||||
|
eventLog.logEvent("INDEX-AUTO-CONVERT-END", "");
|
||||||
logger.info("Auto-conversion finished!");
|
logger.info("Auto-conversion finished!");
|
||||||
}
|
}
|
||||||
catch (IOException ex) {
|
catch (IOException ex) {
|
||||||
|
@ -6,6 +6,7 @@ import nu.marginalia.index.IndexServicesFactory;
|
|||||||
import nu.marginalia.index.query.*;
|
import nu.marginalia.index.query.*;
|
||||||
import nu.marginalia.index.query.filter.QueryFilterStepFromPredicate;
|
import nu.marginalia.index.query.filter.QueryFilterStepFromPredicate;
|
||||||
import nu.marginalia.index.svc.IndexSearchSetsService;
|
import nu.marginalia.index.svc.IndexSearchSetsService;
|
||||||
|
import nu.marginalia.service.control.ServiceEventLog;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@ -36,10 +37,15 @@ public class SearchIndex {
|
|||||||
private final IndexServicesFactory servicesFactory;
|
private final IndexServicesFactory servicesFactory;
|
||||||
private final IndexSearchSetsService searchSetsService;
|
private final IndexSearchSetsService searchSetsService;
|
||||||
|
|
||||||
|
private final ServiceEventLog eventLog;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public SearchIndex(@NotNull IndexServicesFactory servicesFactory, IndexSearchSetsService searchSetsService) {
|
public SearchIndex(@NotNull IndexServicesFactory servicesFactory,
|
||||||
|
IndexSearchSetsService searchSetsService,
|
||||||
|
ServiceEventLog eventLog) {
|
||||||
this.servicesFactory = servicesFactory;
|
this.servicesFactory = servicesFactory;
|
||||||
this.searchSetsService = searchSetsService;
|
this.searchSetsService = searchSetsService;
|
||||||
|
this.eventLog = eventLog;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void init() {
|
public void init() {
|
||||||
@ -51,7 +57,13 @@ public class SearchIndex {
|
|||||||
|
|
||||||
if (indexReader == null) {
|
if (indexReader == null) {
|
||||||
indexReader = servicesFactory.getSearchIndexReader();
|
indexReader = servicesFactory.getSearchIndexReader();
|
||||||
|
eventLog.logEvent("INDEX-INIT", "Index loaded");
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
eventLog.logEvent("INDEX-INIT", "No index loaded");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
catch (Exception ex) {
|
catch (Exception ex) {
|
||||||
logger.error("Uncaught exception", ex);
|
logger.error("Uncaught exception", ex);
|
||||||
@ -63,9 +75,12 @@ public class SearchIndex {
|
|||||||
|
|
||||||
public boolean switchIndex() throws IOException {
|
public boolean switchIndex() throws IOException {
|
||||||
|
|
||||||
|
eventLog.logEvent("CONVERT-INDEX-BEGIN", "");
|
||||||
servicesFactory.convertIndex(searchSetsService.getDomainRankings());
|
servicesFactory.convertIndex(searchSetsService.getDomainRankings());
|
||||||
|
eventLog.logEvent("CONVERT-INDEX-END", "");
|
||||||
System.gc();
|
System.gc();
|
||||||
|
|
||||||
|
eventLog.logEvent("INDEX-SWITCH-BEGIN", "");
|
||||||
Lock lock = indexReplacementLock.writeLock();
|
Lock lock = indexReplacementLock.writeLock();
|
||||||
try {
|
try {
|
||||||
lock.lock();
|
lock.lock();
|
||||||
@ -73,11 +88,15 @@ public class SearchIndex {
|
|||||||
servicesFactory.switchFilesJob().call();
|
servicesFactory.switchFilesJob().call();
|
||||||
|
|
||||||
indexReader = servicesFactory.getSearchIndexReader();
|
indexReader = servicesFactory.getSearchIndexReader();
|
||||||
|
|
||||||
|
eventLog.logEvent("INDEX-SWITCH-OK", "");
|
||||||
}
|
}
|
||||||
catch (Exception ex) {
|
catch (Exception ex) {
|
||||||
|
eventLog.logEvent("INDEX-SWITCH-ERR", "");
|
||||||
logger.error("Uncaught exception", ex);
|
logger.error("Uncaught exception", ex);
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
|
|
||||||
lock.unlock();
|
lock.unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,11 +1,8 @@
|
|||||||
package nu.marginalia.index.svc;
|
package nu.marginalia.index.svc;
|
||||||
|
|
||||||
import com.google.inject.AbstractModule;
|
import com.google.inject.AbstractModule;
|
||||||
import com.google.inject.name.Names;
|
|
||||||
import nu.marginalia.WmsaHome;
|
|
||||||
import nu.marginalia.index.IndexServicesFactory;
|
import nu.marginalia.index.IndexServicesFactory;
|
||||||
import nu.marginalia.index.journal.writer.IndexJournalWriter;
|
import nu.marginalia.index.journal.writer.IndexJournalWriter;
|
||||||
import nu.marginalia.index.journal.writer.IndexJournalWriterImpl;
|
|
||||||
import nu.marginalia.lexicon.KeywordLexicon;
|
import nu.marginalia.lexicon.KeywordLexicon;
|
||||||
import nu.marginalia.lexicon.KeywordLexiconReadOnlyView;
|
import nu.marginalia.lexicon.KeywordLexiconReadOnlyView;
|
||||||
import nu.marginalia.lexicon.journal.KeywordLexiconJournal;
|
import nu.marginalia.lexicon.journal.KeywordLexiconJournal;
|
||||||
@ -13,12 +10,17 @@ import nu.marginalia.ranking.DomainRankings;
|
|||||||
import nu.marginalia.index.svc.searchset.SearchSetAny;
|
import nu.marginalia.index.svc.searchset.SearchSetAny;
|
||||||
import nu.marginalia.index.util.TestUtil;
|
import nu.marginalia.index.util.TestUtil;
|
||||||
import nu.marginalia.index.client.model.query.SearchSetIdentifier;
|
import nu.marginalia.index.client.model.query.SearchSetIdentifier;
|
||||||
|
import nu.marginalia.service.control.ServiceEventLog;
|
||||||
|
import nu.marginalia.service.control.ServiceHeartbeat;
|
||||||
|
import nu.marginalia.service.id.ServiceId;
|
||||||
|
import nu.marginalia.service.module.ServiceConfiguration;
|
||||||
import org.mockito.Mockito;
|
import org.mockito.Mockito;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
@ -62,8 +64,18 @@ public class IndexQueryServiceIntegrationTestModule extends AbstractModule {
|
|||||||
|
|
||||||
bind(IndexJournalWriter.class).toInstance(servicesFactory.createIndexJournalWriter(keywordLexicon));
|
bind(IndexJournalWriter.class).toInstance(servicesFactory.createIndexJournalWriter(keywordLexicon));
|
||||||
|
|
||||||
bind(String.class).annotatedWith(Names.named("service-host")).toInstance("127.0.0.1");
|
bind(ServiceEventLog.class).toInstance(Mockito.mock(ServiceEventLog.class));
|
||||||
bind(Integer.class).annotatedWith(Names.named("service-port")).toProvider(this::randomPort);
|
bind(ServiceHeartbeat.class).toInstance(Mockito.mock(ServiceHeartbeat.class));
|
||||||
|
|
||||||
|
bind(ServiceConfiguration.class).toInstance(new ServiceConfiguration(
|
||||||
|
ServiceId.Index,
|
||||||
|
0,
|
||||||
|
"127.0.0.1",
|
||||||
|
randomPort(),
|
||||||
|
randomPort(),
|
||||||
|
UUID.randomUUID()
|
||||||
|
));
|
||||||
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
|
@ -2,17 +2,13 @@ package nu.marginalia.search;
|
|||||||
|
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import com.google.inject.name.Named;
|
|
||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
import nu.marginalia.WebsiteUrl;
|
import nu.marginalia.WebsiteUrl;
|
||||||
import nu.marginalia.client.Context;
|
import nu.marginalia.client.Context;
|
||||||
import nu.marginalia.model.gson.GsonFactory;
|
import nu.marginalia.model.gson.GsonFactory;
|
||||||
import nu.marginalia.search.svc.SearchFrontPageService;
|
import nu.marginalia.search.svc.SearchFrontPageService;
|
||||||
import nu.marginalia.search.svc.*;
|
import nu.marginalia.search.svc.*;
|
||||||
import nu.marginalia.service.server.Initialization;
|
import nu.marginalia.service.server.*;
|
||||||
import nu.marginalia.service.server.MetricsServer;
|
|
||||||
import nu.marginalia.service.server.Service;
|
|
||||||
import nu.marginalia.service.server.StaticResources;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import spark.Request;
|
import spark.Request;
|
||||||
@ -31,10 +27,7 @@ public class SearchService extends Service {
|
|||||||
|
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
@Inject
|
@Inject
|
||||||
public SearchService(@Named("service-host") String ip,
|
public SearchService(BaseServiceParams params,
|
||||||
@Named("service-port") Integer port,
|
|
||||||
Initialization initialization,
|
|
||||||
MetricsServer metricsServer,
|
|
||||||
WebsiteUrl websiteUrl,
|
WebsiteUrl websiteUrl,
|
||||||
StaticResources staticResources,
|
StaticResources staticResources,
|
||||||
SearchFrontPageService frontPageService,
|
SearchFrontPageService frontPageService,
|
||||||
@ -44,7 +37,7 @@ public class SearchService extends Service {
|
|||||||
SearchQueryService searchQueryService,
|
SearchQueryService searchQueryService,
|
||||||
SearchApiQueryService apiQueryService
|
SearchApiQueryService apiQueryService
|
||||||
) {
|
) {
|
||||||
super(ip, port, initialization, metricsServer);
|
super(params);
|
||||||
|
|
||||||
this.websiteUrl = websiteUrl;
|
this.websiteUrl = websiteUrl;
|
||||||
this.staticResources = staticResources;
|
this.staticResources = staticResources;
|
||||||
|
@ -2,7 +2,6 @@ package nu.marginalia.api;
|
|||||||
|
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import com.google.inject.name.Named;
|
|
||||||
import nu.marginalia.api.model.ApiLicense;
|
import nu.marginalia.api.model.ApiLicense;
|
||||||
import nu.marginalia.api.svc.LicenseService;
|
import nu.marginalia.api.svc.LicenseService;
|
||||||
import nu.marginalia.api.svc.RateLimiterService;
|
import nu.marginalia.api.svc.RateLimiterService;
|
||||||
@ -11,9 +10,7 @@ import nu.marginalia.client.Context;
|
|||||||
import nu.marginalia.model.gson.GsonFactory;
|
import nu.marginalia.model.gson.GsonFactory;
|
||||||
import nu.marginalia.search.client.SearchClient;
|
import nu.marginalia.search.client.SearchClient;
|
||||||
import nu.marginalia.search.client.model.ApiSearchResults;
|
import nu.marginalia.search.client.model.ApiSearchResults;
|
||||||
import nu.marginalia.service.server.Initialization;
|
import nu.marginalia.service.server.*;
|
||||||
import nu.marginalia.service.server.MetricsServer;
|
|
||||||
import nu.marginalia.service.server.Service;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.slf4j.Marker;
|
import org.slf4j.Marker;
|
||||||
@ -36,16 +33,14 @@ public class ApiService extends Service {
|
|||||||
private final Marker queryMarker = MarkerFactory.getMarker("QUERY");
|
private final Marker queryMarker = MarkerFactory.getMarker("QUERY");
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public ApiService(@Named("service-host") String ip,
|
public ApiService(BaseServiceParams params,
|
||||||
@Named("service-port") Integer port,
|
|
||||||
Initialization initialization,
|
|
||||||
MetricsServer metricsServer,
|
|
||||||
SearchClient searchClient,
|
SearchClient searchClient,
|
||||||
ResponseCache responseCache,
|
ResponseCache responseCache,
|
||||||
LicenseService licenseService,
|
LicenseService licenseService,
|
||||||
RateLimiterService rateLimiterService) {
|
RateLimiterService rateLimiterService
|
||||||
|
) {
|
||||||
|
|
||||||
super(ip, port, initialization, metricsServer);
|
super(params);
|
||||||
|
|
||||||
this.searchClient = searchClient;
|
this.searchClient = searchClient;
|
||||||
this.responseCache = responseCache;
|
this.responseCache = responseCache;
|
||||||
|
64
code/services-satellite/control-service/build.gradle
Normal file
64
code/services-satellite/control-service/build.gradle
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
plugins {
|
||||||
|
id 'java'
|
||||||
|
id "io.freefair.lombok" version "5.3.3.3"
|
||||||
|
id 'application'
|
||||||
|
id 'com.palantir.docker' version '0.34.0'
|
||||||
|
id 'jvm-test-suite'
|
||||||
|
}
|
||||||
|
|
||||||
|
java {
|
||||||
|
toolchain {
|
||||||
|
languageVersion.set(JavaLanguageVersion.of(17))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
application {
|
||||||
|
mainClass = 'nu.marginalia.control.ControlMain'
|
||||||
|
applicationName = 'control-service'
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.distZip.enabled = false
|
||||||
|
|
||||||
|
apply from: "$rootProject.projectDir/docker-service.gradle"
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation project(':code:common:db')
|
||||||
|
implementation project(':code:common:model')
|
||||||
|
implementation project(':code:common:service')
|
||||||
|
implementation project(':code:common:config')
|
||||||
|
implementation project(':code:common:service-discovery')
|
||||||
|
implementation project(':code:common:service-client')
|
||||||
|
implementation project(':code:api:search-api')
|
||||||
|
|
||||||
|
|
||||||
|
implementation libs.lombok
|
||||||
|
annotationProcessor libs.lombok
|
||||||
|
implementation libs.bundles.slf4j
|
||||||
|
|
||||||
|
implementation libs.prometheus
|
||||||
|
implementation libs.notnull
|
||||||
|
implementation libs.guice
|
||||||
|
implementation libs.trove
|
||||||
|
implementation libs.spark
|
||||||
|
implementation libs.fastutil
|
||||||
|
implementation libs.bundles.gson
|
||||||
|
implementation libs.bundles.mariadb
|
||||||
|
|
||||||
|
testImplementation libs.bundles.slf4j.test
|
||||||
|
testImplementation libs.bundles.junit
|
||||||
|
testImplementation libs.mockito
|
||||||
|
testImplementation platform('org.testcontainers:testcontainers-bom:1.17.4')
|
||||||
|
testImplementation 'org.testcontainers:mariadb:1.17.4'
|
||||||
|
testImplementation 'org.testcontainers:junit-jupiter:1.17.4'
|
||||||
|
}
|
||||||
|
|
||||||
|
test {
|
||||||
|
useJUnitPlatform()
|
||||||
|
}
|
||||||
|
|
||||||
|
task fastTests(type: Test) {
|
||||||
|
useJUnitPlatform {
|
||||||
|
excludeTags "slow"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,29 @@
|
|||||||
|
package nu.marginalia.control;
|
||||||
|
|
||||||
|
import com.google.inject.Guice;
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
import com.google.inject.Injector;
|
||||||
|
import nu.marginalia.service.MainClass;
|
||||||
|
import nu.marginalia.service.SearchServiceDescriptors;
|
||||||
|
import nu.marginalia.service.id.ServiceId;
|
||||||
|
import nu.marginalia.service.module.ConfigurationModule;
|
||||||
|
import nu.marginalia.service.module.DatabaseModule;
|
||||||
|
import nu.marginalia.service.server.Initialization;
|
||||||
|
|
||||||
|
public class ControlMain extends MainClass {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public ControlMain(ControlService service) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String... args) {
|
||||||
|
init(ServiceId.Control, args);
|
||||||
|
|
||||||
|
Injector injector = Guice.createInjector(
|
||||||
|
new DatabaseModule(),
|
||||||
|
new ConfigurationModule(SearchServiceDescriptors.descriptors, ServiceId.Control));
|
||||||
|
|
||||||
|
injector.getInstance(ControlMain.class);
|
||||||
|
injector.getInstance(Initialization.class).setReady();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
package nu.marginalia.control;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
import nu.marginalia.client.ServiceMonitors;
|
||||||
|
import nu.marginalia.model.gson.GsonFactory;
|
||||||
|
import nu.marginalia.service.server.*;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import spark.Spark;
|
||||||
|
|
||||||
|
public class ControlService extends Service {
|
||||||
|
|
||||||
|
private final Logger logger = LoggerFactory.getLogger(getClass());
|
||||||
|
private final Gson gson = GsonFactory.get();
|
||||||
|
|
||||||
|
private final ServiceMonitors monitors;
|
||||||
|
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public ControlService(BaseServiceParams params,
|
||||||
|
ServiceMonitors monitors,
|
||||||
|
HeartbeatService heartbeatService
|
||||||
|
) {
|
||||||
|
|
||||||
|
super(params);
|
||||||
|
this.monitors = monitors;
|
||||||
|
|
||||||
|
Spark.get("/public/heartbeats", (req, res) -> {
|
||||||
|
res.type("application/json");
|
||||||
|
return heartbeatService.getHeartbeats();
|
||||||
|
}, gson::toJson);
|
||||||
|
|
||||||
|
monitors.subscribe(this::logMonitorStateChange);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void logMonitorStateChange() {
|
||||||
|
logger.info("Service state change: {}", monitors.getRunningServices());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,48 @@
|
|||||||
|
package nu.marginalia.control;
|
||||||
|
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
import com.google.inject.Singleton;
|
||||||
|
import com.zaxxer.hikari.HikariDataSource;
|
||||||
|
import nu.marginalia.control.model.ServiceHeartbeat;
|
||||||
|
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
public class HeartbeatService {
|
||||||
|
private final HikariDataSource dataSource;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public HeartbeatService(HikariDataSource dataSource) {
|
||||||
|
this.dataSource = dataSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ServiceHeartbeat> getHeartbeats() {
|
||||||
|
List<ServiceHeartbeat> heartbeats = new ArrayList<>();
|
||||||
|
|
||||||
|
try (var conn = dataSource.getConnection();
|
||||||
|
var stmt = conn.prepareStatement("""
|
||||||
|
SELECT SERVICE_NAME, SERVICE_BASE, INSTANCE, ALIVE,
|
||||||
|
TIMESTAMPDIFF(MICROSECOND, HEARTBEAT_TIME, CURRENT_TIMESTAMP(6)) AS TSDIFF
|
||||||
|
FROM PROC_SERVICE_HEARTBEAT
|
||||||
|
""")) {
|
||||||
|
|
||||||
|
var rs = stmt.executeQuery();
|
||||||
|
while (rs.next()) {
|
||||||
|
heartbeats.add(new ServiceHeartbeat(
|
||||||
|
rs.getString("SERVICE_NAME"),
|
||||||
|
rs.getString("SERVICE_BASE"),
|
||||||
|
rs.getString("INSTANCE"),
|
||||||
|
rs.getInt("TSDIFF") / 1000.,
|
||||||
|
rs.getBoolean("ALIVE")
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (SQLException ex) {
|
||||||
|
throw new RuntimeException(ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return heartbeats;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
package nu.marginalia.control.model;
|
||||||
|
|
||||||
|
public record ServiceHeartbeat(
|
||||||
|
String serviceId,
|
||||||
|
String serviceBase,
|
||||||
|
String uuid,
|
||||||
|
double lastSeenMillis,
|
||||||
|
boolean alive
|
||||||
|
) {
|
||||||
|
|
||||||
|
}
|
@ -1,7 +1,6 @@
|
|||||||
package nu.marginalia.dating;
|
package nu.marginalia.dating;
|
||||||
|
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import com.google.inject.name.Named;
|
|
||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
import nu.marginalia.browse.DbBrowseDomainsRandom;
|
import nu.marginalia.browse.DbBrowseDomainsRandom;
|
||||||
import nu.marginalia.browse.DbBrowseDomainsSimilarCosine;
|
import nu.marginalia.browse.DbBrowseDomainsSimilarCosine;
|
||||||
@ -11,9 +10,7 @@ import nu.marginalia.renderer.MustacheRenderer;
|
|||||||
import nu.marginalia.renderer.RendererFactory;
|
import nu.marginalia.renderer.RendererFactory;
|
||||||
import nu.marginalia.screenshot.ScreenshotService;
|
import nu.marginalia.screenshot.ScreenshotService;
|
||||||
import nu.marginalia.model.id.EdgeId;
|
import nu.marginalia.model.id.EdgeId;
|
||||||
import nu.marginalia.service.server.Initialization;
|
import nu.marginalia.service.server.*;
|
||||||
import nu.marginalia.service.server.MetricsServer;
|
|
||||||
import nu.marginalia.service.server.Service;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import spark.Request;
|
import spark.Request;
|
||||||
import spark.Response;
|
import spark.Response;
|
||||||
@ -33,17 +30,15 @@ public class DatingService extends Service {
|
|||||||
private final String SESSION_OBJECT_NAME = "so";
|
private final String SESSION_OBJECT_NAME = "so";
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
@Inject
|
@Inject
|
||||||
public DatingService(@Named("service-host") String ip,
|
public DatingService(BaseServiceParams params,
|
||||||
@Named("service-port") Integer port,
|
|
||||||
RendererFactory rendererFactory,
|
RendererFactory rendererFactory,
|
||||||
Initialization initialization,
|
|
||||||
MetricsServer metricsServer,
|
|
||||||
DomainBlacklist blacklist,
|
DomainBlacklist blacklist,
|
||||||
DbBrowseDomainsSimilarCosine browseSimilarCosine,
|
DbBrowseDomainsSimilarCosine browseSimilarCosine,
|
||||||
DbBrowseDomainsRandom browseRandom,
|
DbBrowseDomainsRandom browseRandom,
|
||||||
ScreenshotService screenshotService) {
|
ScreenshotService screenshotService)
|
||||||
|
{
|
||||||
|
|
||||||
super(ip, port, initialization, metricsServer);
|
super(params);
|
||||||
|
|
||||||
this.blacklist = blacklist;
|
this.blacklist = blacklist;
|
||||||
|
|
||||||
|
@ -1,15 +1,11 @@
|
|||||||
package nu.marginalia.explorer;
|
package nu.marginalia.explorer;
|
||||||
|
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import com.google.inject.name.Named;
|
|
||||||
import com.zaxxer.hikari.HikariDataSource;
|
import com.zaxxer.hikari.HikariDataSource;
|
||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
import nu.marginalia.renderer.MustacheRenderer;
|
import nu.marginalia.renderer.MustacheRenderer;
|
||||||
import nu.marginalia.renderer.RendererFactory;
|
import nu.marginalia.renderer.RendererFactory;
|
||||||
import nu.marginalia.service.server.Initialization;
|
import nu.marginalia.service.server.*;
|
||||||
import nu.marginalia.service.server.MetricsServer;
|
|
||||||
import nu.marginalia.service.server.Service;
|
|
||||||
import nu.marginalia.service.server.StaticResources;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import spark.Request;
|
import spark.Request;
|
||||||
import spark.Response;
|
import spark.Response;
|
||||||
@ -42,16 +38,13 @@ public class ExplorerService extends Service {
|
|||||||
|
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
@Inject
|
@Inject
|
||||||
public ExplorerService(@Named("service-host") String ip,
|
public ExplorerService(BaseServiceParams params,
|
||||||
@Named("service-port") Integer port,
|
RendererFactory rendererFactory,
|
||||||
Initialization initialization,
|
HikariDataSource dataSource,
|
||||||
MetricsServer metricsServer,
|
|
||||||
RendererFactory rendererFactory,
|
|
||||||
HikariDataSource dataSource,
|
|
||||||
StaticResources staticResources
|
StaticResources staticResources
|
||||||
) {
|
) {
|
||||||
|
|
||||||
super(ip, port, initialization, metricsServer);
|
super(params);
|
||||||
|
|
||||||
renderer = rendererFactory.renderer("explorer/explorer");
|
renderer = rendererFactory.renderer("explorer/explorer");
|
||||||
|
|
||||||
|
@ -71,6 +71,16 @@ services:
|
|||||||
- "127.0.0.1:7071:4000"
|
- "127.0.0.1:7071:4000"
|
||||||
depends_on:
|
depends_on:
|
||||||
- mariadb
|
- mariadb
|
||||||
|
control-service:
|
||||||
|
<<: *service
|
||||||
|
image: "marginalia.nu/control-service"
|
||||||
|
container_name: "control-service"
|
||||||
|
ports:
|
||||||
|
- "127.0.0.1:5090:5090"
|
||||||
|
- "127.0.0.1:4090:5000"
|
||||||
|
- "127.0.0.1:7090:4000"
|
||||||
|
depends_on:
|
||||||
|
- mariadb
|
||||||
mariadb:
|
mariadb:
|
||||||
image: "mariadb/server:10.3"
|
image: "mariadb/server:10.3"
|
||||||
container_name: "mariadb"
|
container_name: "mariadb"
|
||||||
|
@ -33,6 +33,9 @@ server {
|
|||||||
proxy_pass http://assistant-service:5025/public$request_uri;
|
proxy_pass http://assistant-service:5025/public$request_uri;
|
||||||
access_log off;
|
access_log off;
|
||||||
}
|
}
|
||||||
|
location /control/ {
|
||||||
|
proxy_pass http://control-service:5090/public/;
|
||||||
|
}
|
||||||
location / {
|
location / {
|
||||||
proxy_pass http://search-service:5023/public/;
|
proxy_pass http://search-service:5023/public/;
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ include 'code:services-core:search-service'
|
|||||||
include 'code:services-satellite:api-service'
|
include 'code:services-satellite:api-service'
|
||||||
include 'code:services-satellite:dating-service'
|
include 'code:services-satellite:dating-service'
|
||||||
include 'code:services-satellite:explorer-service'
|
include 'code:services-satellite:explorer-service'
|
||||||
|
include 'code:services-satellite:control-service'
|
||||||
|
|
||||||
include 'code:libraries:array'
|
include 'code:libraries:array'
|
||||||
include 'code:libraries:btree'
|
include 'code:libraries:btree'
|
||||||
|
Loading…
Reference in New Issue
Block a user