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.ec2.compute.strategy; |
20 | |
21 | import static com.google.common.base.Preconditions.checkNotNull; |
22 | import static com.google.common.collect.Iterables.all; |
23 | import static com.google.common.collect.Iterables.transform; |
24 | import static org.jclouds.ec2.compute.util.EC2ComputeUtils.getZoneFromLocationOrNull; |
25 | |
26 | import java.util.Map; |
27 | import java.util.Set; |
28 | import java.util.concurrent.Future; |
29 | import java.util.concurrent.atomic.AtomicReference; |
30 | |
31 | import javax.annotation.Resource; |
32 | import javax.inject.Inject; |
33 | import javax.inject.Named; |
34 | import javax.inject.Provider; |
35 | import javax.inject.Singleton; |
36 | |
37 | import org.jclouds.aws.util.AWSUtils; |
38 | import org.jclouds.compute.config.CustomizationResponse; |
39 | import org.jclouds.compute.domain.NodeMetadata; |
40 | import org.jclouds.compute.domain.Template; |
41 | import org.jclouds.compute.domain.TemplateBuilder; |
42 | import org.jclouds.compute.reference.ComputeServiceConstants; |
43 | import org.jclouds.compute.strategy.CreateNodesInGroupThenAddToSet; |
44 | import org.jclouds.compute.util.ComputeUtils; |
45 | import org.jclouds.domain.Credentials; |
46 | import org.jclouds.ec2.EC2Client; |
47 | import org.jclouds.ec2.compute.domain.RegionAndName; |
48 | import org.jclouds.ec2.compute.predicates.InstancePresent; |
49 | import org.jclouds.ec2.domain.RunningInstance; |
50 | import org.jclouds.ec2.options.RunInstancesOptions; |
51 | import org.jclouds.ec2.reference.EC2Constants; |
52 | import org.jclouds.logging.Logger; |
53 | |
54 | import com.google.common.annotations.VisibleForTesting; |
55 | import com.google.common.base.Function; |
56 | import com.google.common.base.Joiner; |
57 | import com.google.common.base.Predicate; |
58 | import com.google.common.cache.LoadingCache; |
59 | import com.google.common.collect.ImmutableSet; |
60 | import com.google.common.collect.ImmutableSet.Builder; |
61 | import com.google.common.collect.Iterables; |
62 | import com.google.common.collect.Multimap; |
63 | |
64 | /** |
65 | * creates futures that correlate to |
66 | * |
67 | * @author Adrian Cole |
68 | */ |
69 | @Singleton |
70 | public class EC2CreateNodesInGroupThenAddToSet implements CreateNodesInGroupThenAddToSet { |
71 | |
72 | @Resource |
73 | @Named(ComputeServiceConstants.COMPUTE_LOGGER) |
74 | protected Logger logger = Logger.NULL; |
75 | |
76 | @Inject |
77 | @Named(EC2Constants.PROPERTY_EC2_AUTO_ALLOCATE_ELASTIC_IPS) |
78 | @VisibleForTesting |
79 | boolean autoAllocateElasticIps = false; |
80 | |
81 | @VisibleForTesting |
82 | final EC2Client client; |
83 | @VisibleForTesting |
84 | final Predicate<AtomicReference<NodeMetadata>> nodeRunning; |
85 | @VisibleForTesting |
86 | final LoadingCache<RegionAndName, String> elasticIpCache; |
87 | @VisibleForTesting |
88 | final CreateKeyPairAndSecurityGroupsAsNeededAndReturnRunOptions createKeyPairAndSecurityGroupsAsNeededAndReturncustomize; |
89 | @VisibleForTesting |
90 | final Function<RunningInstance, NodeMetadata> runningInstanceToNodeMetadata; |
91 | @VisibleForTesting |
92 | final ComputeUtils utils; |
93 | final InstancePresent instancePresent; |
94 | final LoadingCache<RunningInstance, Credentials> instanceToCredentials; |
95 | final Map<String, Credentials> credentialStore; |
96 | final Provider<TemplateBuilder> templateBuilderProvider; |
97 | |
98 | |
99 | |
100 | @Inject |
101 | protected EC2CreateNodesInGroupThenAddToSet( |
102 | EC2Client client, |
103 | @Named("ELASTICIP") |
104 | LoadingCache<RegionAndName, String> elasticIpCache, |
105 | @Named("NODE_RUNNING") Predicate<AtomicReference<NodeMetadata>> nodeRunning, |
106 | Provider<TemplateBuilder> templateBuilderProvider, |
107 | CreateKeyPairAndSecurityGroupsAsNeededAndReturnRunOptions createKeyPairAndSecurityGroupsAsNeededAndReturncustomize, |
108 | InstancePresent instancePresent, Function<RunningInstance, NodeMetadata> runningInstanceToNodeMetadata, |
109 | LoadingCache<RunningInstance, Credentials> instanceToCredentials, Map<String, Credentials> credentialStore, |
110 | ComputeUtils utils) { |
111 | this.client = checkNotNull(client, "client"); |
112 | this.elasticIpCache = checkNotNull(elasticIpCache, "elasticIpCache"); |
113 | this.nodeRunning = checkNotNull(nodeRunning, "nodeRunning"); |
114 | this.templateBuilderProvider = checkNotNull(templateBuilderProvider, "templateBuilderProvider"); |
115 | this.instancePresent = checkNotNull(instancePresent, "instancePresent"); |
116 | this.createKeyPairAndSecurityGroupsAsNeededAndReturncustomize = checkNotNull( |
117 | createKeyPairAndSecurityGroupsAsNeededAndReturncustomize, |
118 | "createKeyPairAndSecurityGroupsAsNeededAndReturncustomize"); |
119 | this.runningInstanceToNodeMetadata = checkNotNull(runningInstanceToNodeMetadata, "runningInstanceToNodeMetadata"); |
120 | this.instanceToCredentials = checkNotNull(instanceToCredentials, "instanceToCredentials"); |
121 | this.credentialStore = checkNotNull(credentialStore, "credentialStore"); |
122 | this.utils = checkNotNull(utils, "utils"); |
123 | } |
124 | |
125 | public static Function<RunningInstance, RegionAndName> instanceToRegionAndName = new Function<RunningInstance, RegionAndName>() { |
126 | @Override |
127 | public RegionAndName apply(RunningInstance from) { |
128 | return new RegionAndName(from.getRegion(), from.getId()); |
129 | } |
130 | }; |
131 | |
132 | @Override |
133 | public Map<?, Future<Void>> execute(String group, int count, Template template, Set<NodeMetadata> goodNodes, |
134 | Map<NodeMetadata, Exception> badNodes, Multimap<NodeMetadata, CustomizationResponse> customizationResponses) { |
135 | // ensure we don't mutate the input template |
136 | template = templateBuilderProvider.get().fromTemplate(template).build(); |
137 | |
138 | Iterable<String> ips = allocateElasticIpsInRegion(count, template); |
139 | |
140 | Iterable<? extends RunningInstance> started = createKeyPairAndSecurityGroupsAsNeededThenRunInstances(group, |
141 | count, template); |
142 | |
143 | Iterable<RegionAndName> ids = transform(started, instanceToRegionAndName); |
144 | |
145 | String idsString = Joiner.on(',').join(ids); |
146 | if (Iterables.size(ids) > 0) { |
147 | logger.debug("<< started instances(%s)", idsString); |
148 | all(ids, instancePresent); |
149 | logger.debug("<< present instances(%s)", idsString); |
150 | populateCredentials(started); |
151 | } |
152 | |
153 | assignElasticIpsToInstances(ips, started); |
154 | |
155 | return utils.customizeNodesAndAddToGoodMapOrPutExceptionIntoBadMap(template.getOptions(), transform(started, |
156 | runningInstanceToNodeMetadata), goodNodes, badNodes, customizationResponses); |
157 | } |
158 | |
159 | protected void populateCredentials(Iterable<? extends RunningInstance> started) { |
160 | Credentials credentials = null; |
161 | for (RunningInstance instance : started) { |
162 | credentials = instanceToCredentials.apply(instance); |
163 | if (credentials != null) |
164 | break; |
165 | } |
166 | if (credentials != null) |
167 | for (RunningInstance instance : started) |
168 | credentialStore.put("node#" + instance.getRegion() + "/" + instance.getId(), credentials); |
169 | } |
170 | |
171 | protected Iterable<String> allocateElasticIpsInRegion(int count, Template template) { |
172 | |
173 | Builder<String> ips = ImmutableSet.builder(); |
174 | if (!autoAllocateElasticIps) |
175 | return ips.build(); |
176 | |
177 | String region = AWSUtils.getRegionFromLocationOrNull(template.getLocation()); |
178 | logger.debug("<< allocating %d elastic IPs for nodes in region (%s)", count, region); |
179 | |
180 | for (int i = 0; i < count; ++i) { |
181 | ips.add(client.getElasticIPAddressServices().allocateAddressInRegion(region)); |
182 | } |
183 | return ips.build(); |
184 | } |
185 | |
186 | protected void assignElasticIpsToInstances(Iterable<String> ips, Iterable<? extends RunningInstance> startedInstances) { |
187 | |
188 | if (!autoAllocateElasticIps) |
189 | return; |
190 | |
191 | // TODO parallel |
192 | int i = 0; |
193 | for (RunningInstance startedInstance : startedInstances) { |
194 | String ip = Iterables.get(ips, i); |
195 | String region = startedInstance.getRegion(); |
196 | String id = startedInstance.getId(); |
197 | RegionAndName coordinates = new RegionAndName(region, id); |
198 | |
199 | // block until instance is running |
200 | logger.debug(">> awaiting status running instance(%s)", coordinates); |
201 | AtomicReference<NodeMetadata> node = new AtomicReference<NodeMetadata>(runningInstanceToNodeMetadata.apply(startedInstance)); |
202 | nodeRunning.apply(node); |
203 | logger.trace("<< running instance(%s)", coordinates); |
204 | logger.debug(">> associating elastic IP %s to instance %s", ip, coordinates); |
205 | client.getElasticIPAddressServices().associateAddressInRegion(region, ip, id); |
206 | logger.trace("<< associated elastic IP %s to instance %s", ip, coordinates); |
207 | // add mapping of instance to ip into the cache |
208 | elasticIpCache.put(coordinates, ip); |
209 | } |
210 | } |
211 | |
212 | protected Iterable<? extends RunningInstance> createKeyPairAndSecurityGroupsAsNeededThenRunInstances(String group, |
213 | int count, Template template) { |
214 | String region = AWSUtils.getRegionFromLocationOrNull(template.getLocation()); |
215 | String zone = getZoneFromLocationOrNull(template.getLocation()); |
216 | RunInstancesOptions instanceOptions = createKeyPairAndSecurityGroupsAsNeededAndReturncustomize.execute(region, |
217 | group, template); |
218 | return createNodesInRegionAndZone(region, zone, group, count, template, instanceOptions); |
219 | } |
220 | |
221 | protected Iterable<? extends RunningInstance> createNodesInRegionAndZone(String region, String zone, String group, |
222 | int count, Template template, RunInstancesOptions instanceOptions) { |
223 | int countStarted = 0; |
224 | int tries = 0; |
225 | Iterable<? extends RunningInstance> started = ImmutableSet.<RunningInstance> of(); |
226 | |
227 | while (countStarted < count && tries++ < count) { |
228 | if (logger.isDebugEnabled()) |
229 | logger.debug(">> running %d instance region(%s) zone(%s) ami(%s) params(%s)", count - countStarted, region, |
230 | zone, template.getImage().getProviderId(), instanceOptions.buildFormParameters()); |
231 | |
232 | started = Iterables.concat(started, client.getInstanceServices().runInstancesInRegion(region, zone, |
233 | template.getImage().getProviderId(), 1, count - countStarted, instanceOptions)); |
234 | |
235 | countStarted = Iterables.size(started); |
236 | if (countStarted < count) |
237 | logger.debug(">> not enough instances (%d/%d) started, attempting again", countStarted, count); |
238 | } |
239 | return started; |
240 | } |
241 | |
242 | } |