Do not instantiate JsonParser
[openflowplugin.git] / applications / southbound-cli / src / main / java / org / opendaylight / openflowplugin / applications / southboundcli / cli / GetReconciliationStateProvider.java
1 /*
2  * Copyright (c) 2020 Ericsson India Global Services Pvt Ltd. and others.  All rights reserved.
3  *
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
7  */
8 package org.opendaylight.openflowplugin.applications.southboundcli.cli;
9
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;
19 import java.net.URI;
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;
27 import java.util.Map;
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;
38
39 @Command(scope = "openflow", name = "getreconciliationstate",
40         description = "Print reconciliation state for all devices")
41 public class GetReconciliationStateProvider extends OsgiCommandSupport {
42
43     @Option(name = "-d", description = "Node Id")
44     String nodeId;
45
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;
59
60
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;
67     }
68
69     @Override
70     protected Object doExecute() throws Exception {
71         List<String> result = new ArrayList<>();
72         Map<String, String> reconciliationStates  = getClusterwideReconcilitionStates();
73         if (nodeId == null) {
74             if (!reconciliationStates.isEmpty()) {
75                 reconciliationStates.forEach((datapathId, reconciliationState) -> {
76                     String status = String.format("%-17s %-50s", datapathId, reconciliationState);
77                     result.add(status);
78                 });
79                 printReconciliationStates(result);
80             } else {
81                 session.getConsole().println("Reconciliation data not available");
82             }
83         }
84         else {
85             String reconciliationState = getReconciliationStateForNode();
86             if (reconciliationState != null) {
87                 String status = String.format("%-17s %-50s", nodeId, reconciliationState);
88                 result.add(status);
89                 printReconciliationStates(result);
90             } else {
91                 session.getConsole().println("Reconciliation data not available for the specified node");
92             }
93         }
94         return null;
95     }
96
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);
103         }
104         return reconciliationState;
105     }
106
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));
111     }
112
113     private static String getHeaderOutput() {
114         String header = String.format("%-17s %-25s %-25s", "DatapathId", "Reconciliation Status",
115                 "Reconciliation Time");
116         return header;
117     }
118
119     private static String getLineSeparator() {
120         return "-------------------------------------------------------------------";
121     }
122
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) {
134                 try {
135                     if (memberAddress.equals(selfAddress)) {
136                         clusterwideReconcStates.putAll(getLocalStatusSummary());
137                     } else {
138                         clusterwideReconcStates.putAll(getRemoteReconciliationStates(memberAddress));
139                     }
140                 } catch (Exception e) {
141                     LOG.error("Exception while reaching Host {}", memberAddress, e);
142                 }
143             }
144         } else {
145             LOG.info("Could not obtain cluster members or the cluster-command is being executed locally\n");
146         }
147         return clusterwideReconcStates;
148     }
149
150     @SuppressWarnings("IllegalCatch")
151     private Map<String, String> getRemoteReconciliationStates(String ipAddress) {
152         Map<String, String> jmxReconciliationStates = new HashMap<>();
153         try {
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));
160             }
161         } catch (Exception e) {
162             LOG.error("Exception during reconciliation states from device with ip address {}", ipAddress, e);
163         }
164         return jmxReconciliationStates;
165     }
166
167     private Map<String,String> getLocalStatusSummary() {
168         return reconciliationJMXServiceMBean.acquireReconciliationStates();
169     }
170
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")
182                                 .GET()
183                                 .build();
184
185         LOG.debug("sending http request for accessing remote reconcilation");
186         HttpResponse<String> response = HttpClient.newBuilder()
187                                 .connectTimeout(request.timeout().get().plusMillis(1000))
188                                 .build()
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";
198             }
199         }
200         LOG.trace("HTTP Response is - {} for URL {}", respStr, restUrl);
201
202         return respStr;
203     }
204
205
206     String buildRemoteReconcilationUrl(String host) {
207         String targetHostAsString;
208         InetAddress hostInetAddress = InetAddresses.forString(host);
209         if (hostInetAddress instanceof Inet6Address) {
210             targetHostAsString = '[' + hostInetAddress.getHostAddress() + ']';
211         } else {
212             targetHostAsString = hostInetAddress.getHostAddress();
213         }
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();
217     }
218 }