| 1 | /** |
| 2 | * Licensed to jclouds, Inc. (jclouds) under one or more |
| 3 | * contributor license agreements. See the NOTICE file |
| 4 | * distributed with this work for additional information |
| 5 | * regarding copyright ownership. jclouds licenses this file |
| 6 | * to you under the Apache License, Version 2.0 (the |
| 7 | * "License"); you may not use this file except in compliance |
| 8 | * with the License. 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, |
| 13 | * software distributed under the License is distributed on an |
| 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| 15 | * KIND, either express or implied. See the License for the |
| 16 | * specific language governing permissions and limitations |
| 17 | * under the License. |
| 18 | */ |
| 19 | package org.jclouds.trmk.vcloud_0_8.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.trmk.vcloud_0_8.options.AddInternetServiceOptions.Builder.withDescription; |
| 25 | |
| 26 | import java.net.URI; |
| 27 | import java.util.Map; |
| 28 | import java.util.Map.Entry; |
| 29 | import java.util.NoSuchElementException; |
| 30 | import java.util.Set; |
| 31 | |
| 32 | import org.jclouds.javax.annotation.Nullable; |
| 33 | import javax.annotation.Resource; |
| 34 | import javax.inject.Inject; |
| 35 | import javax.inject.Named; |
| 36 | import javax.inject.Provider; |
| 37 | import javax.inject.Singleton; |
| 38 | |
| 39 | import org.jclouds.compute.domain.NodeState; |
| 40 | import org.jclouds.compute.reference.ComputeServiceConstants; |
| 41 | import org.jclouds.compute.strategy.PopulateDefaultLoginCredentialsForImageStrategy; |
| 42 | import org.jclouds.domain.Credentials; |
| 43 | import org.jclouds.logging.Logger; |
| 44 | import org.jclouds.trmk.vcloud_0_8.TerremarkVCloudClient; |
| 45 | import org.jclouds.trmk.vcloud_0_8.domain.InternetService; |
| 46 | import org.jclouds.trmk.vcloud_0_8.domain.Node; |
| 47 | import org.jclouds.trmk.vcloud_0_8.domain.Protocol; |
| 48 | import org.jclouds.trmk.vcloud_0_8.domain.PublicIpAddress; |
| 49 | import org.jclouds.trmk.vcloud_0_8.domain.Status; |
| 50 | import org.jclouds.trmk.vcloud_0_8.domain.Task; |
| 51 | import org.jclouds.trmk.vcloud_0_8.domain.TaskStatus; |
| 52 | import org.jclouds.trmk.vcloud_0_8.domain.TasksList; |
| 53 | import org.jclouds.trmk.vcloud_0_8.domain.VApp; |
| 54 | import org.jclouds.trmk.vcloud_0_8.domain.VAppTemplate; |
| 55 | import org.jclouds.trmk.vcloud_0_8.options.InstantiateVAppTemplateOptions; |
| 56 | import org.jclouds.trmk.vcloud_0_8.suppliers.InternetServiceAndPublicIpAddressSupplier; |
| 57 | |
| 58 | import com.google.common.base.Predicate; |
| 59 | import com.google.common.collect.ImmutableSet; |
| 60 | import com.google.common.collect.Sets; |
| 61 | |
| 62 | /** |
| 63 | * @author Adrian Cole |
| 64 | */ |
| 65 | @Singleton |
| 66 | public class TerremarkVCloudComputeClient { |
| 67 | @Resource |
| 68 | @Named(ComputeServiceConstants.COMPUTE_LOGGER) |
| 69 | protected Logger logger = Logger.NULL; |
| 70 | |
| 71 | protected final TerremarkVCloudClient client; |
| 72 | protected final PopulateDefaultLoginCredentialsForImageStrategy credentialsProvider; |
| 73 | protected final Provider<String> passwordGenerator; |
| 74 | protected final Map<String, Credentials> credentialStore; |
| 75 | protected final InternetServiceAndPublicIpAddressSupplier internetServiceAndPublicIpAddressSupplier; |
| 76 | protected final Map<Status, NodeState> vAppStatusToNodeState; |
| 77 | protected final Predicate<URI> taskTester; |
| 78 | |
| 79 | @Inject |
| 80 | protected TerremarkVCloudComputeClient(TerremarkVCloudClient client, |
| 81 | PopulateDefaultLoginCredentialsForImageStrategy credentialsProvider, |
| 82 | @Named("PASSWORD") Provider<String> passwordGenerator, Predicate<URI> successTester, |
| 83 | Map<Status, NodeState> vAppStatusToNodeState, Map<String, Credentials> credentialStore, |
| 84 | InternetServiceAndPublicIpAddressSupplier internetServiceAndPublicIpAddressSupplier) { |
| 85 | this.client = client; |
| 86 | this.credentialsProvider = credentialsProvider; |
| 87 | this.passwordGenerator = passwordGenerator; |
| 88 | this.credentialStore = credentialStore; |
| 89 | this.internetServiceAndPublicIpAddressSupplier = internetServiceAndPublicIpAddressSupplier; |
| 90 | this.vAppStatusToNodeState = vAppStatusToNodeState; |
| 91 | this.taskTester = successTester; |
| 92 | } |
| 93 | |
| 94 | protected Status getStatus(VApp vApp) { |
| 95 | return vApp.getStatus(); |
| 96 | } |
| 97 | |
| 98 | /** |
| 99 | * Runs through all commands necessary to startup a vApp, opening at least |
| 100 | * one ip address to the public network. These are the steps: |
| 101 | * <p/> |
| 102 | * instantiate -> deploy -> powerOn |
| 103 | * <p/> |
| 104 | * This command blocks until the vApp is in state {@code VAppStatus#ON} |
| 105 | * |
| 106 | * @param VDC |
| 107 | * id of the virtual datacenter {@code VCloudClient#getDefaultVDC} |
| 108 | * @param templateId |
| 109 | * id of the vAppTemplate you wish to instantiate |
| 110 | * @param name |
| 111 | * name of the vApp |
| 112 | * @param cores |
| 113 | * amount of virtual cpu cores |
| 114 | * @param megs |
| 115 | * amount of ram in megabytes |
| 116 | * @param options |
| 117 | * options for instantiating the vApp; null is ok |
| 118 | * @param portsToOpen |
| 119 | * opens the following ports on the public ip address |
| 120 | * @return map contains at least the following properties |
| 121 | * <ol> |
| 122 | * <li>id - vApp id</li> <li>username - console login user</li> <li> |
| 123 | * password - console login password</li> |
| 124 | * </ol> |
| 125 | */ |
| 126 | public VApp start(@Nullable URI VDC, URI templateId, String name, InstantiateVAppTemplateOptions options, |
| 127 | int... portsToOpen) { |
| 128 | // we only get IP addresses after "deploy" |
| 129 | if (portsToOpen.length > 0 && !options.shouldBlock()) |
| 130 | throw new IllegalArgumentException("We cannot open ports on terremark unless we can deploy the vapp"); |
| 131 | String password = null; |
| 132 | VAppTemplate template = client.getVAppTemplate(templateId); |
| 133 | if (template.getDescription().indexOf("Windows") != -1) { |
| 134 | password = passwordGenerator.get(); |
| 135 | options.getProperties().put("password", password); |
| 136 | } |
| 137 | Credentials defaultCredentials = credentialsProvider.execute(template); |
| 138 | checkNotNull(options, "options"); |
| 139 | logger.debug(">> instantiating vApp vDC(%s) template(%s) name(%s) options(%s) ", VDC, templateId, name, options); |
| 140 | |
| 141 | VApp vAppResponse = client.instantiateVAppTemplateInVDC(VDC, templateId, name, options); |
| 142 | logger.debug("<< instantiated VApp(%s)", vAppResponse.getName()); |
| 143 | if (options.shouldDeploy()) { |
| 144 | logger.debug(">> deploying vApp(%s)", vAppResponse.getName()); |
| 145 | |
| 146 | Task task = client.deployVApp(vAppResponse.getHref()); |
| 147 | if (options.shouldBlock()) { |
| 148 | if (!taskTester.apply(task.getHref())) { |
| 149 | throw new RuntimeException(String.format("failed to %s %s: %s", "deploy", vAppResponse.getName(), task)); |
| 150 | } |
| 151 | logger.debug("<< deployed vApp(%s)", vAppResponse.getName()); |
| 152 | if (options.shouldPowerOn()) { |
| 153 | logger.debug(">> powering vApp(%s)", vAppResponse.getName()); |
| 154 | task = client.powerOnVApp(vAppResponse.getHref()); |
| 155 | if (!taskTester.apply(task.getHref())) { |
| 156 | throw new RuntimeException(String.format("failed to %s %s: %s", "powerOn", vAppResponse.getName(), |
| 157 | task)); |
| 158 | } |
| 159 | logger.debug("<< on vApp(%s)", vAppResponse.getName()); |
| 160 | } |
| 161 | } |
| 162 | } |
| 163 | if (password != null) { |
| 164 | credentialStore.put("node#" + vAppResponse.getHref().toASCIIString(), new Credentials( |
| 165 | defaultCredentials.identity, password)); |
| 166 | } |
| 167 | if (portsToOpen.length > 0) |
| 168 | createPublicAddressMappedToPorts(vAppResponse.getHref(), portsToOpen); |
| 169 | return vAppResponse; |
| 170 | } |
| 171 | |
| 172 | public String createPublicAddressMappedToPorts(URI vAppId, int... ports) { |
| 173 | VApp vApp = client.getVApp(vAppId); |
| 174 | PublicIpAddress ip = null; |
| 175 | String privateAddress = getLast(vApp.getNetworkToAddresses().values()); |
| 176 | for (int port : ports) { |
| 177 | InternetService is = null; |
| 178 | Protocol protocol; |
| 179 | switch (port) { |
| 180 | case 22: |
| 181 | protocol = Protocol.TCP; |
| 182 | break; |
| 183 | case 80: |
| 184 | case 8080: |
| 185 | protocol = Protocol.HTTP; |
| 186 | break; |
| 187 | case 443: |
| 188 | protocol = Protocol.HTTPS; |
| 189 | break; |
| 190 | default: |
| 191 | protocol = Protocol.HTTP; |
| 192 | break; |
| 193 | } |
| 194 | if (ip == null) { |
| 195 | |
| 196 | Entry<InternetService, PublicIpAddress> entry = internetServiceAndPublicIpAddressSupplier |
| 197 | .getNewInternetServiceAndIp(vApp, port, protocol); |
| 198 | is = entry.getKey(); |
| 199 | ip = entry.getValue(); |
| 200 | |
| 201 | } else { |
| 202 | logger.debug(">> adding InternetService %s:%s:%d", ip.getAddress(), protocol, port); |
| 203 | is = client.addInternetServiceToExistingIp( |
| 204 | ip.getId(), |
| 205 | vApp.getName() + "-" + port, |
| 206 | protocol, |
| 207 | port, |
| 208 | withDescription(String.format("port %d access to serverId: %s name: %s", port, vApp.getName(), |
| 209 | vApp.getName()))); |
| 210 | } |
| 211 | logger.debug("<< created InternetService(%s) %s:%s:%d", is.getName(), is.getPublicIpAddress().getAddress(), |
| 212 | is.getProtocol(), is.getPort()); |
| 213 | logger.debug(">> adding Node %s:%d -> %s:%d", is.getPublicIpAddress().getAddress(), is.getPort(), |
| 214 | privateAddress, port); |
| 215 | Node node = client.addNode(is.getId(), privateAddress, vApp.getName() + "-" + port, port); |
| 216 | logger.debug("<< added Node(%s)", node.getName()); |
| 217 | } |
| 218 | return ip != null ? ip.getAddress() : null; |
| 219 | } |
| 220 | |
| 221 | private Set<PublicIpAddress> deleteInternetServicesAndNodesAssociatedWithVApp(VApp vApp) { |
| 222 | checkNotNull(vApp.getVDC(), "VDC reference missing for vApp(%s)", vApp.getName()); |
| 223 | Set<PublicIpAddress> ipAddresses = Sets.newHashSet(); |
| 224 | SERVICE: for (InternetService service : client.getAllInternetServicesInVDC(vApp.getVDC().getHref())) { |
| 225 | for (Node node : client.getNodes(service.getId())) { |
| 226 | if (vApp.getNetworkToAddresses().containsValue(node.getIpAddress())) { |
| 227 | ipAddresses.add(service.getPublicIpAddress()); |
| 228 | logger.debug(">> deleting Node(%s) %s:%d -> %s:%d", node.getName(), service.getPublicIpAddress() |
| 229 | .getAddress(), service.getPort(), node.getIpAddress(), node.getPort()); |
| 230 | client.deleteNode(node.getId()); |
| 231 | logger.debug("<< deleted Node(%s)", node.getName()); |
| 232 | Set<Node> nodes = client.getNodes(service.getId()); |
| 233 | if (nodes.size() == 0) { |
| 234 | logger.debug(">> deleting InternetService(%s) %s:%d", service.getName(), service.getPublicIpAddress() |
| 235 | .getAddress(), service.getPort()); |
| 236 | client.deleteInternetService(service.getId()); |
| 237 | logger.debug("<< deleted InternetService(%s)", service.getName()); |
| 238 | continue SERVICE; |
| 239 | } |
| 240 | } |
| 241 | } |
| 242 | } |
| 243 | return ipAddresses; |
| 244 | } |
| 245 | |
| 246 | private void deletePublicIpAddressesWithNoServicesAttached(Set<PublicIpAddress> ipAddresses) { |
| 247 | IPADDRESS: for (PublicIpAddress address : ipAddresses) { |
| 248 | Set<InternetService> services = client.getInternetServicesOnPublicIp(address.getId()); |
| 249 | if (services.size() == 0) { |
| 250 | logger.debug(">> deleting PublicIpAddress(%s) %s", address.getId(), address.getAddress()); |
| 251 | try { |
| 252 | client.deletePublicIp(address.getId()); |
| 253 | logger.debug("<< deleted PublicIpAddress(%s)", address.getId()); |
| 254 | } catch (Exception e) { |
| 255 | logger.trace("cannot delete PublicIpAddress(%s) as it is unsupported", address.getId()); |
| 256 | } |
| 257 | continue IPADDRESS; |
| 258 | } |
| 259 | } |
| 260 | } |
| 261 | |
| 262 | /** |
| 263 | * Destroys dependent resources, powers off and deletes the vApp, blocking |
| 264 | * until the following state transition is complete: |
| 265 | * <p/> |
| 266 | * current -> {@code VAppStatus#OFF} -> deleted |
| 267 | * <p/> |
| 268 | * * deletes the internet service and nodes associated with the vapp. Deletes |
| 269 | * the IP address, if there are no others using it. Finally, it powers off |
| 270 | * and deletes the vapp. Note that we do not call undeploy, as terremark does |
| 271 | * not support the command. |
| 272 | * |
| 273 | * @param vAppId |
| 274 | * vApp to stop |
| 275 | */ |
| 276 | public void stop(URI id) { |
| 277 | VApp vApp = client.getVApp(id); |
| 278 | if (vApp == null) |
| 279 | return; |
| 280 | Set<PublicIpAddress> ipAddresses = deleteInternetServicesAndNodesAssociatedWithVApp(vApp); |
| 281 | deletePublicIpAddressesWithNoServicesAttached(ipAddresses); |
| 282 | if (vApp.getStatus() != Status.OFF) { |
| 283 | try { |
| 284 | powerOffAndWait(vApp); |
| 285 | } catch (IllegalStateException e) { |
| 286 | logger.warn("<< %s vApp(%s)", e.getMessage(), vApp.getName()); |
| 287 | blockOnLastTask(vApp); |
| 288 | powerOffAndWait(vApp); |
| 289 | } |
| 290 | vApp = client.getVApp(id); |
| 291 | logger.debug("<< %s vApp(%s)", vApp.getStatus(), vApp.getName()); |
| 292 | } |
| 293 | logger.debug(">> deleting vApp(%s)", vApp.getName()); |
| 294 | client.deleteVApp(id); |
| 295 | logger.debug("<< deleted vApp(%s))", vApp.getName()); |
| 296 | } |
| 297 | |
| 298 | private void powerOffAndWait(VApp vApp) { |
| 299 | logger.debug(">> powering off vApp(%s), current status: %s", vApp.getName(), vApp.getStatus()); |
| 300 | Task task = client.powerOffVApp(vApp.getHref()); |
| 301 | if (!taskTester.apply(task.getHref())) |
| 302 | throw new RuntimeException(String.format("failed to %s %s: %s", "powerOff", vApp.getName(), task)); |
| 303 | } |
| 304 | |
| 305 | void blockOnLastTask(VApp vApp) { |
| 306 | TasksList list = client.findTasksListInOrgNamed(null, null); |
| 307 | try { |
| 308 | Task lastTask = getLast(filter(list.getTasks(), new Predicate<Task>() { |
| 309 | |
| 310 | public boolean apply(Task input) { |
| 311 | return input.getStatus() == TaskStatus.QUEUED || input.getStatus() == TaskStatus.RUNNING; |
| 312 | } |
| 313 | |
| 314 | })); |
| 315 | if (!taskTester.apply(lastTask.getHref())) |
| 316 | throw new RuntimeException(String.format("failed to %s %s: %s", "powerOff", vApp.getName(), lastTask)); |
| 317 | } catch (NoSuchElementException ex) { |
| 318 | |
| 319 | } |
| 320 | } |
| 321 | |
| 322 | /** |
| 323 | * returns a set of addresses that are only visible to the private network. |
| 324 | * |
| 325 | * @returns empty set if the node is not found |
| 326 | */ |
| 327 | public Set<String> getPrivateAddresses(URI id) { |
| 328 | VApp vApp = client.getVApp(id); |
| 329 | if (vApp != null) |
| 330 | return Sets.newHashSet(vApp.getNetworkToAddresses().values()); |
| 331 | else |
| 332 | return ImmutableSet.<String> of(); |
| 333 | } |
| 334 | |
| 335 | /** |
| 336 | * returns a set of addresses that are publically visible |
| 337 | * |
| 338 | * @returns empty set if the node is not found |
| 339 | */ |
| 340 | public Set<String> getPublicAddresses(URI id) { |
| 341 | VApp vApp = client.getVApp(id); |
| 342 | if (vApp != null) { |
| 343 | Set<String> ipAddresses = Sets.newHashSet(); |
| 344 | for (InternetService service : client.getAllInternetServicesInVDC(vApp.getVDC().getHref())) { |
| 345 | for (Node node : client.getNodes(service.getId())) { |
| 346 | if (vApp.getNetworkToAddresses().containsValue(node.getIpAddress())) { |
| 347 | ipAddresses.add(service.getPublicIpAddress().getAddress()); |
| 348 | } |
| 349 | } |
| 350 | } |
| 351 | return ipAddresses; |
| 352 | } else { |
| 353 | return ImmutableSet.<String> of(); |
| 354 | } |
| 355 | } |
| 356 | |
| 357 | /** |
| 358 | * reboots the vApp, blocking until the following state transition is |
| 359 | * complete: |
| 360 | * <p/> |
| 361 | * current -> {@code VAppStatus#OFF} -> {@code VAppStatus#ON} |
| 362 | * |
| 363 | * @param vAppId |
| 364 | * vApp to reboot |
| 365 | */ |
| 366 | public void reset(URI id) { |
| 367 | VApp vApp = refreshVApp(id); |
| 368 | logger.debug(">> resetting vApp(%s)", vApp.getName()); |
| 369 | Task task = reset(vApp); |
| 370 | if (!taskTester.apply(task.getHref())) { |
| 371 | throw new RuntimeException(String.format("failed to %s %s: %s", "resetVApp", vApp.getName(), task)); |
| 372 | } |
| 373 | logger.debug("<< on vApp(%s)", vApp.getName()); |
| 374 | } |
| 375 | |
| 376 | protected void deleteVApp(VApp vApp) { |
| 377 | logger.debug(">> deleting vApp(%s)", vApp.getName()); |
| 378 | Task task = client.deleteVApp(vApp.getHref()); |
| 379 | if (task != null) |
| 380 | if (!taskTester.apply(task.getHref())) |
| 381 | throw new RuntimeException(String.format("failed to %s %s: %s", "delete", vApp.getName(), task)); |
| 382 | } |
| 383 | |
| 384 | protected VApp refreshVApp(URI id) { |
| 385 | return client.getVApp(id); |
| 386 | } |
| 387 | |
| 388 | protected Task powerOff(VApp vApp) { |
| 389 | return client.powerOffVApp(vApp.getHref()); |
| 390 | } |
| 391 | |
| 392 | protected Task reset(VApp vApp) { |
| 393 | return client.resetVApp(vApp.getHref()); |
| 394 | } |
| 395 | |
| 396 | protected Task undeploy(VApp vApp) { |
| 397 | return client.undeployVApp(vApp.getHref()); |
| 398 | } |
| 399 | } |