blob: c384a44ebb65d9433293bf735f1eda36d2394d3d [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
Jian Lidea0fdb2018-04-02 19:02:48 +090075 protected 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());
135 } catch (Exception e) {
136 throw new IllegalStateException();
137 }
138 }
Jian Li1064e4f2018-05-29 16:16:53 +0900139
140 /**
Jian Li24ec59f2018-05-23 19:01:25 +0900141 * Obtains a floating IP associated with the given instance port.
142 *
143 * @param port instance port
144 * @param fips a collection of floating IPs
145 * @return associated floating IP
146 */
147 public static NetFloatingIP associatedFloatingIp(InstancePort port,
148 Set<NetFloatingIP> fips) {
149 for (NetFloatingIP fip : fips) {
150 if (Strings.isNullOrEmpty(fip.getFixedIpAddress())) {
151 continue;
152 }
153 if (Strings.isNullOrEmpty(fip.getFloatingIpAddress())) {
154 continue;
155 }
156 if (fip.getFixedIpAddress().equals(port.ipAddress().toString())) {
157 return fip;
158 }
159 }
160 return null;
161 }
162
163 /**
164 * Checks whether the given floating IP is associated with a VM.
165 *
166 * @param service openstack network service
167 * @param fip floating IP
168 * @return true if the given floating IP associated with a VM, false otherwise
169 */
170 public static boolean isAssociatedWithVM(OpenstackNetworkService service,
171 NetFloatingIP fip) {
172 Port osPort = service.port(fip.getPortId());
173 if (osPort == null) {
174 return false;
175 }
176
177 if (!Strings.isNullOrEmpty(osPort.getDeviceId())) {
178 Network osNet = service.network(osPort.getNetworkId());
179 if (osNet == null) {
180 final String errorFormat = ERR_FLOW + "no network(%s) exists";
181 final String error = String.format(errorFormat,
182 fip.getFloatingIpAddress(), osPort.getNetworkId());
183 throw new IllegalStateException(error);
184 }
185 return true;
186 } else {
187 return false;
188 }
189 }
190
191 /**
Jian Lia171a432018-06-11 11:52:11 +0900192 * Obtains the gateway node by instance port.
193 *
194 * @param gateways a collection of gateway nodes
195 * @param instPort instance port
196 * @return a gateway node
197 */
198 public static OpenstackNode getGwByInstancePort(Set<OpenstackNode> gateways,
199 InstancePort instPort) {
200 OpenstackNode gw = null;
201 if (instPort != null && instPort.deviceId() != null) {
202 gw = getGwByComputeDevId(gateways, instPort.deviceId());
203 }
204 return gw;
205 }
206
207 /**
Jian Li1064e4f2018-05-29 16:16:53 +0900208 * Obtains the gateway node by device in compute node. Note that the gateway
209 * node is determined by device's device identifier.
210 *
211 * @param gws a collection of gateway nodes
212 * @param deviceId device identifier
213 * @return a gateway node
214 */
215 public static OpenstackNode getGwByComputeDevId(Set<OpenstackNode> gws, DeviceId deviceId) {
216 int numOfGw = gws.size();
217
218 if (numOfGw == 0) {
219 return null;
220 }
221
222 int gwIndex = Math.abs(deviceId.hashCode()) % numOfGw;
223
224 return getGwByIndex(gws, gwIndex);
225 }
226
Jian Li51b844c2018-05-31 10:59:03 +0900227 /**
228 * Obtains a connected openstack client.
229 *
230 * @param osNode openstack node
231 * @return a connected openstack client
232 */
233 public static OSClient getConnectedClient(OpenstackNode osNode) {
234 OpenstackAuth auth = osNode.authentication();
235 String endpoint = buildEndpoint(osNode);
236 Perspective perspective = auth.perspective();
Jian Li1064e4f2018-05-29 16:16:53 +0900237
Jian Li51b844c2018-05-31 10:59:03 +0900238 Config config = getSslConfig();
Jian Li1064e4f2018-05-29 16:16:53 +0900239
Jian Li51b844c2018-05-31 10:59:03 +0900240 try {
241 if (endpoint.contains(KEYSTONE_V2)) {
242 IOSClientBuilder.V2 builder = OSFactory.builderV2()
243 .endpoint(endpoint)
244 .tenantName(auth.project())
245 .credentials(auth.username(), auth.password())
246 .withConfig(config);
247
248 if (perspective != null) {
249 builder.perspective(getFacing(perspective));
250 }
251
252 return builder.authenticate();
253 } else if (endpoint.contains(KEYSTONE_V3)) {
254
255 Identifier project = Identifier.byName(auth.project());
256 Identifier domain = Identifier.byName(DOMAIN_DEFAULT);
257
258 IOSClientBuilder.V3 builder = OSFactory.builderV3()
259 .endpoint(endpoint)
260 .credentials(auth.username(), auth.password(), domain)
261 .scopeToProject(project, domain)
262 .withConfig(config);
263
264 if (perspective != null) {
265 builder.perspective(getFacing(perspective));
266 }
267
268 return builder.authenticate();
269 } else {
270 log.warn("Unrecognized keystone version type");
271 return null;
Jian Li1064e4f2018-05-29 16:16:53 +0900272 }
Jian Li51b844c2018-05-31 10:59:03 +0900273 } catch (AuthenticationException e) {
274 log.error("Authentication failed due to {}", e.toString());
275 return null;
Jian Li1064e4f2018-05-29 16:16:53 +0900276 }
Jian Li1064e4f2018-05-29 16:16:53 +0900277 }
Daniel Parkc4d06402018-05-28 15:57:37 +0900278
279 /**
280 * Extract the interface name with the supplied port.
281 *
282 * @param port port
283 * @return interface name
284 */
285 public static String getIntfNameFromPciAddress(Port port) {
Jian Li51b844c2018-05-31 10:59:03 +0900286
287 if (port.getProfile() == null) {
288 log.error("Port profile is not found");
289 return null;
290 }
291
Daniel Parkc4d06402018-05-28 15:57:37 +0900292 if (port.getProfile() != null && port.getProfile().get(PCISLOT) == null) {
293 log.error("Failed to retrieve the interface name because of no pci_slot information from the port");
294 return null;
295 }
Jian Li51b844c2018-05-31 10:59:03 +0900296
Daniel Parkc4d06402018-05-28 15:57:37 +0900297 String busNumHex = port.getProfile().get(PCISLOT).toString().split(":")[1];
298 String busNumDecimal = String.valueOf(Integer.parseInt(busNumHex, HEX_RADIX));
299
300 String deviceNumHex = port.getProfile().get(PCISLOT).toString()
301 .split(":")[2]
302 .split("\\.")[0];
303 String deviceNumDecimal = String.valueOf(Integer.parseInt(deviceNumHex, HEX_RADIX));
304
305 String functionNumHex = port.getProfile().get(PCISLOT).toString()
306 .split(":")[2]
307 .split("\\.")[1];
308 String functionNumDecimal = String.valueOf(Integer.parseInt(functionNumHex, HEX_RADIX));
309
310 String intfName;
311
312 String vendorInfoForPort = String.valueOf(port.getProfile().get(PCI_VENDOR_INFO));
313
314 if (vendorInfoForPort == null) {
315 log.error("Failed to retrieve the interface name because of no pci vendor information from the port");
316 return null;
317 }
Ray Milkey9dc57392018-06-08 08:52:31 -0700318 String portNamePrefix = portNamePrefixMap().get(vendorInfoForPort);
Jian Li51b844c2018-05-31 10:59:03 +0900319
Daniel Parkc4d06402018-05-28 15:57:37 +0900320 if (functionNumDecimal.equals(ZERO_FUNCTION_NUMBER)) {
321 intfName = portNamePrefix + busNumDecimal + PREFIX_DEVICE_NUMBER + deviceNumDecimal;
322 } else {
323 intfName = portNamePrefix + busNumDecimal + PREFIX_DEVICE_NUMBER + deviceNumDecimal
324 + PREFIX_FUNCTION_NUMBER + functionNumDecimal;
325 }
326
327 return intfName;
328 }
Jian Li51b844c2018-05-31 10:59:03 +0900329
330 /**
Jian Li0b564282018-06-20 00:50:53 +0900331 * Adds router interfaces to openstack admin service.
332 * TODO fix the logic to add router interface to router
333 *
334 * @param osPort port
335 * @param adminService openstack admin service
336 */
337 public static void addRouterIface(Port osPort, OpenstackRouterAdminService adminService) {
338 osPort.getFixedIps().forEach(p -> {
339 JsonNode jsonTree = new ObjectMapper().createObjectNode()
340 .put("id", osPort.getDeviceId())
341 .put("tenant_id", osPort.getTenantId())
342 .put("subnet_id", p.getSubnetId())
343 .put("port_id", osPort.getId());
344 try {
345 RouterInterface rIface = getContext(NeutronRouterInterface.class)
346 .readerFor(NeutronRouterInterface.class)
347 .readValue(jsonTree);
348 if (adminService.routerInterface(rIface.getPortId()) != null) {
349 adminService.updateRouterInterface(rIface);
350 } else {
351 adminService.addRouterInterface(rIface);
352 }
353 } catch (IOException ignore) {
354 }
355 });
356 }
357
358 /**
Jian Li7f70bb72018-07-06 23:35:30 +0900359 * Obtains the property value with specified property key name.
360 *
361 * @param properties a collection of properties
362 * @param name key name
363 * @return mapping value
364 */
365 public static String getPropertyValue(Set<ConfigProperty> properties, String name) {
366 Optional<ConfigProperty> property =
367 properties.stream().filter(p -> p.name().equals(name)).findFirst();
368 return property.map(ConfigProperty::value).orElse(null);
369 }
370
371 /**
Jian Li7f024de2018-07-07 03:51:02 +0900372 * Checks the validity of ARP mode.
373 *
374 * @param arpMode ARP mode
375 * @return returns true if the ARP mode is valid, false otherwise
376 */
377 public static boolean checkArpMode(String arpMode) {
378
379 if (isNullOrEmpty(arpMode)) {
380 return false;
381 } else {
382 return arpMode.equals(PROXY_MODE) || arpMode.equals(BROADCAST_MODE);
383 }
384 }
385
386 /**
Jian Liec5c32b2018-07-13 14:28:58 +0900387 * Swaps current location with old location info.
388 * The revised instance port will be used to mod the flow rules after migration.
389 *
390 * @param instPort instance port
391 * @return location swapped instance port
392 */
393 public static InstancePort swapStaleLocation(InstancePort instPort) {
394 return DefaultInstancePort.builder()
395 .deviceId(instPort.oldDeviceId())
396 .portNumber(instPort.oldPortNumber())
397 .state(instPort.state())
398 .ipAddress(instPort.ipAddress())
399 .macAddress(instPort.macAddress())
400 .networkId(instPort.networkId())
401 .portId(instPort.portId())
402 .build();
403 }
404
405 /**
Jian Li51b844c2018-05-31 10:59:03 +0900406 * Builds up and a complete endpoint URL from gateway node.
407 *
408 * @param node gateway node
409 * @return a complete endpoint URL
410 */
411 private static String buildEndpoint(OpenstackNode node) {
412
413 OpenstackAuth auth = node.authentication();
414
415 StringBuilder endpointSb = new StringBuilder();
416 endpointSb.append(auth.protocol().name().toLowerCase());
417 endpointSb.append("://");
418 endpointSb.append(node.endPoint());
419 endpointSb.append(":");
420 endpointSb.append(auth.port());
421 endpointSb.append("/");
422
423 // in case the version is v3, we need to append identity path into endpoint
424 if (auth.version().equals(KEYSTONE_V3)) {
425 endpointSb.append(IDENTITY_PATH);
426 }
427
428 endpointSb.append(auth.version());
429 return endpointSb.toString();
430 }
431
432 /**
433 * Obtains the SSL config without verifying the certification.
434 *
435 * @return SSL config
436 */
437 private static Config getSslConfig() {
438 // we bypass the SSL certification verification for now
439 // TODO: verify server side SSL using a given certification
440 Config config = Config.newConfig().withSSLVerificationDisabled();
441
442 TrustManager[] trustAllCerts = new TrustManager[]{
443 new X509TrustManager() {
444 public X509Certificate[] getAcceptedIssuers() {
445 return null;
446 }
447
448 public void checkClientTrusted(X509Certificate[] certs,
449 String authType) {
450 }
451
452 public void checkServerTrusted(X509Certificate[] certs,
453 String authType) {
454 }
455 }
456 };
457
458 HostnameVerifier allHostsValid = (hostname, session) -> true;
459
460 try {
461 SSLContext sc = SSLContext.getInstance(SSL_TYPE);
462 sc.init(null, trustAllCerts,
463 new java.security.SecureRandom());
464 HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
465 HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid);
466
467 config.withSSLContext(sc);
468 } catch (Exception e) {
469 log.error("Failed to access OpenStack service due to {}", e.toString());
470 return null;
471 }
472
473 return config;
474 }
475
476 /**
477 * Obtains the facing object with given openstack perspective.
478 *
479 * @param perspective keystone perspective
480 * @return facing object
481 */
482 private static Facing getFacing(Perspective perspective) {
483
484 switch (perspective) {
485 case PUBLIC:
486 return Facing.PUBLIC;
487 case ADMIN:
488 return Facing.ADMIN;
489 case INTERNAL:
490 return Facing.INTERNAL;
491 default:
492 return null;
493 }
494 }
495
496 /**
497 * Obtains gateway instance by giving index number.
498 *
499 * @param gws a collection of gateway nodes
500 * @param index index number
501 * @return gateway instance
502 */
503 private static OpenstackNode getGwByIndex(Set<OpenstackNode> gws, int index) {
504 Map<String, OpenstackNode> hashMap = new HashMap<>();
505 gws.forEach(gw -> hashMap.put(gw.hostname(), gw));
506 TreeMap<String, OpenstackNode> treeMap = new TreeMap<>(hashMap);
507 Iterator<String> iteratorKey = treeMap.keySet().iterator();
508
509 int intIndex = 0;
510 OpenstackNode gw = null;
511 while (iteratorKey.hasNext()) {
512 String key = iteratorKey.next();
513
514 if (intIndex == index) {
515 gw = treeMap.get(key);
516 }
517 intIndex++;
518 }
519 return gw;
520 }
Jian Li091d8d22018-02-20 10:42:06 +0900521}