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 edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
16 import java.lang.reflect.Type;
17 import java.net.Inet6Address;
18 import java.net.InetAddress;
20 import java.net.http.HttpClient;
21 import java.net.http.HttpRequest;
22 import java.net.http.HttpResponse;
23 import java.time.Duration;
24 import java.util.ArrayList;
25 import java.util.HashMap;
26 import java.util.List;
28 import java.util.stream.Collectors;
29 import org.apache.commons.codec.binary.Base64;
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 {
43 @Option(name = "-d", description = "Node Id")
46 private static final Logger LOG = LoggerFactory.getLogger(GetReconciliationStateProvider.class);
47 private static final String URL_PREFIX = "http://";
48 private static final String URL_SEPARATOR = "/";
49 private static final String URL_SEPARATOR_COLON = ":";
50 private static final String HTTP_JOL_OKIA_BASE_URI = "/jolokia/exec/";
51 private static final int HTTP_TIMEOUT = 5000;
52 private final Integer httpPort;
53 private static final String JMX_OBJECT_NAME
54 = "org.opendaylight.openflowplugin.frm:type=ReconciliationState";
55 private static final String JMX_ATTRIBUTE_NAME = "acquireReconciliationStates";
56 private static final String JMX_REST_HTTP_AUTH_UNAME_PWD = "admin:admin";
57 private final ReconciliationJMXServiceMBean reconciliationJMXServiceMBean;
58 private final ClusterMemberInfo clusterMemberInfoProvider;
61 public GetReconciliationStateProvider(final ReconciliationJMXServiceMBean reconciliationJMXServiceMBean,
62 final ClusterMemberInfo clusterMemberInfoProvider,
63 @Nullable Integer httpPort) {
64 this.reconciliationJMXServiceMBean = reconciliationJMXServiceMBean;
65 this.clusterMemberInfoProvider = clusterMemberInfoProvider;
66 this.httpPort = httpPort;
70 protected Object doExecute() throws Exception {
71 List<String> result = new ArrayList<>();
72 Map<String, String> reconciliationStates = getClusterwideReconcilitionStates();
74 if (!reconciliationStates.isEmpty()) {
75 reconciliationStates.forEach((datapathId, reconciliationState) -> {
76 String status = String.format("%-17s %-50s", datapathId, reconciliationState);
79 printReconciliationStates(result);
81 session.getConsole().println("Reconciliation data not available");
85 String reconciliationState = getReconciliationStateForNode();
86 if (reconciliationState != null) {
87 String status = String.format("%-17s %-50s", nodeId, reconciliationState);
89 printReconciliationStates(result);
91 session.getConsole().println("Reconciliation data not available for the specified node");
97 private String getReconciliationStateForNode() {
98 //first checking reconciliation state locally
99 String reconciliationState = reconciliationJMXServiceMBean.acquireReconciliationStates().get(nodeId);
100 if (reconciliationState == null) {
101 //checking reconciliation state in the cluster
102 reconciliationState = getClusterwideReconcilitionStates().get(nodeId);
104 return reconciliationState;
107 private void printReconciliationStates(List<String> result) {
108 session.getConsole().println(getHeaderOutput());
109 session.getConsole().println(getLineSeparator());
110 result.stream().forEach(p -> session.getConsole().println(p));
113 private static String getHeaderOutput() {
114 String header = String.format("%-17s %-25s %-25s", "DatapathId", "Reconciliation Status",
115 "Reconciliation Time");
119 private static String getLineSeparator() {
120 return "-------------------------------------------------------------------";
123 @SuppressWarnings("IllegalCatch")
124 private Map<String,String> getClusterwideReconcilitionStates() {
125 Map<String,String> clusterwideReconcStates = new HashMap<>();
126 List<String> clusterIPAddresses = clusterMemberInfoProvider.getClusterMembers().stream()
127 .map(s -> s.getHostAddress()).collect(Collectors.toList());
128 LOG.debug("The ip address of nodes in the cluster : {}", clusterIPAddresses);
129 if (!clusterIPAddresses.isEmpty()) {
130 String selfAddress = clusterMemberInfoProvider.getSelfAddress() != null
131 ? clusterMemberInfoProvider.getSelfAddress().getHostAddress() : "localhost";
132 LOG.trace("The ip address of local node is {}", selfAddress);
133 for (String memberAddress : clusterIPAddresses) {
135 if (memberAddress.equals(selfAddress)) {
136 clusterwideReconcStates.putAll(getLocalStatusSummary());
138 clusterwideReconcStates.putAll(getRemoteReconciliationStates(memberAddress));
140 } catch (Exception e) {
141 LOG.error("Exception while reaching Host {}", memberAddress, e);
145 LOG.info("Could not obtain cluster members or the cluster-command is being executed locally\n");
147 return clusterwideReconcStates;
150 @SuppressWarnings("IllegalCatch")
151 private Map<String, String> getRemoteReconciliationStates(String ipAddress) {
152 Map<String, String> jmxReconciliationStates = new HashMap<>();
154 String getReconcilationRemoteResponse = invokeRemoteRestOperation(ipAddress);
155 if (getReconcilationRemoteResponse != null) {
156 JsonElement rootObj = JsonParser.parseString(getReconcilationRemoteResponse);
157 String remoteJMXOperationResult = rootObj.getAsJsonObject().get("value").toString();
158 Type type = new TypeToken<HashMap<String, String>>() {}.getType();
159 jmxReconciliationStates.putAll(new Gson().fromJson(remoteJMXOperationResult, type));
161 } catch (Exception e) {
162 LOG.error("Exception during reconciliation states from device with ip address {}", ipAddress, e);
164 return jmxReconciliationStates;
167 private Map<String,String> getLocalStatusSummary() {
168 return reconciliationJMXServiceMBean.acquireReconciliationStates();
171 @SuppressFBWarnings("DM_DEFAULT_ENCODING")
172 private String invokeRemoteRestOperation(String ipAddress) throws Exception {
173 String restUrl = buildRemoteReconcilationUrl(ipAddress);
174 LOG.info("invokeRemoteReconcilationState() REST URL: {}", restUrl);
175 String authString = JMX_REST_HTTP_AUTH_UNAME_PWD;
176 byte[] authEncBytes = Base64.encodeBase64(authString.getBytes());
177 String authStringEnc = new String(authEncBytes);
178 HttpRequest request = HttpRequest.newBuilder(URI.create(restUrl))
179 .timeout(Duration.ofMillis(HTTP_TIMEOUT))
180 .header("Authorization","Basic " + authStringEnc)
181 .header("Accept","application/json")
185 LOG.debug("sending http request for accessing remote reconcilation");
186 HttpResponse<String> response = HttpClient.newBuilder()
187 .connectTimeout(request.timeout().get().plusMillis(1000))
189 .send(request, HttpResponse.BodyHandlers.ofString());
190 // Response code for success should be 200
191 Integer httpResponseCode = response.statusCode();
192 LOG.debug("http response received for remote reconcilation {}", httpResponseCode);
193 String respStr = response.body();
194 if (httpResponseCode > 299) {
195 LOG.error("Non-200 http response code received {} for URL {}", httpResponseCode, restUrl);
196 if (respStr == null || respStr.isEmpty()) {
197 return "Service Status Retrieval failed. HTTP Response Code : " + httpResponseCode + "\n";
200 LOG.trace("HTTP Response is - {} for URL {}", respStr, restUrl);
206 String buildRemoteReconcilationUrl(String host) {
207 String targetHostAsString;
208 InetAddress hostInetAddress = InetAddresses.forString(host);
209 if (hostInetAddress instanceof Inet6Address) {
210 targetHostAsString = '[' + hostInetAddress.getHostAddress() + ']';
212 targetHostAsString = hostInetAddress.getHostAddress();
214 return new StringBuilder().append(URL_PREFIX).append(targetHostAsString).append(URL_SEPARATOR_COLON)
215 .append(httpPort).append(HTTP_JOL_OKIA_BASE_URI).append(JMX_OBJECT_NAME)
216 .append(URL_SEPARATOR).append(JMX_ATTRIBUTE_NAME).toString();