blob: 2ebf9db47a20f3483ace5c958e7bfd05d4540c43 [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 Li1064e4f2018-05-29 16:16:53 +090021import org.onosproject.net.DeviceId;
Jian Lia171a432018-06-11 11:52:11 +090022import org.onosproject.openstacknetworking.api.InstancePort;
Jian Li0b564282018-06-20 00:50:53 +090023import org.onosproject.openstacknetworking.api.OpenstackRouterAdminService;
Jian Li51b844c2018-05-31 10:59:03 +090024import org.onosproject.openstacknode.api.OpenstackAuth;
25import org.onosproject.openstacknode.api.OpenstackAuth.Perspective;
Jian Li1064e4f2018-05-29 16:16:53 +090026import org.onosproject.openstacknode.api.OpenstackNode;
Jian Li51b844c2018-05-31 10:59:03 +090027import org.openstack4j.api.OSClient;
28import org.openstack4j.api.client.IOSClientBuilder;
29import org.openstack4j.api.exceptions.AuthenticationException;
30import org.openstack4j.api.types.Facing;
31import org.openstack4j.core.transport.Config;
Jian Li091d8d22018-02-20 10:42:06 +090032import org.openstack4j.core.transport.ObjectMapperSingleton;
33import org.openstack4j.model.ModelEntity;
Jian Li51b844c2018-05-31 10:59:03 +090034import org.openstack4j.model.common.Identifier;
Jian Lia171a432018-06-11 11:52:11 +090035import org.openstack4j.model.network.Port;
Jian Li0b564282018-06-20 00:50:53 +090036import org.openstack4j.model.network.RouterInterface;
Jian Li51b844c2018-05-31 10:59:03 +090037import org.openstack4j.openstack.OSFactory;
Jian Li0b564282018-06-20 00:50:53 +090038import org.openstack4j.openstack.networking.domain.NeutronRouterInterface;
Jian Li091d8d22018-02-20 10:42:06 +090039import org.slf4j.Logger;
40import org.slf4j.LoggerFactory;
41
Jian Li51b844c2018-05-31 10:59:03 +090042import javax.net.ssl.HostnameVerifier;
43import javax.net.ssl.HttpsURLConnection;
44import javax.net.ssl.SSLContext;
45import javax.net.ssl.TrustManager;
46import javax.net.ssl.X509TrustManager;
Jian Li0b564282018-06-20 00:50:53 +090047import java.io.IOException;
Jian Li091d8d22018-02-20 10:42:06 +090048import java.io.InputStream;
Jian Li51b844c2018-05-31 10:59:03 +090049import java.security.cert.X509Certificate;
Jian Li1064e4f2018-05-29 16:16:53 +090050import java.util.HashMap;
51import java.util.Iterator;
52import java.util.Map;
53import java.util.Set;
54import java.util.TreeMap;
Jian Li091d8d22018-02-20 10:42:06 +090055
56import static com.fasterxml.jackson.databind.SerializationFeature.INDENT_OUTPUT;
Daniel Parkc4d06402018-05-28 15:57:37 +090057import static org.onosproject.openstacknetworking.api.Constants.PCISLOT;
58import static org.onosproject.openstacknetworking.api.Constants.PCI_VENDOR_INFO;
Ray Milkey9dc57392018-06-08 08:52:31 -070059import static org.onosproject.openstacknetworking.api.Constants.portNamePrefixMap;
Jian Li0b564282018-06-20 00:50:53 +090060import static org.openstack4j.core.transport.ObjectMapperSingleton.getContext;
Jian Li091d8d22018-02-20 10:42:06 +090061
62/**
63 * An utility that used in openstack networking app.
64 */
Jian Lidea0fdb2018-04-02 19:02:48 +090065public final class OpenstackNetworkingUtil {
Jian Li091d8d22018-02-20 10:42:06 +090066
Jian Lidea0fdb2018-04-02 19:02:48 +090067 protected static final Logger log = LoggerFactory.getLogger(OpenstackNetworkingUtil.class);
Jian Li091d8d22018-02-20 10:42:06 +090068
Daniel Parkc4d06402018-05-28 15:57:37 +090069 private static final int HEX_RADIX = 16;
Jian Li51b844c2018-05-31 10:59:03 +090070 private static final String ZERO_FUNCTION_NUMBER = "0";
Daniel Parkc4d06402018-05-28 15:57:37 +090071 private static final String PREFIX_DEVICE_NUMBER = "s";
72 private static final String PREFIX_FUNCTION_NUMBER = "f";
73
Jian Li51b844c2018-05-31 10:59:03 +090074 // keystone endpoint related variables
75 private static final String DOMAIN_DEFAULT = "default";
76 private static final String KEYSTONE_V2 = "v2.0";
77 private static final String KEYSTONE_V3 = "v3";
78 private static final String IDENTITY_PATH = "identity/";
79 private static final String SSL_TYPE = "SSL";
80
Jian Li091d8d22018-02-20 10:42:06 +090081 /**
82 * Prevents object instantiation from external.
83 */
Jian Lidea0fdb2018-04-02 19:02:48 +090084 private OpenstackNetworkingUtil() {
Jian Li091d8d22018-02-20 10:42:06 +090085 }
86
87 /**
88 * Interprets JSON string to corresponding openstack model entity object.
89 *
90 * @param input JSON string
91 * @param entityClazz openstack model entity class
92 * @return openstack model entity object
93 */
94 public static ModelEntity jsonToModelEntity(InputStream input, Class entityClazz) {
95 ObjectMapper mapper = new ObjectMapper();
96 try {
97 JsonNode jsonTree = mapper.enable(INDENT_OUTPUT).readTree(input);
98 log.trace(new ObjectMapper().writeValueAsString(jsonTree));
99 return ObjectMapperSingleton.getContext(entityClazz)
100 .readerFor(entityClazz)
101 .readValue(jsonTree);
102 } catch (Exception e) {
103 throw new IllegalArgumentException();
104 }
105 }
Jian Lieb9f77d2018-02-20 11:25:45 +0900106
107 /**
108 * Converts openstack model entity object into JSON object.
109 *
110 * @param entity openstack model entity object
111 * @param entityClazz openstack model entity class
112 * @return JSON object
113 */
114 public static ObjectNode modelEntityToJson(ModelEntity entity, Class entityClazz) {
115 ObjectMapper mapper = new ObjectMapper();
116 try {
117 String strModelEntity = ObjectMapperSingleton.getContext(entityClazz)
118 .writerFor(entityClazz)
119 .writeValueAsString(entity);
120 log.trace(strModelEntity);
121 return (ObjectNode) mapper.readTree(strModelEntity.getBytes());
122 } catch (Exception e) {
123 throw new IllegalStateException();
124 }
125 }
Jian Li1064e4f2018-05-29 16:16:53 +0900126
127 /**
Jian Lia171a432018-06-11 11:52:11 +0900128 * Obtains the gateway node by instance port.
129 *
130 * @param gateways a collection of gateway nodes
131 * @param instPort instance port
132 * @return a gateway node
133 */
134 public static OpenstackNode getGwByInstancePort(Set<OpenstackNode> gateways,
135 InstancePort instPort) {
136 OpenstackNode gw = null;
137 if (instPort != null && instPort.deviceId() != null) {
138 gw = getGwByComputeDevId(gateways, instPort.deviceId());
139 }
140 return gw;
141 }
142
143 /**
Jian Li1064e4f2018-05-29 16:16:53 +0900144 * Obtains the gateway node by device in compute node. Note that the gateway
145 * node is determined by device's device identifier.
146 *
147 * @param gws a collection of gateway nodes
148 * @param deviceId device identifier
149 * @return a gateway node
150 */
151 public static OpenstackNode getGwByComputeDevId(Set<OpenstackNode> gws, DeviceId deviceId) {
152 int numOfGw = gws.size();
153
154 if (numOfGw == 0) {
155 return null;
156 }
157
158 int gwIndex = Math.abs(deviceId.hashCode()) % numOfGw;
159
160 return getGwByIndex(gws, gwIndex);
161 }
162
Jian Li51b844c2018-05-31 10:59:03 +0900163 /**
164 * Obtains a connected openstack client.
165 *
166 * @param osNode openstack node
167 * @return a connected openstack client
168 */
169 public static OSClient getConnectedClient(OpenstackNode osNode) {
170 OpenstackAuth auth = osNode.authentication();
171 String endpoint = buildEndpoint(osNode);
172 Perspective perspective = auth.perspective();
Jian Li1064e4f2018-05-29 16:16:53 +0900173
Jian Li51b844c2018-05-31 10:59:03 +0900174 Config config = getSslConfig();
Jian Li1064e4f2018-05-29 16:16:53 +0900175
Jian Li51b844c2018-05-31 10:59:03 +0900176 try {
177 if (endpoint.contains(KEYSTONE_V2)) {
178 IOSClientBuilder.V2 builder = OSFactory.builderV2()
179 .endpoint(endpoint)
180 .tenantName(auth.project())
181 .credentials(auth.username(), auth.password())
182 .withConfig(config);
183
184 if (perspective != null) {
185 builder.perspective(getFacing(perspective));
186 }
187
188 return builder.authenticate();
189 } else if (endpoint.contains(KEYSTONE_V3)) {
190
191 Identifier project = Identifier.byName(auth.project());
192 Identifier domain = Identifier.byName(DOMAIN_DEFAULT);
193
194 IOSClientBuilder.V3 builder = OSFactory.builderV3()
195 .endpoint(endpoint)
196 .credentials(auth.username(), auth.password(), domain)
197 .scopeToProject(project, domain)
198 .withConfig(config);
199
200 if (perspective != null) {
201 builder.perspective(getFacing(perspective));
202 }
203
204 return builder.authenticate();
205 } else {
206 log.warn("Unrecognized keystone version type");
207 return null;
Jian Li1064e4f2018-05-29 16:16:53 +0900208 }
Jian Li51b844c2018-05-31 10:59:03 +0900209 } catch (AuthenticationException e) {
210 log.error("Authentication failed due to {}", e.toString());
211 return null;
Jian Li1064e4f2018-05-29 16:16:53 +0900212 }
Jian Li1064e4f2018-05-29 16:16:53 +0900213 }
Daniel Parkc4d06402018-05-28 15:57:37 +0900214
215 /**
216 * Extract the interface name with the supplied port.
217 *
218 * @param port port
219 * @return interface name
220 */
221 public static String getIntfNameFromPciAddress(Port port) {
Jian Li51b844c2018-05-31 10:59:03 +0900222
223 if (port.getProfile() == null) {
224 log.error("Port profile is not found");
225 return null;
226 }
227
Daniel Parkc4d06402018-05-28 15:57:37 +0900228 if (port.getProfile() != null && port.getProfile().get(PCISLOT) == null) {
229 log.error("Failed to retrieve the interface name because of no pci_slot information from the port");
230 return null;
231 }
Jian Li51b844c2018-05-31 10:59:03 +0900232
Daniel Parkc4d06402018-05-28 15:57:37 +0900233 String busNumHex = port.getProfile().get(PCISLOT).toString().split(":")[1];
234 String busNumDecimal = String.valueOf(Integer.parseInt(busNumHex, HEX_RADIX));
235
236 String deviceNumHex = port.getProfile().get(PCISLOT).toString()
237 .split(":")[2]
238 .split("\\.")[0];
239 String deviceNumDecimal = String.valueOf(Integer.parseInt(deviceNumHex, HEX_RADIX));
240
241 String functionNumHex = port.getProfile().get(PCISLOT).toString()
242 .split(":")[2]
243 .split("\\.")[1];
244 String functionNumDecimal = String.valueOf(Integer.parseInt(functionNumHex, HEX_RADIX));
245
246 String intfName;
247
248 String vendorInfoForPort = String.valueOf(port.getProfile().get(PCI_VENDOR_INFO));
249
250 if (vendorInfoForPort == null) {
251 log.error("Failed to retrieve the interface name because of no pci vendor information from the port");
252 return null;
253 }
Ray Milkey9dc57392018-06-08 08:52:31 -0700254 String portNamePrefix = portNamePrefixMap().get(vendorInfoForPort);
Jian Li51b844c2018-05-31 10:59:03 +0900255
Daniel Parkc4d06402018-05-28 15:57:37 +0900256 if (functionNumDecimal.equals(ZERO_FUNCTION_NUMBER)) {
257 intfName = portNamePrefix + busNumDecimal + PREFIX_DEVICE_NUMBER + deviceNumDecimal;
258 } else {
259 intfName = portNamePrefix + busNumDecimal + PREFIX_DEVICE_NUMBER + deviceNumDecimal
260 + PREFIX_FUNCTION_NUMBER + functionNumDecimal;
261 }
262
263 return intfName;
264 }
Jian Li51b844c2018-05-31 10:59:03 +0900265
266 /**
Jian Li0b564282018-06-20 00:50:53 +0900267 * Adds router interfaces to openstack admin service.
268 * TODO fix the logic to add router interface to router
269 *
270 * @param osPort port
271 * @param adminService openstack admin service
272 */
273 public static void addRouterIface(Port osPort, OpenstackRouterAdminService adminService) {
274 osPort.getFixedIps().forEach(p -> {
275 JsonNode jsonTree = new ObjectMapper().createObjectNode()
276 .put("id", osPort.getDeviceId())
277 .put("tenant_id", osPort.getTenantId())
278 .put("subnet_id", p.getSubnetId())
279 .put("port_id", osPort.getId());
280 try {
281 RouterInterface rIface = getContext(NeutronRouterInterface.class)
282 .readerFor(NeutronRouterInterface.class)
283 .readValue(jsonTree);
284 if (adminService.routerInterface(rIface.getPortId()) != null) {
285 adminService.updateRouterInterface(rIface);
286 } else {
287 adminService.addRouterInterface(rIface);
288 }
289 } catch (IOException ignore) {
290 }
291 });
292 }
293
294 /**
Jian Li51b844c2018-05-31 10:59:03 +0900295 * Builds up and a complete endpoint URL from gateway node.
296 *
297 * @param node gateway node
298 * @return a complete endpoint URL
299 */
300 private static String buildEndpoint(OpenstackNode node) {
301
302 OpenstackAuth auth = node.authentication();
303
304 StringBuilder endpointSb = new StringBuilder();
305 endpointSb.append(auth.protocol().name().toLowerCase());
306 endpointSb.append("://");
307 endpointSb.append(node.endPoint());
308 endpointSb.append(":");
309 endpointSb.append(auth.port());
310 endpointSb.append("/");
311
312 // in case the version is v3, we need to append identity path into endpoint
313 if (auth.version().equals(KEYSTONE_V3)) {
314 endpointSb.append(IDENTITY_PATH);
315 }
316
317 endpointSb.append(auth.version());
318 return endpointSb.toString();
319 }
320
321 /**
322 * Obtains the SSL config without verifying the certification.
323 *
324 * @return SSL config
325 */
326 private static Config getSslConfig() {
327 // we bypass the SSL certification verification for now
328 // TODO: verify server side SSL using a given certification
329 Config config = Config.newConfig().withSSLVerificationDisabled();
330
331 TrustManager[] trustAllCerts = new TrustManager[]{
332 new X509TrustManager() {
333 public X509Certificate[] getAcceptedIssuers() {
334 return null;
335 }
336
337 public void checkClientTrusted(X509Certificate[] certs,
338 String authType) {
339 }
340
341 public void checkServerTrusted(X509Certificate[] certs,
342 String authType) {
343 }
344 }
345 };
346
347 HostnameVerifier allHostsValid = (hostname, session) -> true;
348
349 try {
350 SSLContext sc = SSLContext.getInstance(SSL_TYPE);
351 sc.init(null, trustAllCerts,
352 new java.security.SecureRandom());
353 HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
354 HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid);
355
356 config.withSSLContext(sc);
357 } catch (Exception e) {
358 log.error("Failed to access OpenStack service due to {}", e.toString());
359 return null;
360 }
361
362 return config;
363 }
364
365 /**
366 * Obtains the facing object with given openstack perspective.
367 *
368 * @param perspective keystone perspective
369 * @return facing object
370 */
371 private static Facing getFacing(Perspective perspective) {
372
373 switch (perspective) {
374 case PUBLIC:
375 return Facing.PUBLIC;
376 case ADMIN:
377 return Facing.ADMIN;
378 case INTERNAL:
379 return Facing.INTERNAL;
380 default:
381 return null;
382 }
383 }
384
385 /**
386 * Obtains gateway instance by giving index number.
387 *
388 * @param gws a collection of gateway nodes
389 * @param index index number
390 * @return gateway instance
391 */
392 private static OpenstackNode getGwByIndex(Set<OpenstackNode> gws, int index) {
393 Map<String, OpenstackNode> hashMap = new HashMap<>();
394 gws.forEach(gw -> hashMap.put(gw.hostname(), gw));
395 TreeMap<String, OpenstackNode> treeMap = new TreeMap<>(hashMap);
396 Iterator<String> iteratorKey = treeMap.keySet().iterator();
397
398 int intIndex = 0;
399 OpenstackNode gw = null;
400 while (iteratorKey.hasNext()) {
401 String key = iteratorKey.next();
402
403 if (intIndex == index) {
404 gw = treeMap.get(key);
405 }
406 intIndex++;
407 }
408 return gw;
409 }
Jian Li091d8d22018-02-20 10:42:06 +0900410}