View Javadoc

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 }