blob: 9e5bc0a08296f8e7bdba78a5ae3ce59aa81b5108 [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;
Jian Li62116942019-09-03 23:10:20 +090021import org.apache.commons.lang3.StringUtils;
Daniel Park5a6a7102018-09-06 23:58:33 +090022import org.onosproject.net.Device;
23import org.onosproject.net.behaviour.BridgeConfig;
24import org.onosproject.net.behaviour.BridgeName;
Daniel Parkc4d06402018-05-28 15:57:37 +090025import org.onosproject.net.device.DeviceService;
Daniel Park5a6a7102018-09-06 23:58:33 +090026import org.onosproject.openstacknode.api.DpdkInterface;
Jian Li51b844c2018-05-31 10:59:03 +090027import org.onosproject.openstacknode.api.OpenstackAuth;
28import org.onosproject.openstacknode.api.OpenstackAuth.Perspective;
Daniel Parkc4d06402018-05-28 15:57:37 +090029import org.onosproject.openstacknode.api.OpenstackNode;
30import org.onosproject.ovsdb.controller.OvsdbClientService;
31import org.onosproject.ovsdb.controller.OvsdbController;
Daniel Park5a6a7102018-09-06 23:58:33 +090032import org.onosproject.ovsdb.controller.OvsdbInterface;
Daniel Parkc4d06402018-05-28 15:57:37 +090033import org.onosproject.ovsdb.controller.OvsdbNodeId;
Jian Li51b844c2018-05-31 10:59:03 +090034import org.openstack4j.api.OSClient;
35import org.openstack4j.api.client.IOSClientBuilder;
36import org.openstack4j.api.exceptions.AuthenticationException;
37import org.openstack4j.api.types.Facing;
38import org.openstack4j.core.transport.Config;
39import org.openstack4j.model.common.Identifier;
40import org.openstack4j.openstack.OSFactory;
Daniel Parkc4d06402018-05-28 15:57:37 +090041import org.slf4j.Logger;
42import org.slf4j.LoggerFactory;
43
Jian Li51b844c2018-05-31 10:59:03 +090044import javax.net.ssl.HostnameVerifier;
45import javax.net.ssl.HttpsURLConnection;
46import javax.net.ssl.SSLContext;
47import javax.net.ssl.TrustManager;
48import javax.net.ssl.X509TrustManager;
Jian Lif1efbe52018-07-17 23:20:16 +090049import java.io.IOException;
Jian Li51b844c2018-05-31 10:59:03 +090050import java.security.cert.X509Certificate;
Jian Li97482c12018-07-03 01:08:23 +090051import java.util.Dictionary;
Daniel Park489645c2018-10-24 11:34:22 +090052import java.util.HashMap;
53import java.util.Iterator;
Daniel Park5a6a7102018-09-06 23:58:33 +090054import java.util.Map;
Daniel Park489645c2018-10-24 11:34:22 +090055import java.util.Set;
56import java.util.TreeMap;
Jian Li97482c12018-07-03 01:08:23 +090057
58import static org.onlab.util.Tools.get;
Jian Li51b844c2018-05-31 10:59:03 +090059
Daniel Parkc4d06402018-05-28 15:57:37 +090060/**
61 * An utility that used in openstack node app.
62 */
63public final class OpenstackNodeUtil {
Daniel Park95985382018-07-23 11:38:07 +090064 private static final Logger log = LoggerFactory.getLogger(OpenstackNodeUtil.class);
Daniel Parkc4d06402018-05-28 15:57:37 +090065
Jian Li51b844c2018-05-31 10:59:03 +090066 // keystone endpoint related variables
67 private static final String DOMAIN_DEFAULT = "default";
68 private static final String KEYSTONE_V2 = "v2.0";
69 private static final String KEYSTONE_V3 = "v3";
Jian Li51b844c2018-05-31 10:59:03 +090070 private static final String SSL_TYPE = "SSL";
71
Jian Lie3141542018-08-13 18:05:43 +090072 private static final int HEX_LENGTH = 16;
73 private static final String OF_PREFIX = "of:";
74 private static final String ZERO = "0";
75
Daniel Park5a6a7102018-09-06 23:58:33 +090076 private static final String DPDK_DEVARGS = "dpdk-devargs";
Daniel Park489645c2018-10-24 11:34:22 +090077 private static final String NOT_AVAILABLE = "N/A";
Daniel Park5a6a7102018-09-06 23:58:33 +090078
Jian Li62116942019-09-03 23:10:20 +090079 private static final int PORT_NAME_MAX_LENGTH = 15;
80
Daniel Parkc4d06402018-05-28 15:57:37 +090081 /**
Jian Li51b844c2018-05-31 10:59:03 +090082 * Prevents object installation from external.
Daniel Parkc4d06402018-05-28 15:57:37 +090083 */
84 private OpenstackNodeUtil() {
85 }
86
87 /**
88 * Checks whether the controller has a connection with an OVSDB that resides
89 * inside the given openstack node.
90 *
91 * @param osNode openstack node
92 * @param ovsdbPort ovsdb port
93 * @param ovsdbController ovsdb controller
94 * @param deviceService device service
95 * @return true if the controller is connected to the OVSDB, false otherwise
96 */
97 public static boolean isOvsdbConnected(OpenstackNode osNode,
98 int ovsdbPort,
99 OvsdbController ovsdbController,
100 DeviceService deviceService) {
Daniel Parke2658ba2018-08-24 22:33:29 +0900101 OvsdbClientService client = getOvsdbClient(osNode, ovsdbPort, ovsdbController);
Daniel Parkc4d06402018-05-28 15:57:37 +0900102 return deviceService.isAvailable(osNode.ovsdb()) &&
103 client != null &&
104 client.isConnected();
105 }
Jian Li51b844c2018-05-31 10:59:03 +0900106
107 /**
Daniel Parke2658ba2018-08-24 22:33:29 +0900108 * Gets the ovsdb client with supplied openstack node.
109 *
110 * @param osNode openstack node
111 * @param ovsdbPort ovsdb port
112 * @param ovsdbController ovsdb controller
113 * @return ovsdb client
114 */
115 public static OvsdbClientService getOvsdbClient(OpenstackNode osNode,
116 int ovsdbPort,
117 OvsdbController ovsdbController) {
118 OvsdbNodeId ovsdb = new OvsdbNodeId(osNode.managementIp(), ovsdbPort);
119 return ovsdbController.getOvsdbClient(ovsdb);
120 }
121
122 /**
Jian Li51b844c2018-05-31 10:59:03 +0900123 * Obtains a connected openstack client.
124 *
125 * @param osNode openstack node
126 * @return a connected openstack client
127 */
128 public static OSClient getConnectedClient(OpenstackNode osNode) {
Jian Lic704b672018-09-04 18:52:53 +0900129 OpenstackAuth auth = osNode.keystoneConfig().authentication();
Jian Li51b844c2018-05-31 10:59:03 +0900130 String endpoint = buildEndpoint(osNode);
131 Perspective perspective = auth.perspective();
132
133 Config config = getSslConfig();
134
135 try {
136 if (endpoint.contains(KEYSTONE_V2)) {
137 IOSClientBuilder.V2 builder = OSFactory.builderV2()
138 .endpoint(endpoint)
139 .tenantName(auth.project())
140 .credentials(auth.username(), auth.password())
141 .withConfig(config);
142
143 if (perspective != null) {
144 builder.perspective(getFacing(perspective));
145 }
146
147 return builder.authenticate();
148 } else if (endpoint.contains(KEYSTONE_V3)) {
149
150 Identifier project = Identifier.byName(auth.project());
151 Identifier domain = Identifier.byName(DOMAIN_DEFAULT);
152
153 IOSClientBuilder.V3 builder = OSFactory.builderV3()
154 .endpoint(endpoint)
155 .credentials(auth.username(), auth.password(), domain)
156 .scopeToProject(project, domain)
157 .withConfig(config);
158
159 if (perspective != null) {
160 builder.perspective(getFacing(perspective));
161 }
162
163 return builder.authenticate();
164 } else {
165 log.warn("Unrecognized keystone version type");
166 return null;
167 }
168 } catch (AuthenticationException e) {
Daniel Park4b24cec2018-11-28 19:21:25 +0900169 log.error("Authentication failed due to {}", e);
Jian Li51b844c2018-05-31 10:59:03 +0900170 return null;
171 }
172 }
173
174 /**
Jian Li97482c12018-07-03 01:08:23 +0900175 * Gets Boolean property from the propertyName
176 * Return null if propertyName is not found.
177 *
178 * @param properties properties to be looked up
179 * @param propertyName the name of the property to look up
180 * @return value when the propertyName is defined or return null
181 */
182 public static Boolean getBooleanProperty(Dictionary<?, ?> properties,
183 String propertyName) {
184 Boolean value;
185 try {
186 String s = get(properties, propertyName);
187 value = Strings.isNullOrEmpty(s) ? null : Boolean.valueOf(s);
188 } catch (ClassCastException e) {
Daniel Park4b24cec2018-11-28 19:21:25 +0900189 log.error("Exception occurred because of {}. set valud to null..", e);
Jian Li97482c12018-07-03 01:08:23 +0900190 value = null;
191 }
192 return value;
193 }
194
195 /**
Jian Lif1efbe52018-07-17 23:20:16 +0900196 * Prints out the JSON string in pretty format.
197 *
198 * @param mapper Object mapper
199 * @param jsonString JSON string
200 * @return pretty formatted JSON string
201 */
202 public static String prettyJson(ObjectMapper mapper, String jsonString) {
203 try {
204 Object jsonObject = mapper.readValue(jsonString, Object.class);
205 return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(jsonObject);
206 } catch (IOException e) {
207 log.debug("Json string parsing exception caused by {}", e);
208 }
209 return null;
210 }
211
212 /**
Jian Lie3141542018-08-13 18:05:43 +0900213 * Generates a DPID (of:0000000000000001) from an index value.
214 *
215 * @param index index value
216 * @return generated DPID
217 */
218 public static String genDpid(long index) {
219 if (index < 0) {
220 return null;
221 }
222
223 String hexStr = Long.toHexString(index);
224
225 StringBuilder zeroPadding = new StringBuilder();
226 for (int i = 0; i < HEX_LENGTH - hexStr.length(); i++) {
227 zeroPadding.append(ZERO);
228 }
229
230 return OF_PREFIX + zeroPadding.toString() + hexStr;
231 }
232
Daniel Park5a6a7102018-09-06 23:58:33 +0900233
234 /**
235 * Adds or removes a network interface (aka port) into a given bridge of openstack node.
236 *
237 * @param osNode openstack node
238 * @param bridgeName bridge name
239 * @param intfName interface name
240 * @param deviceService device service
241 * @param addOrRemove add port is true, remove it otherwise
242 */
243 public static synchronized void addOrRemoveSystemInterface(OpenstackNode osNode,
244 String bridgeName,
245 String intfName,
246 DeviceService deviceService,
247 boolean addOrRemove) {
248
249
250 Device device = deviceService.getDevice(osNode.ovsdb());
251 if (device == null || !device.is(BridgeConfig.class)) {
252 log.info("device is null or this device if not ovsdb device");
253 return;
254 }
255 BridgeConfig bridgeConfig = device.as(BridgeConfig.class);
256
257 if (addOrRemove) {
258 bridgeConfig.addPort(BridgeName.bridgeName(bridgeName), intfName);
259 } else {
260 bridgeConfig.deletePort(BridgeName.bridgeName(bridgeName), intfName);
261 }
262 }
263
264 /**
265 * Adds or removes a dpdk interface into a given openstack node.
266 *
267 * @param osNode openstack node
268 * @param dpdkInterface dpdk interface
269 * @param ovsdbPort ovsdb port
270 * @param ovsdbController ovsdb controller
271 * @param addOrRemove add port is true, remove it otherwise
272 */
273 public static synchronized void addOrRemoveDpdkInterface(OpenstackNode osNode,
274 DpdkInterface dpdkInterface,
275 int ovsdbPort,
276 OvsdbController ovsdbController,
277 boolean addOrRemove) {
278
279 OvsdbClientService client = getOvsdbClient(osNode, ovsdbPort, ovsdbController);
280 if (client == null) {
281 log.info("Failed to get ovsdb client");
282 return;
283 }
284
285 if (addOrRemove) {
Jian Li5ecfd1a2018-12-10 11:41:03 +0900286 Map<String, String> options =
287 ImmutableMap.of(DPDK_DEVARGS, dpdkInterface.pciAddress());
Daniel Park5a6a7102018-09-06 23:58:33 +0900288
289 OvsdbInterface.Builder builder = OvsdbInterface.builder()
290 .name(dpdkInterface.intf())
291 .type(OvsdbInterface.Type.DPDK)
292 .mtu(dpdkInterface.mtu())
293 .options(options);
294
295
296 client.createInterface(dpdkInterface.deviceName(), builder.build());
297 } else {
298 client.dropInterface(dpdkInterface.intf());
299 }
300 }
301
Jian Lie3141542018-08-13 18:05:43 +0900302 /**
Jian Li62116942019-09-03 23:10:20 +0900303 * Re-structures the OVS port name.
304 * The length of OVS port name should be not large than 15.
305 *
306 * @param portName original port name
307 * @return re-structured OVS port name
308 */
309 public static String structurePortName(String portName) {
310
311 // The size of OVS port name should not be larger than 15
312 if (portName.length() > PORT_NAME_MAX_LENGTH) {
313 return StringUtils.substring(portName, 0, PORT_NAME_MAX_LENGTH);
314 }
315
316 return portName;
317 }
318
319 /**
Daniel Park489645c2018-10-24 11:34:22 +0900320 * Obtains the gateway node by openstack node. Note that the gateway
321 * node is determined by device's device identifier.
322 *
323 * @param gws a collection of gateway nodes
324 * @param openstackNode device identifier
325 * @return the hostname of selected gateway node
326 */
Jian Li5ecfd1a2018-12-10 11:41:03 +0900327 public static String getGwByComputeNode(Set<OpenstackNode> gws,
328 OpenstackNode openstackNode) {
Daniel Park489645c2018-10-24 11:34:22 +0900329 int numOfGw = gws.size();
330
331 if (numOfGw == 0) {
332 return NOT_AVAILABLE;
333 }
334
335 if (!openstackNode.type().equals(OpenstackNode.NodeType.COMPUTE)) {
336 return NOT_AVAILABLE;
337 }
338
339 int gwIndex = Math.abs(openstackNode.intgBridge().hashCode()) % numOfGw;
340
341 return getGwByIndex(gws, gwIndex).hostname();
342 }
343
344 /**
345 * Obtains gateway instance by giving index number.
346 *
347 * @param gws a collection of gateway nodes
348 * @param index index number
349 * @return gateway instance
350 */
351 private static OpenstackNode getGwByIndex(Set<OpenstackNode> gws, int index) {
352 Map<String, OpenstackNode> hashMap = new HashMap<>();
353 gws.forEach(gw -> hashMap.put(gw.hostname(), gw));
354 TreeMap<String, OpenstackNode> treeMap = new TreeMap<>(hashMap);
355 Iterator<String> iteratorKey = treeMap.keySet().iterator();
356
357 int intIndex = 0;
358 OpenstackNode gw = null;
359 while (iteratorKey.hasNext()) {
360 String key = iteratorKey.next();
361
362 if (intIndex == index) {
363 gw = treeMap.get(key);
364 }
365 intIndex++;
366 }
367 return gw;
368 }
369
370 /**
Jian Li51b844c2018-05-31 10:59:03 +0900371 * Builds up and a complete endpoint URL from gateway node.
372 *
373 * @param node gateway node
374 * @return a complete endpoint URL
375 */
376 private static String buildEndpoint(OpenstackNode node) {
377
Jian Lic704b672018-09-04 18:52:53 +0900378 OpenstackAuth auth = node.keystoneConfig().authentication();
Jian Li51b844c2018-05-31 10:59:03 +0900379
380 StringBuilder endpointSb = new StringBuilder();
381 endpointSb.append(auth.protocol().name().toLowerCase());
382 endpointSb.append("://");
Jian Lic704b672018-09-04 18:52:53 +0900383 endpointSb.append(node.keystoneConfig().endpoint());
Jian Li51b844c2018-05-31 10:59:03 +0900384 return endpointSb.toString();
385 }
386
387 /**
388 * Obtains the SSL config without verifying the certification.
389 *
390 * @return SSL config
391 */
392 private static Config getSslConfig() {
393 // we bypass the SSL certification verification for now
394 // TODO: verify server side SSL using a given certification
395 Config config = Config.newConfig().withSSLVerificationDisabled();
396
397 TrustManager[] trustAllCerts = new TrustManager[]{
398 new X509TrustManager() {
Daniel Park4b24cec2018-11-28 19:21:25 +0900399 @Override
Jian Li51b844c2018-05-31 10:59:03 +0900400 public X509Certificate[] getAcceptedIssuers() {
401 return null;
402 }
403
Daniel Park4b24cec2018-11-28 19:21:25 +0900404 @Override
Jian Li51b844c2018-05-31 10:59:03 +0900405 public void checkClientTrusted(X509Certificate[] certs,
406 String authType) {
Daniel Park4b24cec2018-11-28 19:21:25 +0900407 return;
Jian Li51b844c2018-05-31 10:59:03 +0900408 }
409
Daniel Park4b24cec2018-11-28 19:21:25 +0900410 @Override
Jian Li51b844c2018-05-31 10:59:03 +0900411 public void checkServerTrusted(X509Certificate[] certs,
412 String authType) {
Daniel Park4b24cec2018-11-28 19:21:25 +0900413 return;
Jian Li51b844c2018-05-31 10:59:03 +0900414 }
415 }
416 };
417
418 HostnameVerifier allHostsValid = (hostname, session) -> true;
419
420 try {
421 SSLContext sc = SSLContext.getInstance(SSL_TYPE);
422 sc.init(null, trustAllCerts,
423 new java.security.SecureRandom());
424 HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
425 HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid);
426
427 config.withSSLContext(sc);
428 } catch (Exception e) {
Daniel Park4b24cec2018-11-28 19:21:25 +0900429 log.error("Failed to access OpenStack service due to {}", e);
Jian Li51b844c2018-05-31 10:59:03 +0900430 return null;
431 }
432
433 return config;
434 }
435
436 /**
437 * Obtains the facing object with given openstack perspective.
438 *
439 * @param perspective keystone perspective
440 * @return facing object
441 */
442 private static Facing getFacing(Perspective perspective) {
443
444 switch (perspective) {
445 case PUBLIC:
446 return Facing.PUBLIC;
447 case ADMIN:
448 return Facing.ADMIN;
449 case INTERNAL:
450 return Facing.INTERNAL;
451 default:
452 return null;
453 }
454 }
Daniel Parkc4d06402018-05-28 15:57:37 +0900455}