EMMA Coverage Report (generated Fri Apr 27 15:03:37 EDT 2012)
[all classes][org.jclouds.ec2.compute]

COVERAGE SUMMARY FOR SOURCE FILE [EC2ComputeService.java]

nameclass, %method, %block, %line, %
EC2ComputeService.java20%  (1/5)6%   (1/18)8%   (39/496)11%  (6/53)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class EC2ComputeService$10%   (0/1)0%   (0/2)0%   (0/9)0%   (0/2)
EC2ComputeService$1 (EC2ComputeService): void 0%   (0/1)0%   (0/6)0%   (0/1)
apply (RunningInstance): String 0%   (0/1)0%   (0/3)0%   (0/1)
     
class EC2ComputeService$20%   (0/1)0%   (0/2)0%   (0/24)0%   (0/4)
EC2ComputeService$2 (EC2ComputeService, KeyPair): void 0%   (0/1)0%   (0/9)0%   (0/1)
apply (RunningInstance): boolean 0%   (0/1)0%   (0/15)0%   (0/3)
     
class EC2ComputeService$30%   (0/1)0%   (0/2)0%   (0/82)0%   (0/9)
EC2ComputeService$3 (EC2ComputeService, String, String): void 0%   (0/1)0%   (0/12)0%   (0/1)
apply (Void): boolean 0%   (0/1)0%   (0/70)0%   (0/8)
     
class EC2ComputeService$40%   (0/1)0%   (0/1)0%   (0/19)0%   (0/1)
<static initializer> 0%   (0/1)0%   (0/19)0%   (0/1)
     
class EC2ComputeService100% (1/1)9%   (1/11)11%  (39/362)15%  (6/41)
access$000 (EC2ComputeService): Logger 0%   (0/1)0%   (0/3)0%   (0/1)
access$100 (EC2ComputeService): Logger 0%   (0/1)0%   (0/3)0%   (0/1)
access$200 (EC2ComputeService): Logger 0%   (0/1)0%   (0/3)0%   (0/1)
cleanUpIncidentalResources (String, String): void 0%   (0/1)0%   (0/14)0%   (0/3)
cleanUpIncidentalResourcesOfDeadNodes (Set): void 0%   (0/1)0%   (0/48)0%   (0/7)
deleteKeyPair (String, String): void 0%   (0/1)0%   (0/159)0%   (0/12)
deleteSecurityGroup (String, String): void 0%   (0/1)0%   (0/73)0%   (0/9)
extractIdsFromInstances (Iterable): ImmutableSet 0%   (0/1)0%   (0/8)0%   (0/1)
templateOptions (): EC2TemplateOptions 0%   (0/1)0%   (0/6)0%   (0/1)
usingKeyPairAndNotDead (KeyPair): Predicate 0%   (0/1)0%   (0/6)0%   (0/1)
EC2ComputeService (ComputeServiceContext, Map, Supplier, Supplier, Supplier, ... 100% (1/1)100% (39/39)100% (6/6)

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 */
19package org.jclouds.ec2.compute;
20 
21import static com.google.common.collect.Iterables.concat;
22import static com.google.common.collect.Iterables.filter;
23import static com.google.common.collect.Iterables.transform;
24import static org.jclouds.compute.config.ComputeServiceProperties.RESOURCENAME_DELIMITER;
25import static org.jclouds.util.Preconditions2.checkNotEmpty;
26 
27import java.util.Map;
28import java.util.Map.Entry;
29import java.util.Set;
30import java.util.concurrent.ConcurrentMap;
31import java.util.concurrent.ExecutorService;
32import java.util.concurrent.atomic.AtomicReference;
33 
34import javax.inject.Named;
35import javax.inject.Provider;
36import javax.inject.Singleton;
37 
38import org.jclouds.Constants;
39import org.jclouds.aws.util.AWSUtils;
40import org.jclouds.collect.Memoized;
41import org.jclouds.compute.ComputeServiceContext;
42import org.jclouds.compute.ImageExtension;
43import org.jclouds.compute.callables.RunScriptOnNode;
44import org.jclouds.compute.domain.Hardware;
45import org.jclouds.compute.domain.Image;
46import org.jclouds.compute.domain.NodeMetadata;
47import org.jclouds.compute.domain.TemplateBuilder;
48import org.jclouds.compute.internal.BaseComputeService;
49import org.jclouds.compute.internal.PersistNodeCredentials;
50import org.jclouds.compute.options.TemplateOptions;
51import org.jclouds.compute.reference.ComputeServiceConstants.Timeouts;
52import org.jclouds.compute.strategy.CreateNodesInGroupThenAddToSet;
53import org.jclouds.compute.strategy.DestroyNodeStrategy;
54import org.jclouds.compute.strategy.GetNodeMetadataStrategy;
55import org.jclouds.compute.strategy.InitializeRunScriptOnNodeOrPlaceInBadMap;
56import org.jclouds.compute.strategy.ListNodesStrategy;
57import org.jclouds.compute.strategy.RebootNodeStrategy;
58import org.jclouds.compute.strategy.ResumeNodeStrategy;
59import org.jclouds.compute.strategy.SuspendNodeStrategy;
60import org.jclouds.domain.Credentials;
61import org.jclouds.domain.Location;
62import org.jclouds.ec2.EC2Client;
63import org.jclouds.ec2.compute.domain.RegionAndName;
64import org.jclouds.ec2.compute.domain.RegionNameAndIngressRules;
65import org.jclouds.ec2.compute.options.EC2TemplateOptions;
66import org.jclouds.ec2.domain.KeyPair;
67import org.jclouds.ec2.domain.RunningInstance;
68import org.jclouds.predicates.Retryables;
69import org.jclouds.scriptbuilder.functions.InitAdminAccess;
70 
71import com.google.common.annotations.VisibleForTesting;
72import com.google.common.base.Function;
73import com.google.common.base.Optional;
74import com.google.common.base.Predicate;
75import com.google.common.base.Supplier;
76import com.google.common.cache.LoadingCache;
77import com.google.common.collect.ImmutableMultimap;
78import com.google.common.collect.ImmutableMultimap.Builder;
79import com.google.common.collect.ImmutableSet;
80import com.google.inject.Inject;
81 
82/**
83 * @author Adrian Cole
84 */
85@Singleton
86public class EC2ComputeService extends BaseComputeService {
87   private final EC2Client ec2Client;
88   private final ConcurrentMap<RegionAndName, KeyPair> credentialsMap;
89   private final LoadingCache<RegionAndName, String> securityGroupMap;
90 
91   @Inject
92   protected EC2ComputeService(ComputeServiceContext context, Map<String, Credentials> credentialStore,
93         @Memoized Supplier<Set<? extends Image>> images, @Memoized Supplier<Set<? extends Hardware>> sizes,
94         @Memoized Supplier<Set<? extends Location>> locations, ListNodesStrategy listNodesStrategy,
95         GetNodeMetadataStrategy getNodeMetadataStrategy, CreateNodesInGroupThenAddToSet runNodesAndAddToSetStrategy,
96         RebootNodeStrategy rebootNodeStrategy, DestroyNodeStrategy destroyNodeStrategy,
97         ResumeNodeStrategy startNodeStrategy, SuspendNodeStrategy stopNodeStrategy,
98         Provider<TemplateBuilder> templateBuilderProvider, Provider<TemplateOptions> templateOptionsProvider,
99         @Named("NODE_RUNNING") Predicate<AtomicReference<NodeMetadata>> nodeRunning,
100         @Named("NODE_TERMINATED") Predicate<AtomicReference<NodeMetadata>> nodeTerminated,
101         @Named("NODE_SUSPENDED") Predicate<AtomicReference<NodeMetadata>> nodeSuspended,
102         InitializeRunScriptOnNodeOrPlaceInBadMap.Factory initScriptRunnerFactory,
103         RunScriptOnNode.Factory runScriptOnNodeFactory, InitAdminAccess initAdminAccess,
104         PersistNodeCredentials persistNodeCredentials, Timeouts timeouts,
105         @Named(Constants.PROPERTY_USER_THREADS) ExecutorService executor, EC2Client ec2Client,
106         ConcurrentMap<RegionAndName, KeyPair> credentialsMap, @Named("SECURITY") LoadingCache<RegionAndName, String> securityGroupMap,
107         Optional<ImageExtension> imageExtension) {
108      super(context, credentialStore, images, sizes, locations, listNodesStrategy, getNodeMetadataStrategy,
109            runNodesAndAddToSetStrategy, rebootNodeStrategy, destroyNodeStrategy, startNodeStrategy, stopNodeStrategy,
110            templateBuilderProvider, templateOptionsProvider, nodeRunning, nodeTerminated, nodeSuspended,
111            initScriptRunnerFactory, initAdminAccess, runScriptOnNodeFactory, persistNodeCredentials, timeouts,
112            executor, imageExtension);
113      this.ec2Client = ec2Client;
114      this.credentialsMap = credentialsMap;
115      this.securityGroupMap = securityGroupMap;
116   }
117 
118   @Inject(optional = true)
119   @Named(RESOURCENAME_DELIMITER)
120   char delimiter = '#';
121 
122   /**
123    * @throws IllegalStateException If the security group was in use
124    */
125   @VisibleForTesting
126   void deleteSecurityGroup(String region, String group) {
127      checkNotEmpty(region, "region");
128      checkNotEmpty(group, "group");
129      String groupName = String.format("jclouds#%s#%s", group, region).replace('#', delimiter);
130      if (ec2Client.getSecurityGroupServices().describeSecurityGroupsInRegion(region, groupName).size() > 0) {
131         logger.debug(">> deleting securityGroup(%s)", groupName);
132         ec2Client.getSecurityGroupServices().deleteSecurityGroupInRegion(region, groupName);
133         // TODO: test this clear happens
134         securityGroupMap.invalidate(new RegionNameAndIngressRules(region, groupName, null, false));
135         logger.debug("<< deleted securityGroup(%s)", groupName);
136      }
137   }
138 
139   @VisibleForTesting
140   void deleteKeyPair(String region, String group) {
141      for (KeyPair keyPair : ec2Client.getKeyPairServices().describeKeyPairsInRegion(region)) {
142         if (
143         // when the keypair is unique per group
144         keyPair.getKeyName().equals("jclouds"+ delimiter + group)
145               || keyPair.getKeyName().matches(String.format("jclouds#%s#%s", group, "[0-9a-f]+").replace('#', delimiter))
146               // old keypair pattern too verbose as it has an unnecessary
147               // region qualifier
148               || keyPair.getKeyName().matches(String.format("jclouds#%s#%s#%s", group, region, "[0-9a-f]+").replace('#', delimiter))) {
149            Set<String> instancesUsingKeyPair = extractIdsFromInstances(filter(concat(ec2Client.getInstanceServices()
150                  .describeInstancesInRegion(region)), usingKeyPairAndNotDead(keyPair)));
151            if (instancesUsingKeyPair.size() > 0) {
152               logger.debug("<< inUse keyPair(%s), by (%s)", keyPair.getKeyName(), instancesUsingKeyPair);
153            } else {
154               logger.debug(">> deleting keyPair(%s)", keyPair.getKeyName());
155               ec2Client.getKeyPairServices().deleteKeyPairInRegion(region, keyPair.getKeyName());
156               // TODO: test this clear happens
157               credentialsMap.remove(new RegionAndName(region, keyPair.getKeyName()));
158               credentialsMap.remove(new RegionAndName(region, group));
159               logger.debug("<< deleted keyPair(%s)", keyPair.getKeyName());
160            }
161         }
162      }
163   }
164 
165   protected ImmutableSet<String> extractIdsFromInstances(Iterable<? extends RunningInstance> deadOnes) {
166      return ImmutableSet.copyOf(transform(deadOnes, new Function<RunningInstance, String>() {
167 
168         @Override
169         public String apply(RunningInstance input) {
170            return input.getId();
171         }
172 
173      }));
174   }
175 
176   protected Predicate<RunningInstance> usingKeyPairAndNotDead(final KeyPair keyPair) {
177      return new Predicate<RunningInstance>() {
178 
179         @Override
180         public boolean apply(RunningInstance input) {
181            switch (input.getInstanceState()) {
182            case TERMINATED:
183            case SHUTTING_DOWN:
184               return false;
185            }
186            return keyPair.getKeyName().equals(input.getKeyName());
187         }
188 
189      };
190   }
191 
192   /**
193    * Cleans implicit keypairs and security groups.
194    */
195   @Override
196   protected void cleanUpIncidentalResourcesOfDeadNodes(Set<? extends NodeMetadata> deadNodes) {
197      Builder<String, String> regionGroups = ImmutableMultimap.builder();
198      for (NodeMetadata nodeMetadata : deadNodes) {
199         if (nodeMetadata.getGroup() != null)
200            regionGroups.put(AWSUtils.parseHandle(nodeMetadata.getId())[0], nodeMetadata.getGroup());
201         }
202      for (Entry<String, String> regionGroup : regionGroups.build().entries()) {
203         cleanUpIncidentalResources(regionGroup.getKey(), regionGroup.getValue());
204      }
205   }
206 
207   protected void cleanUpIncidentalResources(final String region, final String group){
208      // For issue #445, tries to delete security groups first: ec2 throws exception if in use, but
209      // deleting a key pair does not.
210      // This is "belt-and-braces" because deleteKeyPair also does extractIdsFromInstances & usingKeyPairAndNotDead
211      // for us to check if any instances are using the key-pair before we delete it. 
212      // There is (probably?) still a race if someone is creating instances at the same time as deleting them: 
213      // we may delete the key-pair just when the node-being-created was about to rely on the incidental 
214      // resources existing.
215 
216      // Also in #445, in aws-ec2 the deleteSecurityGroup sometimes fails after terminating the final VM using a 
217      // given security group, if called very soon after the VM's state reports terminated. Emprically, it seems that
218      // waiting a small time (e.g. enabling logging or debugging!) then the tests pass. We therefore retry.
219      final int maxAttempts = 3;
220      Retryables.retryNumTimes(new Predicate<Void>() {
221            @Override
222            public boolean apply(Void input) {
223               try {
224                  logger.debug(">> deleting incidentalResources(%s @ %s)", region, group);
225                  deleteSecurityGroup(region, group);
226                  deleteKeyPair(region, group); // not executed if securityGroup was in use
227                  logger.debug("<< deleted incidentalResources(%s @ %s)", region, group);
228                  return true;
229               } catch (IllegalStateException e) {
230                  logger.debug("<< inUse incidentalResources(%s @ %s)", region, group);
231                  return false;
232               }
233            }
234         }, (Void)null, maxAttempts);
235   }
236 
237   /**
238    * returns template options, except of type {@link EC2TemplateOptions}.
239    */
240   @Override
241   public EC2TemplateOptions templateOptions() {
242      return EC2TemplateOptions.class.cast(super.templateOptions());
243   }
244 
245}

[all classes][org.jclouds.ec2.compute]
EMMA 2.0.5312 (C) Vladimir Roubtsov