blob: 609b580fd59a78e668af0ed9335f7448f6fb4849 [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 Li51b844c2018-05-31 10:59:03 +090027import org.onosproject.openstacknode.api.OpenstackAuth;
28import org.onosproject.openstacknode.api.OpenstackAuth.Perspective;
Jian Li1064e4f2018-05-29 16:16:53 +090029import org.onosproject.openstacknode.api.OpenstackNode;
Jian Li51b844c2018-05-31 10:59:03 +090030import org.openstack4j.api.OSClient;
31import org.openstack4j.api.client.IOSClientBuilder;
32import org.openstack4j.api.exceptions.AuthenticationException;
33import org.openstack4j.api.types.Facing;
34import org.openstack4j.core.transport.Config;
Jian Li091d8d22018-02-20 10:42:06 +090035import org.openstack4j.core.transport.ObjectMapperSingleton;
36import org.openstack4j.model.ModelEntity;
Jian Li51b844c2018-05-31 10:59:03 +090037import org.openstack4j.model.common.Identifier;
Jian Li24ec59f2018-05-23 19:01:25 +090038import org.openstack4j.model.network.NetFloatingIP;
39import org.openstack4j.model.network.Network;
Jian Lia171a432018-06-11 11:52:11 +090040import org.openstack4j.model.network.Port;
Jian Li0b564282018-06-20 00:50:53 +090041import org.openstack4j.model.network.RouterInterface;
Jian Li51b844c2018-05-31 10:59:03 +090042import org.openstack4j.openstack.OSFactory;
Jian Li0b564282018-06-20 00:50:53 +090043import org.openstack4j.openstack.networking.domain.NeutronRouterInterface;
Jian Li091d8d22018-02-20 10:42:06 +090044import org.slf4j.Logger;
45import org.slf4j.LoggerFactory;
46
Jian Li51b844c2018-05-31 10:59:03 +090047import javax.net.ssl.HostnameVerifier;
48import javax.net.ssl.HttpsURLConnection;
49import javax.net.ssl.SSLContext;
50import javax.net.ssl.TrustManager;
51import javax.net.ssl.X509TrustManager;
Jian Li0b564282018-06-20 00:50:53 +090052import java.io.IOException;
Jian Li091d8d22018-02-20 10:42:06 +090053import java.io.InputStream;
Jian Li51b844c2018-05-31 10:59:03 +090054import java.security.cert.X509Certificate;
Jian Li1064e4f2018-05-29 16:16:53 +090055import java.util.HashMap;
56import java.util.Iterator;
57import java.util.Map;
Jian Li7f70bb72018-07-06 23:35:30 +090058import java.util.Optional;
Jian Li1064e4f2018-05-29 16:16:53 +090059import java.util.Set;
60import java.util.TreeMap;
Jian Li091d8d22018-02-20 10:42:06 +090061
62import static com.fasterxml.jackson.databind.SerializationFeature.INDENT_OUTPUT;
Jian Li7f024de2018-07-07 03:51:02 +090063import static com.google.common.base.Strings.isNullOrEmpty;
Daniel Parkc4d06402018-05-28 15:57:37 +090064import static org.onosproject.openstacknetworking.api.Constants.PCISLOT;
65import static org.onosproject.openstacknetworking.api.Constants.PCI_VENDOR_INFO;
Ray Milkey9dc57392018-06-08 08:52:31 -070066import static org.onosproject.openstacknetworking.api.Constants.portNamePrefixMap;
Jian Li0b564282018-06-20 00:50:53 +090067import static org.openstack4j.core.transport.ObjectMapperSingleton.getContext;
Jian Li091d8d22018-02-20 10:42:06 +090068
69/**
70 * An utility that used in openstack networking app.
71 */
Jian Lidea0fdb2018-04-02 19:02:48 +090072public final class OpenstackNetworkingUtil {
Jian Li091d8d22018-02-20 10:42:06 +090073
Jian Lidea0fdb2018-04-02 19:02:48 +090074 protected static final Logger log = LoggerFactory.getLogger(OpenstackNetworkingUtil.class);
Jian Li091d8d22018-02-20 10:42:06 +090075
Daniel Parkc4d06402018-05-28 15:57:37 +090076 private static final int HEX_RADIX = 16;
Jian Li51b844c2018-05-31 10:59:03 +090077 private static final String ZERO_FUNCTION_NUMBER = "0";
Daniel Parkc4d06402018-05-28 15:57:37 +090078 private static final String PREFIX_DEVICE_NUMBER = "s";
79 private static final String PREFIX_FUNCTION_NUMBER = "f";
80
Jian Li51b844c2018-05-31 10:59:03 +090081 // keystone endpoint related variables
82 private static final String DOMAIN_DEFAULT = "default";
83 private static final String KEYSTONE_V2 = "v2.0";
84 private static final String KEYSTONE_V3 = "v3";
85 private static final String IDENTITY_PATH = "identity/";
86 private static final String SSL_TYPE = "SSL";
87
Jian Li7f024de2018-07-07 03:51:02 +090088 private static final String PROXY_MODE = "proxy";
89 private static final String BROADCAST_MODE = "broadcast";
90
Jian Li24ec59f2018-05-23 19:01:25 +090091 private static final String ERR_FLOW = "Failed set flows for floating IP %s: ";
92
Jian Li091d8d22018-02-20 10:42:06 +090093 /**
94 * Prevents object instantiation from external.
95 */
Jian Lidea0fdb2018-04-02 19:02:48 +090096 private OpenstackNetworkingUtil() {
Jian Li091d8d22018-02-20 10:42:06 +090097 }
98
99 /**
100 * Interprets JSON string to corresponding openstack model entity object.
101 *
102 * @param input JSON string
103 * @param entityClazz openstack model entity class
104 * @return openstack model entity object
105 */
106 public static ModelEntity jsonToModelEntity(InputStream input, Class entityClazz) {
107 ObjectMapper mapper = new ObjectMapper();
108 try {
109 JsonNode jsonTree = mapper.enable(INDENT_OUTPUT).readTree(input);
110 log.trace(new ObjectMapper().writeValueAsString(jsonTree));
111 return ObjectMapperSingleton.getContext(entityClazz)
112 .readerFor(entityClazz)
113 .readValue(jsonTree);
114 } catch (Exception e) {
115 throw new IllegalArgumentException();
116 }
117 }
Jian Lieb9f77d2018-02-20 11:25:45 +0900118
119 /**
120 * Converts openstack model entity object into JSON object.
121 *
122 * @param entity openstack model entity object
123 * @param entityClazz openstack model entity class
124 * @return JSON object
125 */
126 public static ObjectNode modelEntityToJson(ModelEntity entity, Class entityClazz) {
127 ObjectMapper mapper = new ObjectMapper();
128 try {
129 String strModelEntity = ObjectMapperSingleton.getContext(entityClazz)
130 .writerFor(entityClazz)
131 .writeValueAsString(entity);
132 log.trace(strModelEntity);
133 return (ObjectNode) mapper.readTree(strModelEntity.getBytes());
134 } catch (Exception e) {
135 throw new IllegalStateException();
136 }
137 }
Jian Li1064e4f2018-05-29 16:16:53 +0900138
139 /**
Jian Li24ec59f2018-05-23 19:01:25 +0900140 * Obtains a floating IP associated with the given instance port.
141 *
142 * @param port instance port
143 * @param fips a collection of floating IPs
144 * @return associated floating IP
145 */
146 public static NetFloatingIP associatedFloatingIp(InstancePort port,
147 Set<NetFloatingIP> fips) {
148 for (NetFloatingIP fip : fips) {
149 if (Strings.isNullOrEmpty(fip.getFixedIpAddress())) {
150 continue;
151 }
152 if (Strings.isNullOrEmpty(fip.getFloatingIpAddress())) {
153 continue;
154 }
155 if (fip.getFixedIpAddress().equals(port.ipAddress().toString())) {
156 return fip;
157 }
158 }
159 return null;
160 }
161
162 /**
163 * Checks whether the given floating IP is associated with a VM.
164 *
165 * @param service openstack network service
166 * @param fip floating IP
167 * @return true if the given floating IP associated with a VM, false otherwise
168 */
169 public static boolean isAssociatedWithVM(OpenstackNetworkService service,
170 NetFloatingIP fip) {
171 Port osPort = service.port(fip.getPortId());
172 if (osPort == null) {
173 return false;
174 }
175
176 if (!Strings.isNullOrEmpty(osPort.getDeviceId())) {
177 Network osNet = service.network(osPort.getNetworkId());
178 if (osNet == null) {
179 final String errorFormat = ERR_FLOW + "no network(%s) exists";
180 final String error = String.format(errorFormat,
181 fip.getFloatingIpAddress(), osPort.getNetworkId());
182 throw new IllegalStateException(error);
183 }
184 return true;
185 } else {
186 return false;
187 }
188 }
189
190 /**
Jian Lia171a432018-06-11 11:52:11 +0900191 * Obtains the gateway node by instance port.
192 *
193 * @param gateways a collection of gateway nodes
194 * @param instPort instance port
195 * @return a gateway node
196 */
197 public static OpenstackNode getGwByInstancePort(Set<OpenstackNode> gateways,
198 InstancePort instPort) {
199 OpenstackNode gw = null;
200 if (instPort != null && instPort.deviceId() != null) {
201 gw = getGwByComputeDevId(gateways, instPort.deviceId());
202 }
203 return gw;
204 }
205
206 /**
Jian Li1064e4f2018-05-29 16:16:53 +0900207 * Obtains the gateway node by device in compute node. Note that the gateway
208 * node is determined by device's device identifier.
209 *
210 * @param gws a collection of gateway nodes
211 * @param deviceId device identifier
212 * @return a gateway node
213 */
214 public static OpenstackNode getGwByComputeDevId(Set<OpenstackNode> gws, DeviceId deviceId) {
215 int numOfGw = gws.size();
216
217 if (numOfGw == 0) {
218 return null;
219 }
220
221 int gwIndex = Math.abs(deviceId.hashCode()) % numOfGw;
222
223 return getGwByIndex(gws, gwIndex);
224 }
225
Jian Li51b844c2018-05-31 10:59:03 +0900226 /**
227 * Obtains a connected openstack client.
228 *
229 * @param osNode openstack node
230 * @return a connected openstack client
231 */
232 public static OSClient getConnectedClient(OpenstackNode osNode) {
233 OpenstackAuth auth = osNode.authentication();
234 String endpoint = buildEndpoint(osNode);
235 Perspective perspective = auth.perspective();
Jian Li1064e4f2018-05-29 16:16:53 +0900236
Jian Li51b844c2018-05-31 10:59:03 +0900237 Config config = getSslConfig();
Jian Li1064e4f2018-05-29 16:16:53 +0900238
Jian Li51b844c2018-05-31 10:59:03 +0900239 try {
240 if (endpoint.contains(KEYSTONE_V2)) {
241 IOSClientBuilder.V2 builder = OSFactory.builderV2()
242 .endpoint(endpoint)
243 .tenantName(auth.project())
244 .credentials(auth.username(), auth.password())
245 .withConfig(config);
246
247 if (perspective != null) {
248 builder.perspective(getFacing(perspective));
249 }
250
251 return builder.authenticate();
252 } else if (endpoint.contains(KEYSTONE_V3)) {
253
254 Identifier project = Identifier.byName(auth.project());
255 Identifier domain = Identifier.byName(DOMAIN_DEFAULT);
256
257 IOSClientBuilder.V3 builder = OSFactory.builderV3()
258 .endpoint(endpoint)
259 .credentials(auth.username(), auth.password(), domain)
260 .scopeToProject(project, domain)
261 .withConfig(config);
262
263 if (perspective != null) {
264 builder.perspective(getFacing(perspective));
265 }
266
267 return builder.authenticate();
268 } else {
269 log.warn("Unrecognized keystone version type");
270 return null;
Jian Li1064e4f2018-05-29 16:16:53 +0900271 }
Jian Li51b844c2018-05-31 10:59:03 +0900272 } catch (AuthenticationException e) {
273 log.error("Authentication failed due to {}", e.toString());
274 return null;
Jian Li1064e4f2018-05-29 16:16:53 +0900275 }
Jian Li1064e4f2018-05-29 16:16:53 +0900276 }
Daniel Parkc4d06402018-05-28 15:57:37 +0900277
278 /**
279 * Extract the interface name with the supplied port.
280 *
281 * @param port port
282 * @return interface name
283 */
284 public static String getIntfNameFromPciAddress(Port port) {
Jian Li51b844c2018-05-31 10:59:03 +0900285
286 if (port.getProfile() == null) {
287 log.error("Port profile is not found");
288 return null;
289 }
290
Daniel Parkc4d06402018-05-28 15:57:37 +0900291 if (port.getProfile() != null && port.getProfile().get(PCISLOT) == null) {
292 log.error("Failed to retrieve the interface name because of no pci_slot information from the port");
293 return null;
294 }
Jian Li51b844c2018-05-31 10:59:03 +0900295
Daniel Parkc4d06402018-05-28 15:57:37 +0900296 String busNumHex = port.getProfile().get(PCISLOT).toString().split(":")[1];
297 String busNumDecimal = String.valueOf(Integer.parseInt(busNumHex, HEX_RADIX));
298
299 String deviceNumHex = port.getProfile().get(PCISLOT).toString()
300 .split(":")[2]
301 .split("\\.")[0];
302 String deviceNumDecimal = String.valueOf(Integer.parseInt(deviceNumHex, HEX_RADIX));
303
304 String functionNumHex = port.getProfile().get(PCISLOT).toString()
305 .split(":")[2]
306 .split("\\.")[1];
307 String functionNumDecimal = String.valueOf(Integer.parseInt(functionNumHex, HEX_RADIX));
308
309 String intfName;
310
311 String vendorInfoForPort = String.valueOf(port.getProfile().get(PCI_VENDOR_INFO));
312
313 if (vendorInfoForPort == null) {
314 log.error("Failed to retrieve the interface name because of no pci vendor information from the port");
315 return null;
316 }
Ray Milkey9dc57392018-06-08 08:52:31 -0700317 String portNamePrefix = portNamePrefixMap().get(vendorInfoForPort);
Jian Li51b844c2018-05-31 10:59:03 +0900318
Daniel Parkc4d06402018-05-28 15:57:37 +0900319 if (functionNumDecimal.equals(ZERO_FUNCTION_NUMBER)) {
320 intfName = portNamePrefix + busNumDecimal + PREFIX_DEVICE_NUMBER + deviceNumDecimal;
321 } else {
322 intfName = portNamePrefix + busNumDecimal + PREFIX_DEVICE_NUMBER + deviceNumDecimal
323 + PREFIX_FUNCTION_NUMBER + functionNumDecimal;
324 }
325
326 return intfName;
327 }
Jian Li51b844c2018-05-31 10:59:03 +0900328
329 /**
Jian Li0b564282018-06-20 00:50:53 +0900330 * Adds router interfaces to openstack admin service.
331 * TODO fix the logic to add router interface to router
332 *
333 * @param osPort port
334 * @param adminService openstack admin service
335 */
336 public static void addRouterIface(Port osPort, OpenstackRouterAdminService adminService) {
337 osPort.getFixedIps().forEach(p -> {
338 JsonNode jsonTree = new ObjectMapper().createObjectNode()
339 .put("id", osPort.getDeviceId())
340 .put("tenant_id", osPort.getTenantId())
341 .put("subnet_id", p.getSubnetId())
342 .put("port_id", osPort.getId());
343 try {
344 RouterInterface rIface = getContext(NeutronRouterInterface.class)
345 .readerFor(NeutronRouterInterface.class)
346 .readValue(jsonTree);
347 if (adminService.routerInterface(rIface.getPortId()) != null) {
348 adminService.updateRouterInterface(rIface);
349 } else {
350 adminService.addRouterInterface(rIface);
351 }
352 } catch (IOException ignore) {
353 }
354 });
355 }
356
357 /**
Jian Li7f70bb72018-07-06 23:35:30 +0900358 * Obtains the property value with specified property key name.
359 *
360 * @param properties a collection of properties
361 * @param name key name
362 * @return mapping value
363 */
364 public static String getPropertyValue(Set<ConfigProperty> properties, String name) {
365 Optional<ConfigProperty> property =
366 properties.stream().filter(p -> p.name().equals(name)).findFirst();
367 return property.map(ConfigProperty::value).orElse(null);
368 }
369
370 /**
Jian Li7f024de2018-07-07 03:51:02 +0900371 * Checks the validity of ARP mode.
372 *
373 * @param arpMode ARP mode
374 * @return returns true if the ARP mode is valid, false otherwise
375 */
376 public static boolean checkArpMode(String arpMode) {
377
378 if (isNullOrEmpty(arpMode)) {
379 return false;
380 } else {
381 return arpMode.equals(PROXY_MODE) || arpMode.equals(BROADCAST_MODE);
382 }
383 }
384
385 /**
Jian Li51b844c2018-05-31 10:59:03 +0900386 * Builds up and a complete endpoint URL from gateway node.
387 *
388 * @param node gateway node
389 * @return a complete endpoint URL
390 */
391 private static String buildEndpoint(OpenstackNode node) {
392
393 OpenstackAuth auth = node.authentication();
394
395 StringBuilder endpointSb = new StringBuilder();
396 endpointSb.append(auth.protocol().name().toLowerCase());
397 endpointSb.append("://");
398 endpointSb.append(node.endPoint());
399 endpointSb.append(":");
400 endpointSb.append(auth.port());
401 endpointSb.append("/");
402
403 // in case the version is v3, we need to append identity path into endpoint
404 if (auth.version().equals(KEYSTONE_V3)) {
405 endpointSb.append(IDENTITY_PATH);
406 }
407
408 endpointSb.append(auth.version());
409 return endpointSb.toString();
410 }
411
412 /**
413 * Obtains the SSL config without verifying the certification.
414 *
415 * @return SSL config
416 */
417 private static Config getSslConfig() {
418 // we bypass the SSL certification verification for now
419 // TODO: verify server side SSL using a given certification
420 Config config = Config.newConfig().withSSLVerificationDisabled();
421
422 TrustManager[] trustAllCerts = new TrustManager[]{
423 new X509TrustManager() {
424 public X509Certificate[] getAcceptedIssuers() {
425 return null;
426 }
427
428 public void checkClientTrusted(X509Certificate[] certs,
429 String authType) {
430 }
431
432 public void checkServerTrusted(X509Certificate[] certs,
433 String authType) {
434 }
435 }
436 };
437
438 HostnameVerifier allHostsValid = (hostname, session) -> true;
439
440 try {
441 SSLContext sc = SSLContext.getInstance(SSL_TYPE);
442 sc.init(null, trustAllCerts,
443 new java.security.SecureRandom());
444 HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
445 HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid);
446
447 config.withSSLContext(sc);
448 } catch (Exception e) {
449 log.error("Failed to access OpenStack service due to {}", e.toString());
450 return null;
451 }
452
453 return config;
454 }
455
456 /**
457 * Obtains the facing object with given openstack perspective.
458 *
459 * @param perspective keystone perspective
460 * @return facing object
461 */
462 private static Facing getFacing(Perspective perspective) {
463
464 switch (perspective) {
465 case PUBLIC:
466 return Facing.PUBLIC;
467 case ADMIN:
468 return Facing.ADMIN;
469 case INTERNAL:
470 return Facing.INTERNAL;
471 default:
472 return null;
473 }
474 }
475
476 /**
477 * Obtains gateway instance by giving index number.
478 *
479 * @param gws a collection of gateway nodes
480 * @param index index number
481 * @return gateway instance
482 */
483 private static OpenstackNode getGwByIndex(Set<OpenstackNode> gws, int index) {
484 Map<String, OpenstackNode> hashMap = new HashMap<>();
485 gws.forEach(gw -> hashMap.put(gw.hostname(), gw));
486 TreeMap<String, OpenstackNode> treeMap = new TreeMap<>(hashMap);
487 Iterator<String> iteratorKey = treeMap.keySet().iterator();
488
489 int intIndex = 0;
490 OpenstackNode gw = null;
491 while (iteratorKey.hasNext()) {
492 String key = iteratorKey.next();
493
494 if (intIndex == index) {
495 gw = treeMap.get(key);
496 }
497 intIndex++;
498 }
499 return gw;
500 }
Jian Li091d8d22018-02-20 10:42:06 +0900501}