Java tutorial
/* * Copyright 2013-2014 by Cloudsoft Corporation Limited * * 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 brooklyn.networking.cloudstack.legacy; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import javax.annotation.Nullable; import org.jclouds.cloudstack.CloudStackApi; import org.jclouds.cloudstack.compute.options.CloudStackTemplateOptions; import org.jclouds.cloudstack.domain.AsyncCreateResponse; import org.jclouds.cloudstack.domain.FirewallRule; import org.jclouds.cloudstack.domain.NIC; import org.jclouds.cloudstack.domain.PortForwardingRule; import org.jclouds.cloudstack.domain.VirtualMachine; import org.jclouds.cloudstack.features.VirtualMachineApi; import org.jclouds.cloudstack.options.CreateFirewallRuleOptions; import org.jclouds.compute.ComputeService; import org.jclouds.compute.domain.NodeMetadata; import org.jclouds.compute.domain.OperatingSystem; import org.jclouds.compute.domain.OsFamily; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import brooklyn.config.ConfigKey; import brooklyn.entity.basic.ConfigKeys; import brooklyn.entity.basic.Entities; import brooklyn.event.basic.BasicConfigKey; import brooklyn.location.NoMachinesAvailableException; import brooklyn.location.access.BrooklynAccessUtils; import brooklyn.location.access.PortForwardManager; import brooklyn.location.basic.SshMachineLocation; import brooklyn.location.jclouds.AbstractJcloudsSubnetSshMachineLocation; import brooklyn.location.jclouds.JcloudsLocation; import brooklyn.location.jclouds.JcloudsLocationConfig; import brooklyn.location.jclouds.JcloudsSshMachineLocation; import brooklyn.location.jclouds.templates.PortableTemplateBuilder; import brooklyn.networking.NetworkMultiAddressUtils2; import brooklyn.networking.cloudstack.CloudstackNew40FeaturesClient; import brooklyn.util.ResourceUtils; import brooklyn.util.collections.MutableMap; import brooklyn.util.config.ConfigBag; import brooklyn.util.internal.ssh.SshTool; import brooklyn.util.net.Cidr; import brooklyn.util.net.Networking; import brooklyn.util.repeat.Repeater; import brooklyn.util.ssh.BashCommands; import brooklyn.util.text.Strings; import brooklyn.util.text.TemplateProcessor; import brooklyn.util.time.Duration; import brooklyn.util.time.Time; import com.google.common.base.Optional; import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.base.Splitter; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.common.net.HostAndPort; /** * Legacy {@link JcloudsLocation} for a CloudStack subnet. */ public class LegacyJcloudsCloudstackSubnetLocation extends JcloudsLocation { private static final long serialVersionUID = -6097237757668759966L; private static final Logger LOG = LoggerFactory.getLogger(LegacyJcloudsCloudstackSubnetLocation.class); /* Config on the subnet jclouds location */ public static final ConfigKey<String> CLOUDSTACK_VPC_ID = ConfigKeys.newStringConfigKey("vpcId"); public static final ConfigKey<String> CLOUDSTACK_ZONE_ID = ConfigKeys.newStringConfigKey("zoneId"); public static final ConfigKey<String> CLOUDSTACK_SUBNET_NETWORK_ID = ConfigKeys.newStringConfigKey("networkId"); public static final ConfigKey<String> CLOUDSTACK_SERVICE_NETWORK_ID = ConfigKeys .newStringConfigKey("serviceNetworkId"); // For preventing concurrent calls to obtain private static final Object mutex = new Object(); private static long lastObtainTime = -1; // Required for port forwarding. Set by location creator (e.g. SubnetTierImpl) public static final ConfigKey<PortForwardManager> PORT_FORWARDING_MANAGER = BrooklynAccessUtils.PORT_FORWARDING_MANAGER; // Required for port forwarding. Set by subnet creator public static final ConfigKey<String> CLOUDSTACK_TIER_PUBLIC_IP_ID = ConfigKeys .newStringConfigKey("publicIpId"); // Optional, set by creator public static final ConfigKey<Cidr> MANAGEMENT_ACCESS_CIDR = new BasicConfigKey<Cidr>(Cidr.class, BrooklynAccessUtils.MANAGEMENT_ACCESS_CIDR.getName(), BrooklynAccessUtils.MANAGEMENT_ACCESS_CIDR.getDescription(), new Cidr("10.0.0.0/8")); /* Config on the SSH machine location */ public static final ConfigKey<String> SUBNET_HOSTNAME_CONFIG = ConfigKeys .newStringConfigKey(LegacySubnetTier.SUBNET_HOSTNAME_SENSOR.getName()); public static final ConfigKey<String> VM_IDENTIFIER = ConfigKeys.newStringConfigKey("vm.cloud.identifier"); public LegacyJcloudsCloudstackSubnetLocation(JcloudsLocation parent, ConfigBag map) { super(MutableMap.copyOf(parent.getLocalConfigBag().getAllConfig())); configure(map.getAllConfig()); } public LegacyJcloudsCloudstackSubnetLocation(Map<?, ?> map) { super(map); } public LegacyJcloudsCloudstackSubnetLocation() { super(); } protected <T> T getRequiredConfig(ConfigKey<T> key) { return checkNotNull(getConfig(key), key.getName()); } @Override public JcloudsSshMachineLocation obtain(Map<?, ?> flagsIn) throws NoMachinesAvailableException { PortableTemplateBuilder<PortableTemplateBuilder<?>> tb = new PortableTemplateBuilder<PortableTemplateBuilder<?>>(); tb.locationId(getRequiredConfig(CLOUDSTACK_ZONE_ID)); // the VM seems to prefer the first network in the list (ie as "default" for gateway) // which should be the tier (application) so that app traffic goes via that path // (if you put sharedNetwork first, then it can see a brooklyn machine _not_ // in the sharedNetwork, which is handy for testing) List<String> networkIds = new ArrayList<String>(); networkIds.add(getRequiredConfig(CLOUDSTACK_SUBNET_NETWORK_ID)); String serviceNetworkId = getConfig(CLOUDSTACK_SERVICE_NETWORK_ID); boolean portForwardingMode = Strings.isBlank(serviceNetworkId); if (!portForwardingMode) networkIds.add(serviceNetworkId); tb.options(CloudStackTemplateOptions.Builder.networkIds(networkIds).setupStaticNat(false) .dontAuthorizePublicKey().blockUntilRunning(false)); Map<Object, Object> flags = MutableMap.copyOf(flagsIn).add(TEMPLATE_BUILDER, tb); if (portForwardingMode) flags.put(WAIT_FOR_SSHABLE, false); // gap of 15s does not stop the two NIC's problem // log.info("provision - waiting to acquire mutex "+Thread.currentThread()); // synchronized (mutex) { // log.info("provision - acquired mutex "+Thread.currentThread()); // Time.sleep(15000); // } // log.info("provision - creating machine "+Thread.currentThread()); // Throttle to ensure only one call to obtain per 10 seconds (but calls can overlap) LOG.info("provision - waiting to acquire mutex (" + Thread.currentThread() + ")"); synchronized (mutex) { long now = System.currentTimeMillis(); if (lastObtainTime >= 0 && (now < (lastObtainTime + 10 * 1000))) { LOG.info("provision - waiting for 10s as another obtain call executed recently " + Thread.currentThread()); Time.sleep(10000); } else { LOG.info("provision - contininuing immediately as no other recent call " + Thread.currentThread()); } lastObtainTime = System.currentTimeMillis(); } // And finally obtain the machine JcloudsSshMachineLocation m = super.obtain(flags); // if USE_TWO_NICS -- could check they come up assigned correctly (but probably still too dangerous) // String nodeId = m.getNode().getId(); // CloudstackNew40FeaturesClient client = CloudstackNew40FeaturesClient.newInstance(getEndpoint(), getIdentity(), getCredential()); // VirtualMachine vm = client.getVirtualMachineClient().getVirtualMachine(nodeId); // Set<NIC> nics = vm.getNICs(); // NIC nic = nics.iterator().next(); // if (nic.getNetworkId().equals(getRequiredConfig(CLOUDSTACK_TIER_ID))) { // String ip = nic.getIPAddress(); // // ip sometimes does not match subnet // } return m; } @Override protected JcloudsSshMachineLocation createJcloudsSshMachineLocation(ComputeService computeService, NodeMetadata node, String vmHostname1, Optional<HostAndPort> sshHostAndPort, ConfigBag setup) throws IOException { String subnetSpecificHostname = null; String vmHostname = vmHostname1; String sshHost = vmHostname; Integer sshPort = null; PortForwardManager pfw = null; String publicIpId = null; final String serviceNetworkId = getConfig(CLOUDSTACK_SERVICE_NETWORK_ID); boolean portForwardingMode = Strings.isBlank(serviceNetworkId); LOG.debug("creating subnet JcloudsSshMachineLocation -- port forwarding={}, node={}", new Object[] { node, portForwardingMode }); if (!portForwardingMode) { LOG.debug( "Using service network for Brooklyn access - service network ID is {} - searching for NIC connected to this network", serviceNetworkId); CloudStackApi cloudStackApi = getComputeService().getContext().unwrapApi(CloudStackApi.class); VirtualMachineApi vmClient = cloudStackApi.getVirtualMachineApi(); VirtualMachine vm = vmClient.getVirtualMachine(node.getProviderId()); Iterable<NIC> allNics = vm.getNICs(); Predicate<NIC> isServiceNetwork = new Predicate<NIC>() { @Override public boolean apply(@Nullable NIC input) { return input != null && serviceNetworkId.equals(input.getNetworkId()); } }; Optional<NIC> serviceNic = Iterables.tryFind(allNics, isServiceNetwork); Iterable<NIC> otherNics = Iterables.filter(allNics, Predicates.not(isServiceNetwork)); checkState(serviceNic.isPresent(), "unable to identify NIC connected to service network " + serviceNetworkId); String ipAddress = serviceNic.get().getIPAddress(); checkState(Strings.isNonBlank(ipAddress), "no IP address on the NIC connected to service network " + serviceNetworkId); checkState(!Iterables.isEmpty(otherNics), "VM needs another NIC, in addition to the service network"); // NIC anotherNic = Iterables.get(otherNics, 0); sshHost = ipAddress; sshPort = 22; } else { pfw = getRequiredConfig(PORT_FORWARDING_MANAGER); publicIpId = getRequiredConfig(CLOUDSTACK_TIER_PUBLIC_IP_ID); Cidr cidr = getConfig(MANAGEMENT_ACCESS_CIDR); // others, besides 22! int privatePort = 22; int publicPort = pfw.acquirePublicPort(publicIpId); systemCreatePortForwarding(cidr, publicPort, node, privatePort); sshPort = publicPort; sshHost = checkNotNull(pfw.getPublicIpHostname(publicIpId), "No ip recorded for id %s", publicIpId); } LOG.info("Created VM in " + this + " in subnet at " + node + ", ssh accessible via " + sshHost + ":" + sshPort); // and wait for it to be reachable LOG.debug(" waiting for new VM " + node + " in " + this + " to be port reachable on " + sshHost + ":" + sshPort); boolean isReachable = NetworkMultiAddressUtils2.isAccessible(sshHost, sshPort, TimeUnit.MINUTES.toMillis(15)); if (!isReachable) { throw new IllegalStateException("Unable to contact forwarded SSH port for new VM " + node + " in " + this + " on " + sshHost + ":" + sshPort); } if (!NetworkMultiAddressUtils2.isResolveable(vmHostname)) { String oldHostname = vmHostname; vmHostname = Iterables.getFirst(Iterables.concat(node.getPublicAddresses(), node.getPrivateAddresses()), null); LOG.info("Renaming unresolvable hostname " + oldHostname + " to " + vmHostname); } // "public hostname" might be different // - sometimes it is not pingable from brooklyn (making sensors hard) // - sometimes furthest is the public one, we might want it // (eg if we are in different 10.x subnet - closest not always accessible) // or we might want nearest (if public is not accessible); // and also consider we are on /16 subnet with host, host has another 10.x/8 address, but no public address; // ie furthest might be inaccessible for other reasons // TODO i think the "right" way to do this is to have a pluggable "address chooser" ? LOG.debug(" vmHostname: " + vmHostname); // supply forwarded host and port Map<String, Object> sshConfig = extractSshConfig(setup, node); sshConfig.put(SshMachineLocation.SSH_HOST.getName(), sshHost); if (sshPort != null) sshConfig.put(SshMachineLocation.SSH_PORT.getName(), sshPort); if (LOG.isDebugEnabled()) { LOG.debug( "creating JcloudsSshMachineLocation in subnet {}, service network {}, for {}@{} for {} with {}", new Object[] { getRequiredConfig(CLOUDSTACK_SUBNET_NETWORK_ID), getConfig(CLOUDSTACK_SERVICE_NETWORK_ID), getUser(setup), vmHostname, setup.getDescription(), Entities.sanitize(sshConfig) }); } final JcloudsSshMachineLocation l = new AbstractJcloudsSubnetSshMachineLocation( MutableMap.builder().put("address", Networking.getInetAddressWithFixedName(vmHostname)) .put("displayName", vmHostname).put("user", getUser(setup)) // don't think "config" does anything .putAll(sshConfig).put("config", sshConfig).put("jcloudsParent", this).put("node", node) .put("port", sshPort).put(CALLER_CONTEXT, setup.get(CALLER_CONTEXT)).build(), this, node) { @Override public HostAndPort getSocketEndpointFor(Cidr accessor, int privatePort) { return getPortForwardingTo(accessor, this, privatePort); } }; l.init(); getManagementContext().getLocationManager().manage(l); l.setConfig(SUBNET_HOSTNAME_CONFIG, subnetSpecificHostname); l.setConfig(VM_IDENTIFIER, node.getId()); if (portForwardingMode) { // record port 22 forwarding pfw.associate(publicIpId, sshPort, l, 22); } LOG.debug(" waiting for new VM {} in {} to be SSH reachable on {}:{}", new Object[] { node, this, sshHost, sshPort }); final AtomicBoolean isActive = new AtomicBoolean(false); Repeater.create().repeat(new Runnable() { @Override public void run() { try { int rc = l.execCommands("test accessibility", ImmutableList.of("hostname")); isActive.set(rc == 0); } catch (Throwable t) { isActive.set(false); } } }).every(Duration.FIVE_SECONDS).until(new Callable<Boolean>() { @Override public Boolean call() throws Exception { return isActive.get(); } }).limitTimeTo(Duration.FIVE_MINUTES).run(); LOG.debug(" waited for new VM {} in {} to be SSH reachable on {}:{}, result={}", new Object[] { node, this, sshHost, sshPort, isActive.get() }); OperatingSystem operatingSystem = l.getNode().getOperatingSystem(); if (operatingSystem != null) { OsFamily family = operatingSystem.getFamily(); LOG.info("VM {}: OS family is {}", new Object[] { node, family }); if (family != OsFamily.WINDOWS && family != OsFamily.UNRECOGNIZED) { LOG.warn("VM {}: disabling iptables", new Object[] { node }); l.execScript(MutableMap.of(SshTool.PROP_ALLOCATE_PTY.getName(), true), "disabling requiretty", Arrays.asList(BashCommands.dontRequireTtyForSudo())); l.execScript("disabling iptables", Arrays.asList("sudo /etc/init.d/iptables stop", "sudo chkconfig iptables off")); } else { LOG.warn("VM {}: NOT disabling iptables because OS family is {}", new Object[] { node, family }); } } else { LOG.warn("VM {}: NOT disabling iptables because OS is not detected", new Object[] { node }); } String setupScript = setup.get(JcloudsLocationConfig.CUSTOM_MACHINE_SETUP_SCRIPT_URL); if (Strings.isNonBlank(setupScript)) { String setupVarsString = setup.get(JcloudsLocationConfig.CUSTOM_MACHINE_SETUP_SCRIPT_VARS); Map<String, String> substitutions = (setupVarsString != null) ? Splitter.on(",").withKeyValueSeparator(":").split(setupVarsString) : ImmutableMap.<String, String>of(); String scriptContent = ResourceUtils.create(this).getResourceAsString(setupScript); String script = TemplateProcessor.processTemplateContents(scriptContent, substitutions); l.execScript(MutableMap.of(SshTool.PROP_ALLOCATE_PTY.getName(), true), "disabling requiretty", Arrays.asList(BashCommands.dontRequireTtyForSudo())); l.execCommands("Customizing node " + this, ImmutableList.of(script)); } return l; } protected HostAndPort getManagementPortForwardingTo(JcloudsSshMachineLocation l, int privatePort) { return getPortForwardingTo(getConfig(MANAGEMENT_ACCESS_CIDR), l, privatePort); } protected HostAndPort getPortForwardingTo(Cidr access, JcloudsSshMachineLocation l, int privatePort) { PortForwardManager pfw = getRequiredConfig(PORT_FORWARDING_MANAGER); synchronized (pfw) { HostAndPort hp = pfw.lookup(l, privatePort); if (hp != null) return hp; // String publicIpId = getConfig(CLOUDSTACK_TIER_PUBLIC_IP_ID); int publicPort = pfw.acquirePublicPort(publicIpId, l, privatePort); systemCreatePortForwarding(access, publicPort, l.getNode(), privatePort); return pfw.lookup(l, privatePort); } } protected void systemCreatePortForwarding(Cidr cidr, int publicPort, NodeMetadata node, int privatePort) { // TODO which needs enabling -- private or public port? // should we do this at subnet, for the entire range ? try { String publicIpId = getRequiredConfig(CLOUDSTACK_TIER_PUBLIC_IP_ID); CloudstackNew40FeaturesClient client = CloudstackNew40FeaturesClient.newInstance(getEndpoint(), getIdentity(), getCredential()); String jobid; if (!Strings.isBlank(getConfig(CLOUDSTACK_VPC_ID))) { String tierId = getRequiredConfig(CLOUDSTACK_SUBNET_NETWORK_ID); jobid = client.createPortForwardRuleForVpc(tierId, publicIpId, PortForwardingRule.Protocol.TCP, publicPort, node.getId(), privatePort); client.waitForJobSuccess(jobid); client.createVpcNetworkAcl(tierId, "TCP", cidr.toString(), publicPort, publicPort, null, null, "INGRESS"); // private doesn't need to be opened // client.createVpcNetworkAcl(tierId, "TCP", cidr.toString(), privatePort, privatePort, null, null, "INGRESS"); } else { jobid = client.createPortForwardRuleForVm(publicIpId, PortForwardingRule.Protocol.TCP, publicPort, node.getId(), privatePort); client.waitForJobSuccess(jobid); CreateFirewallRuleOptions options; options = CreateFirewallRuleOptions.Builder.startPort(publicPort).endPort(publicPort) .CIDRs(ImmutableSet.of(cidr.toString())); AsyncCreateResponse job = client.getCloudstackGlobalClient().getFirewallApi() .createFirewallRuleForIpAndProtocol(publicIpId, FirewallRule.Protocol.TCP, options); client.waitForJobSuccess(job.getJobId()); // private doesn't need to be opened // options = CreateFirewallRuleOptions.Builder. // startPort(privatePort).endPort(privatePort).CIDRs(ImmutableSet.of(cidr.toString())); // job = client.getCloudstackGlobalClient().getFirewallClient().createFirewallRuleForIpAndProtocol( // publicIpId, FirewallRule.Protocol.TCP, options); // client.waitForJob(job.getJobId()); } } catch (Exception e) { LOG.warn("Could not create fwd/ACL (possibly already created) to " + this + " port " + privatePort + ": " + e); } } }