org.apache.aurora.scheduler.storage.db.DbJobUpdateStore.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.aurora.scheduler.storage.db.DbJobUpdateStore.java

Source

/**
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.aurora.scheduler.storage.db;

import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;

import javax.inject.Inject;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableSet;

import org.apache.aurora.common.base.MorePreconditions;
import org.apache.aurora.common.stats.StatsProvider;
import org.apache.aurora.gen.JobUpdateAction;
import org.apache.aurora.gen.JobUpdateStatus;
import org.apache.aurora.gen.storage.StoredJobUpdateDetails;
import org.apache.aurora.scheduler.storage.JobUpdateStore;
import org.apache.aurora.scheduler.storage.db.views.DbJobUpdate;
import org.apache.aurora.scheduler.storage.db.views.DbJobUpdateInstructions;
import org.apache.aurora.scheduler.storage.db.views.DbStoredJobUpdateDetails;
import org.apache.aurora.scheduler.storage.entities.IInstanceTaskConfig;
import org.apache.aurora.scheduler.storage.entities.IJobInstanceUpdateEvent;
import org.apache.aurora.scheduler.storage.entities.IJobUpdate;
import org.apache.aurora.scheduler.storage.entities.IJobUpdateDetails;
import org.apache.aurora.scheduler.storage.entities.IJobUpdateEvent;
import org.apache.aurora.scheduler.storage.entities.IJobUpdateInstructions;
import org.apache.aurora.scheduler.storage.entities.IJobUpdateKey;
import org.apache.aurora.scheduler.storage.entities.IJobUpdateQuery;
import org.apache.aurora.scheduler.storage.entities.IJobUpdateSummary;
import org.apache.aurora.scheduler.storage.entities.IMetadata;
import org.apache.aurora.scheduler.storage.entities.IRange;

import static java.util.Objects.requireNonNull;

import static org.apache.aurora.common.inject.TimedInterceptor.Timed;

/**
 * A relational database-backed job update store.
 */
public class DbJobUpdateStore implements JobUpdateStore.Mutable {

    private final JobKeyMapper jobKeyMapper;
    private final JobUpdateDetailsMapper detailsMapper;
    private final JobUpdateEventMapper jobEventMapper;
    private final JobInstanceUpdateEventMapper instanceEventMapper;
    private final TaskConfigManager taskConfigManager;
    private final LoadingCache<JobUpdateStatus, AtomicLong> jobUpdateEventStats;
    private final LoadingCache<JobUpdateAction, AtomicLong> jobUpdateActionStats;

    @Inject
    DbJobUpdateStore(JobKeyMapper jobKeyMapper, JobUpdateDetailsMapper detailsMapper,
            JobUpdateEventMapper jobEventMapper, JobInstanceUpdateEventMapper instanceEventMapper,
            TaskConfigManager taskConfigManager, StatsProvider statsProvider) {

        this.jobKeyMapper = requireNonNull(jobKeyMapper);
        this.detailsMapper = requireNonNull(detailsMapper);
        this.jobEventMapper = requireNonNull(jobEventMapper);
        this.instanceEventMapper = requireNonNull(instanceEventMapper);
        this.taskConfigManager = requireNonNull(taskConfigManager);
        this.jobUpdateEventStats = CacheBuilder.newBuilder().build(new CacheLoader<JobUpdateStatus, AtomicLong>() {
            @Override
            public AtomicLong load(JobUpdateStatus status) {
                return statsProvider.makeCounter(jobUpdateStatusStatName(status));
            }
        });
        for (JobUpdateStatus status : JobUpdateStatus.values()) {
            jobUpdateEventStats.getUnchecked(status).get();
        }
        this.jobUpdateActionStats = CacheBuilder.newBuilder().build(new CacheLoader<JobUpdateAction, AtomicLong>() {
            @Override
            public AtomicLong load(JobUpdateAction action) {
                return statsProvider.makeCounter(jobUpdateActionStatName(action));
            }
        });
        for (JobUpdateAction action : JobUpdateAction.values()) {
            jobUpdateActionStats.getUnchecked(action).get();
        }
    }

    @Timed("job_update_store_save_update")
    @Override
    public void saveJobUpdate(IJobUpdate update, Optional<String> lockToken) {
        requireNonNull(update);
        if (!update.getInstructions().isSetDesiredState() && update.getInstructions().getInitialState().isEmpty()) {
            throw new IllegalArgumentException(
                    "Missing both initial and desired states. At least one is required.");
        }

        IJobUpdateKey key = update.getSummary().getKey();
        jobKeyMapper.merge(key.getJob());
        detailsMapper.insert(update.newBuilder());

        if (lockToken.isPresent()) {
            detailsMapper.insertLockToken(key, lockToken.get());
        }

        if (!update.getSummary().getMetadata().isEmpty()) {
            detailsMapper.insertJobUpdateMetadata(key, IMetadata.toBuildersSet(update.getSummary().getMetadata()));
        }

        // Insert optional instance update overrides.
        Set<IRange> instanceOverrides = update.getInstructions().getSettings().getUpdateOnlyTheseInstances();

        if (!instanceOverrides.isEmpty()) {
            detailsMapper.insertInstanceOverrides(key, IRange.toBuildersSet(instanceOverrides));
        }

        // Insert desired state task config and instance mappings.
        if (update.getInstructions().isSetDesiredState()) {
            IInstanceTaskConfig desired = update.getInstructions().getDesiredState();
            detailsMapper.insertTaskConfig(key, taskConfigManager.insert(desired.getTask()), true,
                    new InsertResult());

            detailsMapper.insertDesiredInstances(key,
                    IRange.toBuildersSet(MorePreconditions.checkNotBlank(desired.getInstances())));
        }

        // Insert initial state task configs and instance mappings.
        if (!update.getInstructions().getInitialState().isEmpty()) {
            for (IInstanceTaskConfig config : update.getInstructions().getInitialState()) {
                InsertResult result = new InsertResult();
                detailsMapper.insertTaskConfig(key, taskConfigManager.insert(config.getTask()), false, result);

                detailsMapper.insertTaskConfigInstances(result.getId(),
                        IRange.toBuildersSet(MorePreconditions.checkNotBlank(config.getInstances())));
            }
        }
    }

    @VisibleForTesting
    static String jobUpdateStatusStatName(JobUpdateStatus status) {
        return "update_transition_" + status;
    }

    @Timed("job_update_store_save_event")
    @Override
    public void saveJobUpdateEvent(IJobUpdateKey key, IJobUpdateEvent event) {
        jobEventMapper.insert(key, event.newBuilder());
        jobUpdateEventStats.getUnchecked(event.getStatus()).incrementAndGet();
    }

    @VisibleForTesting
    static String jobUpdateActionStatName(JobUpdateAction action) {
        return "update_instance_transition_" + action;
    }

    @Timed("job_update_store_save_instance_event")
    @Override
    public void saveJobInstanceUpdateEvent(IJobUpdateKey key, IJobInstanceUpdateEvent event) {
        instanceEventMapper.insert(key, event.newBuilder());
        jobUpdateActionStats.getUnchecked(event.getAction()).incrementAndGet();
    }

    @Timed("job_update_store_delete_all")
    @Override
    public void deleteAllUpdatesAndEvents() {
        detailsMapper.truncate();
    }

    private static final Function<PruneVictim, IJobUpdateKey> GET_UPDATE_KEY = victim -> IJobUpdateKey
            .build(victim.getUpdate());

    @Timed("job_update_store_prune_history")
    @Override
    public Set<IJobUpdateKey> pruneHistory(int perJobRetainCount, long historyPruneThresholdMs) {
        ImmutableSet.Builder<IJobUpdateKey> pruned = ImmutableSet.builder();

        Set<Long> jobKeyIdsToPrune = detailsMapper.selectJobKeysForPruning(perJobRetainCount,
                historyPruneThresholdMs);

        for (long jobKeyId : jobKeyIdsToPrune) {
            Set<PruneVictim> pruneVictims = detailsMapper.selectPruneVictims(jobKeyId, perJobRetainCount,
                    historyPruneThresholdMs);

            detailsMapper.deleteCompletedUpdates(
                    FluentIterable.from(pruneVictims).transform(PruneVictim::getRowId).toSet());
            pruned.addAll(FluentIterable.from(pruneVictims).transform(GET_UPDATE_KEY));
        }

        return pruned.build();
    }

    @Timed("job_update_store_fetch_summaries")
    @Override
    public List<IJobUpdateSummary> fetchJobUpdateSummaries(IJobUpdateQuery query) {
        return IJobUpdateSummary.listFromBuilders(detailsMapper.selectSummaries(query.newBuilder()));
    }

    @Timed("job_update_store_fetch_details_list")
    @Override
    public List<IJobUpdateDetails> fetchJobUpdateDetails(IJobUpdateQuery query) {
        return FluentIterable.from(detailsMapper.selectDetailsList(query.newBuilder()))
                .transform(DbStoredJobUpdateDetails::toThrift).transform(StoredJobUpdateDetails::getDetails)
                .transform(IJobUpdateDetails::build).toList();
    }

    @Timed("job_update_store_fetch_details")
    @Override
    public Optional<IJobUpdateDetails> fetchJobUpdateDetails(final IJobUpdateKey key) {
        return Optional.fromNullable(detailsMapper.selectDetails(key)).transform(DbStoredJobUpdateDetails::toThrift)
                .transform(StoredJobUpdateDetails::getDetails).transform(IJobUpdateDetails::build);
    }

    @Timed("job_update_store_fetch_update")
    @Override
    public Optional<IJobUpdate> fetchJobUpdate(IJobUpdateKey key) {
        return Optional.fromNullable(detailsMapper.selectUpdate(key)).transform(DbJobUpdate::toImmutable);
    }

    @Timed("job_update_store_fetch_instructions")
    @Override
    public Optional<IJobUpdateInstructions> fetchJobUpdateInstructions(IJobUpdateKey key) {
        return Optional.fromNullable(detailsMapper.selectInstructions(key))
                .transform(DbJobUpdateInstructions::toImmutable);
    }

    @Timed("job_update_store_fetch_all_details")
    @Override
    public Set<StoredJobUpdateDetails> fetchAllJobUpdateDetails() {
        return FluentIterable.from(detailsMapper.selectAllDetails()).transform(DbStoredJobUpdateDetails::toThrift)
                .toSet();
    }

    @Timed("job_update_store_get_lock_token")
    @Override
    public Optional<String> getLockToken(IJobUpdateKey key) {
        // We assume here that cascading deletes will cause a lock-update associative row to disappear
        // when the lock is invalidated.  This further assumes that a lock row is deleted when a lock
        // is no longer valid.
        return Optional.fromNullable(detailsMapper.selectLockToken(key));
    }

    @Timed("job_update_store_fetch_instance_events")
    @Override
    public List<IJobInstanceUpdateEvent> fetchInstanceEvents(IJobUpdateKey key, int instanceId) {
        return IJobInstanceUpdateEvent.listFromBuilders(detailsMapper.selectInstanceUpdateEvents(key, instanceId));
    }
}