blob: d478bcc8bab7ba08e7ef5f8fe5baf75ef19b63a2 [file] [log] [blame]
Daniel Parkc4d06402018-05-28 15:57:37 +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.openstacknode.util;
17
Jian Lif1efbe52018-07-17 23:20:16 +090018import com.fasterxml.jackson.databind.ObjectMapper;
Jian Li97482c12018-07-03 01:08:23 +090019import com.google.common.base.Strings;
Daniel Park5a6a7102018-09-06 23:58:33 +090020import com.google.common.collect.ImmutableMap;
21import org.onosproject.net.Device;
22import org.onosproject.net.behaviour.BridgeConfig;
23import org.onosproject.net.behaviour.BridgeName;
Daniel Parkc4d06402018-05-28 15:57:37 +090024import org.onosproject.net.device.DeviceService;
Daniel Park5a6a7102018-09-06 23:58:33 +090025import org.onosproject.openstacknode.api.DpdkInterface;
Jian Li51b844c2018-05-31 10:59:03 +090026import org.onosproject.openstacknode.api.OpenstackAuth;
27import org.onosproject.openstacknode.api.OpenstackAuth.Perspective;
Daniel Parkc4d06402018-05-28 15:57:37 +090028import org.onosproject.openstacknode.api.OpenstackNode;
29import org.onosproject.ovsdb.controller.OvsdbClientService;
30import org.onosproject.ovsdb.controller.OvsdbController;
Daniel Park5a6a7102018-09-06 23:58:33 +090031import org.onosproject.ovsdb.controller.OvsdbInterface;
Daniel Parkc4d06402018-05-28 15:57:37 +090032import org.onosproject.ovsdb.controller.OvsdbNodeId;
Jian Li51b844c2018-05-31 10:59:03 +090033import org.openstack4j.api.OSClient;
34import org.openstack4j.api.client.IOSClientBuilder;
35import org.openstack4j.api.exceptions.AuthenticationException;
36import org.openstack4j.api.types.Facing;
37import org.openstack4j.core.transport.Config;
38import org.openstack4j.model.common.Identifier;
39import org.openstack4j.openstack.OSFactory;
Daniel Parkc4d06402018-05-28 15:57:37 +090040import org.slf4j.Logger;
41import org.slf4j.LoggerFactory;
42
Jian Li51b844c2018-05-31 10:59:03 +090043import javax.net.ssl.HostnameVerifier;
44import javax.net.ssl.HttpsURLConnection;
45import javax.net.ssl.SSLContext;
46import javax.net.ssl.TrustManager;
47import javax.net.ssl.X509TrustManager;
Jian Lif1efbe52018-07-17 23:20:16 +090048import java.io.IOException;
Jian Li51b844c2018-05-31 10:59:03 +090049import java.security.cert.X509Certificate;
Jian Li97482c12018-07-03 01:08:23 +090050import java.util.Dictionary;
Daniel Park489645c2018-10-24 11:34:22 +090051import java.util.HashMap;
52import java.util.Iterator;
Daniel Park5a6a7102018-09-06 23:58:33 +090053import java.util.Map;
Daniel Park489645c2018-10-24 11:34:22 +090054import java.util.Set;
55import java.util.TreeMap;
Jian Li97482c12018-07-03 01:08:23 +090056
57import static org.onlab.util.Tools.get;
Jian Li51b844c2018-05-31 10:59:03 +090058
Daniel Parkc4d06402018-05-28 15:57:37 +090059/**
60 * An utility that used in openstack node app.
61 */
62public final class OpenstackNodeUtil {
Daniel Park95985382018-07-23 11:38:07 +090063 private static final Logger log = LoggerFactory.getLogger(OpenstackNodeUtil.class);
Daniel Parkc4d06402018-05-28 15:57:37 +090064
Jian Li51b844c2018-05-31 10:59:03 +090065 // keystone endpoint related variables
66 private static final String DOMAIN_DEFAULT = "default";
67 private static final String KEYSTONE_V2 = "v2.0";
68 private static final String KEYSTONE_V3 = "v3";
Jian Li51b844c2018-05-31 10:59:03 +090069 private static final String SSL_TYPE = "SSL";
70
Jian Lie3141542018-08-13 18:05:43 +090071 private static final int HEX_LENGTH = 16;
72 private static final String OF_PREFIX = "of:";
73 private static final String ZERO = "0";
74
Daniel Park5a6a7102018-09-06 23:58:33 +090075 private static final String DPDK_DEVARGS = "dpdk-devargs";
Daniel Park489645c2018-10-24 11:34:22 +090076 private static final String NOT_AVAILABLE = "N/A";
Daniel Park5a6a7102018-09-06 23:58:33 +090077
Daniel Parkc4d06402018-05-28 15:57:37 +090078 /**
Jian Li51b844c2018-05-31 10:59:03 +090079 * Prevents object installation from external.
Daniel Parkc4d06402018-05-28 15:57:37 +090080 */
81 private OpenstackNodeUtil() {
82 }
83
84 /**
85 * Checks whether the controller has a connection with an OVSDB that resides
86 * inside the given openstack node.
87 *
88 * @param osNode openstack node
89 * @param ovsdbPort ovsdb port
90 * @param ovsdbController ovsdb controller
91 * @param deviceService device service
92 * @return true if the controller is connected to the OVSDB, false otherwise
93 */
94 public static boolean isOvsdbConnected(OpenstackNode osNode,
95 int ovsdbPort,
96 OvsdbController ovsdbController,
97 DeviceService deviceService) {
Daniel Parke2658ba2018-08-24 22:33:29 +090098 OvsdbClientService client = getOvsdbClient(osNode, ovsdbPort, ovsdbController);
Daniel Parkc4d06402018-05-28 15:57:37 +090099 return deviceService.isAvailable(osNode.ovsdb()) &&
100 client != null &&
101 client.isConnected();
102 }
Jian Li51b844c2018-05-31 10:59:03 +0900103
104 /**
Daniel Parke2658ba2018-08-24 22:33:29 +0900105 * Gets the ovsdb client with supplied openstack node.
106 *
107 * @param osNode openstack node
108 * @param ovsdbPort ovsdb port
109 * @param ovsdbController ovsdb controller
110 * @return ovsdb client
111 */
112 public static OvsdbClientService getOvsdbClient(OpenstackNode osNode,
113 int ovsdbPort,
114 OvsdbController ovsdbController) {
115 OvsdbNodeId ovsdb = new OvsdbNodeId(osNode.managementIp(), ovsdbPort);
116 return ovsdbController.getOvsdbClient(ovsdb);
117 }
118
119 /**
Jian Li51b844c2018-05-31 10:59:03 +0900120 * Obtains a connected openstack client.
121 *
122 * @param osNode openstack node
123 * @return a connected openstack client
124 */
125 public static OSClient getConnectedClient(OpenstackNode osNode) {
Jian Lic704b672018-09-04 18:52:53 +0900126 OpenstackAuth auth = osNode.keystoneConfig().authentication();
Jian Li51b844c2018-05-31 10:59:03 +0900127 String endpoint = buildEndpoint(osNode);
128 Perspective perspective = auth.perspective();
129
130 Config config = getSslConfig();
131
132 try {
133 if (endpoint.contains(KEYSTONE_V2)) {
134 IOSClientBuilder.V2 builder = OSFactory.builderV2()
135 .endpoint(endpoint)
136 .tenantName(auth.project())
137 .credentials(auth.username(), auth.password())
138 .withConfig(config);
139
140 if (perspective != null) {
141 builder.perspective(getFacing(perspective));
142 }
143
144 return builder.authenticate();
145 } else if (endpoint.contains(KEYSTONE_V3)) {
146
147 Identifier project = Identifier.byName(auth.project());
148 Identifier domain = Identifier.byName(DOMAIN_DEFAULT);
149
150 IOSClientBuilder.V3 builder = OSFactory.builderV3()
151 .endpoint(endpoint)
152 .credentials(auth.username(), auth.password(), domain)
153 .scopeToProject(project, domain)
154 .withConfig(config);
155
156 if (perspective != null) {
157 builder.perspective(getFacing(perspective));
158 }
159
160 return builder.authenticate();
161 } else {
162 log.warn("Unrecognized keystone version type");
163 return null;
164 }
165 } catch (AuthenticationException e) {
Daniel Park4b24cec2018-11-28 19:21:25 +0900166 log.error("Authentication failed due to {}", e);
Jian Li51b844c2018-05-31 10:59:03 +0900167 return null;
168 }
169 }
170
171 /**
Jian Li97482c12018-07-03 01:08:23 +0900172 * Gets Boolean property from the propertyName
173 * Return null if propertyName is not found.
174 *
175 * @param properties properties to be looked up
176 * @param propertyName the name of the property to look up
177 * @return value when the propertyName is defined or return null
178 */
179 public static Boolean getBooleanProperty(Dictionary<?, ?> properties,
180 String propertyName) {
181 Boolean value;
182 try {
183 String s = get(properties, propertyName);
184 value = Strings.isNullOrEmpty(s) ? null : Boolean.valueOf(s);
185 } catch (ClassCastException e) {
Daniel Park4b24cec2018-11-28 19:21:25 +0900186 log.error("Exception occurred because of {}. set valud to null..", e);
Jian Li97482c12018-07-03 01:08:23 +0900187 value = null;
188 }
189 return value;
190 }
191
192 /**
Jian Lif1efbe52018-07-17 23:20:16 +0900193 * Prints out the JSON string in pretty format.
194 *
195 * @param mapper Object mapper
196 * @param jsonString JSON string
197 * @return pretty formatted JSON string
198 */
199 public static String prettyJson(ObjectMapper mapper, String jsonString) {
200 try {
201 Object jsonObject = mapper.readValue(jsonString, Object.class);
202 return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(jsonObject);
203 } catch (IOException e) {
204 log.debug("Json string parsing exception caused by {}", e);
205 }
206 return null;
207 }
208
209 /**
Jian Lie3141542018-08-13 18:05:43 +0900210 * Generates a DPID (of:0000000000000001) from an index value.
211 *
212 * @param index index value
213 * @return generated DPID
214 */
215 public static String genDpid(long index) {
216 if (index < 0) {
217 return null;
218 }
219
220 String hexStr = Long.toHexString(index);
221
222 StringBuilder zeroPadding = new StringBuilder();
223 for (int i = 0; i < HEX_LENGTH - hexStr.length(); i++) {
224 zeroPadding.append(ZERO);
225 }
226
227 return OF_PREFIX + zeroPadding.toString() + hexStr;
228 }
229
Daniel Park5a6a7102018-09-06 23:58:33 +0900230
231 /**
232 * Adds or removes a network interface (aka port) into a given bridge of openstack node.
233 *
234 * @param osNode openstack node
235 * @param bridgeName bridge name
236 * @param intfName interface name
237 * @param deviceService device service
238 * @param addOrRemove add port is true, remove it otherwise
239 */
240 public static synchronized void addOrRemoveSystemInterface(OpenstackNode osNode,
241 String bridgeName,
242 String intfName,
243 DeviceService deviceService,
244 boolean addOrRemove) {
245
246
247 Device device = deviceService.getDevice(osNode.ovsdb());
248 if (device == null || !device.is(BridgeConfig.class)) {
249 log.info("device is null or this device if not ovsdb device");
250 return;
251 }
252 BridgeConfig bridgeConfig = device.as(BridgeConfig.class);
253
254 if (addOrRemove) {
255 bridgeConfig.addPort(BridgeName.bridgeName(bridgeName), intfName);
256 } else {
257 bridgeConfig.deletePort(BridgeName.bridgeName(bridgeName), intfName);
258 }
259 }
260
261 /**
262 * Adds or removes a dpdk interface into a given openstack node.
263 *
264 * @param osNode openstack node
265 * @param dpdkInterface dpdk interface
266 * @param ovsdbPort ovsdb port
267 * @param ovsdbController ovsdb controller
268 * @param addOrRemove add port is true, remove it otherwise
269 */
270 public static synchronized void addOrRemoveDpdkInterface(OpenstackNode osNode,
271 DpdkInterface dpdkInterface,
272 int ovsdbPort,
273 OvsdbController ovsdbController,
274 boolean addOrRemove) {
275
276 OvsdbClientService client = getOvsdbClient(osNode, ovsdbPort, ovsdbController);
277 if (client == null) {
278 log.info("Failed to get ovsdb client");
279 return;
280 }
281
282 if (addOrRemove) {
Jian Li5ecfd1a2018-12-10 11:41:03 +0900283 Map<String, String> options =
284 ImmutableMap.of(DPDK_DEVARGS, dpdkInterface.pciAddress());
Daniel Park5a6a7102018-09-06 23:58:33 +0900285
286 OvsdbInterface.Builder builder = OvsdbInterface.builder()
287 .name(dpdkInterface.intf())
288 .type(OvsdbInterface.Type.DPDK)
289 .mtu(dpdkInterface.mtu())
290 .options(options);
291
292
293 client.createInterface(dpdkInterface.deviceName(), builder.build());
294 } else {
295 client.dropInterface(dpdkInterface.intf());
296 }
297 }
298
Jian Lie3141542018-08-13 18:05:43 +0900299 /**
Daniel Park489645c2018-10-24 11:34:22 +0900300 * Obtains the gateway node by openstack node. Note that the gateway
301 * node is determined by device's device identifier.
302 *
303 * @param gws a collection of gateway nodes
304 * @param openstackNode device identifier
305 * @return the hostname of selected gateway node
306 */
Jian Li5ecfd1a2018-12-10 11:41:03 +0900307 public static String getGwByComputeNode(Set<OpenstackNode> gws,
308 OpenstackNode openstackNode) {
Daniel Park489645c2018-10-24 11:34:22 +0900309 int numOfGw = gws.size();
310
311 if (numOfGw == 0) {
312 return NOT_AVAILABLE;
313 }
314
315 if (!openstackNode.type().equals(OpenstackNode.NodeType.COMPUTE)) {
316 return NOT_AVAILABLE;
317 }
318
319 int gwIndex = Math.abs(openstackNode.intgBridge().hashCode()) % numOfGw;
320
321 return getGwByIndex(gws, gwIndex).hostname();
322 }
323
324 /**
325 * Obtains gateway instance by giving index number.
326 *
327 * @param gws a collection of gateway nodes
328 * @param index index number
329 * @return gateway instance
330 */
331 private static OpenstackNode getGwByIndex(Set<OpenstackNode> gws, int index) {
332 Map<String, OpenstackNode> hashMap = new HashMap<>();
333 gws.forEach(gw -> hashMap.put(gw.hostname(), gw));
334 TreeMap<String, OpenstackNode> treeMap = new TreeMap<>(hashMap);
335 Iterator<String> iteratorKey = treeMap.keySet().iterator();
336
337 int intIndex = 0;
338 OpenstackNode gw = null;
339 while (iteratorKey.hasNext()) {
340 String key = iteratorKey.next();
341
342 if (intIndex == index) {
343 gw = treeMap.get(key);
344 }
345 intIndex++;
346 }
347 return gw;
348 }
349
350 /**
Jian Li51b844c2018-05-31 10:59:03 +0900351 * Builds up and a complete endpoint URL from gateway node.
352 *
353 * @param node gateway node
354 * @return a complete endpoint URL
355 */
356 private static String buildEndpoint(OpenstackNode node) {
357
Jian Lic704b672018-09-04 18:52:53 +0900358 OpenstackAuth auth = node.keystoneConfig().authentication();
Jian Li51b844c2018-05-31 10:59:03 +0900359
360 StringBuilder endpointSb = new StringBuilder();
361 endpointSb.append(auth.protocol().name().toLowerCase());
362 endpointSb.append("://");
Jian Lic704b672018-09-04 18:52:53 +0900363 endpointSb.append(node.keystoneConfig().endpoint());
Jian Li51b844c2018-05-31 10:59:03 +0900364 return endpointSb.toString();
365 }
366
367 /**
368 * Obtains the SSL config without verifying the certification.
369 *
370 * @return SSL config
371 */
372 private static Config getSslConfig() {
373 // we bypass the SSL certification verification for now
374 // TODO: verify server side SSL using a given certification
375 Config config = Config.newConfig().withSSLVerificationDisabled();
376
377 TrustManager[] trustAllCerts = new TrustManager[]{
378 new X509TrustManager() {
Daniel Park4b24cec2018-11-28 19:21:25 +0900379 @Override
Jian Li51b844c2018-05-31 10:59:03 +0900380 public X509Certificate[] getAcceptedIssuers() {
381 return null;
382 }
383
Daniel Park4b24cec2018-11-28 19:21:25 +0900384 @Override
Jian Li51b844c2018-05-31 10:59:03 +0900385 public void checkClientTrusted(X509Certificate[] certs,
386 String authType) {
Daniel Park4b24cec2018-11-28 19:21:25 +0900387 return;
Jian Li51b844c2018-05-31 10:59:03 +0900388 }
389
Daniel Park4b24cec2018-11-28 19:21:25 +0900390 @Override
Jian Li51b844c2018-05-31 10:59:03 +0900391 public void checkServerTrusted(X509Certificate[] certs,
392 String authType) {
Daniel Park4b24cec2018-11-28 19:21:25 +0900393 return;
Jian Li51b844c2018-05-31 10:59:03 +0900394 }
395 }
396 };
397
398 HostnameVerifier allHostsValid = (hostname, session) -> true;
399
400 try {
401 SSLContext sc = SSLContext.getInstance(SSL_TYPE);
402 sc.init(null, trustAllCerts,
403 new java.security.SecureRandom());
404 HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
405 HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid);
406
407 config.withSSLContext(sc);
408 } catch (Exception e) {
Daniel Park4b24cec2018-11-28 19:21:25 +0900409 log.error("Failed to access OpenStack service due to {}", e);
Jian Li51b844c2018-05-31 10:59:03 +0900410 return null;
411 }
412
413 return config;
414 }
415
416 /**
417 * Obtains the facing object with given openstack perspective.
418 *
419 * @param perspective keystone perspective
420 * @return facing object
421 */
422 private static Facing getFacing(Perspective perspective) {
423
424 switch (perspective) {
425 case PUBLIC:
426 return Facing.PUBLIC;
427 case ADMIN:
428 return Facing.ADMIN;
429 case INTERNAL:
430 return Facing.INTERNAL;
431 default:
432 return null;
433 }
434 }
Daniel Parkc4d06402018-05-28 15:57:37 +0900435}