blob: 25a7e41b44253e855d3ff15d1909c973bf712f6f [file] [log] [blame]
Jian Li091d8d22018-02-20 10:42:06 +09001/*
2 * Copyright 2018-present Open Networking Foundation
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package org.onosproject.openstacknetworking.util;
17
Daniel Park2ff66b42018-08-01 11:52:45 +090018import com.fasterxml.jackson.core.JsonParseException;
19import com.fasterxml.jackson.core.JsonProcessingException;
20import com.fasterxml.jackson.databind.JsonMappingException;
Jian Li091d8d22018-02-20 10:42:06 +090021import com.fasterxml.jackson.databind.JsonNode;
22import com.fasterxml.jackson.databind.ObjectMapper;
Jian Lieb9f77d2018-02-20 11:25:45 +090023import com.fasterxml.jackson.databind.node.ObjectNode;
Daniel Parka73c2362018-09-17 17:43:25 +090024import com.google.common.base.Charsets;
Jian Li24ec59f2018-05-23 19:01:25 +090025import com.google.common.base.Strings;
Daniel Parka73c2362018-09-17 17:43:25 +090026import com.google.common.collect.Lists;
Jian Li63430202018-08-30 16:24:09 +090027import org.apache.commons.codec.binary.Hex;
28import org.apache.http.HttpException;
29import org.apache.http.HttpRequest;
30import org.apache.http.HttpResponse;
31import org.apache.http.impl.io.DefaultHttpRequestParser;
32import org.apache.http.impl.io.DefaultHttpRequestWriter;
33import org.apache.http.impl.io.DefaultHttpResponseParser;
34import org.apache.http.impl.io.DefaultHttpResponseWriter;
35import org.apache.http.impl.io.HttpTransportMetricsImpl;
36import org.apache.http.impl.io.SessionInputBufferImpl;
37import org.apache.http.impl.io.SessionOutputBufferImpl;
38import org.apache.http.io.HttpMessageWriter;
Daniel Parka73c2362018-09-17 17:43:25 +090039import org.apache.sshd.client.SshClient;
40import org.apache.sshd.client.channel.ClientChannel;
41import org.apache.sshd.client.channel.ClientChannelEvent;
42import org.apache.sshd.client.future.OpenFuture;
43import org.apache.sshd.client.session.ClientSession;
44import org.apache.sshd.common.util.io.NoCloseInputStream;
Daniel Park4fa1f5e2018-10-17 12:41:52 +090045import org.onlab.packet.ARP;
46import org.onlab.packet.Ethernet;
47import org.onlab.packet.Ip4Address;
Daniel Parka73c2362018-09-17 17:43:25 +090048import org.onlab.packet.IpAddress;
Daniel Park4fa1f5e2018-10-17 12:41:52 +090049import org.onlab.packet.MacAddress;
50import org.onlab.packet.VlanId;
Jian Li7f70bb72018-07-06 23:35:30 +090051import org.onosproject.cfg.ConfigProperty;
Jian Li1064e4f2018-05-29 16:16:53 +090052import org.onosproject.net.DeviceId;
Daniel Park95f73312018-07-31 15:48:34 +090053import org.onosproject.net.device.DeviceService;
Daniel Park4fa1f5e2018-10-17 12:41:52 +090054import org.onosproject.net.flow.DefaultTrafficTreatment;
55import org.onosproject.net.flow.TrafficTreatment;
56import org.onosproject.net.packet.DefaultOutboundPacket;
57import org.onosproject.net.packet.PacketService;
Daniel Park7e8c4d82018-08-13 23:47:49 +090058import org.onosproject.openstacknetworking.api.Constants.VnicType;
Daniel Park4fa1f5e2018-10-17 12:41:52 +090059import org.onosproject.openstacknetworking.api.ExternalPeerRouter;
Jian Lia171a432018-06-11 11:52:11 +090060import org.onosproject.openstacknetworking.api.InstancePort;
Jian Li24ec59f2018-05-23 19:01:25 +090061import org.onosproject.openstacknetworking.api.OpenstackNetworkService;
Jian Li7f70bb72018-07-06 23:35:30 +090062import org.onosproject.openstacknetworking.api.OpenstackRouterAdminService;
Jian Liec5c32b2018-07-13 14:28:58 +090063import org.onosproject.openstacknetworking.impl.DefaultInstancePort;
Jian Li51b844c2018-05-31 10:59:03 +090064import org.onosproject.openstacknode.api.OpenstackAuth;
65import org.onosproject.openstacknode.api.OpenstackAuth.Perspective;
Jian Li1064e4f2018-05-29 16:16:53 +090066import org.onosproject.openstacknode.api.OpenstackNode;
Daniel Parka73c2362018-09-17 17:43:25 +090067import org.onosproject.openstacknode.api.OpenstackSshAuth;
Jian Li51b844c2018-05-31 10:59:03 +090068import org.openstack4j.api.OSClient;
69import org.openstack4j.api.client.IOSClientBuilder;
70import org.openstack4j.api.exceptions.AuthenticationException;
71import org.openstack4j.api.types.Facing;
72import org.openstack4j.core.transport.Config;
Jian Li091d8d22018-02-20 10:42:06 +090073import org.openstack4j.core.transport.ObjectMapperSingleton;
74import org.openstack4j.model.ModelEntity;
Jian Li51b844c2018-05-31 10:59:03 +090075import org.openstack4j.model.common.Identifier;
Daniel Park4fa1f5e2018-10-17 12:41:52 +090076import org.openstack4j.model.network.ExternalGateway;
Jian Li24ec59f2018-05-23 19:01:25 +090077import org.openstack4j.model.network.NetFloatingIP;
78import org.openstack4j.model.network.Network;
Jian Lia171a432018-06-11 11:52:11 +090079import org.openstack4j.model.network.Port;
Daniel Park4fa1f5e2018-10-17 12:41:52 +090080import org.openstack4j.model.network.Router;
Jian Li0b564282018-06-20 00:50:53 +090081import org.openstack4j.model.network.RouterInterface;
Daniel Park4fa1f5e2018-10-17 12:41:52 +090082import org.openstack4j.model.network.Subnet;
Jian Li51b844c2018-05-31 10:59:03 +090083import org.openstack4j.openstack.OSFactory;
Jian Li0b564282018-06-20 00:50:53 +090084import org.openstack4j.openstack.networking.domain.NeutronRouterInterface;
Jian Li091d8d22018-02-20 10:42:06 +090085import org.slf4j.Logger;
86import org.slf4j.LoggerFactory;
87
Jian Li63430202018-08-30 16:24:09 +090088import javax.crypto.Mac;
89import javax.crypto.spec.SecretKeySpec;
Jian Li51b844c2018-05-31 10:59:03 +090090import javax.net.ssl.HostnameVerifier;
91import javax.net.ssl.HttpsURLConnection;
92import javax.net.ssl.SSLContext;
93import javax.net.ssl.TrustManager;
94import javax.net.ssl.X509TrustManager;
Jian Li63430202018-08-30 16:24:09 +090095import java.io.ByteArrayInputStream;
96import java.io.ByteArrayOutputStream;
Jian Li0b564282018-06-20 00:50:53 +090097import java.io.IOException;
Jian Li091d8d22018-02-20 10:42:06 +090098import java.io.InputStream;
Daniel Parka73c2362018-09-17 17:43:25 +090099import java.io.OutputStream;
Daniel Park4fa1f5e2018-10-17 12:41:52 +0900100import java.nio.ByteBuffer;
Jian Li51b844c2018-05-31 10:59:03 +0900101import java.security.cert.X509Certificate;
Daniel Parka73c2362018-09-17 17:43:25 +0900102import java.util.Collection;
Jian Li1064e4f2018-05-29 16:16:53 +0900103import java.util.HashMap;
104import java.util.Iterator;
105import java.util.Map;
Daniel Park95f73312018-07-31 15:48:34 +0900106import java.util.Objects;
Jian Li7f70bb72018-07-06 23:35:30 +0900107import java.util.Optional;
Jian Li1064e4f2018-05-29 16:16:53 +0900108import java.util.Set;
109import java.util.TreeMap;
Daniel Parka73c2362018-09-17 17:43:25 +0900110import java.util.concurrent.TimeUnit;
Jian Li091d8d22018-02-20 10:42:06 +0900111
112import static com.fasterxml.jackson.databind.SerializationFeature.INDENT_OUTPUT;
Daniel Park95f73312018-07-31 15:48:34 +0900113import static com.google.common.base.Preconditions.checkNotNull;
Jian Li7f024de2018-07-07 03:51:02 +0900114import static com.google.common.base.Strings.isNullOrEmpty;
Daniel Park95f73312018-07-31 15:48:34 +0900115import static org.onosproject.net.AnnotationKeys.PORT_NAME;
Daniel Parka73c2362018-09-17 17:43:25 +0900116import static org.onosproject.openstacknetworking.api.Constants.DEFAULT_GATEWAY_MAC_STR;
Daniel Parkc4d06402018-05-28 15:57:37 +0900117import static org.onosproject.openstacknetworking.api.Constants.PCISLOT;
118import static org.onosproject.openstacknetworking.api.Constants.PCI_VENDOR_INFO;
Daniel Park7e8c4d82018-08-13 23:47:49 +0900119import static org.onosproject.openstacknetworking.api.Constants.PORT_NAME_PREFIX_VM;
120import static org.onosproject.openstacknetworking.api.Constants.PORT_NAME_VHOST_USER_PREFIX_VM;
Daniel Parkec9d1132018-08-19 11:18:03 +0900121import static org.onosproject.openstacknetworking.api.Constants.UNSUPPORTED_VENDOR;
Ray Milkey9dc57392018-06-08 08:52:31 -0700122import static org.onosproject.openstacknetworking.api.Constants.portNamePrefixMap;
Jian Li0b564282018-06-20 00:50:53 +0900123import static org.openstack4j.core.transport.ObjectMapperSingleton.getContext;
Jian Li091d8d22018-02-20 10:42:06 +0900124
125/**
126 * An utility that used in openstack networking app.
127 */
Jian Lidea0fdb2018-04-02 19:02:48 +0900128public final class OpenstackNetworkingUtil {
Jian Li091d8d22018-02-20 10:42:06 +0900129
Daniel Park95985382018-07-23 11:38:07 +0900130 private static final Logger log = LoggerFactory.getLogger(OpenstackNetworkingUtil.class);
Jian Li091d8d22018-02-20 10:42:06 +0900131
Daniel Parkc4d06402018-05-28 15:57:37 +0900132 private static final int HEX_RADIX = 16;
Jian Li51b844c2018-05-31 10:59:03 +0900133 private static final String ZERO_FUNCTION_NUMBER = "0";
Daniel Parkc4d06402018-05-28 15:57:37 +0900134 private static final String PREFIX_DEVICE_NUMBER = "s";
135 private static final String PREFIX_FUNCTION_NUMBER = "f";
136
Jian Li51b844c2018-05-31 10:59:03 +0900137 // keystone endpoint related variables
138 private static final String DOMAIN_DEFAULT = "default";
139 private static final String KEYSTONE_V2 = "v2.0";
140 private static final String KEYSTONE_V3 = "v3";
Jian Li51b844c2018-05-31 10:59:03 +0900141 private static final String SSL_TYPE = "SSL";
142
Jian Li7f024de2018-07-07 03:51:02 +0900143 private static final String PROXY_MODE = "proxy";
144 private static final String BROADCAST_MODE = "broadcast";
145
Jian Licad36c72018-09-13 17:44:54 +0900146 private static final String ENABLE = "enable";
147 private static final String DISABLE = "disable";
148
Jian Li63430202018-08-30 16:24:09 +0900149 private static final int HTTP_PAYLOAD_BUFFER = 8 * 1024;
150
151 private static final String HMAC_SHA256 = "HmacSHA256";
152
Jian Li24ec59f2018-05-23 19:01:25 +0900153 private static final String ERR_FLOW = "Failed set flows for floating IP %s: ";
154
Daniel Parka73c2362018-09-17 17:43:25 +0900155 private static final String FLAT = "FLAT";
156 private static final String VXLAN = "VXLAN";
157 private static final String VLAN = "VLAN";
158 private static final String DL_DST = "dl_dst=";
159 private static final String NW_DST = "nw_dst=";
160 private static final String DEFAULT_REQUEST_STRING = "sudo ovs-appctl ofproto/trace br-int ip";
161 private static final String IN_PORT = "in_port=";
162 private static final String NW_SRC = "nw_src=";
163 private static final String COMMA = ",";
164 private static final String TUN_ID = "tun_id=";
165
166 private static final long TIMEOUT_MS = 5000;
167 private static final long WAIT_OUTPUT_STREAM_SECOND = 2;
168 private static final int SSH_PORT = 22;
169
Jian Li091d8d22018-02-20 10:42:06 +0900170 /**
171 * Prevents object instantiation from external.
172 */
Jian Lidea0fdb2018-04-02 19:02:48 +0900173 private OpenstackNetworkingUtil() {
Jian Li091d8d22018-02-20 10:42:06 +0900174 }
175
176 /**
177 * Interprets JSON string to corresponding openstack model entity object.
178 *
179 * @param input JSON string
180 * @param entityClazz openstack model entity class
181 * @return openstack model entity object
182 */
183 public static ModelEntity jsonToModelEntity(InputStream input, Class entityClazz) {
184 ObjectMapper mapper = new ObjectMapper();
185 try {
186 JsonNode jsonTree = mapper.enable(INDENT_OUTPUT).readTree(input);
187 log.trace(new ObjectMapper().writeValueAsString(jsonTree));
188 return ObjectMapperSingleton.getContext(entityClazz)
189 .readerFor(entityClazz)
190 .readValue(jsonTree);
191 } catch (Exception e) {
192 throw new IllegalArgumentException();
193 }
194 }
Jian Lieb9f77d2018-02-20 11:25:45 +0900195
196 /**
197 * Converts openstack model entity object into JSON object.
198 *
199 * @param entity openstack model entity object
200 * @param entityClazz openstack model entity class
201 * @return JSON object
202 */
203 public static ObjectNode modelEntityToJson(ModelEntity entity, Class entityClazz) {
204 ObjectMapper mapper = new ObjectMapper();
205 try {
206 String strModelEntity = ObjectMapperSingleton.getContext(entityClazz)
207 .writerFor(entityClazz)
208 .writeValueAsString(entity);
209 log.trace(strModelEntity);
210 return (ObjectNode) mapper.readTree(strModelEntity.getBytes());
Daniel Park95985382018-07-23 11:38:07 +0900211 } catch (IOException e) {
212 log.error("IOException occurred because of {}", e.toString());
Jian Lieb9f77d2018-02-20 11:25:45 +0900213 throw new IllegalStateException();
214 }
215 }
Jian Li1064e4f2018-05-29 16:16:53 +0900216
217 /**
Jian Li24ec59f2018-05-23 19:01:25 +0900218 * Obtains a floating IP associated with the given instance port.
219 *
220 * @param port instance port
221 * @param fips a collection of floating IPs
222 * @return associated floating IP
223 */
224 public static NetFloatingIP associatedFloatingIp(InstancePort port,
225 Set<NetFloatingIP> fips) {
Daniel Park2ff66b42018-08-01 11:52:45 +0900226 for (NetFloatingIP fip : fips) {
227 if (Strings.isNullOrEmpty(fip.getFixedIpAddress())) {
228 continue;
Jian Li24ec59f2018-05-23 19:01:25 +0900229 }
Daniel Park2ff66b42018-08-01 11:52:45 +0900230 if (Strings.isNullOrEmpty(fip.getFloatingIpAddress())) {
231 continue;
232 }
Jian Li6bc29d92018-10-02 13:55:05 +0900233 if (fip.getFixedIpAddress().equals(port.ipAddress().toString()) &&
234 fip.getPortId().equals(port.portId())) {
Daniel Park2ff66b42018-08-01 11:52:45 +0900235 return fip;
236 }
Jian Li24ec59f2018-05-23 19:01:25 +0900237 }
Daniel Park2ff66b42018-08-01 11:52:45 +0900238
Jian Li24ec59f2018-05-23 19:01:25 +0900239 return null;
240 }
241
242 /**
243 * Checks whether the given floating IP is associated with a VM.
244 *
245 * @param service openstack network service
246 * @param fip floating IP
247 * @return true if the given floating IP associated with a VM, false otherwise
248 */
249 public static boolean isAssociatedWithVM(OpenstackNetworkService service,
250 NetFloatingIP fip) {
251 Port osPort = service.port(fip.getPortId());
252 if (osPort == null) {
253 return false;
254 }
255
256 if (!Strings.isNullOrEmpty(osPort.getDeviceId())) {
257 Network osNet = service.network(osPort.getNetworkId());
258 if (osNet == null) {
259 final String errorFormat = ERR_FLOW + "no network(%s) exists";
260 final String error = String.format(errorFormat,
261 fip.getFloatingIpAddress(), osPort.getNetworkId());
262 throw new IllegalStateException(error);
263 }
264 return true;
265 } else {
266 return false;
267 }
268 }
269
270 /**
Jian Lia171a432018-06-11 11:52:11 +0900271 * Obtains the gateway node by instance port.
272 *
273 * @param gateways a collection of gateway nodes
274 * @param instPort instance port
275 * @return a gateway node
276 */
277 public static OpenstackNode getGwByInstancePort(Set<OpenstackNode> gateways,
278 InstancePort instPort) {
279 OpenstackNode gw = null;
280 if (instPort != null && instPort.deviceId() != null) {
281 gw = getGwByComputeDevId(gateways, instPort.deviceId());
282 }
283 return gw;
284 }
285
286 /**
Jian Li1064e4f2018-05-29 16:16:53 +0900287 * Obtains the gateway node by device in compute node. Note that the gateway
288 * node is determined by device's device identifier.
289 *
290 * @param gws a collection of gateway nodes
291 * @param deviceId device identifier
292 * @return a gateway node
293 */
294 public static OpenstackNode getGwByComputeDevId(Set<OpenstackNode> gws, DeviceId deviceId) {
295 int numOfGw = gws.size();
296
297 if (numOfGw == 0) {
298 return null;
299 }
300
301 int gwIndex = Math.abs(deviceId.hashCode()) % numOfGw;
302
303 return getGwByIndex(gws, gwIndex);
304 }
305
Jian Li51b844c2018-05-31 10:59:03 +0900306 /**
307 * Obtains a connected openstack client.
308 *
309 * @param osNode openstack node
310 * @return a connected openstack client
311 */
312 public static OSClient getConnectedClient(OpenstackNode osNode) {
Jian Lic704b672018-09-04 18:52:53 +0900313 OpenstackAuth auth = osNode.keystoneConfig().authentication();
Jian Li51b844c2018-05-31 10:59:03 +0900314 String endpoint = buildEndpoint(osNode);
315 Perspective perspective = auth.perspective();
Jian Li1064e4f2018-05-29 16:16:53 +0900316
Jian Li51b844c2018-05-31 10:59:03 +0900317 Config config = getSslConfig();
Jian Li1064e4f2018-05-29 16:16:53 +0900318
Jian Li51b844c2018-05-31 10:59:03 +0900319 try {
320 if (endpoint.contains(KEYSTONE_V2)) {
321 IOSClientBuilder.V2 builder = OSFactory.builderV2()
322 .endpoint(endpoint)
323 .tenantName(auth.project())
324 .credentials(auth.username(), auth.password())
325 .withConfig(config);
326
327 if (perspective != null) {
328 builder.perspective(getFacing(perspective));
329 }
330
331 return builder.authenticate();
332 } else if (endpoint.contains(KEYSTONE_V3)) {
333
334 Identifier project = Identifier.byName(auth.project());
335 Identifier domain = Identifier.byName(DOMAIN_DEFAULT);
336
337 IOSClientBuilder.V3 builder = OSFactory.builderV3()
338 .endpoint(endpoint)
339 .credentials(auth.username(), auth.password(), domain)
340 .scopeToProject(project, domain)
341 .withConfig(config);
342
343 if (perspective != null) {
344 builder.perspective(getFacing(perspective));
345 }
346
347 return builder.authenticate();
348 } else {
349 log.warn("Unrecognized keystone version type");
350 return null;
Jian Li1064e4f2018-05-29 16:16:53 +0900351 }
Jian Li51b844c2018-05-31 10:59:03 +0900352 } catch (AuthenticationException e) {
353 log.error("Authentication failed due to {}", e.toString());
354 return null;
Jian Li1064e4f2018-05-29 16:16:53 +0900355 }
Jian Li1064e4f2018-05-29 16:16:53 +0900356 }
Daniel Parkc4d06402018-05-28 15:57:37 +0900357
358 /**
359 * Extract the interface name with the supplied port.
360 *
361 * @param port port
362 * @return interface name
363 */
364 public static String getIntfNameFromPciAddress(Port port) {
Daniel Park95985382018-07-23 11:38:07 +0900365 if (port.getProfile() == null || port.getProfile().isEmpty()) {
Jian Li51b844c2018-05-31 10:59:03 +0900366 log.error("Port profile is not found");
367 return null;
368 }
369
Daniel Park95985382018-07-23 11:38:07 +0900370 if (!port.getProfile().containsKey(PCISLOT) ||
371 Strings.isNullOrEmpty(port.getProfile().get(PCISLOT).toString())) {
Daniel Parkc4d06402018-05-28 15:57:37 +0900372 log.error("Failed to retrieve the interface name because of no pci_slot information from the port");
373 return null;
374 }
Jian Li51b844c2018-05-31 10:59:03 +0900375
Daniel Parkc4d06402018-05-28 15:57:37 +0900376 String busNumHex = port.getProfile().get(PCISLOT).toString().split(":")[1];
377 String busNumDecimal = String.valueOf(Integer.parseInt(busNumHex, HEX_RADIX));
378
379 String deviceNumHex = port.getProfile().get(PCISLOT).toString()
380 .split(":")[2]
381 .split("\\.")[0];
382 String deviceNumDecimal = String.valueOf(Integer.parseInt(deviceNumHex, HEX_RADIX));
383
384 String functionNumHex = port.getProfile().get(PCISLOT).toString()
385 .split(":")[2]
386 .split("\\.")[1];
387 String functionNumDecimal = String.valueOf(Integer.parseInt(functionNumHex, HEX_RADIX));
388
389 String intfName;
390
391 String vendorInfoForPort = String.valueOf(port.getProfile().get(PCI_VENDOR_INFO));
392
Daniel Park95985382018-07-23 11:38:07 +0900393 if (!portNamePrefixMap().containsKey(vendorInfoForPort)) {
Daniel Parkec9d1132018-08-19 11:18:03 +0900394 log.warn("Failed to retrieve the interface name because of unsupported prefix for vendor ID {}",
Daniel Park95985382018-07-23 11:38:07 +0900395 vendorInfoForPort);
Daniel Parkec9d1132018-08-19 11:18:03 +0900396 return UNSUPPORTED_VENDOR;
Daniel Parkc4d06402018-05-28 15:57:37 +0900397 }
Ray Milkey9dc57392018-06-08 08:52:31 -0700398 String portNamePrefix = portNamePrefixMap().get(vendorInfoForPort);
Jian Li51b844c2018-05-31 10:59:03 +0900399
Daniel Parkc4d06402018-05-28 15:57:37 +0900400 if (functionNumDecimal.equals(ZERO_FUNCTION_NUMBER)) {
401 intfName = portNamePrefix + busNumDecimal + PREFIX_DEVICE_NUMBER + deviceNumDecimal;
402 } else {
403 intfName = portNamePrefix + busNumDecimal + PREFIX_DEVICE_NUMBER + deviceNumDecimal
404 + PREFIX_FUNCTION_NUMBER + functionNumDecimal;
405 }
406
407 return intfName;
408 }
Jian Li51b844c2018-05-31 10:59:03 +0900409
410 /**
Daniel Park95f73312018-07-31 15:48:34 +0900411 * Check if the given interface is added to the given device or not.
412 *
413 * @param deviceId device ID
414 * @param intfName interface name
415 * @param deviceService device service
416 * @return true if the given interface is added to the given device or false otherwise
417 */
418 public static boolean hasIntfAleadyInDevice(DeviceId deviceId, String intfName, DeviceService deviceService) {
419 checkNotNull(deviceId);
420 checkNotNull(intfName);
421
422 return deviceService.getPorts(deviceId).stream()
423 .anyMatch(port -> Objects.equals(port.annotations().value(PORT_NAME), intfName));
424 }
425
426 /**
Jian Li0b564282018-06-20 00:50:53 +0900427 * Adds router interfaces to openstack admin service.
428 * TODO fix the logic to add router interface to router
429 *
430 * @param osPort port
431 * @param adminService openstack admin service
432 */
433 public static void addRouterIface(Port osPort, OpenstackRouterAdminService adminService) {
434 osPort.getFixedIps().forEach(p -> {
435 JsonNode jsonTree = new ObjectMapper().createObjectNode()
436 .put("id", osPort.getDeviceId())
437 .put("tenant_id", osPort.getTenantId())
438 .put("subnet_id", p.getSubnetId())
439 .put("port_id", osPort.getId());
440 try {
441 RouterInterface rIface = getContext(NeutronRouterInterface.class)
442 .readerFor(NeutronRouterInterface.class)
443 .readValue(jsonTree);
444 if (adminService.routerInterface(rIface.getPortId()) != null) {
445 adminService.updateRouterInterface(rIface);
446 } else {
447 adminService.addRouterInterface(rIface);
448 }
449 } catch (IOException ignore) {
Daniel Park2ff66b42018-08-01 11:52:45 +0900450 log.error("Exception occurred because of {}", ignore.toString());
Jian Li0b564282018-06-20 00:50:53 +0900451 }
452 });
453 }
454
455 /**
Jian Li7f70bb72018-07-06 23:35:30 +0900456 * Obtains the property value with specified property key name.
457 *
458 * @param properties a collection of properties
459 * @param name key name
460 * @return mapping value
461 */
462 public static String getPropertyValue(Set<ConfigProperty> properties, String name) {
463 Optional<ConfigProperty> property =
464 properties.stream().filter(p -> p.name().equals(name)).findFirst();
465 return property.map(ConfigProperty::value).orElse(null);
466 }
467
468 /**
Jian Li9d35bd62018-10-13 01:43:24 +0900469 * Obtains the boolean property value with specified property key name.
470 *
471 * @param properties a collection of properties
472 * @param name key name
473 * @return mapping value
474 */
475 public static boolean getPropertyValueAsBoolean(Set<ConfigProperty> properties, String name) {
476 Optional<ConfigProperty> property =
477 properties.stream().filter(p -> p.name().equals(name)).findFirst();
478
479 return property.map(ConfigProperty::asBoolean).orElse(false);
480 }
481
482 /**
Jian Lif1efbe52018-07-17 23:20:16 +0900483 * Prints out the JSON string in pretty format.
484 *
485 * @param mapper Object mapper
486 * @param jsonString JSON string
487 * @return pretty formatted JSON string
488 */
489 public static String prettyJson(ObjectMapper mapper, String jsonString) {
490 try {
491 Object jsonObject = mapper.readValue(jsonString, Object.class);
492 return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(jsonObject);
Daniel Park2ff66b42018-08-01 11:52:45 +0900493 } catch (JsonParseException e) {
494 log.debug("JsonParseException caused by {}", e);
495 } catch (JsonMappingException e) {
496 log.debug("JsonMappingException caused by {}", e);
497 } catch (JsonProcessingException e) {
498 log.debug("JsonProcessingException caused by {}", e);
Jian Lif1efbe52018-07-17 23:20:16 +0900499 } catch (IOException e) {
Daniel Park2ff66b42018-08-01 11:52:45 +0900500 log.debug("IOException caused by {}", e);
Jian Lif1efbe52018-07-17 23:20:16 +0900501 }
502 return null;
503 }
504
505 /**
Jian Li7f024de2018-07-07 03:51:02 +0900506 * Checks the validity of ARP mode.
507 *
508 * @param arpMode ARP mode
509 * @return returns true if the ARP mode is valid, false otherwise
510 */
511 public static boolean checkArpMode(String arpMode) {
512
513 if (isNullOrEmpty(arpMode)) {
514 return false;
515 } else {
516 return arpMode.equals(PROXY_MODE) || arpMode.equals(BROADCAST_MODE);
517 }
518 }
519
520 /**
Jian Licad36c72018-09-13 17:44:54 +0900521 * Checks the validity of activation flag.
522 *
523 * @param activationFlag activation flag
524 * @return returns true if the activation flag is valid, false otherwise
525 */
526 public static boolean checkActivationFlag(String activationFlag) {
527
528 switch (activationFlag) {
529 case ENABLE:
530 return true;
531 case DISABLE:
532 return false;
533 default:
534 throw new IllegalArgumentException("The given activation flag is not valid!");
535 }
536 }
537
538 /**
Jian Liec5c32b2018-07-13 14:28:58 +0900539 * Swaps current location with old location info.
540 * The revised instance port will be used to mod the flow rules after migration.
541 *
542 * @param instPort instance port
543 * @return location swapped instance port
544 */
545 public static InstancePort swapStaleLocation(InstancePort instPort) {
546 return DefaultInstancePort.builder()
547 .deviceId(instPort.oldDeviceId())
548 .portNumber(instPort.oldPortNumber())
549 .state(instPort.state())
550 .ipAddress(instPort.ipAddress())
551 .macAddress(instPort.macAddress())
552 .networkId(instPort.networkId())
553 .portId(instPort.portId())
554 .build();
555 }
556
557 /**
Daniel Park2ff66b42018-08-01 11:52:45 +0900558 * Compares two router interfaces are equal.
559 * Will be remove this after Openstack4j implements equals.
560 *
561 * @param routerInterface1 router interface
562 * @param routerInterface2 router interface
563 * @return returns true if two router interfaces are equal, false otherwise
564 */
Jian Li63430202018-08-30 16:24:09 +0900565 public static boolean routerInterfacesEquals(RouterInterface routerInterface1,
566 RouterInterface routerInterface2) {
Daniel Park2ff66b42018-08-01 11:52:45 +0900567 return Objects.equals(routerInterface1.getId(), routerInterface2.getId()) &&
568 Objects.equals(routerInterface1.getPortId(), routerInterface2.getPortId()) &&
569 Objects.equals(routerInterface1.getSubnetId(), routerInterface2.getSubnetId()) &&
570 Objects.equals(routerInterface1.getTenantId(), routerInterface2.getTenantId());
571 }
572
Daniel Park7e8c4d82018-08-13 23:47:49 +0900573 public static VnicType vnicType(String portName) {
574 if (portName.startsWith(PORT_NAME_PREFIX_VM) ||
575 portName.startsWith(PORT_NAME_VHOST_USER_PREFIX_VM)) {
576 return VnicType.NORMAL;
577 } else if (isDirectPort(portName)) {
578 return VnicType.DIRECT;
579 } else {
580 return VnicType.UNSUPPORTED;
581 }
582 }
583
Jian Li63430202018-08-30 16:24:09 +0900584 /**
585 * Deserializes raw payload into HttpRequest object.
586 *
587 * @param rawData raw http payload
588 * @return HttpRequest object
589 */
590 public static HttpRequest parseHttpRequest(byte[] rawData) {
591 SessionInputBufferImpl sessionInputBuffer =
592 new SessionInputBufferImpl(
593 new HttpTransportMetricsImpl(), HTTP_PAYLOAD_BUFFER);
594 sessionInputBuffer.bind(new ByteArrayInputStream(rawData));
595 DefaultHttpRequestParser requestParser = new DefaultHttpRequestParser(sessionInputBuffer);
596 try {
597 return requestParser.parse();
598 } catch (IOException | HttpException e) {
599 log.warn("Failed to parse HttpRequest, due to {}", e);
600 }
601
602 return null;
603 }
604
605 /**
606 * Serializes HttpRequest object to byte array.
607 *
608 * @param request http request object
609 * @return byte array
610 */
611 public static byte[] unparseHttpRequest(HttpRequest request) {
612 try {
613 SessionOutputBufferImpl sessionOutputBuffer =
614 new SessionOutputBufferImpl(
615 new HttpTransportMetricsImpl(), HTTP_PAYLOAD_BUFFER);
616
617 ByteArrayOutputStream baos = new ByteArrayOutputStream();
618 sessionOutputBuffer.bind(baos);
619
620 HttpMessageWriter<HttpRequest> requestWriter = new DefaultHttpRequestWriter(
621 sessionOutputBuffer);
622 requestWriter.write(request);
623 sessionOutputBuffer.flush();
624
625 return baos.toByteArray();
626 } catch (HttpException | IOException e) {
627 log.warn("Failed to unparse HttpRequest, due to {}", e);
628 }
629
630 return null;
631 }
632
633 /**
634 * Deserializes raw payload into HttpResponse object.
635 *
636 * @param rawData raw http payload
637 * @return HttpResponse object
638 */
639 public static HttpResponse parseHttpResponse(byte[] rawData) {
640 SessionInputBufferImpl sessionInputBuffer =
641 new SessionInputBufferImpl(
642 new HttpTransportMetricsImpl(), HTTP_PAYLOAD_BUFFER);
643 sessionInputBuffer.bind(new ByteArrayInputStream(rawData));
644 DefaultHttpResponseParser responseParser = new DefaultHttpResponseParser(sessionInputBuffer);
645 try {
646 return responseParser.parse();
647 } catch (IOException | HttpException e) {
648 log.warn("Failed to parse HttpResponse, due to {}", e);
649 }
650
651 return null;
652 }
653
654 /**
655 * Serializes HttpResponse header to byte array.
656 *
657 * @param response http response object
658 * @return byte array
659 */
660 public static byte[] unparseHttpResponseHeader(HttpResponse response) {
661 try {
662 SessionOutputBufferImpl sessionOutputBuffer =
663 new SessionOutputBufferImpl(
664 new HttpTransportMetricsImpl(), HTTP_PAYLOAD_BUFFER);
665
666 ByteArrayOutputStream headerBaos = new ByteArrayOutputStream();
667 sessionOutputBuffer.bind(headerBaos);
668
669 HttpMessageWriter<HttpResponse> responseWriter =
670 new DefaultHttpResponseWriter(sessionOutputBuffer);
671 responseWriter.write(response);
672 sessionOutputBuffer.flush();
673
674 log.debug(headerBaos.toString());
675
676 return headerBaos.toByteArray();
677 } catch (IOException | HttpException e) {
678 log.warn("Failed to unparse HttpResponse headers, due to {}", e);
679 }
680
681 return null;
682 }
683
684 /**
685 * Serializes HttpResponse object to byte array.
686 *
687 * @param response http response object
688 * @return byte array
689 */
690 public static byte[] unparseHttpResponseBody(HttpResponse response) {
691 try {
692 ByteArrayOutputStream baos = new ByteArrayOutputStream();
693 response.getEntity().writeTo(baos);
694
695 log.debug(response.toString());
696 log.debug(baos.toString());
697
698 return baos.toByteArray();
699 } catch (IOException e) {
700 log.warn("Failed to unparse HttpResponse, due to {}", e);
701 }
702
703 return null;
704 }
705
706 /**
707 * Encodes the given data using HmacSHA256 encryption method with given secret key.
708 *
709 * @param key secret key
710 * @param data data to be encrypted
711 * @return Hmac256 encrypted data
712 */
713 public static String hmacEncrypt(String key, String data) {
714 try {
715 Mac sha256Hmac = Mac.getInstance(HMAC_SHA256);
716 SecretKeySpec secretKey = new SecretKeySpec(key.getBytes("UTF-8"), HMAC_SHA256);
717 sha256Hmac.init(secretKey);
718 return Hex.encodeHexString(sha256Hmac.doFinal(data.getBytes("UTF-8")));
719 } catch (Exception e) {
720 log.warn("Failed to encrypt data {} using key {}, due to {}", data, key, e);
721 }
722 return null;
723 }
724
Daniel Parka73c2362018-09-17 17:43:25 +0900725 /**
726 * Creates flow trace request string.
727 *
728 * @param srcIp src ip address
729 * @param dstIp dst ip address
730 * @param srcInstancePort src instance port
731 * @param osNetService openstack networking service
Daniel Park5aef9822018-09-20 18:04:18 +0900732 * @param uplink true if this request is for uplink
Daniel Parka73c2362018-09-17 17:43:25 +0900733 * @return flow trace request string
734 */
735 public static String traceRequestString(String srcIp,
736 String dstIp,
737 InstancePort srcInstancePort,
738 OpenstackNetworkService osNetService, boolean uplink) {
739
740 StringBuilder requestStringBuilder = new StringBuilder(DEFAULT_REQUEST_STRING);
741
742 if (uplink) {
743
744 requestStringBuilder.append(COMMA)
745 .append(IN_PORT)
746 .append(srcInstancePort.portNumber().toString())
747 .append(COMMA)
748 .append(NW_SRC)
749 .append(srcIp)
750 .append(COMMA);
751
752 if (osNetService.networkType(srcInstancePort.networkId()).equals(VXLAN) ||
753 osNetService.networkType(srcInstancePort.networkId()).equals(VLAN)) {
754 if (srcIp.equals(dstIp)) {
755 dstIp = osNetService.gatewayIp(srcInstancePort.portId());
756 requestStringBuilder.append(DL_DST)
757 .append(DEFAULT_GATEWAY_MAC_STR).append(COMMA);
758 } else if (!osNetService.ipPrefix(srcInstancePort.portId()).contains(IpAddress.valueOf(dstIp))) {
759 requestStringBuilder.append(DL_DST)
760 .append(DEFAULT_GATEWAY_MAC_STR)
761 .append(COMMA);
762 }
763 } else {
764 if (srcIp.equals(dstIp)) {
765 dstIp = osNetService.gatewayIp(srcInstancePort.portId());
766 }
767 }
768
769 requestStringBuilder.append(NW_DST)
770 .append(dstIp)
771 .append("\n");
772 } else {
773 requestStringBuilder.append(COMMA)
774 .append(NW_SRC)
775 .append(dstIp)
776 .append(COMMA);
777
778 if (osNetService.networkType(srcInstancePort.networkId()).equals(VXLAN) ||
779 osNetService.networkType(srcInstancePort.networkId()).equals(VLAN)) {
780 requestStringBuilder.append(TUN_ID)
781 .append(osNetService.segmentId(srcInstancePort.networkId()))
782 .append(COMMA);
783 }
784 requestStringBuilder.append(NW_DST)
785 .append(srcIp)
786 .append("\n");
787
788 }
789
790 return requestStringBuilder.toString();
791 }
792
793 /**
794 * Sends flow trace string to node.
795 *
796 * @param requestString reqeust string
797 * @param node src node
798 * @return flow trace result in string format
799 */
800 public static String sendTraceRequestToNode(String requestString,
801 OpenstackNode node) {
802 String traceResult = null;
803 OpenstackSshAuth sshAuth = node.sshAuthInfo();
804
805 try (SshClient client = SshClient.setUpDefaultClient()) {
806 client.start();
807
808 try (ClientSession session = client
809 .connect(sshAuth.id(), node.managementIp().getIp4Address().toString(), SSH_PORT)
810 .verify(TIMEOUT_MS, TimeUnit.SECONDS).getSession()) {
811 session.addPasswordIdentity(sshAuth.password());
812 session.auth().verify(TIMEOUT_MS, TimeUnit.SECONDS);
813
814
815 try (ClientChannel channel = session.createChannel(ClientChannel.CHANNEL_SHELL)) {
816
817 log.debug("requestString: {}", requestString);
818 final InputStream inputStream =
819 new ByteArrayInputStream(requestString.getBytes());
820
821 OutputStream outputStream = new ByteArrayOutputStream();
822 OutputStream errStream = new ByteArrayOutputStream();
823
824 channel.setIn(new NoCloseInputStream(inputStream));
825 channel.setErr(errStream);
826 channel.setOut(outputStream);
827
828 Collection<ClientChannelEvent> eventList = Lists.newArrayList();
829 eventList.add(ClientChannelEvent.OPENED);
830
831 OpenFuture channelFuture = channel.open();
832
833 if (channelFuture.await(TIMEOUT_MS, TimeUnit.SECONDS)) {
834
835 long timeoutExpiredMs = System.currentTimeMillis() + TIMEOUT_MS;
836
837 while (!channelFuture.isOpened()) {
838 if ((timeoutExpiredMs - System.currentTimeMillis()) <= 0) {
839 log.error("Failed to open channel");
840 return null;
841 }
842 }
843 TimeUnit.SECONDS.sleep(WAIT_OUTPUT_STREAM_SECOND);
844
845 traceResult = ((ByteArrayOutputStream) outputStream).toString(Charsets.UTF_8.name());
846
847 channel.close();
848 }
849 } finally {
850 session.close();
851 }
852 } finally {
853 client.stop();
854 }
855
856 } catch (Exception e) {
857 log.error("Exception occurred because of {}", e.toString());
858 }
859
860 return traceResult;
861 }
862
Daniel Park4fa1f5e2018-10-17 12:41:52 +0900863 /**
864 * Returns the floating ip with supplied instance port.
865 *
866 * @param instancePort instance port
867 * @param osRouterAdminService openstack router admin service
868 * @return floating ip
869 */
870 public static NetFloatingIP floatingIpByInstancePort(InstancePort instancePort,
871 OpenstackRouterAdminService osRouterAdminService) {
872 return osRouterAdminService.floatingIps().stream()
873 .filter(netFloatingIP -> netFloatingIP.getPortId() != null)
874 .filter(netFloatingIP -> netFloatingIP.getPortId().equals(instancePort.portId()))
875 .findAny().orElse(null);
876 }
877
878 /**
879 * Sends GARP packet with supplied floating ip information.
880 *
881 * @param floatingIP floating ip
882 * @param instancePort instance port
883 * @param vlanId vlain id
884 * @param gatewayNode gateway node
885 * @param packetService packet service
886 */
Jian Li32b03622018-11-06 17:54:24 +0900887 public static void processGarpPacketForFloatingIp(NetFloatingIP floatingIP,
888 InstancePort instancePort,
889 VlanId vlanId,
890 OpenstackNode gatewayNode,
891 PacketService packetService) {
Daniel Park4fa1f5e2018-10-17 12:41:52 +0900892 Ethernet ethernet = buildGratuitousArpPacket(floatingIP, instancePort, vlanId);
893
894 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
895 .setOutput(gatewayNode.uplinkPortNum()).build();
896
897 packetService.emit(new DefaultOutboundPacket(gatewayNode.intgBridge(), treatment,
898 ByteBuffer.wrap(ethernet.serialize())));
899 }
900
901 /**
902 * Returns the external peer router with supplied network information.
903 *
904 * @param network network
905 * @param osNetworkService openstack network service
906 * @param osRouterAdminService openstack router admin service
907 * @return external peer router
908 */
909 public static ExternalPeerRouter externalPeerRouterForNetwork(Network network,
910 OpenstackNetworkService osNetworkService,
911 OpenstackRouterAdminService osRouterAdminService) {
912 if (network == null) {
913 return null;
914 }
915
916 Subnet subnet = osNetworkService.subnets(network.getId()).stream().findAny().orElse(null);
917
918 if (subnet == null) {
919 return null;
920 }
921
922 RouterInterface osRouterIface = osRouterAdminService.routerInterfaces().stream()
923 .filter(i -> Objects.equals(i.getSubnetId(), subnet.getId()))
924 .findAny().orElse(null);
925 if (osRouterIface == null) {
926 return null;
927 }
928
929 Router osRouter = osRouterAdminService.router(osRouterIface.getId());
930 if (osRouter == null) {
931 return null;
932 }
933 if (osRouter.getExternalGatewayInfo() == null) {
934 return null;
935 }
936
937 ExternalGateway exGatewayInfo = osRouter.getExternalGatewayInfo();
938 return osNetworkService.externalPeerRouter(exGatewayInfo);
939
940 }
941
Daniel Park7e8c4d82018-08-13 23:47:49 +0900942 private static boolean isDirectPort(String portName) {
Daniel Parkec9d1132018-08-19 11:18:03 +0900943 return portNamePrefixMap().values().stream().anyMatch(p -> portName.startsWith(p));
Daniel Park7e8c4d82018-08-13 23:47:49 +0900944 }
945
Daniel Park2ff66b42018-08-01 11:52:45 +0900946 /**
Daniel Park4fa1f5e2018-10-17 12:41:52 +0900947 * Returns GARP packet with supplied floating ip and instance port information.
948 *
949 * @param floatingIP floating ip
950 * @param instancePort instance port
951 * @param vlanId vlan id
952 * @return GARP packet
953 */
954 private static Ethernet buildGratuitousArpPacket(NetFloatingIP floatingIP,
955 InstancePort instancePort,
956 VlanId vlanId) {
957 Ethernet ethernet = new Ethernet();
958 ethernet.setDestinationMACAddress(MacAddress.BROADCAST);
959 ethernet.setSourceMACAddress(instancePort.macAddress());
960 ethernet.setEtherType(Ethernet.TYPE_ARP);
961 ethernet.setVlanID(vlanId.id());
962
963 ARP arp = new ARP();
964 arp.setOpCode(ARP.OP_REPLY);
965 arp.setProtocolType(ARP.PROTO_TYPE_IP);
966 arp.setHardwareType(ARP.HW_TYPE_ETHERNET);
967
968 arp.setProtocolAddressLength((byte) Ip4Address.BYTE_LENGTH);
969 arp.setHardwareAddressLength((byte) Ethernet.DATALAYER_ADDRESS_LENGTH);
970
971 arp.setSenderHardwareAddress(instancePort.macAddress().toBytes());
972 arp.setTargetHardwareAddress(MacAddress.BROADCAST.toBytes());
973
974 arp.setSenderProtocolAddress(Ip4Address.valueOf(floatingIP.getFloatingIpAddress()).toInt());
975 arp.setTargetProtocolAddress(Ip4Address.valueOf(floatingIP.getFloatingIpAddress()).toInt());
976
977 ethernet.setPayload(arp);
978
979 return ethernet;
980 }
981
982 /**
Jian Li51b844c2018-05-31 10:59:03 +0900983 * Builds up and a complete endpoint URL from gateway node.
984 *
985 * @param node gateway node
986 * @return a complete endpoint URL
987 */
988 private static String buildEndpoint(OpenstackNode node) {
989
Jian Lic704b672018-09-04 18:52:53 +0900990 OpenstackAuth auth = node.keystoneConfig().authentication();
Jian Li51b844c2018-05-31 10:59:03 +0900991
992 StringBuilder endpointSb = new StringBuilder();
993 endpointSb.append(auth.protocol().name().toLowerCase());
994 endpointSb.append("://");
Jian Lic704b672018-09-04 18:52:53 +0900995 endpointSb.append(node.keystoneConfig().endpoint());
Jian Li51b844c2018-05-31 10:59:03 +0900996 return endpointSb.toString();
997 }
998
999 /**
1000 * Obtains the SSL config without verifying the certification.
1001 *
1002 * @return SSL config
1003 */
1004 private static Config getSslConfig() {
1005 // we bypass the SSL certification verification for now
1006 // TODO: verify server side SSL using a given certification
1007 Config config = Config.newConfig().withSSLVerificationDisabled();
1008
1009 TrustManager[] trustAllCerts = new TrustManager[]{
1010 new X509TrustManager() {
1011 public X509Certificate[] getAcceptedIssuers() {
1012 return null;
1013 }
1014
1015 public void checkClientTrusted(X509Certificate[] certs,
1016 String authType) {
1017 }
1018
1019 public void checkServerTrusted(X509Certificate[] certs,
1020 String authType) {
1021 }
1022 }
1023 };
1024
1025 HostnameVerifier allHostsValid = (hostname, session) -> true;
1026
1027 try {
1028 SSLContext sc = SSLContext.getInstance(SSL_TYPE);
1029 sc.init(null, trustAllCerts,
1030 new java.security.SecureRandom());
1031 HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
1032 HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid);
1033
1034 config.withSSLContext(sc);
1035 } catch (Exception e) {
1036 log.error("Failed to access OpenStack service due to {}", e.toString());
1037 return null;
1038 }
1039
1040 return config;
1041 }
1042
1043 /**
1044 * Obtains the facing object with given openstack perspective.
1045 *
1046 * @param perspective keystone perspective
1047 * @return facing object
1048 */
1049 private static Facing getFacing(Perspective perspective) {
1050
1051 switch (perspective) {
1052 case PUBLIC:
1053 return Facing.PUBLIC;
1054 case ADMIN:
1055 return Facing.ADMIN;
1056 case INTERNAL:
1057 return Facing.INTERNAL;
1058 default:
1059 return null;
1060 }
1061 }
1062
1063 /**
1064 * Obtains gateway instance by giving index number.
1065 *
1066 * @param gws a collection of gateway nodes
1067 * @param index index number
1068 * @return gateway instance
1069 */
1070 private static OpenstackNode getGwByIndex(Set<OpenstackNode> gws, int index) {
1071 Map<String, OpenstackNode> hashMap = new HashMap<>();
1072 gws.forEach(gw -> hashMap.put(gw.hostname(), gw));
1073 TreeMap<String, OpenstackNode> treeMap = new TreeMap<>(hashMap);
1074 Iterator<String> iteratorKey = treeMap.keySet().iterator();
1075
1076 int intIndex = 0;
1077 OpenstackNode gw = null;
1078 while (iteratorKey.hasNext()) {
1079 String key = iteratorKey.next();
1080
1081 if (intIndex == index) {
1082 gw = treeMap.get(key);
1083 }
1084 intIndex++;
1085 }
1086 return gw;
1087 }
Jian Li63430202018-08-30 16:24:09 +09001088}