blob: 277e58e3100ea5c931ebf8fed072ea6a79d366cb [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) {
166 log.error("Authentication failed due to {}", e.toString());
167 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) {
186 value = null;
187 }
188 return value;
189 }
190
191 /**
Jian Lif1efbe52018-07-17 23:20:16 +0900192 * Prints out the JSON string in pretty format.
193 *
194 * @param mapper Object mapper
195 * @param jsonString JSON string
196 * @return pretty formatted JSON string
197 */
198 public static String prettyJson(ObjectMapper mapper, String jsonString) {
199 try {
200 Object jsonObject = mapper.readValue(jsonString, Object.class);
201 return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(jsonObject);
202 } catch (IOException e) {
203 log.debug("Json string parsing exception caused by {}", e);
204 }
205 return null;
206 }
207
208 /**
Jian Lie3141542018-08-13 18:05:43 +0900209 * Generates a DPID (of:0000000000000001) from an index value.
210 *
211 * @param index index value
212 * @return generated DPID
213 */
214 public static String genDpid(long index) {
215 if (index < 0) {
216 return null;
217 }
218
219 String hexStr = Long.toHexString(index);
220
221 StringBuilder zeroPadding = new StringBuilder();
222 for (int i = 0; i < HEX_LENGTH - hexStr.length(); i++) {
223 zeroPadding.append(ZERO);
224 }
225
226 return OF_PREFIX + zeroPadding.toString() + hexStr;
227 }
228
Daniel Park5a6a7102018-09-06 23:58:33 +0900229
230 /**
231 * Adds or removes a network interface (aka port) into a given bridge of openstack node.
232 *
233 * @param osNode openstack node
234 * @param bridgeName bridge name
235 * @param intfName interface name
236 * @param deviceService device service
237 * @param addOrRemove add port is true, remove it otherwise
238 */
239 public static synchronized void addOrRemoveSystemInterface(OpenstackNode osNode,
240 String bridgeName,
241 String intfName,
242 DeviceService deviceService,
243 boolean addOrRemove) {
244
245
246 Device device = deviceService.getDevice(osNode.ovsdb());
247 if (device == null || !device.is(BridgeConfig.class)) {
248 log.info("device is null or this device if not ovsdb device");
249 return;
250 }
251 BridgeConfig bridgeConfig = device.as(BridgeConfig.class);
252
253 if (addOrRemove) {
254 bridgeConfig.addPort(BridgeName.bridgeName(bridgeName), intfName);
255 } else {
256 bridgeConfig.deletePort(BridgeName.bridgeName(bridgeName), intfName);
257 }
258 }
259
260 /**
261 * Adds or removes a dpdk interface into a given openstack node.
262 *
263 * @param osNode openstack node
264 * @param dpdkInterface dpdk interface
265 * @param ovsdbPort ovsdb port
266 * @param ovsdbController ovsdb controller
267 * @param addOrRemove add port is true, remove it otherwise
268 */
269 public static synchronized void addOrRemoveDpdkInterface(OpenstackNode osNode,
270 DpdkInterface dpdkInterface,
271 int ovsdbPort,
272 OvsdbController ovsdbController,
273 boolean addOrRemove) {
274
275 OvsdbClientService client = getOvsdbClient(osNode, ovsdbPort, ovsdbController);
276 if (client == null) {
277 log.info("Failed to get ovsdb client");
278 return;
279 }
280
281 if (addOrRemove) {
Jian Li5ecfd1a2018-12-10 11:41:03 +0900282 Map<String, String> options =
283 ImmutableMap.of(DPDK_DEVARGS, dpdkInterface.pciAddress());
Daniel Park5a6a7102018-09-06 23:58:33 +0900284
285 OvsdbInterface.Builder builder = OvsdbInterface.builder()
286 .name(dpdkInterface.intf())
287 .type(OvsdbInterface.Type.DPDK)
288 .mtu(dpdkInterface.mtu())
289 .options(options);
290
291
292 client.createInterface(dpdkInterface.deviceName(), builder.build());
293 } else {
294 client.dropInterface(dpdkInterface.intf());
295 }
296 }
297
Jian Lie3141542018-08-13 18:05:43 +0900298 /**
Daniel Park489645c2018-10-24 11:34:22 +0900299 * Obtains the gateway node by openstack node. Note that the gateway
300 * node is determined by device's device identifier.
301 *
302 * @param gws a collection of gateway nodes
303 * @param openstackNode device identifier
304 * @return the hostname of selected gateway node
305 */
Jian Li5ecfd1a2018-12-10 11:41:03 +0900306 public static String getGwByComputeNode(Set<OpenstackNode> gws,
307 OpenstackNode openstackNode) {
Daniel Park489645c2018-10-24 11:34:22 +0900308 int numOfGw = gws.size();
309
310 if (numOfGw == 0) {
311 return NOT_AVAILABLE;
312 }
313
314 if (!openstackNode.type().equals(OpenstackNode.NodeType.COMPUTE)) {
315 return NOT_AVAILABLE;
316 }
317
318 int gwIndex = Math.abs(openstackNode.intgBridge().hashCode()) % numOfGw;
319
320 return getGwByIndex(gws, gwIndex).hostname();
321 }
322
323 /**
324 * Obtains gateway instance by giving index number.
325 *
326 * @param gws a collection of gateway nodes
327 * @param index index number
328 * @return gateway instance
329 */
330 private static OpenstackNode getGwByIndex(Set<OpenstackNode> gws, int index) {
331 Map<String, OpenstackNode> hashMap = new HashMap<>();
332 gws.forEach(gw -> hashMap.put(gw.hostname(), gw));
333 TreeMap<String, OpenstackNode> treeMap = new TreeMap<>(hashMap);
334 Iterator<String> iteratorKey = treeMap.keySet().iterator();
335
336 int intIndex = 0;
337 OpenstackNode gw = null;
338 while (iteratorKey.hasNext()) {
339 String key = iteratorKey.next();
340
341 if (intIndex == index) {
342 gw = treeMap.get(key);
343 }
344 intIndex++;
345 }
346 return gw;
347 }
348
349 /**
Jian Li51b844c2018-05-31 10:59:03 +0900350 * Builds up and a complete endpoint URL from gateway node.
351 *
352 * @param node gateway node
353 * @return a complete endpoint URL
354 */
355 private static String buildEndpoint(OpenstackNode node) {
356
Jian Lic704b672018-09-04 18:52:53 +0900357 OpenstackAuth auth = node.keystoneConfig().authentication();
Jian Li51b844c2018-05-31 10:59:03 +0900358
359 StringBuilder endpointSb = new StringBuilder();
360 endpointSb.append(auth.protocol().name().toLowerCase());
361 endpointSb.append("://");
Jian Lic704b672018-09-04 18:52:53 +0900362 endpointSb.append(node.keystoneConfig().endpoint());
Jian Li51b844c2018-05-31 10:59:03 +0900363 return endpointSb.toString();
364 }
365
366 /**
367 * Obtains the SSL config without verifying the certification.
368 *
369 * @return SSL config
370 */
371 private static Config getSslConfig() {
372 // we bypass the SSL certification verification for now
373 // TODO: verify server side SSL using a given certification
374 Config config = Config.newConfig().withSSLVerificationDisabled();
375
376 TrustManager[] trustAllCerts = new TrustManager[]{
377 new X509TrustManager() {
378 public X509Certificate[] getAcceptedIssuers() {
379 return null;
380 }
381
382 public void checkClientTrusted(X509Certificate[] certs,
383 String authType) {
384 }
385
386 public void checkServerTrusted(X509Certificate[] certs,
387 String authType) {
388 }
389 }
390 };
391
392 HostnameVerifier allHostsValid = (hostname, session) -> true;
393
394 try {
395 SSLContext sc = SSLContext.getInstance(SSL_TYPE);
396 sc.init(null, trustAllCerts,
397 new java.security.SecureRandom());
398 HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
399 HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid);
400
401 config.withSSLContext(sc);
402 } catch (Exception e) {
403 log.error("Failed to access OpenStack service due to {}", e.toString());
404 return null;
405 }
406
407 return config;
408 }
409
410 /**
411 * Obtains the facing object with given openstack perspective.
412 *
413 * @param perspective keystone perspective
414 * @return facing object
415 */
416 private static Facing getFacing(Perspective perspective) {
417
418 switch (perspective) {
419 case PUBLIC:
420 return Facing.PUBLIC;
421 case ADMIN:
422 return Facing.ADMIN;
423 case INTERNAL:
424 return Facing.INTERNAL;
425 default:
426 return null;
427 }
428 }
Daniel Parkc4d06402018-05-28 15:57:37 +0900429}