| 1 | /** |
| 2 | * |
| 3 | * Copyright (C) 2011 Cloud Conscious, LLC. <info@cloudconscious.com> |
| 4 | * |
| 5 | * ==================================================================== |
| 6 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 7 | * you may not use this file except in compliance with the License. |
| 8 | * You may obtain a copy of the License at |
| 9 | * |
| 10 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 11 | * |
| 12 | * Unless required by applicable law or agreed to in writing, software |
| 13 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 15 | * See the License for the specific language governing permissions and |
| 16 | * limitations under the License. |
| 17 | * ==================================================================== |
| 18 | */ |
| 19 | package org.jclouds.vcloud.terremark.compute; |
| 20 | |
| 21 | import static com.google.common.base.Preconditions.checkNotNull; |
| 22 | import static com.google.common.collect.Iterables.filter; |
| 23 | import static com.google.common.collect.Iterables.getLast; |
| 24 | import static org.jclouds.vcloud.terremark.options.AddInternetServiceOptions.Builder.withDescription; |
| 25 | |
| 26 | import java.net.URI; |
| 27 | import java.util.Map; |
| 28 | import java.util.NoSuchElementException; |
| 29 | import java.util.Set; |
| 30 | import java.util.Map.Entry; |
| 31 | |
| 32 | import javax.annotation.Nullable; |
| 33 | import javax.inject.Inject; |
| 34 | import javax.inject.Named; |
| 35 | import javax.inject.Provider; |
| 36 | import javax.inject.Singleton; |
| 37 | |
| 38 | import org.jclouds.compute.domain.NodeState; |
| 39 | import org.jclouds.compute.strategy.PopulateDefaultLoginCredentialsForImageStrategy; |
| 40 | import org.jclouds.domain.Credentials; |
| 41 | import org.jclouds.vcloud.compute.internal.VCloudExpressComputeClientImpl; |
| 42 | import org.jclouds.vcloud.domain.Status; |
| 43 | import org.jclouds.vcloud.domain.Task; |
| 44 | import org.jclouds.vcloud.domain.TaskStatus; |
| 45 | import org.jclouds.vcloud.domain.TasksList; |
| 46 | import org.jclouds.vcloud.domain.VCloudExpressVApp; |
| 47 | import org.jclouds.vcloud.domain.VCloudExpressVAppTemplate; |
| 48 | import org.jclouds.vcloud.options.InstantiateVAppTemplateOptions; |
| 49 | import org.jclouds.vcloud.terremark.TerremarkVCloudClient; |
| 50 | import org.jclouds.vcloud.terremark.domain.InternetService; |
| 51 | import org.jclouds.vcloud.terremark.domain.Node; |
| 52 | import org.jclouds.vcloud.terremark.domain.Protocol; |
| 53 | import org.jclouds.vcloud.terremark.domain.PublicIpAddress; |
| 54 | import org.jclouds.vcloud.terremark.options.TerremarkInstantiateVAppTemplateOptions; |
| 55 | import org.jclouds.vcloud.terremark.suppliers.InternetServiceAndPublicIpAddressSupplier; |
| 56 | |
| 57 | import com.google.common.base.Predicate; |
| 58 | import com.google.common.collect.ImmutableSet; |
| 59 | import com.google.common.collect.Sets; |
| 60 | |
| 61 | /** |
| 62 | * @author Adrian Cole |
| 63 | */ |
| 64 | @Singleton |
| 65 | public class TerremarkVCloudComputeClient extends VCloudExpressComputeClientImpl { |
| 66 | |
| 67 | protected final TerremarkVCloudClient client; |
| 68 | protected final PopulateDefaultLoginCredentialsForImageStrategy credentialsProvider; |
| 69 | protected final Provider<String> passwordGenerator; |
| 70 | protected final Map<String, Credentials> credentialStore; |
| 71 | protected final InternetServiceAndPublicIpAddressSupplier internetServiceAndPublicIpAddressSupplier; |
| 72 | |
| 73 | @Inject |
| 74 | protected TerremarkVCloudComputeClient(TerremarkVCloudClient client, |
| 75 | PopulateDefaultLoginCredentialsForImageStrategy credentialsProvider, |
| 76 | @Named("PASSWORD") Provider<String> passwordGenerator, Predicate<URI> successTester, |
| 77 | Map<Status, NodeState> vAppStatusToNodeState, Map<String, Credentials> credentialStore, |
| 78 | InternetServiceAndPublicIpAddressSupplier internetServiceAndPublicIpAddressSupplier) { |
| 79 | super(client, successTester, vAppStatusToNodeState); |
| 80 | this.client = client; |
| 81 | this.credentialsProvider = credentialsProvider; |
| 82 | this.passwordGenerator = passwordGenerator; |
| 83 | this.credentialStore = credentialStore; |
| 84 | this.internetServiceAndPublicIpAddressSupplier = internetServiceAndPublicIpAddressSupplier; |
| 85 | } |
| 86 | |
| 87 | @Override |
| 88 | public VCloudExpressVApp start(@Nullable URI VDC, URI templateId, String name, |
| 89 | InstantiateVAppTemplateOptions options, int... portsToOpen) { |
| 90 | if (options.getDiskSizeKilobytes() != null) { |
| 91 | logger.warn("trmk does not support resizing the primary disk; unsetting disk size"); |
| 92 | } |
| 93 | // we only get IP addresses after "deploy" |
| 94 | if (portsToOpen.length > 0 && !options.shouldBlock()) |
| 95 | throw new IllegalArgumentException("We cannot open ports on terremark unless we can deploy the vapp"); |
| 96 | String password = null; |
| 97 | VCloudExpressVAppTemplate template = client.getVAppTemplate(templateId); |
| 98 | if (template.getDescription().indexOf("Windows") != -1 |
| 99 | && options instanceof TerremarkInstantiateVAppTemplateOptions) { |
| 100 | password = passwordGenerator.get(); |
| 101 | TerremarkInstantiateVAppTemplateOptions.class.cast(options).getProperties().put("password", password); |
| 102 | } |
| 103 | Credentials defaultCredentials = credentialsProvider.execute(template); |
| 104 | |
| 105 | VCloudExpressVApp vAppResponse = super.start(VDC, templateId, name, options, portsToOpen); |
| 106 | if (password != null) { |
| 107 | credentialStore.put("node#" + vAppResponse.getHref().toASCIIString(), new Credentials( |
| 108 | defaultCredentials.identity, password)); |
| 109 | } |
| 110 | if (portsToOpen.length > 0) |
| 111 | createPublicAddressMappedToPorts(vAppResponse.getHref(), portsToOpen); |
| 112 | return vAppResponse; |
| 113 | } |
| 114 | |
| 115 | public String createPublicAddressMappedToPorts(URI vAppId, int... ports) { |
| 116 | VCloudExpressVApp vApp = client.getVApp(vAppId); |
| 117 | PublicIpAddress ip = null; |
| 118 | String privateAddress = getLast(vApp.getNetworkToAddresses().values()); |
| 119 | for (int port : ports) { |
| 120 | InternetService is = null; |
| 121 | Protocol protocol; |
| 122 | switch (port) { |
| 123 | case 22: |
| 124 | protocol = Protocol.TCP; |
| 125 | break; |
| 126 | case 80: |
| 127 | case 8080: |
| 128 | protocol = Protocol.HTTP; |
| 129 | break; |
| 130 | case 443: |
| 131 | protocol = Protocol.HTTPS; |
| 132 | break; |
| 133 | default: |
| 134 | protocol = Protocol.HTTP; |
| 135 | break; |
| 136 | } |
| 137 | if (ip == null) { |
| 138 | |
| 139 | Entry<InternetService, PublicIpAddress> entry = internetServiceAndPublicIpAddressSupplier |
| 140 | .getNewInternetServiceAndIp(vApp, port, protocol); |
| 141 | is = entry.getKey(); |
| 142 | ip = entry.getValue(); |
| 143 | |
| 144 | } else { |
| 145 | logger.debug(">> adding InternetService %s:%s:%d", ip.getAddress(), protocol, port); |
| 146 | is = client.addInternetServiceToExistingIp(ip.getId(), vApp.getName() + "-" + port, protocol, port, |
| 147 | withDescription(String.format("port %d access to serverId: %s name: %s", port, vApp.getName(), |
| 148 | vApp.getName()))); |
| 149 | } |
| 150 | logger.debug("<< created InternetService(%s) %s:%s:%d", is.getName(), is.getPublicIpAddress().getAddress(), is |
| 151 | .getProtocol(), is.getPort()); |
| 152 | logger.debug(">> adding Node %s:%d -> %s:%d", is.getPublicIpAddress().getAddress(), is.getPort(), |
| 153 | privateAddress, port); |
| 154 | Node node = client.addNode(is.getId(), privateAddress, vApp.getName() + "-" + port, port); |
| 155 | logger.debug("<< added Node(%s)", node.getName()); |
| 156 | } |
| 157 | return ip != null ? ip.getAddress() : null; |
| 158 | } |
| 159 | |
| 160 | private Set<PublicIpAddress> deleteInternetServicesAndNodesAssociatedWithVApp(VCloudExpressVApp vApp) { |
| 161 | checkNotNull(vApp.getVDC(), "VDC reference missing for vApp(%s)", vApp.getName()); |
| 162 | Set<PublicIpAddress> ipAddresses = Sets.newHashSet(); |
| 163 | SERVICE: for (InternetService service : client.getAllInternetServicesInVDC(vApp.getVDC().getHref())) { |
| 164 | for (Node node : client.getNodes(service.getId())) { |
| 165 | if (vApp.getNetworkToAddresses().containsValue(node.getIpAddress())) { |
| 166 | ipAddresses.add(service.getPublicIpAddress()); |
| 167 | logger.debug(">> deleting Node(%s) %s:%d -> %s:%d", node.getName(), service.getPublicIpAddress() |
| 168 | .getAddress(), service.getPort(), node.getIpAddress(), node.getPort()); |
| 169 | client.deleteNode(node.getId()); |
| 170 | logger.debug("<< deleted Node(%s)", node.getName()); |
| 171 | Set<Node> nodes = client.getNodes(service.getId()); |
| 172 | if (nodes.size() == 0) { |
| 173 | logger.debug(">> deleting InternetService(%s) %s:%d", service.getName(), service.getPublicIpAddress() |
| 174 | .getAddress(), service.getPort()); |
| 175 | client.deleteInternetService(service.getId()); |
| 176 | logger.debug("<< deleted InternetService(%s)", service.getName()); |
| 177 | continue SERVICE; |
| 178 | } |
| 179 | } |
| 180 | } |
| 181 | } |
| 182 | return ipAddresses; |
| 183 | } |
| 184 | |
| 185 | private void deletePublicIpAddressesWithNoServicesAttached(Set<PublicIpAddress> ipAddresses) { |
| 186 | IPADDRESS: for (PublicIpAddress address : ipAddresses) { |
| 187 | Set<InternetService> services = client.getInternetServicesOnPublicIp(address.getId()); |
| 188 | if (services.size() == 0) { |
| 189 | logger.debug(">> deleting PublicIpAddress(%s) %s", address.getId(), address.getAddress()); |
| 190 | try { |
| 191 | client.deletePublicIp(address.getId()); |
| 192 | logger.debug("<< deleted PublicIpAddress(%s)", address.getId()); |
| 193 | } catch (Exception e) { |
| 194 | logger.trace("cannot delete PublicIpAddress(%s) as it is unsupported", address.getId()); |
| 195 | } |
| 196 | continue IPADDRESS; |
| 197 | } |
| 198 | } |
| 199 | } |
| 200 | |
| 201 | /** |
| 202 | * deletes the internet service and nodes associated with the vapp. Deletes the IP address, if |
| 203 | * there are no others using it. Finally, it powers off and deletes the vapp. Note that we do not |
| 204 | * call undeploy, as terremark does not support the command. |
| 205 | */ |
| 206 | @Override |
| 207 | public void stop(URI id) { |
| 208 | VCloudExpressVApp vApp = client.getVApp(id); |
| 209 | Set<PublicIpAddress> ipAddresses = deleteInternetServicesAndNodesAssociatedWithVApp(vApp); |
| 210 | deletePublicIpAddressesWithNoServicesAttached(ipAddresses); |
| 211 | if (vApp.getStatus() != Status.OFF) { |
| 212 | try { |
| 213 | powerOffAndWait(vApp); |
| 214 | } catch (IllegalStateException e) { |
| 215 | logger.warn("<< %s vApp(%s)", e.getMessage(), vApp.getName()); |
| 216 | blockOnLastTask(vApp); |
| 217 | powerOffAndWait(vApp); |
| 218 | } |
| 219 | vApp = client.getVApp(id); |
| 220 | logger.debug("<< %s vApp(%s)", vApp.getStatus(), vApp.getName()); |
| 221 | } |
| 222 | logger.debug(">> deleting vApp(%s)", vApp.getName()); |
| 223 | client.deleteVApp(id); |
| 224 | logger.debug("<< deleted vApp(%s))", vApp.getName()); |
| 225 | } |
| 226 | |
| 227 | private void powerOffAndWait(VCloudExpressVApp vApp) { |
| 228 | logger.debug(">> powering off vApp(%s), current status: %s", vApp.getName(), vApp.getStatus()); |
| 229 | Task task = client.powerOffVApp(vApp.getHref()); |
| 230 | if (!taskTester.apply(task.getHref())) |
| 231 | throw new RuntimeException(String.format("failed to %s %s: %s", "powerOff", vApp.getName(), task)); |
| 232 | } |
| 233 | |
| 234 | void blockOnLastTask(VCloudExpressVApp vApp) { |
| 235 | TasksList list = client.findTasksListInOrgNamed(null); |
| 236 | try { |
| 237 | Task lastTask = getLast(filter(list.getTasks(), new Predicate<Task>() { |
| 238 | |
| 239 | @Override |
| 240 | public boolean apply(Task input) { |
| 241 | return input.getStatus() == TaskStatus.QUEUED || input.getStatus() == TaskStatus.RUNNING; |
| 242 | } |
| 243 | |
| 244 | })); |
| 245 | if (!taskTester.apply(lastTask.getHref())) |
| 246 | throw new RuntimeException(String.format("failed to %s %s: %s", "powerOff", vApp.getName(), lastTask)); |
| 247 | } catch (NoSuchElementException ex) { |
| 248 | |
| 249 | } |
| 250 | } |
| 251 | |
| 252 | /** |
| 253 | * @returns empty set if the node is not found |
| 254 | */ |
| 255 | @Override |
| 256 | public Set<String> getPrivateAddresses(URI id) { |
| 257 | VCloudExpressVApp vApp = client.getVApp(id); |
| 258 | if (vApp != null) |
| 259 | return Sets.newHashSet(vApp.getNetworkToAddresses().values()); |
| 260 | else |
| 261 | return ImmutableSet.<String> of(); |
| 262 | } |
| 263 | |
| 264 | /** |
| 265 | * @returns empty set if the node is not found |
| 266 | */ |
| 267 | @Override |
| 268 | public Set<String> getPublicAddresses(URI id) { |
| 269 | VCloudExpressVApp vApp = client.getVApp(id); |
| 270 | if (vApp != null) { |
| 271 | Set<String> ipAddresses = Sets.newHashSet(); |
| 272 | for (InternetService service : client.getAllInternetServicesInVDC(vApp.getVDC().getHref())) { |
| 273 | for (Node node : client.getNodes(service.getId())) { |
| 274 | if (vApp.getNetworkToAddresses().containsValue(node.getIpAddress())) { |
| 275 | ipAddresses.add(service.getPublicIpAddress().getAddress()); |
| 276 | } |
| 277 | } |
| 278 | } |
| 279 | return ipAddresses; |
| 280 | } else { |
| 281 | return ImmutableSet.<String> of(); |
| 282 | } |
| 283 | } |
| 284 | } |