/*- * -\-\- * Helios Client * -- * 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.common.descriptors; import static org.junit.Assert.assertEquals; import com.google.common.collect.ImmutableList; import java.util.Date; import org.junit.Test; /** * Verifies that the computed hash of a Job stays the same even if we add new fields to the Job. * * <p>A helios client (the CLI, a user of TemporaryJobs, etc) will construct a Job, compute it's * hash, and send them to the server as JSON. The server will compute the same hash of all of the * Job's fields and check that the hash matches.</p> * * <p>Whenever we update the server-side code, we can't be sure that all of the clients are at the * same version. In fact we want to avoid any changes that would require the server and (every * single) client to be at the same version. Therefore we have to be careful whenever adding new * fields to the Job (or new fields to its fields) that we do not change the computed hash that a * newer server would compute for a Job-and-hash sent by an older client.</p> * * <p>What this means in practice is that we cannot add new fields or sub-fields to the Job class * and assign default non-null or non-empty values to them within their class or declaration. * The JSON serialization used by the Job class when computing <code>hash = sha1(json(job))</code> * will not include any empty (null or empty collections) in the resulting JSON string.</p> * * <p>This test verifies that static Job instances retain the same computed hash from this point * forward.</p> */ public class JobIdHashIsStableTest { // a job with all of it's fields (as of writing-time) set // don't add new fields to this Job instance! If you are trying to add a new field to the Job // class, instead add a new test to this class private static final Job JOB = Job.newBuilder() .addEnv("ENV", "VAR") .addMetadata("META", "DATA") .addPort("port1", PortMapping.of(80)) .addVolume("/tmp") .addRegistration(ServiceEndpoint.of("svc", "tcp"), ServicePorts.of("port1")) .setAddCapabilities(ImmutableList.of("cap1", "cap2")) .setCommand(ImmutableList.of("echo", "hi")) .setDropCapabilities(ImmutableList.of("cap3")) .setExpires(new Date(1484270348)) .setGracePeriod(10) .setHealthCheck(HealthCheck.newHttpHealthCheck() .setPath("/hello") .setPort("port1") .build() ) .setHostname("hostname") .setImage("image:1.2.3") .setName("a-full-job") .setNetworkMode("host") .setRegistrationDomain("domain") .setResources(new Resources(1L, 2L, 3L, "4")) .setSecondsToWaitBeforeKill(99) .setSecurityOpt(ImmutableList.of("opt")) .setToken("token") .setVersion("1") .build(); private static void assertExpectedHash(final String expectedHash, final Job job) { assertEquals("If this hash changed, you are probably introducing a " + "backwards-incompatible change to the Job class!", expectedHash, job.getId().getHash() ); } @Test public void testInitialFullJob() { // don't change these hard-coded hashes unless you are really sure you want to // break compatibility assertExpectedHash("9693b4579ac48ed41f317ecf5edc5496b005b79d", JOB); } @Test public void testMinimalJob() { final Job job = Job.newBuilder() .setName("foo") .setVersion("bar") .setImage("foo/bar:1") .build(); assertExpectedHash("3f5c6e4aef30e1ab68931dbd66a83226410b87e9", job); } }