Skip to content
This repository has been archived by the owner on Sep 12, 2024. It is now read-only.

Commit

Permalink
Merge pull request #345 from spotify/rohan/decorators
Browse files Browse the repository at this point in the history
Add support for binding volumes to all containers
  • Loading branch information
rohansingh committed Jan 21, 2015
2 parents 565367d + 1abea35 commit 9a6b7e1
Show file tree
Hide file tree
Showing 9 changed files with 233 additions and 20 deletions.
7 changes: 5 additions & 2 deletions docs/how_to_deploy.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ Takes options:

`--admin ADMIN` admin http port (default: 5802) the master will listen on.

Example `/etc/default/helios-master`
Example `/etc/default/helios-master`:

ENABLED=true

Expand Down Expand Up @@ -179,7 +179,10 @@ Takes options:
Port allocation range, start:end (end exclusive).
(default: 20000:32768)

Example `/etc/default/helios-agent
`--bind VOLUME` Bind the given volume into all containers. You may specify multiply --bind
arguments. Each bind must conform to the [`docker run -v` syntax](https://docs.docker.com/reference/run/#volume-shared-filesystems).

Example `/etc/default/helios-agent`:

ENABLED=true

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ public class AgentConfig extends Configuration {
private int adminPort;
private InetSocketAddress httpEndpoint;
private boolean noHttp;
private List<String> binds;

public boolean isInhibitMetrics() {
return inhibitMetrics;
Expand Down Expand Up @@ -270,4 +271,13 @@ public AgentConfig setNoHttp(boolean noHttp) {
public boolean getNoHttp() {
return noHttp;
}

public List<String> getBinds() {
return binds;
}

public AgentConfig setBinds(List<String> binds) {
this.binds = binds;
return this;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@

import static com.google.common.io.BaseEncoding.base16;
import static com.google.common.net.InetAddresses.isInetAddress;
import static com.spotify.helios.agent.BindVolumeContainerDecorator.isValidBind;
import static net.sourceforge.argparse4j.impl.Arguments.append;
import static net.sourceforge.argparse4j.impl.Arguments.storeTrue;

Expand All @@ -62,6 +63,7 @@ public class AgentParser extends ServiceParser {
private Argument portRangeArg;
private Argument agentIdArg;
private Argument dnsArg;
private Argument bindArg;

public AgentParser(final String... args) throws ArgumentParserException {
super("helios-agent", "Spotify Helios Agent", args);
Expand Down Expand Up @@ -138,13 +140,23 @@ public AgentParser(final String... args) throws ArgumentParserException {

final List<String> dns = options.getList(dnsArg.getDest());
if (!dns.isEmpty()) {
for (String d : dns) {
for (final String d : dns) {
if (!isInetAddress(d)) {
throw new IllegalArgumentException("Invalid IP address " + d);
}
}
}
agentConfig.setDns(dns);

final List<String> binds = options.getList(bindArg.getDest());
if (!binds.isEmpty()) {
for (final String b : binds) {
if (!isValidBind(b)) {
throw new IllegalArgumentException("Invalid bind " + b);
}
}
}
agentConfig.setBinds(binds);
}

@Override
Expand Down Expand Up @@ -196,6 +208,10 @@ protected void addArgs(final ArgumentParser parser) {
.setDefault(new ArrayList<String>())
.help("Dns servers to use.");

bindArg = parser.addArgument("--bind")
.action(append())
.setDefault(new ArrayList<String>())
.help("volumes to bind to all containers");
}

public AgentConfig getAgentConfig() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

import com.google.common.base.Suppliers;
import com.google.common.base.Throwables;
import com.google.common.collect.Lists;
import com.google.common.io.Resources;
import com.google.common.util.concurrent.AbstractIdleService;

Expand Down Expand Up @@ -71,6 +72,7 @@
import java.nio.channels.OverlappingFileLockException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

Expand Down Expand Up @@ -234,12 +236,20 @@ public AgentService(final AgentConfig config, final Environment environment)

final String namespace = "helios-" + id;

final List<ContainerDecorator> decorators = Lists.newArrayList();

if (!isNullOrEmpty(config.getRedirectToSyslog())) {
decorators.add(new SyslogRedirectingContainerDecorator(config.getRedirectToSyslog()));
}

if (!config.getBinds().isEmpty()) {
decorators.add(new BindVolumeContainerDecorator(config.getBinds()));
}

final SupervisorFactory supervisorFactory = new SupervisorFactory(
model, monitoredDockerClient,
config.getEnvVars(), serviceRegistrar,
config.getRedirectToSyslog() != null
? new SyslogRedirectingContainerDecorator(config.getRedirectToSyslog())
: new NoOpContainerDecorator(),
decorators,
config.getName(),
metrics.getSupervisorMetrics(),
namespace,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
* Copyright (c) 2014 Spotify AB.
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.agent;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;

import com.spotify.docker.client.messages.ContainerConfig;
import com.spotify.docker.client.messages.HostConfig;
import com.spotify.docker.client.messages.ImageInfo;
import com.spotify.helios.common.descriptors.Job;

import java.util.List;

import static com.google.common.base.Strings.isNullOrEmpty;

/**
* Bind mounts user-specified volumes into all containers.
*/
public class BindVolumeContainerDecorator implements ContainerDecorator {

private final List<String> binds;

public BindVolumeContainerDecorator(final List<String> binds) {
this.binds = ImmutableList.copyOf(binds);
}

public static boolean isValidBind(final String bind) {
if (isNullOrEmpty(bind)) {
return false;
}

final String[] parts = bind.split(":");

if (isNullOrEmpty(parts[0])) {
return false;
}
if ((parts.length > 1) && isNullOrEmpty(parts[1])) {
return false;
}
if ((parts.length > 2) && !parts[2].equals("ro") && !parts[2].equals("rw")) {
return false;
}
if (parts.length > 3) {
return false;
}

return true;
}

@Override
public void decorateHostConfig(HostConfig.Builder hostConfig) {
final List<String> b = Lists.newArrayList();

if (hostConfig.binds() != null) {
b.addAll(hostConfig.binds());
}

b.addAll(this.binds);

hostConfig.binds(b);
}

@Override
public void decorateContainerConfig(Job job, ImageInfo imageInfo,
ContainerConfig.Builder containerConfig) {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public class SupervisorFactory {
private final String namespace;
private final Map<String, String> envVars;
private final ServiceRegistrar registrar;
private final ContainerDecorator containerDecorator;
private final List<ContainerDecorator> containerDecorators;
private final String host;
private final SupervisorMetrics metrics;
private final String defaultRegistrationDomain;
Expand All @@ -53,7 +53,7 @@ public class SupervisorFactory {
public SupervisorFactory(final AgentModel model, final DockerClient dockerClient,
final Map<String, String> envVars,
final ServiceRegistrar registrar,
final ContainerDecorator containerDecorator,
final List<ContainerDecorator> containerDecorators,
final String host,
final SupervisorMetrics supervisorMetrics,
final String namespace,
Expand All @@ -64,7 +64,7 @@ public SupervisorFactory(final AgentModel model, final DockerClient dockerClient
this.model = checkNotNull(model, "model");
this.envVars = checkNotNull(envVars, "envVars");
this.registrar = registrar;
this.containerDecorator = containerDecorator;
this.containerDecorators = containerDecorators;
this.host = host;
this.metrics = supervisorMetrics;
this.defaultRegistrationDomain = checkNotNull(defaultRegistrationDomain,
Expand All @@ -86,7 +86,7 @@ public Supervisor create(final Job job, final String existingContainerId,
.job(job)
.ports(ports)
.envVars(envVars)
.containerDecorator(containerDecorator)
.containerDecorators(containerDecorators)
.namespace(namespace)
.defaultRegistrationDomain(defaultRegistrationDomain)
.dns(dns)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ public class TaskConfig {
private final Map<String, Integer> ports;
private final Job job;
private final Map<String, String> envVars;
private final ContainerDecorator containerDecorator;
private final List<ContainerDecorator> containerDecorators;
private final String namespace;
private final String defaultRegistrationDomain;
private final List<String> dns;
Expand All @@ -78,7 +78,7 @@ private TaskConfig(final Builder builder) {
this.ports = checkNotNull(builder.ports, "ports");
this.job = checkNotNull(builder.job, "job");
this.envVars = checkNotNull(builder.envVars, "envVars");
this.containerDecorator = checkNotNull(builder.containerDecorator, "containerDecorator");
this.containerDecorators = checkNotNull(builder.containerDecorators, "containerDecorators");
this.namespace = checkNotNull(builder.namespace, "namespace");
this.defaultRegistrationDomain = checkNotNull(builder.defaultRegistrationDomain,
"defaultRegistrationDomain");
Expand All @@ -100,19 +100,25 @@ public String containerName() {
*/
public ContainerConfig containerConfig(final ImageInfo imageInfo) {
final ContainerConfig.Builder builder = ContainerConfig.builder();

builder.image(job.getImage());
builder.cmd(job.getCommand());
builder.env(containerEnvStrings());
builder.exposedPorts(containerExposedPorts());
builder.volumes(volumes());

final Resources resources = job.getResources();
if (resources != null) {
builder.memory(resources.getMemory());
builder.memorySwap(resources.getMemorySwap());
builder.cpuset(resources.getCpuset());
builder.cpuShares(resources.getCpuShares());
}
containerDecorator.decorateContainerConfig(job, imageInfo, builder);

for (final ContainerDecorator decorator : containerDecorators) {
decorator.decorateContainerConfig(job, imageInfo, builder);
}

return builder.build();
}

Expand Down Expand Up @@ -245,7 +251,11 @@ public HostConfig hostConfig() {
.binds(binds())
.portBindings(portBindings())
.dns(dns);
containerDecorator.decorateHostConfig(builder);

for (final ContainerDecorator decorator : containerDecorators) {
decorator.decorateHostConfig(builder);
}

return builder.build();
}

Expand Down Expand Up @@ -309,7 +319,7 @@ private Builder() {
private Job job;
private Map<String, Integer> ports = Collections.emptyMap();
private Map<String, String> envVars = Collections.emptyMap();
private ContainerDecorator containerDecorator = new NoOpContainerDecorator();
private List<ContainerDecorator> containerDecorators = Collections.EMPTY_LIST;
private String namespace;
private String defaultRegistrationDomain = "";
private List<String> dns = Collections.emptyList();
Expand Down Expand Up @@ -339,8 +349,8 @@ public Builder envVars(final Map<String, String> envVars) {
return this;
}

public Builder containerDecorator(final ContainerDecorator containerDecorator) {
this.containerDecorator = containerDecorator;
public Builder containerDecorators(final List<ContainerDecorator> containerDecorators) {
this.containerDecorators = containerDecorators;
return this;
}

Expand All @@ -366,7 +376,7 @@ public String toString() {
.add("host", host)
.add("ports", ports)
.add("envVars", envVars)
.add("containerDecorator", containerDecorator)
.add("containerDecorators", containerDecorators)
.add("defaultRegistrationDomain", defaultRegistrationDomain)
.toString();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@

package com.spotify.helios.agent;

import com.google.common.collect.ImmutableList;

import com.spotify.docker.client.DockerClient;
import com.spotify.docker.client.DockerTimeoutException;
import com.spotify.docker.client.ImageNotFoundException;
Expand Down Expand Up @@ -67,7 +69,7 @@ public void test() throws Throwable {
.namespace("test")
.host(HOST)
.job(JOB)
.containerDecorator(containerDecorator)
.containerDecorators(ImmutableList.of(containerDecorator))
.build())
.docker(mockDocker)
.listener(new TaskRunner.NopListener())
Expand Down Expand Up @@ -98,7 +100,7 @@ public void testPullTimeoutVariation() throws Throwable {
.namespace("test")
.host(HOST)
.job(JOB)
.containerDecorator(containerDecorator)
.containerDecorators(ImmutableList.of(containerDecorator))
.build())
.docker(mockDocker)
.listener(new TaskRunner.NopListener())
Expand Down
Loading

0 comments on commit 9a6b7e1

Please sign in to comment.