All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.kestra.jdbc.repository.AbstractJdbcServiceInstanceRepository Maven / Gradle / Ivy

There is a newer version: 0.19.11
Show newest version
package io.kestra.jdbc.repository;

import io.kestra.core.repositories.ArrayListTotal;
import io.kestra.core.repositories.ServiceInstanceRepositoryInterface;
import io.kestra.core.server.Service;
import io.kestra.core.server.ServiceInstance;
import io.kestra.core.server.ServiceStateTransition;
import io.micronaut.data.model.Pageable;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.jooq.Configuration;
import org.jooq.DSLContext;
import org.jooq.Field;
import org.jooq.Record;
import org.jooq.Record1;
import org.jooq.SelectConditionStep;
import org.jooq.Table;
import org.jooq.TransactionalCallable;
import org.jooq.TransactionalRunnable;

import java.time.Instant;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

import static org.jooq.impl.DSL.using;

@Getter
@Slf4j
public abstract class AbstractJdbcServiceInstanceRepository extends AbstractJdbcRepository implements ServiceInstanceRepositoryInterface {

    private static final Field STATE = field("state");
    private static final Field TYPE = field("service_type");
    private static final Field VALUE = field("value");
    private static final Field UPDATED_AT = field("updated_at", Instant.class);
    private static final Field CREATED_AT = field("created_at", Instant.class);
    private static final Field SERVICE_ID = field("service_id");

    protected io.kestra.jdbc.AbstractJdbcRepository jdbcRepository;

    public AbstractJdbcServiceInstanceRepository(final io.kestra.jdbc.AbstractJdbcRepository jdbcRepository) {
        this.jdbcRepository = jdbcRepository;
    }

    /**
     * {@inheritDoc}
     **/
    @Override
    public Optional findById(final String id) {
        return jdbcRepository.getDslContextWrapper().transactionResult(
            configuration -> findById(id, configuration, false)
        );
    }

    public Optional findById(final String id,
                                              final Configuration configuration,
                                              final boolean isForUpdate) {

        SelectConditionStep> query = using(configuration)
            .select(VALUE)
            .from(table())
            .where(SERVICE_ID.eq(id));

        return isForUpdate ?
            this.jdbcRepository.fetchOne(query.forUpdate()) :
            this.jdbcRepository.fetchOne(query);
    }

    /**
     * {@inheritDoc}
     **/
    @Override
    public List findAllInstancesInState(final Service.ServiceState state) {
        return this.jdbcRepository.getDslContextWrapper()
            .transactionResult(configuration -> {
                SelectConditionStep> query = using(configuration)
                    .select(VALUE)
                    .from(table())
                    .where(STATE.eq(state.name()));
                return this.jdbcRepository.fetch(query);
            });
    }

    /**
     * {@inheritDoc}
     **/
    @Override
    public List findAllInstancesInStates(final Set states) {
        return this.jdbcRepository.getDslContextWrapper()
            .transactionResult(configuration -> findAllInstancesInStates(configuration, states, false));
    }

    /**
     * {@inheritDoc}
     **/
    @Override
    public List findAllInstancesBetween(final Service.ServiceType type, final Instant from, final Instant to) {
        return jdbcRepository.getDslContextWrapper().transactionResult(configuration -> {
            SelectConditionStep> query = using(configuration)
                .select(VALUE)
                .from(table())
                .where(TYPE.eq(type.name()))
                .and(CREATED_AT.lt(to))
                .and(UPDATED_AT.ge(from));

            return this.jdbcRepository.fetch(query);
        });
    }

    public List findAllInstancesInStates(final Configuration configuration,
                                                          final Set states,
                                                          final boolean isForUpdate) {
        SelectConditionStep> query = using(configuration)
            .select(VALUE)
            .from(table())
            .where(STATE.in(states.stream().map(Enum::name).toList()));

        return isForUpdate ?
            this.jdbcRepository.fetch(query.forUpdate()) :
            this.jdbcRepository.fetch(query);
    }

    /**
     * Finds all service instances which are NOT {@link Service.ServiceState#RUNNING}.
     *
     * @return the list of {@link ServiceInstance}.
     */
    public List findAllNonRunningInstances() {
        return jdbcRepository.getDslContextWrapper().transactionResult(
            configuration -> findAllNonRunningInstances(configuration, false)
        );
    }

    /**
     * Finds all service instances which are NOT {@link Service.ServiceState#RUNNING}.
     *
     * @return the list of {@link ServiceInstance}.
     */
    public List findAllNonRunningInstances(final Configuration configuration,
                                                            final boolean isForUpdate) {
        SelectConditionStep> query = using(configuration)
            .select(VALUE)
            .from(table())
            .where(STATE.notIn(Service.ServiceState.CREATED.name(), Service.ServiceState.RUNNING.name()));

        return isForUpdate ?
            this.jdbcRepository.fetch(query.forUpdate()) :
            this.jdbcRepository.fetch(query);
    }

    /**
     * Finds all service instances which are {@link Service.ServiceState#NOT_RUNNING}.
     *
     * @return the list of {@link ServiceInstance}.
     */
    public List findAllInstancesInNotRunningState() {
        return jdbcRepository.getDslContextWrapper().transactionResult(
            configuration -> findAllInstancesInNotRunningState(configuration, false)
        );
    }

    /**
     * Finds all service instances which are {@link Service.ServiceState#NOT_RUNNING}.
     *
     * @return the list of {@link ServiceInstance}.
     */
    public List findAllInstancesInNotRunningState(final Configuration configuration,
                                                                   final boolean isForUpdate) {
        SelectConditionStep> query = using(configuration)
            .select(VALUE)
            .from(table())
            .where(STATE.eq(Service.ServiceState.NOT_RUNNING.name()));

        return isForUpdate ?
            this.jdbcRepository.fetch(query.forUpdate()) :
            this.jdbcRepository.fetch(query);
    }

    public void transaction(final TransactionalRunnable runnable) {
        this.jdbcRepository
            .getDslContextWrapper()
            .transaction(runnable);
    }

    public  T transactionResult(final TransactionalCallable runnable) {
        return this.jdbcRepository
            .getDslContextWrapper()
            .transactionResult(runnable);
    }

    public void delete(DSLContext context, ServiceInstance instance) {
        this.jdbcRepository.delete(context, instance);
    }

    /**
     * {@inheritDoc}
     **/
    @Override
    public void delete(final ServiceInstance instance) {
        this.jdbcRepository.delete(instance);
    }

    /**
     * {@inheritDoc}
     **/
    @Override
    public ServiceInstance save(final ServiceInstance instance) {
        this.jdbcRepository.persist(instance, this.jdbcRepository.persistFields(instance));
        return instance;
    }

    /**
     * {@inheritDoc}
     **/
    @Override
    public List findAll() {
        return this.jdbcRepository
            .getDslContextWrapper()
            .transactionResult(configuration -> this.jdbcRepository.fetch(
                using(configuration).select(VALUE).from(table()))
            );
    }

    /**
     * {@inheritDoc}
     **/
    @Override
    public ArrayListTotal find(final Pageable pageable,
                                                final Set states,
                                                final Set types) {
        return this.jdbcRepository
            .getDslContextWrapper()
            .transactionResult(configuration -> {
                DSLContext context = using(configuration);
                SelectConditionStep> select = context.select(VALUE).from(table()).where("1=1");
                if (states != null && !states.isEmpty()) {
                    select = select.and(STATE.in(states.stream().map(Enum::name).toList()));
                }
                if (types != null && !types.isEmpty()) {
                    select = select.and(TYPE.in(types.stream().map(Enum::name).toList()));
                }
                return this.jdbcRepository.fetchPage(context, select, pageable);
            });
    }

    /**
     * {@inheritDoc}
     **/
    @Override
    public ServiceStateTransition.Response mayTransitionServiceTo(final ServiceInstance instance,
                                                                  final Service.ServiceState newState,
                                                                  final String reason) {
        return transactionResult(configuration -> mayTransitServiceTo(configuration, instance, newState, reason));
    }

    /**
     * Attempt to transition the state of a given service to given new state.
     * This method may not update the service if the transition is not valid.
     *
     * @param instance the service instance.
     * @param newState the new state of the service.
     * @return an optional of the {@link ServiceInstance} or {@link Optional#empty()} if the service is not running.
     */
    public ServiceStateTransition.Response mayTransitServiceTo(final Configuration configuration,
                                                               final ServiceInstance instance,
                                                               final Service.ServiceState newState,
                                                               final String reason) {
        ImmutablePair result = mayUpdateStatusById(
            configuration,
            instance,
            newState,
            reason
        );
        return ServiceStateTransition.logTransitionAndGetResponse(instance, newState, result);
    }

    /**
     * Attempt to transition the state of a given service to given new state.
     * This method may not update the service if the transition is not valid.
     *
     * @param instance the new service instance.
     * @param newState the new state of the service.
     * @return an {@link Optional} of {@link ImmutablePair} holding the old (left), and new {@link ServiceInstance} or {@code null} if transition failed (right).
     * Otherwise, an {@link Optional#empty()} if the no service can be found.
     */
    private ImmutablePair mayUpdateStatusById(final Configuration configuration,
                                                                                final ServiceInstance instance,
                                                                                final Service.ServiceState newState,
                                                                                final String reason) {
        // Find the ServiceInstance to be updated
        Optional optional = findById(instance.id(), configuration, true);

        // Check whether service was found.
        if (optional.isEmpty()) {
            return null;
        }

        // Check whether the status transition is valid before saving.
        final ServiceInstance before = optional.get();
        if (before.state().isValidTransition(newState)) {
            ServiceInstance updated = before
                .state(newState, Instant.now(), reason)
                .server(instance.server())
                .metrics(instance.metrics());
            return new ImmutablePair<>(before, save(updated));
        }
        return new ImmutablePair<>(before, null);
    }

    private Table table() {
        return this.jdbcRepository.getTable();
    }

    /** {@inheritDoc} **/
    @Override
    public Function sortMapping() {
        Map mapper = Map.of(
            "createdAt", CREATED_AT.getName(),
            "updatedAt", UPDATED_AT.getName(),
            "serviceId", SERVICE_ID.getName()
        );
        return mapper::get;
    }
}