/*- * -\-\- * Helios Services * -- * Copyright (C) 2016 Spotify AB * -- * 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 com.spotify.helios.rollingupdate; import static com.google.common.base.Preconditions.checkNotNull; import static com.spotify.helios.servicescommon.Reactor.Callback; import static java.util.concurrent.TimeUnit.SECONDS; import com.google.common.collect.Maps; import com.google.common.util.concurrent.AbstractIdleService; import com.spotify.helios.common.descriptors.DeploymentGroup; import com.spotify.helios.master.HostMatcher; import com.spotify.helios.master.MasterModel; import com.spotify.helios.servicescommon.Reactor; import com.spotify.helios.servicescommon.ReactorFactory; import java.util.List; import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Deploys and undeploys jobs to implement the desired deployment group state. */ public class RollingUpdateService extends AbstractIdleService { private static final Logger log = LoggerFactory.getLogger(RollingUpdateService.class); private static final long UPDATE_INTERVAL = SECONDS.toMillis(1); private static final long HOST_UPDATE_INTERVAL = SECONDS.toMillis(1); private final MasterModel masterModel; private final Reactor hostUpdateReactor; private final Reactor rollingUpdateReactor; /** * Create a new RollingUpdateService. * * @param masterModel The {@link MasterModel} to use for retrieving data. * @param reactorFactory The factory to use for creating reactors. */ public RollingUpdateService(final MasterModel masterModel, final ReactorFactory reactorFactory) { this.masterModel = checkNotNull(masterModel, "masterModel"); checkNotNull(reactorFactory, "reactorFactory"); this.hostUpdateReactor = reactorFactory.create("hostUpdate", new UpdateDeploymentGroupHosts(), HOST_UPDATE_INTERVAL); this.rollingUpdateReactor = reactorFactory.create("rollingUpdate", new RollingUpdate(), UPDATE_INTERVAL); } @Override protected void startUp() throws Exception { hostUpdateReactor.startAsync().awaitRunning(); hostUpdateReactor.signal(); rollingUpdateReactor.startAsync().awaitRunning(); rollingUpdateReactor.signal(); } @Override protected void shutDown() throws Exception { hostUpdateReactor.stopAsync().awaitTerminated(); rollingUpdateReactor.stopAsync().awaitTerminated(); } /** * Updates the list of hosts associated with a deployment group. Called by the hostUpdateReactor. */ private class UpdateDeploymentGroupHosts implements Callback { @Override public void run(final boolean timeout) throws InterruptedException { final List<String> allHosts = masterModel.listHosts(); final Map<String, Map<String, String>> hostsToLabels = Maps.newHashMap(); // determine all hosts and their labels for (final String host : allHosts) { hostsToLabels.put(host, masterModel.getHostLabels(host)); } final HostMatcher hostMatcher = new HostMatcher(hostsToLabels); for (final DeploymentGroup dg : masterModel.getDeploymentGroups().values()) { final List<String> matchingHosts = hostMatcher.getMatchingHosts(dg); try { masterModel.updateDeploymentGroupHosts(dg.getName(), matchingHosts); } catch (Exception e) { log.warn("error processing hosts update for deployment group: {} - {}", dg.getName(), e); } } } } /** * Processes rolling update tasks. Called by the rollingUpdateReactor. */ private class RollingUpdate implements Callback { @Override public void run(final boolean timeout) throws InterruptedException { try { masterModel.rollingUpdateStep(); } catch (Exception e) { log.error("error processing rolling update step: {}", e); } } } }