blob: 2d8bd12082caafc05db30d5c2a3b4b41a3edca72 [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;
Daniel Parkc4d06402018-05-28 15:57:37 +090063import static org.onosproject.openstacknetworking.api.Constants.PCISLOT;
64import static org.onosproject.openstacknetworking.api.Constants.PCI_VENDOR_INFO;
Ray Milkey9dc57392018-06-08 08:52:31 -070065import static org.onosproject.openstacknetworking.api.Constants.portNamePrefixMap;
Jian Li0b564282018-06-20 00:50:53 +090066import static org.openstack4j.core.transport.ObjectMapperSingleton.getContext;
Jian Li091d8d22018-02-20 10:42:06 +090067
68/**
69 * An utility that used in openstack networking app.
70 */
Jian Lidea0fdb2018-04-02 19:02:48 +090071public final class OpenstackNetworkingUtil {
Jian Li091d8d22018-02-20 10:42:06 +090072
Jian Lidea0fdb2018-04-02 19:02:48 +090073 protected static final Logger log = LoggerFactory.getLogger(OpenstackNetworkingUtil.class);
Jian Li091d8d22018-02-20 10:42:06 +090074
Daniel Parkc4d06402018-05-28 15:57:37 +090075 private static final int HEX_RADIX = 16;
Jian Li51b844c2018-05-31 10:59:03 +090076 private static final String ZERO_FUNCTION_NUMBER = "0";
Daniel Parkc4d06402018-05-28 15:57:37 +090077 private static final String PREFIX_DEVICE_NUMBER = "s";
78 private static final String PREFIX_FUNCTION_NUMBER = "f";
79
Jian Li51b844c2018-05-31 10:59:03 +090080 // keystone endpoint related variables
81 private static final String DOMAIN_DEFAULT = "default";
82 private static final String KEYSTONE_V2 = "v2.0";
83 private static final String KEYSTONE_V3 = "v3";
84 private static final String IDENTITY_PATH = "identity/";
85 private static final String SSL_TYPE = "SSL";
86
Jian Li24ec59f2018-05-23 19:01:25 +090087 private static final String ERR_FLOW = "Failed set flows for floating IP %s: ";
88
Jian Li091d8d22018-02-20 10:42:06 +090089 /**
90 * Prevents object instantiation from external.
91 */
Jian Lidea0fdb2018-04-02 19:02:48 +090092 private OpenstackNetworkingUtil() {
Jian Li091d8d22018-02-20 10:42:06 +090093 }
94
95 /**
96 * Interprets JSON string to corresponding openstack model entity object.
97 *
98 * @param input JSON string
99 * @param entityClazz openstack model entity class
100 * @return openstack model entity object
101 */
102 public static ModelEntity jsonToModelEntity(InputStream input, Class entityClazz) {
103 ObjectMapper mapper = new ObjectMapper();
104 try {
105 JsonNode jsonTree = mapper.enable(INDENT_OUTPUT).readTree(input);
106 log.trace(new ObjectMapper().writeValueAsString(jsonTree));
107 return ObjectMapperSingleton.getContext(entityClazz)
108 .readerFor(entityClazz)
109 .readValue(jsonTree);
110 } catch (Exception e) {
111 throw new IllegalArgumentException();
112 }
113 }
Jian Lieb9f77d2018-02-20 11:25:45 +0900114
115 /**
116 * Converts openstack model entity object into JSON object.
117 *
118 * @param entity openstack model entity object
119 * @param entityClazz openstack model entity class
120 * @return JSON object
121 */
122 public static ObjectNode modelEntityToJson(ModelEntity entity, Class entityClazz) {
123 ObjectMapper mapper = new ObjectMapper();
124 try {
125 String strModelEntity = ObjectMapperSingleton.getContext(entityClazz)
126 .writerFor(entityClazz)
127 .writeValueAsString(entity);
128 log.trace(strModelEntity);
129 return (ObjectNode) mapper.readTree(strModelEntity.getBytes());
130 } catch (Exception e) {
131 throw new IllegalStateException();
132 }
133 }
Jian Li1064e4f2018-05-29 16:16:53 +0900134
135 /**
Jian Li24ec59f2018-05-23 19:01:25 +0900136 * Obtains a floating IP associated with the given instance port.
137 *
138 * @param port instance port
139 * @param fips a collection of floating IPs
140 * @return associated floating IP
141 */
142 public static NetFloatingIP associatedFloatingIp(InstancePort port,
143 Set<NetFloatingIP> fips) {
144 for (NetFloatingIP fip : fips) {
145 if (Strings.isNullOrEmpty(fip.getFixedIpAddress())) {
146 continue;
147 }
148 if (Strings.isNullOrEmpty(fip.getFloatingIpAddress())) {
149 continue;
150 }
151 if (fip.getFixedIpAddress().equals(port.ipAddress().toString())) {
152 return fip;
153 }
154 }
155 return null;
156 }
157
158 /**
159 * Checks whether the given floating IP is associated with a VM.
160 *
161 * @param service openstack network service
162 * @param fip floating IP
163 * @return true if the given floating IP associated with a VM, false otherwise
164 */
165 public static boolean isAssociatedWithVM(OpenstackNetworkService service,
166 NetFloatingIP fip) {
167 Port osPort = service.port(fip.getPortId());
168 if (osPort == null) {
169 return false;
170 }
171
172 if (!Strings.isNullOrEmpty(osPort.getDeviceId())) {
173 Network osNet = service.network(osPort.getNetworkId());
174 if (osNet == null) {
175 final String errorFormat = ERR_FLOW + "no network(%s) exists";
176 final String error = String.format(errorFormat,
177 fip.getFloatingIpAddress(), osPort.getNetworkId());
178 throw new IllegalStateException(error);
179 }
180 return true;
181 } else {
182 return false;
183 }
184 }
185
186 /**
Jian Lia171a432018-06-11 11:52:11 +0900187 * Obtains the gateway node by instance port.
188 *
189 * @param gateways a collection of gateway nodes
190 * @param instPort instance port
191 * @return a gateway node
192 */
193 public static OpenstackNode getGwByInstancePort(Set<OpenstackNode> gateways,
194 InstancePort instPort) {
195 OpenstackNode gw = null;
196 if (instPort != null && instPort.deviceId() != null) {
197 gw = getGwByComputeDevId(gateways, instPort.deviceId());
198 }
199 return gw;
200 }
201
202 /**
Jian Li1064e4f2018-05-29 16:16:53 +0900203 * Obtains the gateway node by device in compute node. Note that the gateway
204 * node is determined by device's device identifier.
205 *
206 * @param gws a collection of gateway nodes
207 * @param deviceId device identifier
208 * @return a gateway node
209 */
210 public static OpenstackNode getGwByComputeDevId(Set<OpenstackNode> gws, DeviceId deviceId) {
211 int numOfGw = gws.size();
212
213 if (numOfGw == 0) {
214 return null;
215 }
216
217 int gwIndex = Math.abs(deviceId.hashCode()) % numOfGw;
218
219 return getGwByIndex(gws, gwIndex);
220 }
221
Jian Li51b844c2018-05-31 10:59:03 +0900222 /**
223 * Obtains a connected openstack client.
224 *
225 * @param osNode openstack node
226 * @return a connected openstack client
227 */
228 public static OSClient getConnectedClient(OpenstackNode osNode) {
229 OpenstackAuth auth = osNode.authentication();
230 String endpoint = buildEndpoint(osNode);
231 Perspective perspective = auth.perspective();
Jian Li1064e4f2018-05-29 16:16:53 +0900232
Jian Li51b844c2018-05-31 10:59:03 +0900233 Config config = getSslConfig();
Jian Li1064e4f2018-05-29 16:16:53 +0900234
Jian Li51b844c2018-05-31 10:59:03 +0900235 try {
236 if (endpoint.contains(KEYSTONE_V2)) {
237 IOSClientBuilder.V2 builder = OSFactory.builderV2()
238 .endpoint(endpoint)
239 .tenantName(auth.project())
240 .credentials(auth.username(), auth.password())
241 .withConfig(config);
242
243 if (perspective != null) {
244 builder.perspective(getFacing(perspective));
245 }
246
247 return builder.authenticate();
248 } else if (endpoint.contains(KEYSTONE_V3)) {
249
250 Identifier project = Identifier.byName(auth.project());
251 Identifier domain = Identifier.byName(DOMAIN_DEFAULT);
252
253 IOSClientBuilder.V3 builder = OSFactory.builderV3()
254 .endpoint(endpoint)
255 .credentials(auth.username(), auth.password(), domain)
256 .scopeToProject(project, domain)
257 .withConfig(config);
258
259 if (perspective != null) {
260 builder.perspective(getFacing(perspective));
261 }
262
263 return builder.authenticate();
264 } else {
265 log.warn("Unrecognized keystone version type");
266 return null;
Jian Li1064e4f2018-05-29 16:16:53 +0900267 }
Jian Li51b844c2018-05-31 10:59:03 +0900268 } catch (AuthenticationException e) {
269 log.error("Authentication failed due to {}", e.toString());
270 return null;
Jian Li1064e4f2018-05-29 16:16:53 +0900271 }
Jian Li1064e4f2018-05-29 16:16:53 +0900272 }
Daniel Parkc4d06402018-05-28 15:57:37 +0900273
274 /**
275 * Extract the interface name with the supplied port.
276 *
277 * @param port port
278 * @return interface name
279 */
280 public static String getIntfNameFromPciAddress(Port port) {
Jian Li51b844c2018-05-31 10:59:03 +0900281
282 if (port.getProfile() == null) {
283 log.error("Port profile is not found");
284 return null;
285 }
286
Daniel Parkc4d06402018-05-28 15:57:37 +0900287 if (port.getProfile() != null && port.getProfile().get(PCISLOT) == null) {
288 log.error("Failed to retrieve the interface name because of no pci_slot information from the port");
289 return null;
290 }
Jian Li51b844c2018-05-31 10:59:03 +0900291
Daniel Parkc4d06402018-05-28 15:57:37 +0900292 String busNumHex = port.getProfile().get(PCISLOT).toString().split(":")[1];
293 String busNumDecimal = String.valueOf(Integer.parseInt(busNumHex, HEX_RADIX));
294
295 String deviceNumHex = port.getProfile().get(PCISLOT).toString()
296 .split(":")[2]
297 .split("\\.")[0];
298 String deviceNumDecimal = String.valueOf(Integer.parseInt(deviceNumHex, HEX_RADIX));
299
300 String functionNumHex = port.getProfile().get(PCISLOT).toString()
301 .split(":")[2]
302 .split("\\.")[1];
303 String functionNumDecimal = String.valueOf(Integer.parseInt(functionNumHex, HEX_RADIX));
304
305 String intfName;
306
307 String vendorInfoForPort = String.valueOf(port.getProfile().get(PCI_VENDOR_INFO));
308
309 if (vendorInfoForPort == null) {
310 log.error("Failed to retrieve the interface name because of no pci vendor information from the port");
311 return null;
312 }
Ray Milkey9dc57392018-06-08 08:52:31 -0700313 String portNamePrefix = portNamePrefixMap().get(vendorInfoForPort);
Jian Li51b844c2018-05-31 10:59:03 +0900314
Daniel Parkc4d06402018-05-28 15:57:37 +0900315 if (functionNumDecimal.equals(ZERO_FUNCTION_NUMBER)) {
316 intfName = portNamePrefix + busNumDecimal + PREFIX_DEVICE_NUMBER + deviceNumDecimal;
317 } else {
318 intfName = portNamePrefix + busNumDecimal + PREFIX_DEVICE_NUMBER + deviceNumDecimal
319 + PREFIX_FUNCTION_NUMBER + functionNumDecimal;
320 }
321
322 return intfName;
323 }
Jian Li51b844c2018-05-31 10:59:03 +0900324
325 /**
Jian Li0b564282018-06-20 00:50:53 +0900326 * Adds router interfaces to openstack admin service.
327 * TODO fix the logic to add router interface to router
328 *
329 * @param osPort port
330 * @param adminService openstack admin service
331 */
332 public static void addRouterIface(Port osPort, OpenstackRouterAdminService adminService) {
333 osPort.getFixedIps().forEach(p -> {
334 JsonNode jsonTree = new ObjectMapper().createObjectNode()
335 .put("id", osPort.getDeviceId())
336 .put("tenant_id", osPort.getTenantId())
337 .put("subnet_id", p.getSubnetId())
338 .put("port_id", osPort.getId());
339 try {
340 RouterInterface rIface = getContext(NeutronRouterInterface.class)
341 .readerFor(NeutronRouterInterface.class)
342 .readValue(jsonTree);
343 if (adminService.routerInterface(rIface.getPortId()) != null) {
344 adminService.updateRouterInterface(rIface);
345 } else {
346 adminService.addRouterInterface(rIface);
347 }
348 } catch (IOException ignore) {
349 }
350 });
351 }
352
353 /**
Jian Li7f70bb72018-07-06 23:35:30 +0900354 * Obtains the property value with specified property key name.
355 *
356 * @param properties a collection of properties
357 * @param name key name
358 * @return mapping value
359 */
360 public static String getPropertyValue(Set<ConfigProperty> properties, String name) {
361 Optional<ConfigProperty> property =
362 properties.stream().filter(p -> p.name().equals(name)).findFirst();
363 return property.map(ConfigProperty::value).orElse(null);
364 }
365
366 /**
Jian Li51b844c2018-05-31 10:59:03 +0900367 * Builds up and a complete endpoint URL from gateway node.
368 *
369 * @param node gateway node
370 * @return a complete endpoint URL
371 */
372 private static String buildEndpoint(OpenstackNode node) {
373
374 OpenstackAuth auth = node.authentication();
375
376 StringBuilder endpointSb = new StringBuilder();
377 endpointSb.append(auth.protocol().name().toLowerCase());
378 endpointSb.append("://");
379 endpointSb.append(node.endPoint());
380 endpointSb.append(":");
381 endpointSb.append(auth.port());
382 endpointSb.append("/");
383
384 // in case the version is v3, we need to append identity path into endpoint
385 if (auth.version().equals(KEYSTONE_V3)) {
386 endpointSb.append(IDENTITY_PATH);
387 }
388
389 endpointSb.append(auth.version());
390 return endpointSb.toString();
391 }
392
393 /**
394 * Obtains the SSL config without verifying the certification.
395 *
396 * @return SSL config
397 */
398 private static Config getSslConfig() {
399 // we bypass the SSL certification verification for now
400 // TODO: verify server side SSL using a given certification
401 Config config = Config.newConfig().withSSLVerificationDisabled();
402
403 TrustManager[] trustAllCerts = new TrustManager[]{
404 new X509TrustManager() {
405 public X509Certificate[] getAcceptedIssuers() {
406 return null;
407 }
408
409 public void checkClientTrusted(X509Certificate[] certs,
410 String authType) {
411 }
412
413 public void checkServerTrusted(X509Certificate[] certs,
414 String authType) {
415 }
416 }
417 };
418
419 HostnameVerifier allHostsValid = (hostname, session) -> true;
420
421 try {
422 SSLContext sc = SSLContext.getInstance(SSL_TYPE);
423 sc.init(null, trustAllCerts,
424 new java.security.SecureRandom());
425 HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
426 HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid);
427
428 config.withSSLContext(sc);
429 } catch (Exception e) {
430 log.error("Failed to access OpenStack service due to {}", e.toString());
431 return null;
432 }
433
434 return config;
435 }
436
437 /**
438 * Obtains the facing object with given openstack perspective.
439 *
440 * @param perspective keystone perspective
441 * @return facing object
442 */
443 private static Facing getFacing(Perspective perspective) {
444
445 switch (perspective) {
446 case PUBLIC:
447 return Facing.PUBLIC;
448 case ADMIN:
449 return Facing.ADMIN;
450 case INTERNAL:
451 return Facing.INTERNAL;
452 default:
453 return null;
454 }
455 }
456
457 /**
458 * Obtains gateway instance by giving index number.
459 *
460 * @param gws a collection of gateway nodes
461 * @param index index number
462 * @return gateway instance
463 */
464 private static OpenstackNode getGwByIndex(Set<OpenstackNode> gws, int index) {
465 Map<String, OpenstackNode> hashMap = new HashMap<>();
466 gws.forEach(gw -> hashMap.put(gw.hostname(), gw));
467 TreeMap<String, OpenstackNode> treeMap = new TreeMap<>(hashMap);
468 Iterator<String> iteratorKey = treeMap.keySet().iterator();
469
470 int intIndex = 0;
471 OpenstackNode gw = null;
472 while (iteratorKey.hasNext()) {
473 String key = iteratorKey.next();
474
475 if (intIndex == index) {
476 gw = treeMap.get(key);
477 }
478 intIndex++;
479 }
480 return gw;
481 }
Jian Li091d8d22018-02-20 10:42:06 +0900482}