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.functions; |
20 | |
21 | import static com.google.common.base.Preconditions.checkNotNull; |
22 | import static com.google.common.base.Predicates.not; |
23 | import static com.google.common.collect.Iterables.filter; |
24 | import static org.jclouds.compute.config.ComputeServiceProperties.RESOURCENAME_DELIMITER; |
25 | |
26 | import java.util.List; |
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.Resource; |
33 | import javax.inject.Named; |
34 | import javax.inject.Singleton; |
35 | |
36 | import org.jclouds.collect.Memoized; |
37 | import org.jclouds.compute.domain.Hardware; |
38 | import org.jclouds.compute.domain.HardwareBuilder; |
39 | import org.jclouds.compute.domain.Image; |
40 | import org.jclouds.compute.domain.NodeMetadata; |
41 | import org.jclouds.compute.domain.NodeMetadataBuilder; |
42 | import org.jclouds.compute.domain.NodeState; |
43 | import org.jclouds.compute.domain.Volume; |
44 | import org.jclouds.compute.domain.internal.VolumeImpl; |
45 | import org.jclouds.domain.Credentials; |
46 | import org.jclouds.domain.Location; |
47 | import org.jclouds.domain.LoginCredentials; |
48 | import org.jclouds.ec2.compute.domain.RegionAndName; |
49 | import org.jclouds.ec2.domain.BlockDevice; |
50 | import org.jclouds.ec2.domain.InstanceState; |
51 | import org.jclouds.ec2.domain.RootDeviceType; |
52 | import org.jclouds.ec2.domain.RunningInstance; |
53 | import org.jclouds.logging.Logger; |
54 | import org.jclouds.util.InetAddresses2.IsPrivateIPAddress; |
55 | |
56 | import com.google.common.annotations.VisibleForTesting; |
57 | import com.google.common.base.Function; |
58 | import com.google.common.base.Predicate; |
59 | import com.google.common.base.Strings; |
60 | import com.google.common.base.Supplier; |
61 | import com.google.common.cache.CacheLoader; |
62 | import com.google.common.cache.LoadingCache; |
63 | import com.google.common.collect.ImmutableSet; |
64 | import com.google.common.collect.Iterables; |
65 | import com.google.common.collect.Lists; |
66 | import com.google.common.collect.Sets; |
67 | import com.google.common.collect.ImmutableSet.Builder; |
68 | import com.google.common.util.concurrent.UncheckedExecutionException; |
69 | import com.google.inject.Inject; |
70 | |
71 | /** |
72 | * @author Adrian Cole |
73 | */ |
74 | @Singleton |
75 | public class RunningInstanceToNodeMetadata implements Function<RunningInstance, NodeMetadata> { |
76 | |
77 | @Resource |
78 | protected Logger logger = Logger.NULL; |
79 | |
80 | protected final Supplier<Set<? extends Location>> locations; |
81 | protected final Supplier<Set<? extends Hardware>> hardware; |
82 | protected final Supplier<LoadingCache<RegionAndName, ? extends Image>> imageMap; |
83 | protected final Map<String, Credentials> credentialStore; |
84 | protected final Map<InstanceState, NodeState> instanceToNodeState; |
85 | |
86 | @Inject |
87 | protected RunningInstanceToNodeMetadata(Map<InstanceState, NodeState> instanceToNodeState, |
88 | Map<String, Credentials> credentialStore, Supplier<LoadingCache<RegionAndName, ? extends Image>> imageMap, |
89 | @Memoized Supplier<Set<? extends Location>> locations, @Memoized Supplier<Set<? extends Hardware>> hardware) { |
90 | this.locations = checkNotNull(locations, "locations"); |
91 | this.hardware = checkNotNull(hardware, "hardware"); |
92 | this.imageMap = checkNotNull(imageMap, "imageMap"); |
93 | this.instanceToNodeState = checkNotNull(instanceToNodeState, "instanceToNodeState"); |
94 | this.credentialStore = checkNotNull(credentialStore, "credentialStore"); |
95 | } |
96 | |
97 | @Override |
98 | public NodeMetadata apply(RunningInstance instance) { |
99 | if (instance == null || instance.getId() == null) |
100 | return null; |
101 | NodeMetadataBuilder builder = new NodeMetadataBuilder(); |
102 | builder = buildInstance(instance, builder); |
103 | return builder.build(); |
104 | } |
105 | |
106 | protected NodeMetadataBuilder buildInstance(final RunningInstance instance, NodeMetadataBuilder builder) { |
107 | builder.providerId(instance.getId()); |
108 | builder.id(instance.getRegion() + "/" + instance.getId()); |
109 | String group = getGroupForInstance(instance); |
110 | builder.group(group); |
111 | // standard convention from aws-ec2, which might not be re-used outside. |
112 | if (instance.getPrivateDnsName() != null) |
113 | builder.hostname(instance.getPrivateDnsName().replaceAll("\\..*", "")); |
114 | addCredentialsForInstance(builder, instance); |
115 | builder.state(instanceToNodeState.get(instance.getInstanceState())); |
116 | |
117 | // collect all ip addresses into one bundle in case the api mistakenly put a private address |
118 | // into the public address field |
119 | Builder<String> addressesBuilder = ImmutableSet.builder(); |
120 | if (Strings.emptyToNull(instance.getIpAddress()) != null) |
121 | addressesBuilder.add(instance.getIpAddress()); |
122 | if (Strings.emptyToNull(instance.getPrivateIpAddress()) != null) |
123 | addressesBuilder.add(instance.getPrivateIpAddress()); |
124 | |
125 | Set<String> addresses = addressesBuilder.build(); |
126 | |
127 | builder.publicAddresses(filter(addresses, not(IsPrivateIPAddress.INSTANCE))); |
128 | builder.privateAddresses(filter(addresses, IsPrivateIPAddress.INSTANCE)); |
129 | builder.hardware(parseHardware(instance)); |
130 | Location location = getLocationForAvailabilityZoneOrRegion(instance); |
131 | builder.location(location); |
132 | builder.imageId(instance.getRegion() + "/" + instance.getImageId()); |
133 | |
134 | // extract the operating system from the image |
135 | RegionAndName regionAndName = new RegionAndName(instance.getRegion(), instance.getImageId()); |
136 | try { |
137 | Image image = imageMap.get().getUnchecked(regionAndName); |
138 | if (image != null) |
139 | builder.operatingSystem(image.getOperatingSystem()); |
140 | } catch (CacheLoader.InvalidCacheLoadException e) { |
141 | logger.debug("image not found for %s: %s", regionAndName, e); |
142 | } catch (UncheckedExecutionException e) { |
143 | logger.debug("error getting image for %s: %s", regionAndName, e); |
144 | } |
145 | return builder; |
146 | } |
147 | |
148 | protected void addCredentialsForInstance(NodeMetadataBuilder builder, RunningInstance instance) { |
149 | builder.credentials(LoginCredentials.fromCredentials(credentialStore.get("node#" + instance.getRegion() + "/" |
150 | + instance.getId()))); |
151 | } |
152 | |
153 | protected Hardware parseHardware(final RunningInstance instance) { |
154 | Hardware hardware = getHardwareForInstance(instance); |
155 | |
156 | if (hardware != null) { |
157 | hardware = HardwareBuilder.fromHardware(hardware).volumes(addEBS(instance, hardware.getVolumes())).build(); |
158 | } |
159 | return hardware; |
160 | } |
161 | |
162 | @VisibleForTesting |
163 | static List<Volume> addEBS(final RunningInstance instance, Iterable<? extends Volume> volumes) { |
164 | Iterable<Volume> ebsVolumes = Iterables.transform(instance.getEbsBlockDevices().entrySet(), |
165 | new Function<Entry<String, BlockDevice>, Volume>() { |
166 | |
167 | @Override |
168 | public Volume apply(Entry<String, BlockDevice> from) { |
169 | return new VolumeImpl(from.getValue().getVolumeId(), Volume.Type.SAN, null, from.getKey(), |
170 | instance.getRootDeviceName() != null |
171 | && instance.getRootDeviceName().equals(from.getKey()), true); |
172 | } |
173 | }); |
174 | |
175 | if (instance.getRootDeviceType() == RootDeviceType.EBS) { |
176 | volumes = Iterables.filter(volumes, new Predicate<Volume>() { |
177 | |
178 | @Override |
179 | public boolean apply(Volume input) { |
180 | return !input.isBootDevice(); |
181 | } |
182 | |
183 | }); |
184 | |
185 | } |
186 | return Lists.newArrayList(Iterables.concat(volumes, ebsVolumes)); |
187 | |
188 | } |
189 | |
190 | @VisibleForTesting |
191 | String getGroupForInstance(final RunningInstance instance) { |
192 | String group = parseGroupFrom(instance, instance.getGroupIds()); |
193 | if(group == null && instance.getKeyName() != null) { |
194 | // when not using a generated security group, e.g. in VPC, try from key: |
195 | group = parseGroupFrom(instance, Sets.newHashSet(instance.getKeyName())); |
196 | } |
197 | return group; |
198 | } |
199 | |
200 | @Inject(optional = true) |
201 | @Named(RESOURCENAME_DELIMITER) |
202 | char delimiter = '#'; |
203 | |
204 | private String parseGroupFrom(final RunningInstance instance, final Set<String> data) { |
205 | String group = null; |
206 | try { |
207 | group = Iterables.getOnlyElement(Iterables.filter(data, new Predicate<String>() { |
208 | |
209 | @Override |
210 | public boolean apply(String input) { |
211 | return input.startsWith("jclouds" + delimiter) && input.contains(delimiter + instance.getRegion()); |
212 | } |
213 | })).split(delimiter + "")[1]; |
214 | } catch (NoSuchElementException e) { |
215 | logger.debug("no group parsed from %s's data: %s", instance.getId(), data); |
216 | } catch (IllegalArgumentException e) { |
217 | logger.debug("too many groups match %s%s; %s's data: %s", "jclouds", delimiter, instance.getId(), data); |
218 | } |
219 | return group; |
220 | } |
221 | |
222 | @VisibleForTesting |
223 | Hardware getHardwareForInstance(final RunningInstance instance) { |
224 | try { |
225 | return Iterables.find(hardware.get(), new Predicate<Hardware>() { |
226 | |
227 | @Override |
228 | public boolean apply(Hardware input) { |
229 | return input.getId().equals(instance.getInstanceType()); |
230 | } |
231 | |
232 | }); |
233 | } catch (NoSuchElementException e) { |
234 | logger.debug("couldn't match instance type %s in: %s", instance.getInstanceType(), hardware.get()); |
235 | return null; |
236 | } |
237 | } |
238 | |
239 | private Location getLocationForAvailabilityZoneOrRegion(final RunningInstance instance) { |
240 | Location location = findLocationWithId(instance.getAvailabilityZone()); |
241 | if (location == null) |
242 | location = findLocationWithId(instance.getRegion()); |
243 | return location; |
244 | } |
245 | |
246 | private Location findLocationWithId(final String locationId) { |
247 | if (locationId == null) |
248 | return null; |
249 | try { |
250 | Location location = Iterables.find(locations.get(), new Predicate<Location>() { |
251 | |
252 | @Override |
253 | public boolean apply(Location input) { |
254 | return input.getId().equals(locationId); |
255 | } |
256 | |
257 | }); |
258 | return location; |
259 | |
260 | } catch (NoSuchElementException e) { |
261 | logger.debug("couldn't match instance location %s in: %s", locationId, locations.get()); |
262 | return null; |
263 | } |
264 | } |
265 | |
266 | } |