2 * Copyright (c) 2020 Ericsson India Global Services Pvt Ltd. and others. All rights reserved.
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 * and is available at http://www.eclipse.org/legal/epl-v10.html
8 package org.opendaylight.openflowplugin.applications.southboundcli.cli;
10 import com.google.common.net.InetAddresses;
11 import com.google.gson.Gson;
12 import com.google.gson.JsonElement;
13 import com.google.gson.JsonParser;
14 import com.google.gson.reflect.TypeToken;
15 import java.lang.reflect.Type;
16 import java.net.Inet6Address;
17 import java.net.InetAddress;
19 import java.net.http.HttpClient;
20 import java.net.http.HttpRequest;
21 import java.net.http.HttpResponse;
22 import java.nio.charset.StandardCharsets;
23 import java.time.Duration;
24 import java.util.ArrayList;
25 import java.util.Base64;
26 import java.util.HashMap;
27 import java.util.List;
29 import java.util.stream.Collectors;
30 import org.apache.karaf.shell.commands.Command;
31 import org.apache.karaf.shell.commands.Option;
32 import org.apache.karaf.shell.console.OsgiCommandSupport;
33 import org.eclipse.jdt.annotation.Nullable;
34 import org.opendaylight.infrautils.diagstatus.ClusterMemberInfo;
35 import org.opendaylight.openflowplugin.applications.frm.ReconciliationJMXServiceMBean;
36 import org.slf4j.Logger;
37 import org.slf4j.LoggerFactory;
39 @Command(scope = "openflow", name = "getreconciliationstate",
40 description = "Print reconciliation state for all devices")
41 public class GetReconciliationStateProvider extends OsgiCommandSupport {
42 private static final Logger LOG = LoggerFactory.getLogger(GetReconciliationStateProvider.class);
44 // FIXME: this is fugly: it is an unexpressed dependency on FRM bean as well as Jolokia
45 private static final String URL_PREFIX = "http://";
46 private static final String URL_SEPARATOR = "/";
47 private static final String URL_SEPARATOR_COLON = ":";
48 private static final String HTTP_JOL_OKIA_BASE_URI = "/jolokia/exec/";
49 private static final int HTTP_TIMEOUT = 5000;
50 private static final String JMX_OBJECT_NAME = "org.opendaylight.openflowplugin.frm:type=ReconciliationState";
51 private static final String JMX_ATTRIBUTE_NAME = "acquireReconciliationStates";
52 // FIXME: hard-coded credentials
53 private static final String JMX_REST_HTTP_AUTH_UNAME_PWD = "admin:admin";
55 @Option(name = "-d", description = "Node Id")
58 private final Integer httpPort;
59 private final ReconciliationJMXServiceMBean reconciliationJMXServiceMBean;
60 private final ClusterMemberInfo clusterMemberInfoProvider;
62 public GetReconciliationStateProvider(final ReconciliationJMXServiceMBean reconciliationJMXServiceMBean,
63 final ClusterMemberInfo clusterMemberInfoProvider,
64 final @Nullable Integer httpPort) {
65 this.reconciliationJMXServiceMBean = reconciliationJMXServiceMBean;
66 this.clusterMemberInfoProvider = clusterMemberInfoProvider;
67 this.httpPort = httpPort;
71 protected Object doExecute() {
72 List<String> result = new ArrayList<>();
73 Map<String, String> reconciliationStates = getClusterwideReconcilitionStates();
75 if (!reconciliationStates.isEmpty()) {
76 reconciliationStates.forEach((datapathId, reconciliationState) -> {
77 String status = String.format("%-17s %-50s", datapathId, reconciliationState);
80 printReconciliationStates(result);
82 session.getConsole().println("Reconciliation data not available");
86 String reconciliationState = getReconciliationStateForNode();
87 if (reconciliationState != null) {
88 String status = String.format("%-17s %-50s", nodeId, reconciliationState);
90 printReconciliationStates(result);
92 session.getConsole().println("Reconciliation data not available for the specified node");
98 private String getReconciliationStateForNode() {
99 //first checking reconciliation state locally
100 String reconciliationState = reconciliationJMXServiceMBean.acquireReconciliationStates().get(nodeId);
101 if (reconciliationState == null) {
102 //checking reconciliation state in the cluster
103 reconciliationState = getClusterwideReconcilitionStates().get(nodeId);
105 return reconciliationState;
108 private void printReconciliationStates(final List<String> result) {
109 session.getConsole().println(getHeaderOutput());
110 session.getConsole().println(getLineSeparator());
111 result.stream().forEach(p -> session.getConsole().println(p));
114 private static String getHeaderOutput() {
115 return String.format("%-17s %-25s %-25s", "DatapathId", "Reconciliation Status", "Reconciliation Time");
118 private static String getLineSeparator() {
119 return "-------------------------------------------------------------------";
122 @SuppressWarnings("IllegalCatch")
123 private Map<String,String> getClusterwideReconcilitionStates() {
124 Map<String,String> clusterwideReconcStates = new HashMap<>();
125 List<String> clusterIPAddresses = clusterMemberInfoProvider.getClusterMembers().stream()
126 .map(InetAddress::getHostAddress).collect(Collectors.toList());
127 LOG.debug("The ip address of nodes in the cluster : {}", clusterIPAddresses);
128 if (!clusterIPAddresses.isEmpty()) {
129 String selfAddress = clusterMemberInfoProvider.getSelfAddress() != null
130 ? clusterMemberInfoProvider.getSelfAddress().getHostAddress() : "localhost";
131 LOG.trace("The ip address of local node is {}", selfAddress);
132 for (String memberAddress : clusterIPAddresses) {
134 if (memberAddress.equals(selfAddress)) {
135 clusterwideReconcStates.putAll(getLocalStatusSummary());
137 clusterwideReconcStates.putAll(getRemoteReconciliationStates(memberAddress));
139 } catch (Exception e) {
140 LOG.error("Exception while reaching Host {}", memberAddress, e);
144 LOG.info("Could not obtain cluster members or the cluster-command is being executed locally\n");
146 return clusterwideReconcStates;
149 @SuppressWarnings("IllegalCatch")
150 private Map<String, String> getRemoteReconciliationStates(final String ipAddress) {
151 Map<String, String> jmxReconciliationStates = new HashMap<>();
153 String getReconcilationRemoteResponse = invokeRemoteRestOperation(ipAddress);
154 if (getReconcilationRemoteResponse != null) {
155 JsonElement rootObj = JsonParser.parseString(getReconcilationRemoteResponse);
156 String remoteJMXOperationResult = rootObj.getAsJsonObject().get("value").toString();
157 Type type = new TypeToken<HashMap<String, String>>() {}.getType();
158 jmxReconciliationStates.putAll(new Gson().fromJson(remoteJMXOperationResult, type));
160 } catch (Exception e) {
161 LOG.error("Exception during reconciliation states from device with ip address {}", ipAddress, e);
163 return jmxReconciliationStates;
166 private Map<String,String> getLocalStatusSummary() {
167 return reconciliationJMXServiceMBean.acquireReconciliationStates();
170 private String invokeRemoteRestOperation(final String ipAddress) throws Exception {
171 String restUrl = buildRemoteReconcilationUrl(ipAddress);
172 LOG.info("invokeRemoteReconcilationState() REST URL: {}", restUrl);
173 String authString = JMX_REST_HTTP_AUTH_UNAME_PWD;
174 String authStringEnc = Base64.getEncoder().encodeToString(authString.getBytes(StandardCharsets.UTF_8));
175 HttpRequest request = HttpRequest.newBuilder(URI.create(restUrl))
176 .timeout(Duration.ofMillis(HTTP_TIMEOUT))
177 .header("Authorization", "Basic " + authStringEnc)
178 .header("Accept", "application/json")
182 LOG.debug("sending http request for accessing remote reconcilation");
183 HttpResponse<String> response = HttpClient.newBuilder()
184 .connectTimeout(request.timeout().orElseThrow().plusMillis(1000))
186 .send(request, HttpResponse.BodyHandlers.ofString());
187 // Response code for success should be 200
188 Integer httpResponseCode = response.statusCode();
189 LOG.debug("http response received for remote reconcilation {}", httpResponseCode);
190 String respStr = response.body();
191 if (httpResponseCode > 299) {
192 LOG.error("Non-200 http response code received {} for URL {}", httpResponseCode, restUrl);
193 if (respStr == null || respStr.isEmpty()) {
194 return "Service Status Retrieval failed. HTTP Response Code : " + httpResponseCode + "\n";
197 LOG.trace("HTTP Response is - {} for URL {}", respStr, restUrl);
203 String buildRemoteReconcilationUrl(final String host) {
204 String targetHostAsString;
205 InetAddress hostInetAddress = InetAddresses.forString(host);
206 if (hostInetAddress instanceof Inet6Address) {
207 targetHostAsString = '[' + hostInetAddress.getHostAddress() + ']';
209 targetHostAsString = hostInetAddress.getHostAddress();
211 return new StringBuilder().append(URL_PREFIX).append(targetHostAsString).append(URL_SEPARATOR_COLON)
212 .append(httpPort).append(HTTP_JOL_OKIA_BASE_URI).append(JMX_OBJECT_NAME)
213 .append(URL_SEPARATOR).append(JMX_ATTRIBUTE_NAME).toString();