View Javadoc

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 }