blob: b7550176a6afb7834b866986cfcae567bbec7973 [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;
45import org.onlab.packet.IpAddress;
Jian Li7f70bb72018-07-06 23:35:30 +090046import org.onosproject.cfg.ConfigProperty;
Jian Li1064e4f2018-05-29 16:16:53 +090047import org.onosproject.net.DeviceId;
Daniel Park95f73312018-07-31 15:48:34 +090048import org.onosproject.net.device.DeviceService;
Daniel Park7e8c4d82018-08-13 23:47:49 +090049import org.onosproject.openstacknetworking.api.Constants.VnicType;
Jian Lia171a432018-06-11 11:52:11 +090050import org.onosproject.openstacknetworking.api.InstancePort;
Jian Li24ec59f2018-05-23 19:01:25 +090051import org.onosproject.openstacknetworking.api.OpenstackNetworkService;
Jian Li7f70bb72018-07-06 23:35:30 +090052import org.onosproject.openstacknetworking.api.OpenstackRouterAdminService;
Jian Liec5c32b2018-07-13 14:28:58 +090053import org.onosproject.openstacknetworking.impl.DefaultInstancePort;
Jian Li51b844c2018-05-31 10:59:03 +090054import org.onosproject.openstacknode.api.OpenstackAuth;
55import org.onosproject.openstacknode.api.OpenstackAuth.Perspective;
Jian Li1064e4f2018-05-29 16:16:53 +090056import org.onosproject.openstacknode.api.OpenstackNode;
Daniel Parka73c2362018-09-17 17:43:25 +090057import org.onosproject.openstacknode.api.OpenstackSshAuth;
Jian Li51b844c2018-05-31 10:59:03 +090058import org.openstack4j.api.OSClient;
59import org.openstack4j.api.client.IOSClientBuilder;
60import org.openstack4j.api.exceptions.AuthenticationException;
61import org.openstack4j.api.types.Facing;
62import org.openstack4j.core.transport.Config;
Jian Li091d8d22018-02-20 10:42:06 +090063import org.openstack4j.core.transport.ObjectMapperSingleton;
64import org.openstack4j.model.ModelEntity;
Jian Li51b844c2018-05-31 10:59:03 +090065import org.openstack4j.model.common.Identifier;
Jian Li24ec59f2018-05-23 19:01:25 +090066import org.openstack4j.model.network.NetFloatingIP;
67import org.openstack4j.model.network.Network;
Jian Lia171a432018-06-11 11:52:11 +090068import org.openstack4j.model.network.Port;
Jian Li0b564282018-06-20 00:50:53 +090069import org.openstack4j.model.network.RouterInterface;
Jian Li51b844c2018-05-31 10:59:03 +090070import org.openstack4j.openstack.OSFactory;
Jian Li0b564282018-06-20 00:50:53 +090071import org.openstack4j.openstack.networking.domain.NeutronRouterInterface;
Jian Li091d8d22018-02-20 10:42:06 +090072import org.slf4j.Logger;
73import org.slf4j.LoggerFactory;
74
Jian Li63430202018-08-30 16:24:09 +090075import javax.crypto.Mac;
76import javax.crypto.spec.SecretKeySpec;
Jian Li51b844c2018-05-31 10:59:03 +090077import javax.net.ssl.HostnameVerifier;
78import javax.net.ssl.HttpsURLConnection;
79import javax.net.ssl.SSLContext;
80import javax.net.ssl.TrustManager;
81import javax.net.ssl.X509TrustManager;
Jian Li63430202018-08-30 16:24:09 +090082import java.io.ByteArrayInputStream;
83import java.io.ByteArrayOutputStream;
Jian Li0b564282018-06-20 00:50:53 +090084import java.io.IOException;
Jian Li091d8d22018-02-20 10:42:06 +090085import java.io.InputStream;
Daniel Parka73c2362018-09-17 17:43:25 +090086import java.io.OutputStream;
Jian Li51b844c2018-05-31 10:59:03 +090087import java.security.cert.X509Certificate;
Daniel Parka73c2362018-09-17 17:43:25 +090088import java.util.Collection;
Jian Li1064e4f2018-05-29 16:16:53 +090089import java.util.HashMap;
90import java.util.Iterator;
91import java.util.Map;
Daniel Park95f73312018-07-31 15:48:34 +090092import java.util.Objects;
Jian Li7f70bb72018-07-06 23:35:30 +090093import java.util.Optional;
Jian Li1064e4f2018-05-29 16:16:53 +090094import java.util.Set;
95import java.util.TreeMap;
Daniel Parka73c2362018-09-17 17:43:25 +090096import java.util.concurrent.TimeUnit;
Jian Li091d8d22018-02-20 10:42:06 +090097
98import static com.fasterxml.jackson.databind.SerializationFeature.INDENT_OUTPUT;
Daniel Park95f73312018-07-31 15:48:34 +090099import static com.google.common.base.Preconditions.checkNotNull;
Jian Li7f024de2018-07-07 03:51:02 +0900100import static com.google.common.base.Strings.isNullOrEmpty;
Daniel Park95f73312018-07-31 15:48:34 +0900101import static org.onosproject.net.AnnotationKeys.PORT_NAME;
Daniel Parka73c2362018-09-17 17:43:25 +0900102import static org.onosproject.openstacknetworking.api.Constants.DEFAULT_GATEWAY_MAC_STR;
Daniel Parkc4d06402018-05-28 15:57:37 +0900103import static org.onosproject.openstacknetworking.api.Constants.PCISLOT;
104import static org.onosproject.openstacknetworking.api.Constants.PCI_VENDOR_INFO;
Daniel Park7e8c4d82018-08-13 23:47:49 +0900105import static org.onosproject.openstacknetworking.api.Constants.PORT_NAME_PREFIX_VM;
106import static org.onosproject.openstacknetworking.api.Constants.PORT_NAME_VHOST_USER_PREFIX_VM;
Daniel Parkec9d1132018-08-19 11:18:03 +0900107import static org.onosproject.openstacknetworking.api.Constants.UNSUPPORTED_VENDOR;
Ray Milkey9dc57392018-06-08 08:52:31 -0700108import static org.onosproject.openstacknetworking.api.Constants.portNamePrefixMap;
Jian Li0b564282018-06-20 00:50:53 +0900109import static org.openstack4j.core.transport.ObjectMapperSingleton.getContext;
Jian Li091d8d22018-02-20 10:42:06 +0900110
111/**
112 * An utility that used in openstack networking app.
113 */
Jian Lidea0fdb2018-04-02 19:02:48 +0900114public final class OpenstackNetworkingUtil {
Jian Li091d8d22018-02-20 10:42:06 +0900115
Daniel Park95985382018-07-23 11:38:07 +0900116 private static final Logger log = LoggerFactory.getLogger(OpenstackNetworkingUtil.class);
Jian Li091d8d22018-02-20 10:42:06 +0900117
Daniel Parkc4d06402018-05-28 15:57:37 +0900118 private static final int HEX_RADIX = 16;
Jian Li51b844c2018-05-31 10:59:03 +0900119 private static final String ZERO_FUNCTION_NUMBER = "0";
Daniel Parkc4d06402018-05-28 15:57:37 +0900120 private static final String PREFIX_DEVICE_NUMBER = "s";
121 private static final String PREFIX_FUNCTION_NUMBER = "f";
122
Jian Li51b844c2018-05-31 10:59:03 +0900123 // keystone endpoint related variables
124 private static final String DOMAIN_DEFAULT = "default";
125 private static final String KEYSTONE_V2 = "v2.0";
126 private static final String KEYSTONE_V3 = "v3";
Jian Li51b844c2018-05-31 10:59:03 +0900127 private static final String SSL_TYPE = "SSL";
128
Jian Li7f024de2018-07-07 03:51:02 +0900129 private static final String PROXY_MODE = "proxy";
130 private static final String BROADCAST_MODE = "broadcast";
131
Jian Licad36c72018-09-13 17:44:54 +0900132 private static final String ENABLE = "enable";
133 private static final String DISABLE = "disable";
134
Jian Li63430202018-08-30 16:24:09 +0900135 private static final int HTTP_PAYLOAD_BUFFER = 8 * 1024;
136
137 private static final String HMAC_SHA256 = "HmacSHA256";
138
Jian Li24ec59f2018-05-23 19:01:25 +0900139 private static final String ERR_FLOW = "Failed set flows for floating IP %s: ";
140
Daniel Parka73c2362018-09-17 17:43:25 +0900141 private static final String FLAT = "FLAT";
142 private static final String VXLAN = "VXLAN";
143 private static final String VLAN = "VLAN";
144 private static final String DL_DST = "dl_dst=";
145 private static final String NW_DST = "nw_dst=";
146 private static final String DEFAULT_REQUEST_STRING = "sudo ovs-appctl ofproto/trace br-int ip";
147 private static final String IN_PORT = "in_port=";
148 private static final String NW_SRC = "nw_src=";
149 private static final String COMMA = ",";
150 private static final String TUN_ID = "tun_id=";
151
152 private static final long TIMEOUT_MS = 5000;
153 private static final long WAIT_OUTPUT_STREAM_SECOND = 2;
154 private static final int SSH_PORT = 22;
155
Jian Li091d8d22018-02-20 10:42:06 +0900156 /**
157 * Prevents object instantiation from external.
158 */
Jian Lidea0fdb2018-04-02 19:02:48 +0900159 private OpenstackNetworkingUtil() {
Jian Li091d8d22018-02-20 10:42:06 +0900160 }
161
162 /**
163 * Interprets JSON string to corresponding openstack model entity object.
164 *
165 * @param input JSON string
166 * @param entityClazz openstack model entity class
167 * @return openstack model entity object
168 */
169 public static ModelEntity jsonToModelEntity(InputStream input, Class entityClazz) {
170 ObjectMapper mapper = new ObjectMapper();
171 try {
172 JsonNode jsonTree = mapper.enable(INDENT_OUTPUT).readTree(input);
173 log.trace(new ObjectMapper().writeValueAsString(jsonTree));
174 return ObjectMapperSingleton.getContext(entityClazz)
175 .readerFor(entityClazz)
176 .readValue(jsonTree);
177 } catch (Exception e) {
178 throw new IllegalArgumentException();
179 }
180 }
Jian Lieb9f77d2018-02-20 11:25:45 +0900181
182 /**
183 * Converts openstack model entity object into JSON object.
184 *
185 * @param entity openstack model entity object
186 * @param entityClazz openstack model entity class
187 * @return JSON object
188 */
189 public static ObjectNode modelEntityToJson(ModelEntity entity, Class entityClazz) {
190 ObjectMapper mapper = new ObjectMapper();
191 try {
192 String strModelEntity = ObjectMapperSingleton.getContext(entityClazz)
193 .writerFor(entityClazz)
194 .writeValueAsString(entity);
195 log.trace(strModelEntity);
196 return (ObjectNode) mapper.readTree(strModelEntity.getBytes());
Daniel Park95985382018-07-23 11:38:07 +0900197 } catch (IOException e) {
198 log.error("IOException occurred because of {}", e.toString());
Jian Lieb9f77d2018-02-20 11:25:45 +0900199 throw new IllegalStateException();
200 }
201 }
Jian Li1064e4f2018-05-29 16:16:53 +0900202
203 /**
Jian Li24ec59f2018-05-23 19:01:25 +0900204 * Obtains a floating IP associated with the given instance port.
205 *
206 * @param port instance port
207 * @param fips a collection of floating IPs
208 * @return associated floating IP
209 */
210 public static NetFloatingIP associatedFloatingIp(InstancePort port,
211 Set<NetFloatingIP> fips) {
Daniel Park2ff66b42018-08-01 11:52:45 +0900212 for (NetFloatingIP fip : fips) {
213 if (Strings.isNullOrEmpty(fip.getFixedIpAddress())) {
214 continue;
Jian Li24ec59f2018-05-23 19:01:25 +0900215 }
Daniel Park2ff66b42018-08-01 11:52:45 +0900216 if (Strings.isNullOrEmpty(fip.getFloatingIpAddress())) {
217 continue;
218 }
Jian Li6bc29d92018-10-02 13:55:05 +0900219 if (fip.getFixedIpAddress().equals(port.ipAddress().toString()) &&
220 fip.getPortId().equals(port.portId())) {
Daniel Park2ff66b42018-08-01 11:52:45 +0900221 return fip;
222 }
Jian Li24ec59f2018-05-23 19:01:25 +0900223 }
Daniel Park2ff66b42018-08-01 11:52:45 +0900224
Jian Li24ec59f2018-05-23 19:01:25 +0900225 return null;
226 }
227
228 /**
229 * Checks whether the given floating IP is associated with a VM.
230 *
231 * @param service openstack network service
232 * @param fip floating IP
233 * @return true if the given floating IP associated with a VM, false otherwise
234 */
235 public static boolean isAssociatedWithVM(OpenstackNetworkService service,
236 NetFloatingIP fip) {
237 Port osPort = service.port(fip.getPortId());
238 if (osPort == null) {
239 return false;
240 }
241
242 if (!Strings.isNullOrEmpty(osPort.getDeviceId())) {
243 Network osNet = service.network(osPort.getNetworkId());
244 if (osNet == null) {
245 final String errorFormat = ERR_FLOW + "no network(%s) exists";
246 final String error = String.format(errorFormat,
247 fip.getFloatingIpAddress(), osPort.getNetworkId());
248 throw new IllegalStateException(error);
249 }
250 return true;
251 } else {
252 return false;
253 }
254 }
255
256 /**
Jian Lia171a432018-06-11 11:52:11 +0900257 * Obtains the gateway node by instance port.
258 *
259 * @param gateways a collection of gateway nodes
260 * @param instPort instance port
261 * @return a gateway node
262 */
263 public static OpenstackNode getGwByInstancePort(Set<OpenstackNode> gateways,
264 InstancePort instPort) {
265 OpenstackNode gw = null;
266 if (instPort != null && instPort.deviceId() != null) {
267 gw = getGwByComputeDevId(gateways, instPort.deviceId());
268 }
269 return gw;
270 }
271
272 /**
Jian Li1064e4f2018-05-29 16:16:53 +0900273 * Obtains the gateway node by device in compute node. Note that the gateway
274 * node is determined by device's device identifier.
275 *
276 * @param gws a collection of gateway nodes
277 * @param deviceId device identifier
278 * @return a gateway node
279 */
280 public static OpenstackNode getGwByComputeDevId(Set<OpenstackNode> gws, DeviceId deviceId) {
281 int numOfGw = gws.size();
282
283 if (numOfGw == 0) {
284 return null;
285 }
286
287 int gwIndex = Math.abs(deviceId.hashCode()) % numOfGw;
288
289 return getGwByIndex(gws, gwIndex);
290 }
291
Jian Li51b844c2018-05-31 10:59:03 +0900292 /**
293 * Obtains a connected openstack client.
294 *
295 * @param osNode openstack node
296 * @return a connected openstack client
297 */
298 public static OSClient getConnectedClient(OpenstackNode osNode) {
Jian Lic704b672018-09-04 18:52:53 +0900299 OpenstackAuth auth = osNode.keystoneConfig().authentication();
Jian Li51b844c2018-05-31 10:59:03 +0900300 String endpoint = buildEndpoint(osNode);
301 Perspective perspective = auth.perspective();
Jian Li1064e4f2018-05-29 16:16:53 +0900302
Jian Li51b844c2018-05-31 10:59:03 +0900303 Config config = getSslConfig();
Jian Li1064e4f2018-05-29 16:16:53 +0900304
Jian Li51b844c2018-05-31 10:59:03 +0900305 try {
306 if (endpoint.contains(KEYSTONE_V2)) {
307 IOSClientBuilder.V2 builder = OSFactory.builderV2()
308 .endpoint(endpoint)
309 .tenantName(auth.project())
310 .credentials(auth.username(), auth.password())
311 .withConfig(config);
312
313 if (perspective != null) {
314 builder.perspective(getFacing(perspective));
315 }
316
317 return builder.authenticate();
318 } else if (endpoint.contains(KEYSTONE_V3)) {
319
320 Identifier project = Identifier.byName(auth.project());
321 Identifier domain = Identifier.byName(DOMAIN_DEFAULT);
322
323 IOSClientBuilder.V3 builder = OSFactory.builderV3()
324 .endpoint(endpoint)
325 .credentials(auth.username(), auth.password(), domain)
326 .scopeToProject(project, domain)
327 .withConfig(config);
328
329 if (perspective != null) {
330 builder.perspective(getFacing(perspective));
331 }
332
333 return builder.authenticate();
334 } else {
335 log.warn("Unrecognized keystone version type");
336 return null;
Jian Li1064e4f2018-05-29 16:16:53 +0900337 }
Jian Li51b844c2018-05-31 10:59:03 +0900338 } catch (AuthenticationException e) {
339 log.error("Authentication failed due to {}", e.toString());
340 return null;
Jian Li1064e4f2018-05-29 16:16:53 +0900341 }
Jian Li1064e4f2018-05-29 16:16:53 +0900342 }
Daniel Parkc4d06402018-05-28 15:57:37 +0900343
344 /**
345 * Extract the interface name with the supplied port.
346 *
347 * @param port port
348 * @return interface name
349 */
350 public static String getIntfNameFromPciAddress(Port port) {
Daniel Park95985382018-07-23 11:38:07 +0900351 if (port.getProfile() == null || port.getProfile().isEmpty()) {
Jian Li51b844c2018-05-31 10:59:03 +0900352 log.error("Port profile is not found");
353 return null;
354 }
355
Daniel Park95985382018-07-23 11:38:07 +0900356 if (!port.getProfile().containsKey(PCISLOT) ||
357 Strings.isNullOrEmpty(port.getProfile().get(PCISLOT).toString())) {
Daniel Parkc4d06402018-05-28 15:57:37 +0900358 log.error("Failed to retrieve the interface name because of no pci_slot information from the port");
359 return null;
360 }
Jian Li51b844c2018-05-31 10:59:03 +0900361
Daniel Parkc4d06402018-05-28 15:57:37 +0900362 String busNumHex = port.getProfile().get(PCISLOT).toString().split(":")[1];
363 String busNumDecimal = String.valueOf(Integer.parseInt(busNumHex, HEX_RADIX));
364
365 String deviceNumHex = port.getProfile().get(PCISLOT).toString()
366 .split(":")[2]
367 .split("\\.")[0];
368 String deviceNumDecimal = String.valueOf(Integer.parseInt(deviceNumHex, HEX_RADIX));
369
370 String functionNumHex = port.getProfile().get(PCISLOT).toString()
371 .split(":")[2]
372 .split("\\.")[1];
373 String functionNumDecimal = String.valueOf(Integer.parseInt(functionNumHex, HEX_RADIX));
374
375 String intfName;
376
377 String vendorInfoForPort = String.valueOf(port.getProfile().get(PCI_VENDOR_INFO));
378
Daniel Park95985382018-07-23 11:38:07 +0900379 if (!portNamePrefixMap().containsKey(vendorInfoForPort)) {
Daniel Parkec9d1132018-08-19 11:18:03 +0900380 log.warn("Failed to retrieve the interface name because of unsupported prefix for vendor ID {}",
Daniel Park95985382018-07-23 11:38:07 +0900381 vendorInfoForPort);
Daniel Parkec9d1132018-08-19 11:18:03 +0900382 return UNSUPPORTED_VENDOR;
Daniel Parkc4d06402018-05-28 15:57:37 +0900383 }
Ray Milkey9dc57392018-06-08 08:52:31 -0700384 String portNamePrefix = portNamePrefixMap().get(vendorInfoForPort);
Jian Li51b844c2018-05-31 10:59:03 +0900385
Daniel Parkc4d06402018-05-28 15:57:37 +0900386 if (functionNumDecimal.equals(ZERO_FUNCTION_NUMBER)) {
387 intfName = portNamePrefix + busNumDecimal + PREFIX_DEVICE_NUMBER + deviceNumDecimal;
388 } else {
389 intfName = portNamePrefix + busNumDecimal + PREFIX_DEVICE_NUMBER + deviceNumDecimal
390 + PREFIX_FUNCTION_NUMBER + functionNumDecimal;
391 }
392
393 return intfName;
394 }
Jian Li51b844c2018-05-31 10:59:03 +0900395
396 /**
Daniel Park95f73312018-07-31 15:48:34 +0900397 * Check if the given interface is added to the given device or not.
398 *
399 * @param deviceId device ID
400 * @param intfName interface name
401 * @param deviceService device service
402 * @return true if the given interface is added to the given device or false otherwise
403 */
404 public static boolean hasIntfAleadyInDevice(DeviceId deviceId, String intfName, DeviceService deviceService) {
405 checkNotNull(deviceId);
406 checkNotNull(intfName);
407
408 return deviceService.getPorts(deviceId).stream()
409 .anyMatch(port -> Objects.equals(port.annotations().value(PORT_NAME), intfName));
410 }
411
412 /**
Jian Li0b564282018-06-20 00:50:53 +0900413 * Adds router interfaces to openstack admin service.
414 * TODO fix the logic to add router interface to router
415 *
416 * @param osPort port
417 * @param adminService openstack admin service
418 */
419 public static void addRouterIface(Port osPort, OpenstackRouterAdminService adminService) {
420 osPort.getFixedIps().forEach(p -> {
421 JsonNode jsonTree = new ObjectMapper().createObjectNode()
422 .put("id", osPort.getDeviceId())
423 .put("tenant_id", osPort.getTenantId())
424 .put("subnet_id", p.getSubnetId())
425 .put("port_id", osPort.getId());
426 try {
427 RouterInterface rIface = getContext(NeutronRouterInterface.class)
428 .readerFor(NeutronRouterInterface.class)
429 .readValue(jsonTree);
430 if (adminService.routerInterface(rIface.getPortId()) != null) {
431 adminService.updateRouterInterface(rIface);
432 } else {
433 adminService.addRouterInterface(rIface);
434 }
435 } catch (IOException ignore) {
Daniel Park2ff66b42018-08-01 11:52:45 +0900436 log.error("Exception occurred because of {}", ignore.toString());
Jian Li0b564282018-06-20 00:50:53 +0900437 }
438 });
439 }
440
441 /**
Jian Li7f70bb72018-07-06 23:35:30 +0900442 * Obtains the property value with specified property key name.
443 *
444 * @param properties a collection of properties
445 * @param name key name
446 * @return mapping value
447 */
448 public static String getPropertyValue(Set<ConfigProperty> properties, String name) {
449 Optional<ConfigProperty> property =
450 properties.stream().filter(p -> p.name().equals(name)).findFirst();
451 return property.map(ConfigProperty::value).orElse(null);
452 }
453
454 /**
Jian Li9d35bd62018-10-13 01:43:24 +0900455 * Obtains the boolean property value with specified property key name.
456 *
457 * @param properties a collection of properties
458 * @param name key name
459 * @return mapping value
460 */
461 public static boolean getPropertyValueAsBoolean(Set<ConfigProperty> properties, String name) {
462 Optional<ConfigProperty> property =
463 properties.stream().filter(p -> p.name().equals(name)).findFirst();
464
465 return property.map(ConfigProperty::asBoolean).orElse(false);
466 }
467
468 /**
Jian Lif1efbe52018-07-17 23:20:16 +0900469 * Prints out the JSON string in pretty format.
470 *
471 * @param mapper Object mapper
472 * @param jsonString JSON string
473 * @return pretty formatted JSON string
474 */
475 public static String prettyJson(ObjectMapper mapper, String jsonString) {
476 try {
477 Object jsonObject = mapper.readValue(jsonString, Object.class);
478 return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(jsonObject);
Daniel Park2ff66b42018-08-01 11:52:45 +0900479 } catch (JsonParseException e) {
480 log.debug("JsonParseException caused by {}", e);
481 } catch (JsonMappingException e) {
482 log.debug("JsonMappingException caused by {}", e);
483 } catch (JsonProcessingException e) {
484 log.debug("JsonProcessingException caused by {}", e);
Jian Lif1efbe52018-07-17 23:20:16 +0900485 } catch (IOException e) {
Daniel Park2ff66b42018-08-01 11:52:45 +0900486 log.debug("IOException caused by {}", e);
Jian Lif1efbe52018-07-17 23:20:16 +0900487 }
488 return null;
489 }
490
491 /**
Jian Li7f024de2018-07-07 03:51:02 +0900492 * Checks the validity of ARP mode.
493 *
494 * @param arpMode ARP mode
495 * @return returns true if the ARP mode is valid, false otherwise
496 */
497 public static boolean checkArpMode(String arpMode) {
498
499 if (isNullOrEmpty(arpMode)) {
500 return false;
501 } else {
502 return arpMode.equals(PROXY_MODE) || arpMode.equals(BROADCAST_MODE);
503 }
504 }
505
506 /**
Jian Licad36c72018-09-13 17:44:54 +0900507 * Checks the validity of activation flag.
508 *
509 * @param activationFlag activation flag
510 * @return returns true if the activation flag is valid, false otherwise
511 */
512 public static boolean checkActivationFlag(String activationFlag) {
513
514 switch (activationFlag) {
515 case ENABLE:
516 return true;
517 case DISABLE:
518 return false;
519 default:
520 throw new IllegalArgumentException("The given activation flag is not valid!");
521 }
522 }
523
524 /**
Jian Liec5c32b2018-07-13 14:28:58 +0900525 * Swaps current location with old location info.
526 * The revised instance port will be used to mod the flow rules after migration.
527 *
528 * @param instPort instance port
529 * @return location swapped instance port
530 */
531 public static InstancePort swapStaleLocation(InstancePort instPort) {
532 return DefaultInstancePort.builder()
533 .deviceId(instPort.oldDeviceId())
534 .portNumber(instPort.oldPortNumber())
535 .state(instPort.state())
536 .ipAddress(instPort.ipAddress())
537 .macAddress(instPort.macAddress())
538 .networkId(instPort.networkId())
539 .portId(instPort.portId())
540 .build();
541 }
542
543 /**
Daniel Park2ff66b42018-08-01 11:52:45 +0900544 * Compares two router interfaces are equal.
545 * Will be remove this after Openstack4j implements equals.
546 *
547 * @param routerInterface1 router interface
548 * @param routerInterface2 router interface
549 * @return returns true if two router interfaces are equal, false otherwise
550 */
Jian Li63430202018-08-30 16:24:09 +0900551 public static boolean routerInterfacesEquals(RouterInterface routerInterface1,
552 RouterInterface routerInterface2) {
Daniel Park2ff66b42018-08-01 11:52:45 +0900553 return Objects.equals(routerInterface1.getId(), routerInterface2.getId()) &&
554 Objects.equals(routerInterface1.getPortId(), routerInterface2.getPortId()) &&
555 Objects.equals(routerInterface1.getSubnetId(), routerInterface2.getSubnetId()) &&
556 Objects.equals(routerInterface1.getTenantId(), routerInterface2.getTenantId());
557 }
558
Daniel Park7e8c4d82018-08-13 23:47:49 +0900559 public static VnicType vnicType(String portName) {
560 if (portName.startsWith(PORT_NAME_PREFIX_VM) ||
561 portName.startsWith(PORT_NAME_VHOST_USER_PREFIX_VM)) {
562 return VnicType.NORMAL;
563 } else if (isDirectPort(portName)) {
564 return VnicType.DIRECT;
565 } else {
566 return VnicType.UNSUPPORTED;
567 }
568 }
569
Jian Li63430202018-08-30 16:24:09 +0900570 /**
571 * Deserializes raw payload into HttpRequest object.
572 *
573 * @param rawData raw http payload
574 * @return HttpRequest object
575 */
576 public static HttpRequest parseHttpRequest(byte[] rawData) {
577 SessionInputBufferImpl sessionInputBuffer =
578 new SessionInputBufferImpl(
579 new HttpTransportMetricsImpl(), HTTP_PAYLOAD_BUFFER);
580 sessionInputBuffer.bind(new ByteArrayInputStream(rawData));
581 DefaultHttpRequestParser requestParser = new DefaultHttpRequestParser(sessionInputBuffer);
582 try {
583 return requestParser.parse();
584 } catch (IOException | HttpException e) {
585 log.warn("Failed to parse HttpRequest, due to {}", e);
586 }
587
588 return null;
589 }
590
591 /**
592 * Serializes HttpRequest object to byte array.
593 *
594 * @param request http request object
595 * @return byte array
596 */
597 public static byte[] unparseHttpRequest(HttpRequest request) {
598 try {
599 SessionOutputBufferImpl sessionOutputBuffer =
600 new SessionOutputBufferImpl(
601 new HttpTransportMetricsImpl(), HTTP_PAYLOAD_BUFFER);
602
603 ByteArrayOutputStream baos = new ByteArrayOutputStream();
604 sessionOutputBuffer.bind(baos);
605
606 HttpMessageWriter<HttpRequest> requestWriter = new DefaultHttpRequestWriter(
607 sessionOutputBuffer);
608 requestWriter.write(request);
609 sessionOutputBuffer.flush();
610
611 return baos.toByteArray();
612 } catch (HttpException | IOException e) {
613 log.warn("Failed to unparse HttpRequest, due to {}", e);
614 }
615
616 return null;
617 }
618
619 /**
620 * Deserializes raw payload into HttpResponse object.
621 *
622 * @param rawData raw http payload
623 * @return HttpResponse object
624 */
625 public static HttpResponse parseHttpResponse(byte[] rawData) {
626 SessionInputBufferImpl sessionInputBuffer =
627 new SessionInputBufferImpl(
628 new HttpTransportMetricsImpl(), HTTP_PAYLOAD_BUFFER);
629 sessionInputBuffer.bind(new ByteArrayInputStream(rawData));
630 DefaultHttpResponseParser responseParser = new DefaultHttpResponseParser(sessionInputBuffer);
631 try {
632 return responseParser.parse();
633 } catch (IOException | HttpException e) {
634 log.warn("Failed to parse HttpResponse, due to {}", e);
635 }
636
637 return null;
638 }
639
640 /**
641 * Serializes HttpResponse header to byte array.
642 *
643 * @param response http response object
644 * @return byte array
645 */
646 public static byte[] unparseHttpResponseHeader(HttpResponse response) {
647 try {
648 SessionOutputBufferImpl sessionOutputBuffer =
649 new SessionOutputBufferImpl(
650 new HttpTransportMetricsImpl(), HTTP_PAYLOAD_BUFFER);
651
652 ByteArrayOutputStream headerBaos = new ByteArrayOutputStream();
653 sessionOutputBuffer.bind(headerBaos);
654
655 HttpMessageWriter<HttpResponse> responseWriter =
656 new DefaultHttpResponseWriter(sessionOutputBuffer);
657 responseWriter.write(response);
658 sessionOutputBuffer.flush();
659
660 log.debug(headerBaos.toString());
661
662 return headerBaos.toByteArray();
663 } catch (IOException | HttpException e) {
664 log.warn("Failed to unparse HttpResponse headers, due to {}", e);
665 }
666
667 return null;
668 }
669
670 /**
671 * Serializes HttpResponse object to byte array.
672 *
673 * @param response http response object
674 * @return byte array
675 */
676 public static byte[] unparseHttpResponseBody(HttpResponse response) {
677 try {
678 ByteArrayOutputStream baos = new ByteArrayOutputStream();
679 response.getEntity().writeTo(baos);
680
681 log.debug(response.toString());
682 log.debug(baos.toString());
683
684 return baos.toByteArray();
685 } catch (IOException e) {
686 log.warn("Failed to unparse HttpResponse, due to {}", e);
687 }
688
689 return null;
690 }
691
692 /**
693 * Encodes the given data using HmacSHA256 encryption method with given secret key.
694 *
695 * @param key secret key
696 * @param data data to be encrypted
697 * @return Hmac256 encrypted data
698 */
699 public static String hmacEncrypt(String key, String data) {
700 try {
701 Mac sha256Hmac = Mac.getInstance(HMAC_SHA256);
702 SecretKeySpec secretKey = new SecretKeySpec(key.getBytes("UTF-8"), HMAC_SHA256);
703 sha256Hmac.init(secretKey);
704 return Hex.encodeHexString(sha256Hmac.doFinal(data.getBytes("UTF-8")));
705 } catch (Exception e) {
706 log.warn("Failed to encrypt data {} using key {}, due to {}", data, key, e);
707 }
708 return null;
709 }
710
Daniel Parka73c2362018-09-17 17:43:25 +0900711 /**
712 * Creates flow trace request string.
713 *
714 * @param srcIp src ip address
715 * @param dstIp dst ip address
716 * @param srcInstancePort src instance port
717 * @param osNetService openstack networking service
Daniel Park5aef9822018-09-20 18:04:18 +0900718 * @param uplink true if this request is for uplink
Daniel Parka73c2362018-09-17 17:43:25 +0900719 * @return flow trace request string
720 */
721 public static String traceRequestString(String srcIp,
722 String dstIp,
723 InstancePort srcInstancePort,
724 OpenstackNetworkService osNetService, boolean uplink) {
725
726 StringBuilder requestStringBuilder = new StringBuilder(DEFAULT_REQUEST_STRING);
727
728 if (uplink) {
729
730 requestStringBuilder.append(COMMA)
731 .append(IN_PORT)
732 .append(srcInstancePort.portNumber().toString())
733 .append(COMMA)
734 .append(NW_SRC)
735 .append(srcIp)
736 .append(COMMA);
737
738 if (osNetService.networkType(srcInstancePort.networkId()).equals(VXLAN) ||
739 osNetService.networkType(srcInstancePort.networkId()).equals(VLAN)) {
740 if (srcIp.equals(dstIp)) {
741 dstIp = osNetService.gatewayIp(srcInstancePort.portId());
742 requestStringBuilder.append(DL_DST)
743 .append(DEFAULT_GATEWAY_MAC_STR).append(COMMA);
744 } else if (!osNetService.ipPrefix(srcInstancePort.portId()).contains(IpAddress.valueOf(dstIp))) {
745 requestStringBuilder.append(DL_DST)
746 .append(DEFAULT_GATEWAY_MAC_STR)
747 .append(COMMA);
748 }
749 } else {
750 if (srcIp.equals(dstIp)) {
751 dstIp = osNetService.gatewayIp(srcInstancePort.portId());
752 }
753 }
754
755 requestStringBuilder.append(NW_DST)
756 .append(dstIp)
757 .append("\n");
758 } else {
759 requestStringBuilder.append(COMMA)
760 .append(NW_SRC)
761 .append(dstIp)
762 .append(COMMA);
763
764 if (osNetService.networkType(srcInstancePort.networkId()).equals(VXLAN) ||
765 osNetService.networkType(srcInstancePort.networkId()).equals(VLAN)) {
766 requestStringBuilder.append(TUN_ID)
767 .append(osNetService.segmentId(srcInstancePort.networkId()))
768 .append(COMMA);
769 }
770 requestStringBuilder.append(NW_DST)
771 .append(srcIp)
772 .append("\n");
773
774 }
775
776 return requestStringBuilder.toString();
777 }
778
779 /**
780 * Sends flow trace string to node.
781 *
782 * @param requestString reqeust string
783 * @param node src node
784 * @return flow trace result in string format
785 */
786 public static String sendTraceRequestToNode(String requestString,
787 OpenstackNode node) {
788 String traceResult = null;
789 OpenstackSshAuth sshAuth = node.sshAuthInfo();
790
791 try (SshClient client = SshClient.setUpDefaultClient()) {
792 client.start();
793
794 try (ClientSession session = client
795 .connect(sshAuth.id(), node.managementIp().getIp4Address().toString(), SSH_PORT)
796 .verify(TIMEOUT_MS, TimeUnit.SECONDS).getSession()) {
797 session.addPasswordIdentity(sshAuth.password());
798 session.auth().verify(TIMEOUT_MS, TimeUnit.SECONDS);
799
800
801 try (ClientChannel channel = session.createChannel(ClientChannel.CHANNEL_SHELL)) {
802
803 log.debug("requestString: {}", requestString);
804 final InputStream inputStream =
805 new ByteArrayInputStream(requestString.getBytes());
806
807 OutputStream outputStream = new ByteArrayOutputStream();
808 OutputStream errStream = new ByteArrayOutputStream();
809
810 channel.setIn(new NoCloseInputStream(inputStream));
811 channel.setErr(errStream);
812 channel.setOut(outputStream);
813
814 Collection<ClientChannelEvent> eventList = Lists.newArrayList();
815 eventList.add(ClientChannelEvent.OPENED);
816
817 OpenFuture channelFuture = channel.open();
818
819 if (channelFuture.await(TIMEOUT_MS, TimeUnit.SECONDS)) {
820
821 long timeoutExpiredMs = System.currentTimeMillis() + TIMEOUT_MS;
822
823 while (!channelFuture.isOpened()) {
824 if ((timeoutExpiredMs - System.currentTimeMillis()) <= 0) {
825 log.error("Failed to open channel");
826 return null;
827 }
828 }
829 TimeUnit.SECONDS.sleep(WAIT_OUTPUT_STREAM_SECOND);
830
831 traceResult = ((ByteArrayOutputStream) outputStream).toString(Charsets.UTF_8.name());
832
833 channel.close();
834 }
835 } finally {
836 session.close();
837 }
838 } finally {
839 client.stop();
840 }
841
842 } catch (Exception e) {
843 log.error("Exception occurred because of {}", e.toString());
844 }
845
846 return traceResult;
847 }
848
Daniel Park7e8c4d82018-08-13 23:47:49 +0900849 private static boolean isDirectPort(String portName) {
Daniel Parkec9d1132018-08-19 11:18:03 +0900850 return portNamePrefixMap().values().stream().anyMatch(p -> portName.startsWith(p));
Daniel Park7e8c4d82018-08-13 23:47:49 +0900851 }
852
Daniel Park2ff66b42018-08-01 11:52:45 +0900853 /**
Jian Li51b844c2018-05-31 10:59:03 +0900854 * Builds up and a complete endpoint URL from gateway node.
855 *
856 * @param node gateway node
857 * @return a complete endpoint URL
858 */
859 private static String buildEndpoint(OpenstackNode node) {
860
Jian Lic704b672018-09-04 18:52:53 +0900861 OpenstackAuth auth = node.keystoneConfig().authentication();
Jian Li51b844c2018-05-31 10:59:03 +0900862
863 StringBuilder endpointSb = new StringBuilder();
864 endpointSb.append(auth.protocol().name().toLowerCase());
865 endpointSb.append("://");
Jian Lic704b672018-09-04 18:52:53 +0900866 endpointSb.append(node.keystoneConfig().endpoint());
Jian Li51b844c2018-05-31 10:59:03 +0900867 return endpointSb.toString();
868 }
869
870 /**
871 * Obtains the SSL config without verifying the certification.
872 *
873 * @return SSL config
874 */
875 private static Config getSslConfig() {
876 // we bypass the SSL certification verification for now
877 // TODO: verify server side SSL using a given certification
878 Config config = Config.newConfig().withSSLVerificationDisabled();
879
880 TrustManager[] trustAllCerts = new TrustManager[]{
881 new X509TrustManager() {
882 public X509Certificate[] getAcceptedIssuers() {
883 return null;
884 }
885
886 public void checkClientTrusted(X509Certificate[] certs,
887 String authType) {
888 }
889
890 public void checkServerTrusted(X509Certificate[] certs,
891 String authType) {
892 }
893 }
894 };
895
896 HostnameVerifier allHostsValid = (hostname, session) -> true;
897
898 try {
899 SSLContext sc = SSLContext.getInstance(SSL_TYPE);
900 sc.init(null, trustAllCerts,
901 new java.security.SecureRandom());
902 HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
903 HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid);
904
905 config.withSSLContext(sc);
906 } catch (Exception e) {
907 log.error("Failed to access OpenStack service due to {}", e.toString());
908 return null;
909 }
910
911 return config;
912 }
913
914 /**
915 * Obtains the facing object with given openstack perspective.
916 *
917 * @param perspective keystone perspective
918 * @return facing object
919 */
920 private static Facing getFacing(Perspective perspective) {
921
922 switch (perspective) {
923 case PUBLIC:
924 return Facing.PUBLIC;
925 case ADMIN:
926 return Facing.ADMIN;
927 case INTERNAL:
928 return Facing.INTERNAL;
929 default:
930 return null;
931 }
932 }
933
934 /**
935 * Obtains gateway instance by giving index number.
936 *
937 * @param gws a collection of gateway nodes
938 * @param index index number
939 * @return gateway instance
940 */
941 private static OpenstackNode getGwByIndex(Set<OpenstackNode> gws, int index) {
942 Map<String, OpenstackNode> hashMap = new HashMap<>();
943 gws.forEach(gw -> hashMap.put(gw.hostname(), gw));
944 TreeMap<String, OpenstackNode> treeMap = new TreeMap<>(hashMap);
945 Iterator<String> iteratorKey = treeMap.keySet().iterator();
946
947 int intIndex = 0;
948 OpenstackNode gw = null;
949 while (iteratorKey.hasNext()) {
950 String key = iteratorKey.next();
951
952 if (intIndex == index) {
953 gw = treeMap.get(key);
954 }
955 intIndex++;
956 }
957 return gw;
958 }
Jian Li63430202018-08-30 16:24:09 +0900959}