blob: 9ece6126eaebbc1213c8e8eefc7928bb46ea63d4 [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
18import com.fasterxml.jackson.databind.JsonNode;
19import com.fasterxml.jackson.databind.ObjectMapper;
Jian Lieb9f77d2018-02-20 11:25:45 +090020import com.fasterxml.jackson.databind.node.ObjectNode;
Jian Li24ec59f2018-05-23 19:01:25 +090021import com.google.common.base.Strings;
Jian Li7f70bb72018-07-06 23:35:30 +090022import org.onosproject.cfg.ConfigProperty;
Jian Li1064e4f2018-05-29 16:16:53 +090023import org.onosproject.net.DeviceId;
Jian Lia171a432018-06-11 11:52:11 +090024import org.onosproject.openstacknetworking.api.InstancePort;
Jian Li24ec59f2018-05-23 19:01:25 +090025import org.onosproject.openstacknetworking.api.OpenstackNetworkService;
Jian Li7f70bb72018-07-06 23:35:30 +090026import org.onosproject.openstacknetworking.api.OpenstackRouterAdminService;
Jian Liec5c32b2018-07-13 14:28:58 +090027import org.onosproject.openstacknetworking.impl.DefaultInstancePort;
Jian Li51b844c2018-05-31 10:59:03 +090028import org.onosproject.openstacknode.api.OpenstackAuth;
29import org.onosproject.openstacknode.api.OpenstackAuth.Perspective;
Jian Li1064e4f2018-05-29 16:16:53 +090030import org.onosproject.openstacknode.api.OpenstackNode;
Jian Li51b844c2018-05-31 10:59:03 +090031import org.openstack4j.api.OSClient;
32import org.openstack4j.api.client.IOSClientBuilder;
33import org.openstack4j.api.exceptions.AuthenticationException;
34import org.openstack4j.api.types.Facing;
35import org.openstack4j.core.transport.Config;
Jian Li091d8d22018-02-20 10:42:06 +090036import org.openstack4j.core.transport.ObjectMapperSingleton;
37import org.openstack4j.model.ModelEntity;
Jian Li51b844c2018-05-31 10:59:03 +090038import org.openstack4j.model.common.Identifier;
Jian Li24ec59f2018-05-23 19:01:25 +090039import org.openstack4j.model.network.NetFloatingIP;
40import org.openstack4j.model.network.Network;
Jian Lia171a432018-06-11 11:52:11 +090041import org.openstack4j.model.network.Port;
Jian Li0b564282018-06-20 00:50:53 +090042import org.openstack4j.model.network.RouterInterface;
Jian Li51b844c2018-05-31 10:59:03 +090043import org.openstack4j.openstack.OSFactory;
Jian Li0b564282018-06-20 00:50:53 +090044import org.openstack4j.openstack.networking.domain.NeutronRouterInterface;
Jian Li091d8d22018-02-20 10:42:06 +090045import org.slf4j.Logger;
46import org.slf4j.LoggerFactory;
47
Jian Li51b844c2018-05-31 10:59:03 +090048import javax.net.ssl.HostnameVerifier;
49import javax.net.ssl.HttpsURLConnection;
50import javax.net.ssl.SSLContext;
51import javax.net.ssl.TrustManager;
52import javax.net.ssl.X509TrustManager;
Jian Li0b564282018-06-20 00:50:53 +090053import java.io.IOException;
Jian Li091d8d22018-02-20 10:42:06 +090054import java.io.InputStream;
Jian Li51b844c2018-05-31 10:59:03 +090055import java.security.cert.X509Certificate;
Jian Li1064e4f2018-05-29 16:16:53 +090056import java.util.HashMap;
57import java.util.Iterator;
58import java.util.Map;
Jian Li7f70bb72018-07-06 23:35:30 +090059import java.util.Optional;
Jian Li1064e4f2018-05-29 16:16:53 +090060import java.util.Set;
61import java.util.TreeMap;
Jian Li091d8d22018-02-20 10:42:06 +090062
63import static com.fasterxml.jackson.databind.SerializationFeature.INDENT_OUTPUT;
Jian Li7f024de2018-07-07 03:51:02 +090064import static com.google.common.base.Strings.isNullOrEmpty;
Daniel Parkc4d06402018-05-28 15:57:37 +090065import static org.onosproject.openstacknetworking.api.Constants.PCISLOT;
66import static org.onosproject.openstacknetworking.api.Constants.PCI_VENDOR_INFO;
Ray Milkey9dc57392018-06-08 08:52:31 -070067import static org.onosproject.openstacknetworking.api.Constants.portNamePrefixMap;
Jian Li0b564282018-06-20 00:50:53 +090068import static org.openstack4j.core.transport.ObjectMapperSingleton.getContext;
Jian Li091d8d22018-02-20 10:42:06 +090069
70/**
71 * An utility that used in openstack networking app.
72 */
Jian Lidea0fdb2018-04-02 19:02:48 +090073public final class OpenstackNetworkingUtil {
Jian Li091d8d22018-02-20 10:42:06 +090074
Daniel Park95985382018-07-23 11:38:07 +090075 private static final Logger log = LoggerFactory.getLogger(OpenstackNetworkingUtil.class);
Jian Li091d8d22018-02-20 10:42:06 +090076
Daniel Parkc4d06402018-05-28 15:57:37 +090077 private static final int HEX_RADIX = 16;
Jian Li51b844c2018-05-31 10:59:03 +090078 private static final String ZERO_FUNCTION_NUMBER = "0";
Daniel Parkc4d06402018-05-28 15:57:37 +090079 private static final String PREFIX_DEVICE_NUMBER = "s";
80 private static final String PREFIX_FUNCTION_NUMBER = "f";
81
Jian Li51b844c2018-05-31 10:59:03 +090082 // keystone endpoint related variables
83 private static final String DOMAIN_DEFAULT = "default";
84 private static final String KEYSTONE_V2 = "v2.0";
85 private static final String KEYSTONE_V3 = "v3";
86 private static final String IDENTITY_PATH = "identity/";
87 private static final String SSL_TYPE = "SSL";
88
Jian Li7f024de2018-07-07 03:51:02 +090089 private static final String PROXY_MODE = "proxy";
90 private static final String BROADCAST_MODE = "broadcast";
91
Jian Li24ec59f2018-05-23 19:01:25 +090092 private static final String ERR_FLOW = "Failed set flows for floating IP %s: ";
93
Jian Li091d8d22018-02-20 10:42:06 +090094 /**
95 * Prevents object instantiation from external.
96 */
Jian Lidea0fdb2018-04-02 19:02:48 +090097 private OpenstackNetworkingUtil() {
Jian Li091d8d22018-02-20 10:42:06 +090098 }
99
100 /**
101 * Interprets JSON string to corresponding openstack model entity object.
102 *
103 * @param input JSON string
104 * @param entityClazz openstack model entity class
105 * @return openstack model entity object
106 */
107 public static ModelEntity jsonToModelEntity(InputStream input, Class entityClazz) {
108 ObjectMapper mapper = new ObjectMapper();
109 try {
110 JsonNode jsonTree = mapper.enable(INDENT_OUTPUT).readTree(input);
111 log.trace(new ObjectMapper().writeValueAsString(jsonTree));
112 return ObjectMapperSingleton.getContext(entityClazz)
113 .readerFor(entityClazz)
114 .readValue(jsonTree);
115 } catch (Exception e) {
116 throw new IllegalArgumentException();
117 }
118 }
Jian Lieb9f77d2018-02-20 11:25:45 +0900119
120 /**
121 * Converts openstack model entity object into JSON object.
122 *
123 * @param entity openstack model entity object
124 * @param entityClazz openstack model entity class
125 * @return JSON object
126 */
127 public static ObjectNode modelEntityToJson(ModelEntity entity, Class entityClazz) {
128 ObjectMapper mapper = new ObjectMapper();
129 try {
130 String strModelEntity = ObjectMapperSingleton.getContext(entityClazz)
131 .writerFor(entityClazz)
132 .writeValueAsString(entity);
133 log.trace(strModelEntity);
134 return (ObjectNode) mapper.readTree(strModelEntity.getBytes());
Daniel Park95985382018-07-23 11:38:07 +0900135 } catch (IOException e) {
136 log.error("IOException occurred because of {}", e.toString());
Jian Lieb9f77d2018-02-20 11:25:45 +0900137 throw new IllegalStateException();
138 }
139 }
Jian Li1064e4f2018-05-29 16:16:53 +0900140
141 /**
Jian Li24ec59f2018-05-23 19:01:25 +0900142 * Obtains a floating IP associated with the given instance port.
143 *
144 * @param port instance port
145 * @param fips a collection of floating IPs
146 * @return associated floating IP
147 */
148 public static NetFloatingIP associatedFloatingIp(InstancePort port,
149 Set<NetFloatingIP> fips) {
Daniel Park95985382018-07-23 11:38:07 +0900150 try {
151 for (NetFloatingIP fip : fips) {
152 if (Strings.isNullOrEmpty(fip.getFixedIpAddress())) {
153 continue;
154 }
155 if (Strings.isNullOrEmpty(fip.getFloatingIpAddress())) {
156 continue;
157 }
158 if (fip.getFixedIpAddress().equals(port.ipAddress().toString())) {
159 return fip;
160 }
Jian Li24ec59f2018-05-23 19:01:25 +0900161 }
Daniel Park95985382018-07-23 11:38:07 +0900162 } catch (NullPointerException e) {
163 log.error("Exception occurred because of {}", e.toString());
164 throw new NullPointerException();
Jian Li24ec59f2018-05-23 19:01:25 +0900165 }
166 return null;
167 }
168
169 /**
170 * Checks whether the given floating IP is associated with a VM.
171 *
172 * @param service openstack network service
173 * @param fip floating IP
174 * @return true if the given floating IP associated with a VM, false otherwise
175 */
176 public static boolean isAssociatedWithVM(OpenstackNetworkService service,
177 NetFloatingIP fip) {
178 Port osPort = service.port(fip.getPortId());
179 if (osPort == null) {
180 return false;
181 }
182
183 if (!Strings.isNullOrEmpty(osPort.getDeviceId())) {
184 Network osNet = service.network(osPort.getNetworkId());
185 if (osNet == null) {
186 final String errorFormat = ERR_FLOW + "no network(%s) exists";
187 final String error = String.format(errorFormat,
188 fip.getFloatingIpAddress(), osPort.getNetworkId());
189 throw new IllegalStateException(error);
190 }
191 return true;
192 } else {
193 return false;
194 }
195 }
196
197 /**
Jian Lia171a432018-06-11 11:52:11 +0900198 * Obtains the gateway node by instance port.
199 *
200 * @param gateways a collection of gateway nodes
201 * @param instPort instance port
202 * @return a gateway node
203 */
204 public static OpenstackNode getGwByInstancePort(Set<OpenstackNode> gateways,
205 InstancePort instPort) {
206 OpenstackNode gw = null;
207 if (instPort != null && instPort.deviceId() != null) {
208 gw = getGwByComputeDevId(gateways, instPort.deviceId());
209 }
210 return gw;
211 }
212
213 /**
Jian Li1064e4f2018-05-29 16:16:53 +0900214 * Obtains the gateway node by device in compute node. Note that the gateway
215 * node is determined by device's device identifier.
216 *
217 * @param gws a collection of gateway nodes
218 * @param deviceId device identifier
219 * @return a gateway node
220 */
221 public static OpenstackNode getGwByComputeDevId(Set<OpenstackNode> gws, DeviceId deviceId) {
222 int numOfGw = gws.size();
223
224 if (numOfGw == 0) {
225 return null;
226 }
227
228 int gwIndex = Math.abs(deviceId.hashCode()) % numOfGw;
229
230 return getGwByIndex(gws, gwIndex);
231 }
232
Jian Li51b844c2018-05-31 10:59:03 +0900233 /**
234 * Obtains a connected openstack client.
235 *
236 * @param osNode openstack node
237 * @return a connected openstack client
238 */
239 public static OSClient getConnectedClient(OpenstackNode osNode) {
240 OpenstackAuth auth = osNode.authentication();
241 String endpoint = buildEndpoint(osNode);
242 Perspective perspective = auth.perspective();
Jian Li1064e4f2018-05-29 16:16:53 +0900243
Jian Li51b844c2018-05-31 10:59:03 +0900244 Config config = getSslConfig();
Jian Li1064e4f2018-05-29 16:16:53 +0900245
Jian Li51b844c2018-05-31 10:59:03 +0900246 try {
247 if (endpoint.contains(KEYSTONE_V2)) {
248 IOSClientBuilder.V2 builder = OSFactory.builderV2()
249 .endpoint(endpoint)
250 .tenantName(auth.project())
251 .credentials(auth.username(), auth.password())
252 .withConfig(config);
253
254 if (perspective != null) {
255 builder.perspective(getFacing(perspective));
256 }
257
258 return builder.authenticate();
259 } else if (endpoint.contains(KEYSTONE_V3)) {
260
261 Identifier project = Identifier.byName(auth.project());
262 Identifier domain = Identifier.byName(DOMAIN_DEFAULT);
263
264 IOSClientBuilder.V3 builder = OSFactory.builderV3()
265 .endpoint(endpoint)
266 .credentials(auth.username(), auth.password(), domain)
267 .scopeToProject(project, domain)
268 .withConfig(config);
269
270 if (perspective != null) {
271 builder.perspective(getFacing(perspective));
272 }
273
274 return builder.authenticate();
275 } else {
276 log.warn("Unrecognized keystone version type");
277 return null;
Jian Li1064e4f2018-05-29 16:16:53 +0900278 }
Jian Li51b844c2018-05-31 10:59:03 +0900279 } catch (AuthenticationException e) {
280 log.error("Authentication failed due to {}", e.toString());
281 return null;
Jian Li1064e4f2018-05-29 16:16:53 +0900282 }
Jian Li1064e4f2018-05-29 16:16:53 +0900283 }
Daniel Parkc4d06402018-05-28 15:57:37 +0900284
285 /**
286 * Extract the interface name with the supplied port.
287 *
288 * @param port port
289 * @return interface name
290 */
291 public static String getIntfNameFromPciAddress(Port port) {
Jian Li51b844c2018-05-31 10:59:03 +0900292
Daniel Park95985382018-07-23 11:38:07 +0900293
294 if (port.getProfile() == null || port.getProfile().isEmpty()) {
Jian Li51b844c2018-05-31 10:59:03 +0900295 log.error("Port profile is not found");
296 return null;
297 }
298
Daniel Park95985382018-07-23 11:38:07 +0900299 if (!port.getProfile().containsKey(PCISLOT) ||
300 Strings.isNullOrEmpty(port.getProfile().get(PCISLOT).toString())) {
Daniel Parkc4d06402018-05-28 15:57:37 +0900301 log.error("Failed to retrieve the interface name because of no pci_slot information from the port");
302 return null;
303 }
Jian Li51b844c2018-05-31 10:59:03 +0900304
Daniel Parkc4d06402018-05-28 15:57:37 +0900305 String busNumHex = port.getProfile().get(PCISLOT).toString().split(":")[1];
306 String busNumDecimal = String.valueOf(Integer.parseInt(busNumHex, HEX_RADIX));
307
308 String deviceNumHex = port.getProfile().get(PCISLOT).toString()
309 .split(":")[2]
310 .split("\\.")[0];
311 String deviceNumDecimal = String.valueOf(Integer.parseInt(deviceNumHex, HEX_RADIX));
312
313 String functionNumHex = port.getProfile().get(PCISLOT).toString()
314 .split(":")[2]
315 .split("\\.")[1];
316 String functionNumDecimal = String.valueOf(Integer.parseInt(functionNumHex, HEX_RADIX));
317
318 String intfName;
319
320 String vendorInfoForPort = String.valueOf(port.getProfile().get(PCI_VENDOR_INFO));
321
Daniel Park95985382018-07-23 11:38:07 +0900322 if (!portNamePrefixMap().containsKey(vendorInfoForPort)) {
323 log.error("Failed to retrieve the interface name because of no port name prefix for vendor ID {}",
324 vendorInfoForPort);
Daniel Parkc4d06402018-05-28 15:57:37 +0900325 return null;
326 }
Ray Milkey9dc57392018-06-08 08:52:31 -0700327 String portNamePrefix = portNamePrefixMap().get(vendorInfoForPort);
Jian Li51b844c2018-05-31 10:59:03 +0900328
Daniel Parkc4d06402018-05-28 15:57:37 +0900329 if (functionNumDecimal.equals(ZERO_FUNCTION_NUMBER)) {
330 intfName = portNamePrefix + busNumDecimal + PREFIX_DEVICE_NUMBER + deviceNumDecimal;
331 } else {
332 intfName = portNamePrefix + busNumDecimal + PREFIX_DEVICE_NUMBER + deviceNumDecimal
333 + PREFIX_FUNCTION_NUMBER + functionNumDecimal;
334 }
335
336 return intfName;
337 }
Jian Li51b844c2018-05-31 10:59:03 +0900338
339 /**
Jian Li0b564282018-06-20 00:50:53 +0900340 * Adds router interfaces to openstack admin service.
341 * TODO fix the logic to add router interface to router
342 *
343 * @param osPort port
344 * @param adminService openstack admin service
345 */
346 public static void addRouterIface(Port osPort, OpenstackRouterAdminService adminService) {
347 osPort.getFixedIps().forEach(p -> {
348 JsonNode jsonTree = new ObjectMapper().createObjectNode()
349 .put("id", osPort.getDeviceId())
350 .put("tenant_id", osPort.getTenantId())
351 .put("subnet_id", p.getSubnetId())
352 .put("port_id", osPort.getId());
353 try {
354 RouterInterface rIface = getContext(NeutronRouterInterface.class)
355 .readerFor(NeutronRouterInterface.class)
356 .readValue(jsonTree);
357 if (adminService.routerInterface(rIface.getPortId()) != null) {
358 adminService.updateRouterInterface(rIface);
359 } else {
360 adminService.addRouterInterface(rIface);
361 }
362 } catch (IOException ignore) {
363 }
364 });
365 }
366
367 /**
Jian Li7f70bb72018-07-06 23:35:30 +0900368 * Obtains the property value with specified property key name.
369 *
370 * @param properties a collection of properties
371 * @param name key name
372 * @return mapping value
373 */
374 public static String getPropertyValue(Set<ConfigProperty> properties, String name) {
375 Optional<ConfigProperty> property =
376 properties.stream().filter(p -> p.name().equals(name)).findFirst();
377 return property.map(ConfigProperty::value).orElse(null);
378 }
379
380 /**
Jian Lif1efbe52018-07-17 23:20:16 +0900381 * Prints out the JSON string in pretty format.
382 *
383 * @param mapper Object mapper
384 * @param jsonString JSON string
385 * @return pretty formatted JSON string
386 */
387 public static String prettyJson(ObjectMapper mapper, String jsonString) {
388 try {
389 Object jsonObject = mapper.readValue(jsonString, Object.class);
390 return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(jsonObject);
391 } catch (IOException e) {
392 log.debug("Json string parsing exception caused by {}", e);
393 }
394 return null;
395 }
396
397 /**
Jian Li7f024de2018-07-07 03:51:02 +0900398 * Checks the validity of ARP mode.
399 *
400 * @param arpMode ARP mode
401 * @return returns true if the ARP mode is valid, false otherwise
402 */
403 public static boolean checkArpMode(String arpMode) {
404
405 if (isNullOrEmpty(arpMode)) {
406 return false;
407 } else {
408 return arpMode.equals(PROXY_MODE) || arpMode.equals(BROADCAST_MODE);
409 }
410 }
411
412 /**
Jian Liec5c32b2018-07-13 14:28:58 +0900413 * Swaps current location with old location info.
414 * The revised instance port will be used to mod the flow rules after migration.
415 *
416 * @param instPort instance port
417 * @return location swapped instance port
418 */
419 public static InstancePort swapStaleLocation(InstancePort instPort) {
420 return DefaultInstancePort.builder()
421 .deviceId(instPort.oldDeviceId())
422 .portNumber(instPort.oldPortNumber())
423 .state(instPort.state())
424 .ipAddress(instPort.ipAddress())
425 .macAddress(instPort.macAddress())
426 .networkId(instPort.networkId())
427 .portId(instPort.portId())
428 .build();
429 }
430
431 /**
Jian Li51b844c2018-05-31 10:59:03 +0900432 * Builds up and a complete endpoint URL from gateway node.
433 *
434 * @param node gateway node
435 * @return a complete endpoint URL
436 */
437 private static String buildEndpoint(OpenstackNode node) {
438
439 OpenstackAuth auth = node.authentication();
440
441 StringBuilder endpointSb = new StringBuilder();
442 endpointSb.append(auth.protocol().name().toLowerCase());
443 endpointSb.append("://");
444 endpointSb.append(node.endPoint());
445 endpointSb.append(":");
446 endpointSb.append(auth.port());
447 endpointSb.append("/");
448
449 // in case the version is v3, we need to append identity path into endpoint
450 if (auth.version().equals(KEYSTONE_V3)) {
451 endpointSb.append(IDENTITY_PATH);
452 }
453
454 endpointSb.append(auth.version());
455 return endpointSb.toString();
456 }
457
458 /**
459 * Obtains the SSL config without verifying the certification.
460 *
461 * @return SSL config
462 */
463 private static Config getSslConfig() {
464 // we bypass the SSL certification verification for now
465 // TODO: verify server side SSL using a given certification
466 Config config = Config.newConfig().withSSLVerificationDisabled();
467
468 TrustManager[] trustAllCerts = new TrustManager[]{
469 new X509TrustManager() {
470 public X509Certificate[] getAcceptedIssuers() {
471 return null;
472 }
473
474 public void checkClientTrusted(X509Certificate[] certs,
475 String authType) {
476 }
477
478 public void checkServerTrusted(X509Certificate[] certs,
479 String authType) {
480 }
481 }
482 };
483
484 HostnameVerifier allHostsValid = (hostname, session) -> true;
485
486 try {
487 SSLContext sc = SSLContext.getInstance(SSL_TYPE);
488 sc.init(null, trustAllCerts,
489 new java.security.SecureRandom());
490 HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
491 HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid);
492
493 config.withSSLContext(sc);
494 } catch (Exception e) {
495 log.error("Failed to access OpenStack service due to {}", e.toString());
496 return null;
497 }
498
499 return config;
500 }
501
502 /**
503 * Obtains the facing object with given openstack perspective.
504 *
505 * @param perspective keystone perspective
506 * @return facing object
507 */
508 private static Facing getFacing(Perspective perspective) {
509
510 switch (perspective) {
511 case PUBLIC:
512 return Facing.PUBLIC;
513 case ADMIN:
514 return Facing.ADMIN;
515 case INTERNAL:
516 return Facing.INTERNAL;
517 default:
518 return null;
519 }
520 }
521
522 /**
523 * Obtains gateway instance by giving index number.
524 *
525 * @param gws a collection of gateway nodes
526 * @param index index number
527 * @return gateway instance
528 */
529 private static OpenstackNode getGwByIndex(Set<OpenstackNode> gws, int index) {
530 Map<String, OpenstackNode> hashMap = new HashMap<>();
531 gws.forEach(gw -> hashMap.put(gw.hostname(), gw));
532 TreeMap<String, OpenstackNode> treeMap = new TreeMap<>(hashMap);
533 Iterator<String> iteratorKey = treeMap.keySet().iterator();
534
535 int intIndex = 0;
536 OpenstackNode gw = null;
537 while (iteratorKey.hasNext()) {
538 String key = iteratorKey.next();
539
540 if (intIndex == index) {
541 gw = treeMap.get(key);
542 }
543 intIndex++;
544 }
545 return gw;
546 }
Jian Li091d8d22018-02-20 10:42:06 +0900547}