blob: 9914957c02a63c03a3ff1a3dfd08010bccaf0153 [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 Li1064e4f2018-05-29 16:16:53 +090022import org.onosproject.net.DeviceId;
Jian Lia171a432018-06-11 11:52:11 +090023import org.onosproject.openstacknetworking.api.InstancePort;
Jian Li0b564282018-06-20 00:50:53 +090024import org.onosproject.openstacknetworking.api.OpenstackRouterAdminService;
Jian Li24ec59f2018-05-23 19:01:25 +090025import org.onosproject.openstacknetworking.api.OpenstackNetworkService;
Jian Li51b844c2018-05-31 10:59:03 +090026import org.onosproject.openstacknode.api.OpenstackAuth;
27import org.onosproject.openstacknode.api.OpenstackAuth.Perspective;
Jian Li1064e4f2018-05-29 16:16:53 +090028import org.onosproject.openstacknode.api.OpenstackNode;
Jian Li51b844c2018-05-31 10:59:03 +090029import org.openstack4j.api.OSClient;
30import org.openstack4j.api.client.IOSClientBuilder;
31import org.openstack4j.api.exceptions.AuthenticationException;
32import org.openstack4j.api.types.Facing;
33import org.openstack4j.core.transport.Config;
Jian Li091d8d22018-02-20 10:42:06 +090034import org.openstack4j.core.transport.ObjectMapperSingleton;
35import org.openstack4j.model.ModelEntity;
Jian Li51b844c2018-05-31 10:59:03 +090036import org.openstack4j.model.common.Identifier;
Jian Li24ec59f2018-05-23 19:01:25 +090037import org.openstack4j.model.network.NetFloatingIP;
38import org.openstack4j.model.network.Network;
Jian Lia171a432018-06-11 11:52:11 +090039import org.openstack4j.model.network.Port;
Jian Li0b564282018-06-20 00:50:53 +090040import org.openstack4j.model.network.RouterInterface;
Jian Li51b844c2018-05-31 10:59:03 +090041import org.openstack4j.openstack.OSFactory;
Jian Li0b564282018-06-20 00:50:53 +090042import org.openstack4j.openstack.networking.domain.NeutronRouterInterface;
Jian Li091d8d22018-02-20 10:42:06 +090043import org.slf4j.Logger;
44import org.slf4j.LoggerFactory;
45
Jian Li51b844c2018-05-31 10:59:03 +090046import javax.net.ssl.HostnameVerifier;
47import javax.net.ssl.HttpsURLConnection;
48import javax.net.ssl.SSLContext;
49import javax.net.ssl.TrustManager;
50import javax.net.ssl.X509TrustManager;
Jian Li0b564282018-06-20 00:50:53 +090051import java.io.IOException;
Jian Li091d8d22018-02-20 10:42:06 +090052import java.io.InputStream;
Jian Li51b844c2018-05-31 10:59:03 +090053import java.security.cert.X509Certificate;
Jian Li1064e4f2018-05-29 16:16:53 +090054import java.util.HashMap;
55import java.util.Iterator;
56import java.util.Map;
57import java.util.Set;
58import java.util.TreeMap;
Jian Li091d8d22018-02-20 10:42:06 +090059
60import static com.fasterxml.jackson.databind.SerializationFeature.INDENT_OUTPUT;
Daniel Parkc4d06402018-05-28 15:57:37 +090061import static org.onosproject.openstacknetworking.api.Constants.PCISLOT;
62import static org.onosproject.openstacknetworking.api.Constants.PCI_VENDOR_INFO;
Ray Milkey9dc57392018-06-08 08:52:31 -070063import static org.onosproject.openstacknetworking.api.Constants.portNamePrefixMap;
Jian Li0b564282018-06-20 00:50:53 +090064import static org.openstack4j.core.transport.ObjectMapperSingleton.getContext;
Jian Li091d8d22018-02-20 10:42:06 +090065
66/**
67 * An utility that used in openstack networking app.
68 */
Jian Lidea0fdb2018-04-02 19:02:48 +090069public final class OpenstackNetworkingUtil {
Jian Li091d8d22018-02-20 10:42:06 +090070
Jian Lidea0fdb2018-04-02 19:02:48 +090071 protected static final Logger log = LoggerFactory.getLogger(OpenstackNetworkingUtil.class);
Jian Li091d8d22018-02-20 10:42:06 +090072
Daniel Parkc4d06402018-05-28 15:57:37 +090073 private static final int HEX_RADIX = 16;
Jian Li51b844c2018-05-31 10:59:03 +090074 private static final String ZERO_FUNCTION_NUMBER = "0";
Daniel Parkc4d06402018-05-28 15:57:37 +090075 private static final String PREFIX_DEVICE_NUMBER = "s";
76 private static final String PREFIX_FUNCTION_NUMBER = "f";
77
Jian Li51b844c2018-05-31 10:59:03 +090078 // keystone endpoint related variables
79 private static final String DOMAIN_DEFAULT = "default";
80 private static final String KEYSTONE_V2 = "v2.0";
81 private static final String KEYSTONE_V3 = "v3";
82 private static final String IDENTITY_PATH = "identity/";
83 private static final String SSL_TYPE = "SSL";
84
Jian Li24ec59f2018-05-23 19:01:25 +090085 private static final String ERR_FLOW = "Failed set flows for floating IP %s: ";
86
Jian Li091d8d22018-02-20 10:42:06 +090087 /**
88 * Prevents object instantiation from external.
89 */
Jian Lidea0fdb2018-04-02 19:02:48 +090090 private OpenstackNetworkingUtil() {
Jian Li091d8d22018-02-20 10:42:06 +090091 }
92
93 /**
94 * Interprets JSON string to corresponding openstack model entity object.
95 *
96 * @param input JSON string
97 * @param entityClazz openstack model entity class
98 * @return openstack model entity object
99 */
100 public static ModelEntity jsonToModelEntity(InputStream input, Class entityClazz) {
101 ObjectMapper mapper = new ObjectMapper();
102 try {
103 JsonNode jsonTree = mapper.enable(INDENT_OUTPUT).readTree(input);
104 log.trace(new ObjectMapper().writeValueAsString(jsonTree));
105 return ObjectMapperSingleton.getContext(entityClazz)
106 .readerFor(entityClazz)
107 .readValue(jsonTree);
108 } catch (Exception e) {
109 throw new IllegalArgumentException();
110 }
111 }
Jian Lieb9f77d2018-02-20 11:25:45 +0900112
113 /**
114 * Converts openstack model entity object into JSON object.
115 *
116 * @param entity openstack model entity object
117 * @param entityClazz openstack model entity class
118 * @return JSON object
119 */
120 public static ObjectNode modelEntityToJson(ModelEntity entity, Class entityClazz) {
121 ObjectMapper mapper = new ObjectMapper();
122 try {
123 String strModelEntity = ObjectMapperSingleton.getContext(entityClazz)
124 .writerFor(entityClazz)
125 .writeValueAsString(entity);
126 log.trace(strModelEntity);
127 return (ObjectNode) mapper.readTree(strModelEntity.getBytes());
128 } catch (Exception e) {
129 throw new IllegalStateException();
130 }
131 }
Jian Li1064e4f2018-05-29 16:16:53 +0900132
133 /**
Jian Li24ec59f2018-05-23 19:01:25 +0900134 * Obtains a floating IP associated with the given instance port.
135 *
136 * @param port instance port
137 * @param fips a collection of floating IPs
138 * @return associated floating IP
139 */
140 public static NetFloatingIP associatedFloatingIp(InstancePort port,
141 Set<NetFloatingIP> fips) {
142 for (NetFloatingIP fip : fips) {
143 if (Strings.isNullOrEmpty(fip.getFixedIpAddress())) {
144 continue;
145 }
146 if (Strings.isNullOrEmpty(fip.getFloatingIpAddress())) {
147 continue;
148 }
149 if (fip.getFixedIpAddress().equals(port.ipAddress().toString())) {
150 return fip;
151 }
152 }
153 return null;
154 }
155
156 /**
157 * Checks whether the given floating IP is associated with a VM.
158 *
159 * @param service openstack network service
160 * @param fip floating IP
161 * @return true if the given floating IP associated with a VM, false otherwise
162 */
163 public static boolean isAssociatedWithVM(OpenstackNetworkService service,
164 NetFloatingIP fip) {
165 Port osPort = service.port(fip.getPortId());
166 if (osPort == null) {
167 return false;
168 }
169
170 if (!Strings.isNullOrEmpty(osPort.getDeviceId())) {
171 Network osNet = service.network(osPort.getNetworkId());
172 if (osNet == null) {
173 final String errorFormat = ERR_FLOW + "no network(%s) exists";
174 final String error = String.format(errorFormat,
175 fip.getFloatingIpAddress(), osPort.getNetworkId());
176 throw new IllegalStateException(error);
177 }
178 return true;
179 } else {
180 return false;
181 }
182 }
183
184 /**
Jian Lia171a432018-06-11 11:52:11 +0900185 * Obtains the gateway node by instance port.
186 *
187 * @param gateways a collection of gateway nodes
188 * @param instPort instance port
189 * @return a gateway node
190 */
191 public static OpenstackNode getGwByInstancePort(Set<OpenstackNode> gateways,
192 InstancePort instPort) {
193 OpenstackNode gw = null;
194 if (instPort != null && instPort.deviceId() != null) {
195 gw = getGwByComputeDevId(gateways, instPort.deviceId());
196 }
197 return gw;
198 }
199
200 /**
Jian Li1064e4f2018-05-29 16:16:53 +0900201 * Obtains the gateway node by device in compute node. Note that the gateway
202 * node is determined by device's device identifier.
203 *
204 * @param gws a collection of gateway nodes
205 * @param deviceId device identifier
206 * @return a gateway node
207 */
208 public static OpenstackNode getGwByComputeDevId(Set<OpenstackNode> gws, DeviceId deviceId) {
209 int numOfGw = gws.size();
210
211 if (numOfGw == 0) {
212 return null;
213 }
214
215 int gwIndex = Math.abs(deviceId.hashCode()) % numOfGw;
216
217 return getGwByIndex(gws, gwIndex);
218 }
219
Jian Li51b844c2018-05-31 10:59:03 +0900220 /**
221 * Obtains a connected openstack client.
222 *
223 * @param osNode openstack node
224 * @return a connected openstack client
225 */
226 public static OSClient getConnectedClient(OpenstackNode osNode) {
227 OpenstackAuth auth = osNode.authentication();
228 String endpoint = buildEndpoint(osNode);
229 Perspective perspective = auth.perspective();
Jian Li1064e4f2018-05-29 16:16:53 +0900230
Jian Li51b844c2018-05-31 10:59:03 +0900231 Config config = getSslConfig();
Jian Li1064e4f2018-05-29 16:16:53 +0900232
Jian Li51b844c2018-05-31 10:59:03 +0900233 try {
234 if (endpoint.contains(KEYSTONE_V2)) {
235 IOSClientBuilder.V2 builder = OSFactory.builderV2()
236 .endpoint(endpoint)
237 .tenantName(auth.project())
238 .credentials(auth.username(), auth.password())
239 .withConfig(config);
240
241 if (perspective != null) {
242 builder.perspective(getFacing(perspective));
243 }
244
245 return builder.authenticate();
246 } else if (endpoint.contains(KEYSTONE_V3)) {
247
248 Identifier project = Identifier.byName(auth.project());
249 Identifier domain = Identifier.byName(DOMAIN_DEFAULT);
250
251 IOSClientBuilder.V3 builder = OSFactory.builderV3()
252 .endpoint(endpoint)
253 .credentials(auth.username(), auth.password(), domain)
254 .scopeToProject(project, domain)
255 .withConfig(config);
256
257 if (perspective != null) {
258 builder.perspective(getFacing(perspective));
259 }
260
261 return builder.authenticate();
262 } else {
263 log.warn("Unrecognized keystone version type");
264 return null;
Jian Li1064e4f2018-05-29 16:16:53 +0900265 }
Jian Li51b844c2018-05-31 10:59:03 +0900266 } catch (AuthenticationException e) {
267 log.error("Authentication failed due to {}", e.toString());
268 return null;
Jian Li1064e4f2018-05-29 16:16:53 +0900269 }
Jian Li1064e4f2018-05-29 16:16:53 +0900270 }
Daniel Parkc4d06402018-05-28 15:57:37 +0900271
272 /**
273 * Extract the interface name with the supplied port.
274 *
275 * @param port port
276 * @return interface name
277 */
278 public static String getIntfNameFromPciAddress(Port port) {
Jian Li51b844c2018-05-31 10:59:03 +0900279
280 if (port.getProfile() == null) {
281 log.error("Port profile is not found");
282 return null;
283 }
284
Daniel Parkc4d06402018-05-28 15:57:37 +0900285 if (port.getProfile() != null && port.getProfile().get(PCISLOT) == null) {
286 log.error("Failed to retrieve the interface name because of no pci_slot information from the port");
287 return null;
288 }
Jian Li51b844c2018-05-31 10:59:03 +0900289
Daniel Parkc4d06402018-05-28 15:57:37 +0900290 String busNumHex = port.getProfile().get(PCISLOT).toString().split(":")[1];
291 String busNumDecimal = String.valueOf(Integer.parseInt(busNumHex, HEX_RADIX));
292
293 String deviceNumHex = port.getProfile().get(PCISLOT).toString()
294 .split(":")[2]
295 .split("\\.")[0];
296 String deviceNumDecimal = String.valueOf(Integer.parseInt(deviceNumHex, HEX_RADIX));
297
298 String functionNumHex = port.getProfile().get(PCISLOT).toString()
299 .split(":")[2]
300 .split("\\.")[1];
301 String functionNumDecimal = String.valueOf(Integer.parseInt(functionNumHex, HEX_RADIX));
302
303 String intfName;
304
305 String vendorInfoForPort = String.valueOf(port.getProfile().get(PCI_VENDOR_INFO));
306
307 if (vendorInfoForPort == null) {
308 log.error("Failed to retrieve the interface name because of no pci vendor information from the port");
309 return null;
310 }
Ray Milkey9dc57392018-06-08 08:52:31 -0700311 String portNamePrefix = portNamePrefixMap().get(vendorInfoForPort);
Jian Li51b844c2018-05-31 10:59:03 +0900312
Daniel Parkc4d06402018-05-28 15:57:37 +0900313 if (functionNumDecimal.equals(ZERO_FUNCTION_NUMBER)) {
314 intfName = portNamePrefix + busNumDecimal + PREFIX_DEVICE_NUMBER + deviceNumDecimal;
315 } else {
316 intfName = portNamePrefix + busNumDecimal + PREFIX_DEVICE_NUMBER + deviceNumDecimal
317 + PREFIX_FUNCTION_NUMBER + functionNumDecimal;
318 }
319
320 return intfName;
321 }
Jian Li51b844c2018-05-31 10:59:03 +0900322
323 /**
Jian Li0b564282018-06-20 00:50:53 +0900324 * Adds router interfaces to openstack admin service.
325 * TODO fix the logic to add router interface to router
326 *
327 * @param osPort port
328 * @param adminService openstack admin service
329 */
330 public static void addRouterIface(Port osPort, OpenstackRouterAdminService adminService) {
331 osPort.getFixedIps().forEach(p -> {
332 JsonNode jsonTree = new ObjectMapper().createObjectNode()
333 .put("id", osPort.getDeviceId())
334 .put("tenant_id", osPort.getTenantId())
335 .put("subnet_id", p.getSubnetId())
336 .put("port_id", osPort.getId());
337 try {
338 RouterInterface rIface = getContext(NeutronRouterInterface.class)
339 .readerFor(NeutronRouterInterface.class)
340 .readValue(jsonTree);
341 if (adminService.routerInterface(rIface.getPortId()) != null) {
342 adminService.updateRouterInterface(rIface);
343 } else {
344 adminService.addRouterInterface(rIface);
345 }
346 } catch (IOException ignore) {
347 }
348 });
349 }
350
351 /**
Jian Li51b844c2018-05-31 10:59:03 +0900352 * Builds up and a complete endpoint URL from gateway node.
353 *
354 * @param node gateway node
355 * @return a complete endpoint URL
356 */
357 private static String buildEndpoint(OpenstackNode node) {
358
359 OpenstackAuth auth = node.authentication();
360
361 StringBuilder endpointSb = new StringBuilder();
362 endpointSb.append(auth.protocol().name().toLowerCase());
363 endpointSb.append("://");
364 endpointSb.append(node.endPoint());
365 endpointSb.append(":");
366 endpointSb.append(auth.port());
367 endpointSb.append("/");
368
369 // in case the version is v3, we need to append identity path into endpoint
370 if (auth.version().equals(KEYSTONE_V3)) {
371 endpointSb.append(IDENTITY_PATH);
372 }
373
374 endpointSb.append(auth.version());
375 return endpointSb.toString();
376 }
377
378 /**
379 * Obtains the SSL config without verifying the certification.
380 *
381 * @return SSL config
382 */
383 private static Config getSslConfig() {
384 // we bypass the SSL certification verification for now
385 // TODO: verify server side SSL using a given certification
386 Config config = Config.newConfig().withSSLVerificationDisabled();
387
388 TrustManager[] trustAllCerts = new TrustManager[]{
389 new X509TrustManager() {
390 public X509Certificate[] getAcceptedIssuers() {
391 return null;
392 }
393
394 public void checkClientTrusted(X509Certificate[] certs,
395 String authType) {
396 }
397
398 public void checkServerTrusted(X509Certificate[] certs,
399 String authType) {
400 }
401 }
402 };
403
404 HostnameVerifier allHostsValid = (hostname, session) -> true;
405
406 try {
407 SSLContext sc = SSLContext.getInstance(SSL_TYPE);
408 sc.init(null, trustAllCerts,
409 new java.security.SecureRandom());
410 HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
411 HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid);
412
413 config.withSSLContext(sc);
414 } catch (Exception e) {
415 log.error("Failed to access OpenStack service due to {}", e.toString());
416 return null;
417 }
418
419 return config;
420 }
421
422 /**
423 * Obtains the facing object with given openstack perspective.
424 *
425 * @param perspective keystone perspective
426 * @return facing object
427 */
428 private static Facing getFacing(Perspective perspective) {
429
430 switch (perspective) {
431 case PUBLIC:
432 return Facing.PUBLIC;
433 case ADMIN:
434 return Facing.ADMIN;
435 case INTERNAL:
436 return Facing.INTERNAL;
437 default:
438 return null;
439 }
440 }
441
442 /**
443 * Obtains gateway instance by giving index number.
444 *
445 * @param gws a collection of gateway nodes
446 * @param index index number
447 * @return gateway instance
448 */
449 private static OpenstackNode getGwByIndex(Set<OpenstackNode> gws, int index) {
450 Map<String, OpenstackNode> hashMap = new HashMap<>();
451 gws.forEach(gw -> hashMap.put(gw.hostname(), gw));
452 TreeMap<String, OpenstackNode> treeMap = new TreeMap<>(hashMap);
453 Iterator<String> iteratorKey = treeMap.keySet().iterator();
454
455 int intIndex = 0;
456 OpenstackNode gw = null;
457 while (iteratorKey.hasNext()) {
458 String key = iteratorKey.next();
459
460 if (intIndex == index) {
461 gw = treeMap.get(key);
462 }
463 intIndex++;
464 }
465 return gw;
466 }
Jian Li091d8d22018-02-20 10:42:06 +0900467}