Migrate users of Optional.get()
[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 java.lang.reflect.Type;
16 import java.net.Inet6Address;
17 import java.net.InetAddress;
18 import java.net.URI;
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;
28 import java.util.Map;
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;
38
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);
43
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";
54
55     @Option(name = "-d", description = "Node Id")
56     String nodeId;
57
58     private final Integer httpPort;
59     private final ReconciliationJMXServiceMBean reconciliationJMXServiceMBean;
60     private final ClusterMemberInfo clusterMemberInfoProvider;
61
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;
68     }
69
70     @Override
71     protected Object doExecute() {
72         List<String> result = new ArrayList<>();
73         Map<String, String> reconciliationStates  = getClusterwideReconcilitionStates();
74         if (nodeId == null) {
75             if (!reconciliationStates.isEmpty()) {
76                 reconciliationStates.forEach((datapathId, reconciliationState) -> {
77                     String status = String.format("%-17s %-50s", datapathId, reconciliationState);
78                     result.add(status);
79                 });
80                 printReconciliationStates(result);
81             } else {
82                 session.getConsole().println("Reconciliation data not available");
83             }
84         }
85         else {
86             String reconciliationState = getReconciliationStateForNode();
87             if (reconciliationState != null) {
88                 String status = String.format("%-17s %-50s", nodeId, reconciliationState);
89                 result.add(status);
90                 printReconciliationStates(result);
91             } else {
92                 session.getConsole().println("Reconciliation data not available for the specified node");
93             }
94         }
95         return null;
96     }
97
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);
104         }
105         return reconciliationState;
106     }
107
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));
112     }
113
114     private static String getHeaderOutput() {
115         return String.format("%-17s %-25s %-25s", "DatapathId", "Reconciliation Status", "Reconciliation Time");
116     }
117
118     private static String getLineSeparator() {
119         return "-------------------------------------------------------------------";
120     }
121
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) {
133                 try {
134                     if (memberAddress.equals(selfAddress)) {
135                         clusterwideReconcStates.putAll(getLocalStatusSummary());
136                     } else {
137                         clusterwideReconcStates.putAll(getRemoteReconciliationStates(memberAddress));
138                     }
139                 } catch (Exception e) {
140                     LOG.error("Exception while reaching Host {}", memberAddress, e);
141                 }
142             }
143         } else {
144             LOG.info("Could not obtain cluster members or the cluster-command is being executed locally\n");
145         }
146         return clusterwideReconcStates;
147     }
148
149     @SuppressWarnings("IllegalCatch")
150     private Map<String, String> getRemoteReconciliationStates(final String ipAddress) {
151         Map<String, String> jmxReconciliationStates = new HashMap<>();
152         try {
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));
159             }
160         } catch (Exception e) {
161             LOG.error("Exception during reconciliation states from device with ip address {}", ipAddress, e);
162         }
163         return jmxReconciliationStates;
164     }
165
166     private Map<String,String> getLocalStatusSummary() {
167         return reconciliationJMXServiceMBean.acquireReconciliationStates();
168     }
169
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")
179                                 .GET()
180                                 .build();
181
182         LOG.debug("sending http request for accessing remote reconcilation");
183         HttpResponse<String> response = HttpClient.newBuilder()
184                                 .connectTimeout(request.timeout().orElseThrow().plusMillis(1000))
185                                 .build()
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";
195             }
196         }
197         LOG.trace("HTTP Response is - {} for URL {}", respStr, restUrl);
198
199         return respStr;
200     }
201
202
203     String buildRemoteReconcilationUrl(final String host) {
204         String targetHostAsString;
205         InetAddress hostInetAddress = InetAddresses.forString(host);
206         if (hostInetAddress instanceof Inet6Address) {
207             targetHostAsString = '[' + hostInetAddress.getHostAddress() + ']';
208         } else {
209             targetHostAsString = hostInetAddress.getHostAddress();
210         }
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();
214     }
215 }