/* * 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 org.apache.provisionr.commands; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Optional; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import java.util.List; import java.util.Map; import org.apache.felix.gogo.commands.Command; import org.apache.felix.gogo.commands.Option; import org.apache.provisionr.api.Provisionr; import org.apache.provisionr.api.hardware.BlockDevice; import org.apache.provisionr.api.hardware.Hardware; import org.apache.provisionr.api.pool.Pool; import org.apache.provisionr.api.provider.Provider; import org.apache.provisionr.api.software.Software; import org.apache.provisionr.core.templates.PoolTemplate; /** * A typical call looks like this: * <p/> * $ provisionr:create --id amazon --key web-1 --size 5 --hardware-type m1.small \ * --port 80 --port 443 --package nginx --package gunicorn --package python-pip */ @Command(scope = "provisionr", name = "create", description = "Create pool of virtual machines") public class CreatePoolCommand extends CreateCommand { @Option(name = "-k", aliases = "--key", description = "Unique business identifier for this pool", required = true) private String key; @Option(name = "-s", aliases = "--size", description = "Expected pool size") private int size = 1; @Option(name = "-h", aliases = "--hardware-type", description = "Virtual machine hardware type") private String hardwareType = "t1.micro"; @Option(name = "--port", description = "Firewall ports that need to be open for any TCP traffic " + "(multi-valued). SSH (22) is always open by default.", multiValued = true) private List<Integer> ports = Lists.newArrayList(); @Option(name = "--volume", description = "Block devices that will be attached to each instance. " + "(multi-valued) Expects the following format: [mapping]:[size in GB]. ", multiValued = true) private List<String> blockDeviceOptions = Lists.newArrayList(); @Option(name = "-o", aliases = "--provider-options", description = "Provider-specific options (multi-valued). " + "Expects either the key=value format or just plain key. If value is not specified, defaults to 'true'. " + "Supported values: spotBid=x.xxx (Amazon).", multiValued = true) private List<String> providerOptions = Lists.newArrayList(); @Option(name = "--image-id", description = "The id of the OS image with which the machines will be created.") private String imageId = ""; @Option(name = "--cached-image", description = "If the machines will have their packages and files downloaded " + "or not. If creating the machines from an existent image, software might already be installed.") private boolean cachedImage = false; public CreatePoolCommand(List<Provisionr> services, List<PoolTemplate> templates, String publicKeyPath, String privateKeyPath) { super(services, templates, publicKeyPath, privateKeyPath); } @Override protected Object doExecute() { checkArgument(size > 0, "size should be a positive integer"); Provisionr service = getService(); final Pool pool = createPoolFromArgumentsAndServiceDefaults(service); final String processInstanceId = service.startPoolManagementProcess(key, pool); return String.format("Pool management process started (id: %s)", processInstanceId); } Pool createPoolFromArgumentsAndServiceDefaults(Provisionr service) { final Optional<Provider> defaultProvider = getDefaultProvider(service); /* append the provider options that were passed in and rebuild the default provider */ // TODO: this currently does not support overriding default options, it will throw an exception Map<String, String> options = ImmutableMap.<String, String>builder() .putAll(defaultProvider.get().getOptions()) // default options .putAll(parseProviderOptions(providerOptions)) // options added by the user .build(); Provider provider = defaultProvider.get().toBuilder().options(options).createProvider(); final Software software = Software.builder() .packages(getPackages()) .imageId(imageId) .cachedImage(cachedImage) .createSoftware(); final Hardware hardware = Hardware.builder() .type(hardwareType) .blockDevices(parseBlockDeviceOptions(blockDeviceOptions)) .createHardware(); final Pool pool = Pool.builder() .provider(provider) .hardware(hardware) .software(software) .network(buildNetwork(ports)) .adminAccess(collectCurrentUserCredentialsForAdminAccess()) .minSize(size) .expectedSize(size) .bootstrapTimeInSeconds(getBootstrapTimeout()) .createPool(); return getTemplate() != null ? applyTemplate(pool) : pool; } private List<BlockDevice> parseBlockDeviceOptions(List<String> options) { List<BlockDevice> result = Lists.newArrayList(); for (String option : options) { String[] parts = option.split(":"); checkArgument(parts.length == 2, "The arguments for the --volume option must be mapping:size"); result.add(BlockDevice.builder().name(parts[0]).size(Integer.parseInt(parts[1])).createBlockDevice()); } return result; } private Map<String, String> parseProviderOptions(List<String> providerOptions) { Map<String, String> result = Maps.newHashMap(); for (String option : providerOptions) { String[] parts = option.split("="); String value = parts.length > 1 ? parts[1] : "true"; result.put(parts[0], value); } return result; } @VisibleForTesting void setKey(String key) { this.key = checkNotNull(key, "key is null"); } @VisibleForTesting void setSize(int size) { checkArgument(size > 0, "size should be a positive number"); this.size = size; } @VisibleForTesting void setHardwareType(String hardwareType) { this.hardwareType = checkNotNull(hardwareType, "hardwareType is null"); } @VisibleForTesting void setPorts(List<Integer> ports) { this.ports = ImmutableList.copyOf(ports); } @VisibleForTesting void setProviderOptions(List<String> providerOptions) { this.providerOptions = ImmutableList.copyOf(providerOptions); } @VisibleForTesting void setBlockDeviceOptions(List<String> blockDeviceOptions) { this.blockDeviceOptions = ImmutableList.copyOf(blockDeviceOptions); } @VisibleForTesting void setCachedImage(boolean cachedImage) { this.cachedImage = cachedImage; } }