use of com.spotify.helios.common.descriptors.RolloutTask in project helios by spotify.
the class ZooKeeperMasterModel method rollingUpdateStep.
@Override
public void rollingUpdateStep() {
final ZooKeeperClient client = provider.get("rollingUpdateStep");
final Map<String, VersionedValue<DeploymentGroupTasks>> tasksMap = getDeploymentGroupTasks(client);
for (final Map.Entry<String, VersionedValue<DeploymentGroupTasks>> entry : tasksMap.entrySet()) {
final String deploymentGroupName = entry.getKey();
final VersionedValue<DeploymentGroupTasks> versionedTasks = entry.getValue();
final DeploymentGroupTasks tasks = versionedTasks.value();
final int taskIndex = tasks.getTaskIndex();
log.info("rolling-update step on deployment-group {}. Doing taskIndex {} of {}: {}. ", deploymentGroupName, taskIndex, tasks.getRolloutTasks().size(), tasks.getRolloutTasks().get(taskIndex));
try {
final RollingUpdateOpFactory opFactory = new RollingUpdateOpFactory(tasks, DEPLOYMENT_GROUP_EVENT_FACTORY);
final RolloutTask task = tasks.getRolloutTasks().get(taskIndex);
final RollingUpdateOp op = processRollingUpdateTask(client, opFactory, task, tasks.getDeploymentGroup());
if (!op.operations().isEmpty()) {
final List<ZooKeeperOperation> ops = Lists.newArrayList();
ops.add(check(Paths.statusDeploymentGroupTasks(deploymentGroupName), versionedTasks.version()));
ops.addAll(op.operations());
log.info("rolling-update step on deployment-group: name={}, zookeeper operations={}", deploymentGroupName, ops);
try {
client.transaction(ops);
emitEvents(deploymentGroupEventTopic, op.events());
} catch (BadVersionException e) {
// some other master beat us in processing this rolling update step. not exceptional.
// ideally we would check the path in the exception, but curator doesn't provide a path
// for exceptions thrown as part of a transaction.
log.info("rolling-update step on deployment-group was processed by another master" + ": name={}, zookeeper operations={}", deploymentGroupName, ops);
} catch (KeeperException e) {
log.error("rolling-update on deployment-group {} failed. {}", deploymentGroupName, e.getMessage(), e);
}
}
} catch (final Exception e) {
log.error("error processing rolling update step for {}", deploymentGroupName, e);
}
}
}
use of com.spotify.helios.common.descriptors.RolloutTask in project helios by spotify.
the class RollingUpdateOpFactory method start.
public RollingUpdateOp start(final DeploymentGroup deploymentGroup, final ZooKeeperClient client) throws KeeperException {
client.ensurePath(Paths.statusDeploymentGroupTasks());
final List<ZooKeeperOperation> ops = Lists.newArrayList();
final List<Map<String, Object>> events = Lists.newArrayList();
final List<RolloutTask> rolloutTasks = tasks.getRolloutTasks();
events.add(eventFactory.rollingUpdateStarted(deploymentGroup));
final Stat tasksStat = client.exists(Paths.statusDeploymentGroupTasks(deploymentGroup.getName()));
if (tasksStat == null) {
// Create the tasks path if it doesn't already exist. The following operations (delete or set)
// assume the node already exists. If the tasks path is created/deleted before the transaction
// is committed it will fail. This will on occasion generate a user-visible error but is
// better than having inconsistent state.
ops.add(create(Paths.statusDeploymentGroupTasks(deploymentGroup.getName())));
}
final DeploymentGroupStatus status;
if (rolloutTasks.isEmpty()) {
status = DeploymentGroupStatus.newBuilder().setState(DONE).build();
ops.add(delete(Paths.statusDeploymentGroupTasks(deploymentGroup.getName())));
events.add(eventFactory.rollingUpdateDone(deploymentGroup));
} else {
final DeploymentGroupTasks tasks = DeploymentGroupTasks.newBuilder().setRolloutTasks(rolloutTasks).setTaskIndex(0).setDeploymentGroup(deploymentGroup).build();
status = DeploymentGroupStatus.newBuilder().setState(ROLLING_OUT).build();
ops.add(set(Paths.statusDeploymentGroupTasks(deploymentGroup.getName()), tasks));
}
// NOTE: If the DG was removed this set() cause the transaction to fail, because removing
// the DG removes this node. It's *important* that there's an operation that causes the
// transaction to fail if the DG was removed or we'll end up with inconsistent state.
ops.add(set(Paths.statusDeploymentGroup(deploymentGroup.getName()), status));
return new RollingUpdateOp(ImmutableList.copyOf(ops), ImmutableList.copyOf(events));
}
use of com.spotify.helios.common.descriptors.RolloutTask in project helios by spotify.
the class RollingUpdateOpFactory method nextTask.
public RollingUpdateOp nextTask(final List<ZooKeeperOperation> operations) {
final List<ZooKeeperOperation> ops = Lists.newArrayList(operations);
final List<Map<String, Object>> events = Lists.newArrayList();
final RolloutTask task = tasks.getRolloutTasks().get(tasks.getTaskIndex());
// Update the task index, delete tasks if done
if (tasks.getTaskIndex() + 1 == tasks.getRolloutTasks().size()) {
final DeploymentGroupStatus status = DeploymentGroupStatus.newBuilder().setState(DONE).build();
// We are done -> delete tasks & update status
ops.add(delete(Paths.statusDeploymentGroupTasks(deploymentGroup.getName())));
ops.add(set(Paths.statusDeploymentGroup(deploymentGroup.getName()), status));
// Emit an event signalling that we're DONE!
events.add(eventFactory.rollingUpdateDone(deploymentGroup));
} else {
ops.add(set(Paths.statusDeploymentGroupTasks(deploymentGroup.getName()), tasks.toBuilder().setTaskIndex(tasks.getTaskIndex() + 1).build()));
// the task was effectively a no-op.
if (!operations.isEmpty()) {
events.add(eventFactory.rollingUpdateTaskSucceeded(deploymentGroup, task));
}
}
return new RollingUpdateOp(ImmutableList.copyOf(ops), ImmutableList.copyOf(events));
}
use of com.spotify.helios.common.descriptors.RolloutTask in project helios by spotify.
the class DeploymentGroupTest method testUpdateDeploymentGroupHosts.
private void testUpdateDeploymentGroupHosts(final RolloutOptions rolloutOptions) throws Exception {
final ZooKeeperClient client = spy(this.client);
final ZooKeeperMasterModel masterModel = spy(newMasterModel(client));
// Return a job so we can add a real deployment group.
final Job job = Job.newBuilder().setCommand(ImmutableList.of("COMMAND")).setImage("IMAGE").setName("JOB_NAME").setVersion("VERSION").build();
doReturn(job).when(masterModel).getJob(job.getId());
// Add a real deployment group.
final DeploymentGroup dg = DeploymentGroup.newBuilder().setName(GROUP_NAME).setHostSelectors(ImmutableList.of(HostSelector.parse("role=melmac"))).setJobId(job.getId()).setRolloutOptions(rolloutOptions).setRollingUpdateReason(MANUAL).build();
masterModel.addDeploymentGroup(dg);
// Setup some hosts
final String oldHost = "host1";
final String newHost = "host2";
client.ensurePath(Paths.configHost(oldHost));
client.ensurePath(Paths.configHost(newHost));
client.ensurePath(Paths.statusHostUp(oldHost));
client.ensurePath(Paths.statusHostUp(newHost));
// Give the deployment group a host.
client.setData(Paths.statusDeploymentGroupHosts(dg.getName()), Json.asBytes(ImmutableList.of(oldHost)));
// And a status...
client.setData(Paths.statusDeploymentGroup(dg.getName()), DeploymentGroupStatus.newBuilder().setState(DONE).build().toJsonBytes());
// Switch out our host!
// TODO(negz): Use an unchanged host, make sure ordering remains the same.
masterModel.updateDeploymentGroupHosts(dg.getName(), ImmutableList.of(newHost));
verify(client, times(2)).transaction(opCaptor.capture());
final DeploymentGroup changed = dg.toBuilder().setRollingUpdateReason(HOSTS_CHANGED).build();
// Ensure we set the DG status to HOSTS_CHANGED.
// This means we triggered a rolling update.
final ZooKeeperOperation setDeploymentGroupHostChanged = set(Paths.configDeploymentGroup(dg.getName()), changed);
// Ensure ZK tasks are written to:
// - Perform a rolling undeploy for the removed (old) host
// - Perform a rolling update for the added (new) host and the unchanged host
final List<RolloutTask> tasks = ImmutableList.<RolloutTask>builder().addAll(RollingUndeployPlanner.of(changed).plan(singletonList(oldHost))).addAll(RollingUpdatePlanner.of(changed).plan(singletonList(newHost))).build();
final ZooKeeperOperation setDeploymentGroupTasks = set(Paths.statusDeploymentGroupTasks(dg.getName()), DeploymentGroupTasks.newBuilder().setRolloutTasks(tasks).setTaskIndex(0).setDeploymentGroup(changed).build());
assertThat(opCaptor.getValue(), hasItems(setDeploymentGroupHostChanged, setDeploymentGroupTasks));
}
use of com.spotify.helios.common.descriptors.RolloutTask in project helios by spotify.
the class RollingUpdateOpFactoryTest method testStartHostsChanged.
@Test
public void testStartHostsChanged() throws Exception {
// Create a DeploymentGroupTasks object with some rolloutTasks.
final ArrayList<RolloutTask> rolloutTasks = Lists.newArrayList(RolloutTask.of(RolloutTask.Action.UNDEPLOY_OLD_JOBS, "host1"), RolloutTask.of(RolloutTask.Action.DEPLOY_NEW_JOB, "host1"), RolloutTask.of(RolloutTask.Action.AWAIT_RUNNING, "host1"));
final DeploymentGroupTasks deploymentGroupTasks = DeploymentGroupTasks.newBuilder().setTaskIndex(0).setRolloutTasks(rolloutTasks).setDeploymentGroup(HOSTS_CHANGED_DEPLOYMENT_GROUP).build();
final RollingUpdateOpFactory opFactory = new RollingUpdateOpFactory(deploymentGroupTasks, eventFactory);
final ZooKeeperClient client = mock(ZooKeeperClient.class);
when(client.exists(anyString())).thenReturn(null);
final RollingUpdateOp op = opFactory.start(HOSTS_CHANGED_DEPLOYMENT_GROUP, client);
// Three ZK operations should return:
// * create tasks node
// * set the task index to 0
// * another to set the status to ROLLING_OUT
assertEquals(ImmutableSet.of(new CreateEmpty("/status/deployment-group-tasks/my_group"), new SetData("/status/deployment-group-tasks/my_group", DeploymentGroupTasks.newBuilder().setRolloutTasks(rolloutTasks).setTaskIndex(0).setDeploymentGroup(HOSTS_CHANGED_DEPLOYMENT_GROUP).build().toJsonBytes()), new SetData("/status/deployment-groups/my_group", DeploymentGroupStatus.newBuilder().setState(DeploymentGroupStatus.State.ROLLING_OUT).build().toJsonBytes())), ImmutableSet.copyOf(op.operations()));
// Two events should return: rollingUpdateStarted and rollingUpdateDone
assertEquals(1, op.events().size());
verify(eventFactory).rollingUpdateStarted(HOSTS_CHANGED_DEPLOYMENT_GROUP);
}
Aggregations