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

com.spotify.helios.common.descriptors.Job Maven / Gradle / Ivy

There is a newer version: 0.9.283
Show newest version
/*
 * Copyright (c) 2014 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.common.descriptors;

import static com.google.common.base.Charsets.UTF_8;
import static com.google.common.base.MoreObjects.firstNonNull;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Throwables.propagate;
import static com.spotify.helios.common.Hash.sha1digest;
import static java.util.Collections.emptyList;
import static java.util.Collections.emptyMap;
import static java.util.Collections.emptySet;

import com.spotify.helios.common.Json;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.base.Optional;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.google.common.io.BaseEncoding;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.io.IOException;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

/**
 * Represents a Helios job.
 *
 * An sample expression of it in JSON might be:
 * 
 * {
 *   "addCapabilities" : [ "IPC_LOCK", "SYSLOG" ],
 *   "dropCapabilities" : [ "SYS_BOOT", "KILL" ],
 *   "created" : 1410308461448,
 *   "command" : [ "server", "serverconfig.yaml" ],
 *   "env" : {
 *     "JVM_ARGS" : "-Ddw.feature.randomFeatureFlagEnabled=true"
 *   },
 *   "expires" : null,
 *   "gracePeriod": 60,
 *   "healthCheck" : {
 *     "type" : "http",
 *     "path" : "/healthcheck",
 *     "port" : "http-admin"
 *   },
 *   "id" : "myservice:0.5:3539b7bc2235d53f79e6e8511942bbeaa8816265",
 *   "image" : "myregistry:80/janedoe/myservice:0.5-98c6ff4",
 *   "hostname": "myhost",
 *   "metadata": {
 *     "foo": "bar
 *   },
 *   "networkMode" : "bridge",
 *   "ports" : {
 *     "http" : {
 *       "externalPort" : 8060,
 *       "internalPort" : 8080,
 *       "protocol" : "tcp"
 *     },
 *     "http-admin" : {
 *       "externalPort" : 8061,
 *       "internalPort" : 8081,
 *       "protocol" : "tcp"
 *     }
 *   },
 *   "registration" : {
 *     "service/http" : {
 *       "ports" : {
 *         "http" : { }
 *       }
 *     }
 *   },
 *   "registrationDomain" : "",
 *   "resources" : {
 *     "memory" : 10485760,
 *     "memorySwap" : 10485760,
 *     "cpuset" : "0",
 *     "cpuShares" : 512
 *   }
 *   "securityOpt" : [ "label:user:USER", "apparmor:PROFILE" ],
 *   "token": "insecure-access-token",
 *   "volumes" : {
 *     "/destination/path/in/container.yaml:ro" : "/source/path/in/host.yaml"
 *   }
 * }
 * 
*/ @JsonIgnoreProperties(ignoreUnknown = true) public class Job extends Descriptor implements Comparable { public static final Map EMPTY_ENV = emptyMap(); public static final Resources EMPTY_RESOURCES = null; public static final Map EMPTY_PORTS = emptyMap(); public static final Long EMPTY_CREATED = null; public static final List EMPTY_COMMAND = emptyList(); public static final Map EMPTY_REGISTRATION = emptyMap(); public static final Integer EMPTY_GRACE_PERIOD = null; public static final Map EMPTY_VOLUMES = emptyMap(); public static final String EMPTY_MOUNT = ""; public static final Date EMPTY_EXPIRES = null; public static final String EMPTY_REGISTRATION_DOMAIN = ""; public static final String EMPTY_CREATING_USER = null; public static final String EMPTY_TOKEN = ""; public static final HealthCheck EMPTY_HEALTH_CHECK = null; public static final List EMPTY_SECURITY_OPT = emptyList(); public static final String DEFAULT_NETWORK_MODE = "bridge"; public static final String EMPTY_HOSTNAME = null; public static final Map EMPTY_METADATA = emptyMap(); public static final Set EMPTY_CAPS = emptySet(); public static final Integer EMPTY_SECONDS_TO_WAIT = null; private final JobId id; private final String image; private final String hostname; private final Long created; private final List command; private final Map env; private final Resources resources; private final Map ports; private final Map registration; private final Integer gracePeriod; private final Map volumes; private final Date expires; private final String registrationDomain; private final String creatingUser; private final String token; private final HealthCheck healthCheck; private final List securityOpt; private final String networkMode; private final Map metadata; private final Set addCapabilities; private final Set dropCapabilities; private final Integer secondsToWaitBeforeKill; /** * Create a Job. * * @param id The id of the job. * @param image The docker image to use. * @param hostname The hostname to pass to the container. * @param created The timestamp in milliseconds of when the job was created. * This should only be set by the server. * @param command The command to pass to the container. * @param env Environment variables to set * @param resources Resource specification for the container. * @param ports The ports you wish to expose from the container. * @param registration Configuration information for the discovery service (if applicable) * @param gracePeriod How long to let the container run after deregistering with the discovery * service. If nothing is configured in registration, this option is ignored. * @param volumes Docker volumes to mount. * @param expires If set, a timestamp at which the job and any deployments will be removed. * @param registrationDomain If set, overrides the default domain in which discovery service * registration occurs. What is allowed here will vary based upon the discovery service * plugin used. * @param creatingUser The user creating the job. * @param token The token needed to manipulate this job. * @param healthCheck A health check Helios will execute on the container. * @param securityOpt A list of strings denoting security options for running Docker containers, * i.e. `docker run --security-opt`. * See Docker docs. * @param networkMode Sets the networking mode for the container. Supported values are: bridge, * host, and container:<name|id>. * See Docker docs. * @param metadata Arbitrary key-value pairs that can be stored with the Job. Optional. * @param addCapabilities Linux capabilities to add for the container. Optional. * @param dropCapabilities Linux capabilities to drop for the container. Optional. * @see Docker run reference * @param secondsToWaitBeforeKill The time to ask Docker to wait after sending a SIGTERM to the * container's main process before sending it a SIGKILL. Optional. */ public Job( @JsonProperty("id") final JobId id, @JsonProperty("image") final String image, @JsonProperty("hostname") final String hostname, @JsonProperty("created") @Nullable final Long created, @JsonProperty("command") @Nullable final List command, @JsonProperty("env") @Nullable final Map env, @JsonProperty("resources") @Nullable final Resources resources, @JsonProperty("ports") @Nullable final Map ports, @JsonProperty("registration") @Nullable final Map registration, @JsonProperty("gracePeriod") @Nullable final Integer gracePeriod, @JsonProperty("volumes") @Nullable final Map volumes, @JsonProperty("expires") @Nullable final Date expires, @JsonProperty("registrationDomain") @Nullable String registrationDomain, @JsonProperty("creatingUser") @Nullable String creatingUser, @JsonProperty("token") @Nullable String token, @JsonProperty("healthCheck") @Nullable HealthCheck healthCheck, @JsonProperty("securityOpt") @Nullable final List securityOpt, @JsonProperty("networkMode") @Nullable final String networkMode, @JsonProperty("metadata") @Nullable final Map metadata, @JsonProperty("addCapabilities") @Nullable final Set addCapabilities, @JsonProperty("dropCapabilities") @Nullable final Set dropCapabilities, @JsonProperty("secondsToWaitBeforeKill") @Nullable final Integer secondsToWaitBeforeKill) { this.id = id; this.image = image; // Optional this.hostname = Optional.fromNullable(hostname).orNull(); this.created = Optional.fromNullable(created).orNull(); this.command = Optional.fromNullable(command).or(EMPTY_COMMAND); this.env = Optional.fromNullable(env).or(EMPTY_ENV); this.resources = Optional.fromNullable(resources).orNull(); this.ports = Optional.fromNullable(ports).or(EMPTY_PORTS); this.registration = Optional.fromNullable(registration).or(EMPTY_REGISTRATION); this.gracePeriod = Optional.fromNullable(gracePeriod).orNull(); this.volumes = Optional.fromNullable(volumes).or(EMPTY_VOLUMES); this.expires = expires; this.registrationDomain = Optional.fromNullable(registrationDomain) .or(EMPTY_REGISTRATION_DOMAIN); this.creatingUser = Optional.fromNullable(creatingUser).orNull(); this.token = Optional.fromNullable(token).or(EMPTY_TOKEN); this.healthCheck = Optional.fromNullable(healthCheck).orNull(); this.securityOpt = Optional.fromNullable(securityOpt).or(EMPTY_SECURITY_OPT); this.networkMode = Optional.fromNullable(networkMode).orNull(); this.metadata = Optional.fromNullable(metadata).or(EMPTY_METADATA); this.addCapabilities = firstNonNull(addCapabilities, EMPTY_CAPS); this.dropCapabilities = firstNonNull(dropCapabilities, EMPTY_CAPS); this.secondsToWaitBeforeKill = secondsToWaitBeforeKill; } private Job(final JobId id, final Builder.Parameters p) { this.id = id; this.image = p.image; this.hostname = p.hostname; this.created = p.created; this.command = ImmutableList.copyOf(checkNotNull(p.command, "command")); this.env = ImmutableMap.copyOf(checkNotNull(p.env, "env")); this.resources = p.resources; this.ports = ImmutableMap.copyOf(checkNotNull(p.ports, "ports")); this.registration = ImmutableMap.copyOf(checkNotNull(p.registration, "registration")); this.gracePeriod = p.gracePeriod; this.volumes = ImmutableMap.copyOf(checkNotNull(p.volumes, "volumes")); this.expires = p.expires; this.registrationDomain = Optional.fromNullable(p.registrationDomain) .or(EMPTY_REGISTRATION_DOMAIN); this.creatingUser = p.creatingUser; this.token = p.token; this.healthCheck = p.healthCheck; this.securityOpt = p.securityOpt; this.networkMode = p.networkMode; this.metadata = ImmutableMap.copyOf(p.metadata); this.addCapabilities = ImmutableSet.copyOf(p.addCapabilities); this.dropCapabilities = ImmutableSet.copyOf(p.dropCapabilities); this.secondsToWaitBeforeKill = p.secondsToWaitBeforeKill; } public JobId getId() { return id; } public String getImage() { return image; } public String getHostname() { return hostname; } public Long getCreated() { return created; } public List getCommand() { return command; } public Map getEnv() { return env; } public Resources getResources() { return resources; } public Map getPorts() { return ports; } public Map getRegistration() { return registration; } public String getRegistrationDomain() { return registrationDomain; } public Integer getGracePeriod() { return gracePeriod; } public Map getVolumes() { return volumes; } public Date getExpires() { return expires; } public String getCreatingUser() { return creatingUser; } public String getToken() { return token; } public HealthCheck getHealthCheck() { return healthCheck; } public List getSecurityOpt() { return securityOpt; } public String getNetworkMode() { return networkMode; } public Map getMetadata() { return metadata; } public Set getAddCapabilities() { return addCapabilities; } public Set getDropCapabilities() { return dropCapabilities; } public Integer getSecondsToWaitBeforeKill() { return secondsToWaitBeforeKill; } public static Builder newBuilder() { return new Builder(); } @Override public int compareTo(@NotNull final Job o) { return id.compareTo(o.getId()); } @Override public boolean equals(final Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } final Job that = (Job) o; return Objects.equals(this.id, that.id) && Objects.equals(this.image, that.image) && Objects.equals(this.hostname, that.hostname) && Objects.equals(this.expires, that.expires) && Objects.equals(this.created, that.created) && Objects.equals(this.command, that.command) && Objects.equals(this.env, that.env) && Objects.equals(this.resources, that.resources) && Objects.equals(this.ports, that.ports) && Objects.equals(this.registration, that.registration) && Objects.equals(this.registrationDomain, that.registrationDomain) && Objects.equals(this.gracePeriod, that.gracePeriod) && Objects.equals(this.volumes, that.volumes) && Objects.equals(this.creatingUser, that.creatingUser) && Objects.equals(this.token, that.token) && Objects.equals(this.healthCheck, that.healthCheck) && Objects.equals(this.securityOpt, that.securityOpt) && Objects.equals(this.networkMode, that.networkMode) && Objects.equals(this.metadata, that.metadata) && Objects.equals(this.addCapabilities, that.addCapabilities) && Objects.equals(this.dropCapabilities, that.dropCapabilities) && Objects.equals(this.secondsToWaitBeforeKill, that.secondsToWaitBeforeKill); } @Override public int hashCode() { return Objects.hash( id, image, hostname, expires, created, command, env, resources, ports, registration, registrationDomain, gracePeriod, volumes, creatingUser, token, healthCheck, securityOpt, networkMode, metadata, addCapabilities, dropCapabilities, secondsToWaitBeforeKill); } @Override public String toString() { return "Job{" + "id=" + id + ", image='" + image + '\'' + ", hostname='" + hostname + '\'' + ", created=" + created + ", command=" + command + ", env=" + env + ", resources=" + resources + ", ports=" + ports + ", registration=" + registration + ", gracePeriod=" + gracePeriod + ", volumes=" + volumes + ", expires=" + expires + ", registrationDomain='" + registrationDomain + '\'' + ", creatingUser='" + creatingUser + '\'' + ", token='" + token + '\'' + ", healthCheck=" + healthCheck + ", securityOpt=" + securityOpt + ", networkMode='" + networkMode + '\'' + ", metadata=" + metadata + ", addCapabilities=" + addCapabilities + ", dropCapabilities=" + dropCapabilities + ", secondsToWaitBeforeKill=" + secondsToWaitBeforeKill + '}'; } public Builder toBuilder() { final Builder builder = newBuilder(); if (id != null) { builder.setName(id.getName()) .setVersion(id.getVersion()); } return builder.setImage(image) .setHostname(hostname) .setCreated(created) .setCommand(command) .setEnv(env) .setResources(resources) .setPorts(ports) .setRegistration(registration) .setGracePeriod(gracePeriod) .setVolumes(volumes) .setExpires(expires) .setRegistrationDomain(registrationDomain) .setCreatingUser(creatingUser) .setToken(token) .setHealthCheck(healthCheck) .setSecurityOpt(securityOpt) .setNetworkMode(networkMode) .setMetadata(metadata) .setAddCapabilities(addCapabilities) .setDropCapabilities(dropCapabilities) .setSecondsToWaitBeforeKill(secondsToWaitBeforeKill); } public static class Builder implements Cloneable { private final Parameters p; private String hash; private Builder() { this.p = new Parameters(); } public Builder(final String hash, final Parameters parameters) { this.hash = hash; this.p = parameters; } private static class Parameters implements Cloneable { public String registrationDomain; public String name; public String version; public String image; public String hostname; public Long created; public List command; public Map env; public Resources resources; public Map ports; public Map registration; public Integer gracePeriod; public Map volumes; public Date expires; public String creatingUser; public String token; public HealthCheck healthCheck; public List securityOpt; public String networkMode; public Map metadata; public Set addCapabilities; public Set dropCapabilities; public Integer secondsToWaitBeforeKill; private Parameters() { this.created = EMPTY_CREATED; this.command = EMPTY_COMMAND; this.env = Maps.newHashMap(EMPTY_ENV); this.resources = EMPTY_RESOURCES; this.ports = Maps.newHashMap(EMPTY_PORTS); this.registration = Maps.newHashMap(EMPTY_REGISTRATION); this.gracePeriod = EMPTY_GRACE_PERIOD; this.volumes = Maps.newHashMap(EMPTY_VOLUMES); this.registrationDomain = EMPTY_REGISTRATION_DOMAIN; this.creatingUser = EMPTY_CREATING_USER; this.token = EMPTY_TOKEN; this.healthCheck = EMPTY_HEALTH_CHECK; this.securityOpt = EMPTY_SECURITY_OPT; this.metadata = Maps.newHashMap(); this.addCapabilities = EMPTY_CAPS; this.dropCapabilities = EMPTY_CAPS; } private Parameters(final Parameters p) { this.name = p.name; this.version = p.version; this.image = p.image; this.hostname = p.hostname; this.created = p.created; this.command = ImmutableList.copyOf(p.command); this.env = Maps.newHashMap(p.env); this.resources = p.resources; this.ports = Maps.newHashMap(p.ports); this.registration = Maps.newHashMap(p.registration); this.gracePeriod = p.gracePeriod; this.volumes = Maps.newHashMap(p.volumes); this.expires = p.expires; this.registrationDomain = p.registrationDomain; this.creatingUser = p.creatingUser; this.token = p.token; this.healthCheck = p.healthCheck; this.securityOpt = p.securityOpt; this.networkMode = p.networkMode; this.metadata = p.metadata; this.addCapabilities = p.addCapabilities; this.dropCapabilities = p.dropCapabilities; this.secondsToWaitBeforeKill = p.secondsToWaitBeforeKill; } private Parameters withoutMetaParameters() { final Parameters clone = new Parameters(this); clone.creatingUser = null; clone.created = null; return clone; } } public Builder setRegistrationDomain(final String domain) { this.p.registrationDomain = domain; return this; } public Builder setCreatingUser(final String creatingUser) { this.p.creatingUser = creatingUser; return this; } public Builder setToken(final String token) { this.p.token = token; return this; } public Builder setHash(final String hash) { this.hash = hash; return this; } public Builder setName(final String name) { p.name = name; return this; } public Builder setVersion(final String version) { p.version = version; return this; } public Builder setImage(final String image) { p.image = image; return this; } public Builder setHostname(final String hostname) { p.hostname = hostname; return this; } public Builder setCreated(final Long created) { p.created = created; return this; } public Builder setCommand(final List command) { p.command = ImmutableList.copyOf(command); return this; } public Builder setEnv(final Map env) { p.env = Maps.newHashMap(env); return this; } public Builder setResources(final Resources resources) { p.resources = resources; return this; } public Builder addEnv(final String key, final String value) { p.env.put(key, value); return this; } public Builder setPorts(final Map ports) { p.ports = Maps.newHashMap(ports); return this; } public Builder addPort(final String name, final PortMapping port) { p.ports.put(name, port); return this; } public Builder setRegistration(final Map registration) { p.registration = Maps.newHashMap(registration); return this; } public Builder addRegistration(final ServiceEndpoint endpoint, final ServicePorts ports) { p.registration.put(endpoint, ports); return this; } public Builder setGracePeriod(final Integer gracePeriod) { p.gracePeriod = gracePeriod; return this; } public Builder setVolumes(final Map volumes) { p.volumes = Maps.newHashMap(volumes); return this; } public Builder addVolume(final String path) { p.volumes.put(path, EMPTY_MOUNT); return this; } public Builder addVolume(final String path, final String source) { p.volumes.put(path, source); return this; } public Builder setExpires(final Date expires) { p.expires = expires; return this; } public Builder setHealthCheck(final HealthCheck healthCheck) { p.healthCheck = healthCheck; return this; } public Builder setSecurityOpt(final List securityOpt) { p.securityOpt = ImmutableList.copyOf(securityOpt); return this; } public Builder setNetworkMode(final String networkMode) { p.networkMode = networkMode; return this; } public Builder setMetadata(final Map metadata) { p.metadata = Maps.newHashMap(metadata); return this; } public Builder addMetadata(final String name, final String value) { p.metadata.put(name, value); return this; } public Builder setAddCapabilities(final Collection addCapabilities) { p.addCapabilities = ImmutableSet.copyOf(addCapabilities); return this; } public Builder setDropCapabilities(final Collection dropCapabilities) { p.dropCapabilities = ImmutableSet.copyOf(dropCapabilities); return this; } public Builder setSecondsToWaitBeforeKill(final Integer secondsToWaitBeforeKill) { p.secondsToWaitBeforeKill = secondsToWaitBeforeKill; return this; } public String getName() { return p.name; } public String getVersion() { return p.version; } public String getImage() { return p.image; } public String getHostname() { return p.hostname; } public List getCommand() { return p.command; } public Map getEnv() { return ImmutableMap.copyOf(p.env); } public Map getPorts() { return ImmutableMap.copyOf(p.ports); } public Map getRegistration() { return ImmutableMap.copyOf(p.registration); } public String getRegistrationDomain() { return p.registrationDomain; } public Integer getGracePeriod() { return p.gracePeriod; } public Map getVolumes() { return ImmutableMap.copyOf(p.volumes); } public Date getExpires() { return p.expires; } public String getCreatingUser() { return p.creatingUser; } public Resources getResources() { return p.resources; } public HealthCheck getHealthCheck() { return p.healthCheck; } public List getSecurityOpt() { return p.securityOpt; } public String getNetworkMode() { return p.networkMode; } public Map getMetadata() { return ImmutableMap.copyOf(p.metadata); } public Set getAddCapabilities() { return p.addCapabilities; } public Set getDropCapabilities() { return p.dropCapabilities; } public Integer secondsToWaitBeforeKill() { return p.secondsToWaitBeforeKill; } @SuppressWarnings({"CloneDoesntDeclareCloneNotSupportedException", "CloneDoesntCallSuperClone"}) @Override public Builder clone() { return new Builder(hash, new Parameters(p)); } public Job build() { final String configHash; try { configHash = hex(Json.sha1digest(p.withoutMetaParameters())); } catch (IOException e) { throw propagate(e); } final String hash; if (!Strings.isNullOrEmpty(this.hash)) { hash = this.hash; } else { if (p.name != null && p.version != null) { final String input = String.format("%s:%s:%s", p.name, p.version, configHash); hash = hex(sha1digest(input.getBytes(UTF_8))); } else { hash = null; } } final JobId id = new JobId(p.name, p.version, hash); return new Job(id, p); } public Job buildWithoutHash() { final JobId id = new JobId(p.name, p.version); return new Job(id, p); } private String hex(final byte[] bytes) { return BaseEncoding.base16().lowerCase().encode(bytes); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy