diff options
Diffstat (limited to 'actn-interface-tools/restconf-client/ctl/src/main/java/org/onap/integration/actninterfacetools/protocol/restconf/ctl/RestConfSBControllerImpl.java')
-rw-r--r-- | actn-interface-tools/restconf-client/ctl/src/main/java/org/onap/integration/actninterfacetools/protocol/restconf/ctl/RestConfSBControllerImpl.java | 512 |
1 files changed, 512 insertions, 0 deletions
diff --git a/actn-interface-tools/restconf-client/ctl/src/main/java/org/onap/integration/actninterfacetools/protocol/restconf/ctl/RestConfSBControllerImpl.java b/actn-interface-tools/restconf-client/ctl/src/main/java/org/onap/integration/actninterfacetools/protocol/restconf/ctl/RestConfSBControllerImpl.java new file mode 100644 index 0000000..71f8797 --- /dev/null +++ b/actn-interface-tools/restconf-client/ctl/src/main/java/org/onap/integration/actninterfacetools/protocol/restconf/ctl/RestConfSBControllerImpl.java @@ -0,0 +1,512 @@ +/* + * ============LICENSE_START======================================================= + * Actn Interface Tools + * ================================================================================ + * Copyright (C) 2023 Huawei Canada Limited. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ +package org.onap.integration.actninterfacetools.protocol.restconf.ctl; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.google.common.collect.ImmutableMap; +import org.apache.http.client.methods.HttpPatch; +import org.apache.http.conn.ssl.AllowAllHostnameVerifier; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.ssl.SSLContextBuilder; +import org.glassfish.jersey.client.ChunkedInput; +import org.glassfish.jersey.client.authentication.HttpAuthenticationFeature; +import org.onap.integration.actninterfacetools.protocol.restconf.PncInstance; +import org.onap.integration.actninterfacetools.protocol.restconf.RestConfSBController; +import org.onap.integration.actninterfacetools.protocol.restconf.RestconfNotificationEventListener; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; +import javax.ws.rs.client.Client; +import javax.ws.rs.client.ClientBuilder; +import javax.ws.rs.client.Entity; +import javax.ws.rs.client.WebTarget; +import javax.ws.rs.core.GenericType; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import java.io.IOException; +import java.net.InetAddress; +import java.nio.charset.StandardCharsets; +import java.security.KeyManagementException; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +/** + * The implementation of RestConfSBController. + */ + +public class RestConfSBControllerImpl implements RestConfSBController { + private static volatile RestConfSBControllerImpl restConfClientInstance = null; + private static final int STATUS_OK = Response.Status.OK.getStatusCode(); + private static final int STATUS_CREATED = Response.Status.CREATED.getStatusCode(); + private static final int STATUS_ACCEPTED = Response.Status.ACCEPTED.getStatusCode(); + private static final int PARTIAL_CONTENT = Response.Status.PARTIAL_CONTENT.getStatusCode(); + protected static final String DOUBLESLASH = "/"; + protected static final String COLON = ":"; + private static final String XML = "xml"; + private static final String JSON = "json"; + private static final String HTTPS = "https"; + private static final String AUTHORIZATION_PROPERTY = "authorization"; + private static final String BASIC_AUTH_PREFIX = "Basic "; + private static final Logger log = LoggerFactory + .getLogger(RestConfSBControllerImpl.class); + + // TODO: for the Ibis release when both RESTCONF server and RESTCONF client + // fully support root resource discovery, ROOT_RESOURCE constant will be + // removed and rather the value would get discovered dynamically. + private static final String ROOT_RESOURCE = "/restconf"; + + private static final String RESOURCE_PATH_PREFIX = "/data/"; + private static final String NOTIFICATION_PATH_PREFIX = "/streams/"; + + private Map<UUID, Set<RestconfNotificationEventListener>> + restconfNotificationListenerMap = new ConcurrentHashMap<>(); + private Map<UUID, GetChunksRunnable> runnableTable = new ConcurrentHashMap<>(); + private final Map<UUID, PncInstance> pncMap = new ConcurrentHashMap<>(); + private final Map<UUID, Client> clientMap = new ConcurrentHashMap<>(); + + ExecutorService executor = Executors.newCachedThreadPool(); + + private RestConfSBControllerImpl(){ + + } + public static RestConfSBControllerImpl getRestConfClient(){ + if(restConfClientInstance != null){ + return restConfClientInstance; + } + synchronized (RestConfSBControllerImpl.class) { + if(restConfClientInstance == null){ + restConfClientInstance = new RestConfSBControllerImpl(); + + } + return restConfClientInstance; + } + } + + public void activate() { + log.info("RESTCONF SBI Started"); + } + + public void deactivate() { + log.info("RESTCONF SBI Stopped"); + executor.shutdown(); + this.clientMap.clear(); + this.pncMap.clear(); + } + + public Map<UUID, PncInstance> getPncInstances() { + log.trace("RESTCONF SBI::getDevices"); + return ImmutableMap.copyOf(pncMap); + } + + public PncInstance getPncInstance(UUID pncInfo) { + log.trace("RESTCONF SBI::getDevice with deviceId"); + return pncMap.get(pncInfo); + } + + public PncInstance getPncInstance(InetAddress ip, int port) { + log.trace("RESTCONF SBI::getDevice with ip and port"); + return pncMap.values().stream().filter(v -> v.ip().equals(ip) && v.port() == port).findFirst().get(); + } + + public void addPncInstance(PncInstance pncInstance) { + log.trace("RESTCONF SBI::addDevice"); + if (!pncMap.containsKey(pncInstance.pncId())) { + Client client = ignoreSslClient(); + if (pncInstance.username() != null) { + String username = pncInstance.username(); + String password = pncInstance.password() == null ? "" : pncInstance.password(); + client.register(HttpAuthenticationFeature.basic(username, password)); + } + clientMap.put(pncInstance.pncId(), client); + pncMap.put(pncInstance.pncId(), pncInstance); + } else { + log.warn("Trying to add a device that is already existing {}", pncInstance.pncId()); + } + } + private Client ignoreSslClient() { + SSLContext sslcontext = null; + + try { + sslcontext = SSLContext.getInstance("TLS"); + sslcontext.init(null, new TrustManager[]{new X509TrustManager() { + public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException { + } + + public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException { + } + + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } + }}, new java.security.SecureRandom()); + } catch (NoSuchAlgorithmException | KeyManagementException e) { + e.printStackTrace(); + } + + return ClientBuilder.newBuilder().sslContext(sslcontext).hostnameVerifier((s1, s2) -> true).build(); + } + + public void removeDevice(UUID pncId) { + log.trace("RESTCONF SBI::removeDevice"); + clientMap.remove(pncId); + pncMap.remove(pncId); + } + @Override + public boolean post(UUID pncId, String request, ObjectNode payload, + String mediaType) { + request = discoverRootResource(pncId) + RESOURCE_PATH_PREFIX + + request; + return checkStatusCode(post(pncId, request, payload, typeOfMediaType(mediaType))); + } + private MediaType typeOfMediaType(String type) { + switch (type) { + case XML: + return MediaType.APPLICATION_XML_TYPE; + case JSON: + return MediaType.APPLICATION_JSON_TYPE; + case MediaType.WILDCARD: + return MediaType.WILDCARD_TYPE; + default: + throw new IllegalArgumentException("Unsupported media type " + type); + + } + } + private boolean checkStatusCode(int statusCode) { + if (statusCode == STATUS_OK || statusCode == STATUS_CREATED || statusCode == STATUS_ACCEPTED) { + return true; + } else { + log.error("Failed request, HTTP error code : " + statusCode); + return false; + } + } + @Override + public int post(UUID pndId, String request, ObjectNode payload, MediaType mediaType) { + Response response = getResponse(pndId, request, payload, mediaType); + if (response == null) { + return Response.Status.NO_CONTENT.getStatusCode(); + } + return response.getStatus(); + } + private Response getResponse(UUID pncId, String request, ObjectNode payload, MediaType mediaType) { + + WebTarget wt = getWebTarget(pncId, request); + + Response response = null; + if (payload != null) { + try { + response = wt.request(mediaType) + .post(Entity.entity(payload.toString(), mediaType)); + } catch (Exception e) { + log.error("Cannot do POST {} request on device {} because can't read payload", request, pncId); + } + } else { + response = wt.request(mediaType).post(Entity.entity(null, mediaType)); + } + return response; + } + protected WebTarget getWebTarget(UUID pncId, String request) { + log.info("Sending request to URL {} ", getUrlString(pncId, request)); + return clientMap.get(pncId).target(getUrlString(pncId, request)); + } + protected String getUrlString(UUID pncId, String request) { + PncInstance pncInstance = pncMap.get(pncId); + if (pncInstance == null) { + log.warn("restSbDevice cannot be NULL!"); + return ""; + } + if (pncInstance.url() != null) { + return pncInstance.protocol() + COLON + DOUBLESLASH + pncInstance.url() + request; + } else { + return pncInstance.protocol() + COLON + DOUBLESLASH + pncInstance.ip().toString() + + COLON + pncInstance.port() + request; + } + } + @Override + public <T> T post(UUID pncId, String request, ObjectNode payload, + String mediaType, Class<T> responseClass) { + request = discoverRootResource(pncId) + RESOURCE_PATH_PREFIX + + request; + return post(pncId, request, payload, typeOfMediaType(mediaType), responseClass); + } + @Override + public <T> T post(UUID device, String request, ObjectNode payload, MediaType mediaType, + Class<T> responseClass) { + Response response = getResponse(device, request, payload, mediaType); + if (response != null && response.hasEntity()) { + return responseClass == Response.class ? (T) response : response.readEntity(responseClass); + } + log.error("Response from device {} for request {} contains no entity", device, request); + return null; + } + @Override + public boolean put(UUID pncId, String request, ObjectNode payload, + String mediaType) { + request = discoverRootResource(pncId) + RESOURCE_PATH_PREFIX + + request; + return checkStatusCode(put(pncId, request, payload, typeOfMediaType(mediaType))); + } + @Override + public int put(UUID pncId, String request, ObjectNode payload, MediaType mediaType) { + + WebTarget wt = getWebTarget(pncId, request); + + Response response = null; + if (payload != null) { + try { + response = wt.request(mediaType).put(Entity.entity(payload.toString(), mediaType)); + } catch (Exception e) { + log.error("Cannot do PUT {} request on device {} because can't read payload", request, pncId); + } + } else { + response = wt.request(mediaType).put(Entity.entity(null, mediaType)); + } + + if (response == null) { + return Response.Status.NO_CONTENT.getStatusCode(); + } + return response.getStatus(); + } + @Override + public ObjectNode get(UUID pncId, String request, String mediaType) { + request = discoverRootResource(pncId) + RESOURCE_PATH_PREFIX + + request; + return get(pncId, request, typeOfMediaType(mediaType)); + } + public ObjectNode get(UUID pncId, String request, MediaType mediaType) { + WebTarget wt = getWebTarget(pncId, request); + + Response s = wt.request(mediaType).get(); + + if (checkReply(s)) { + try { + String json = s.readEntity((String.class)); + return new ObjectMapper().readTree(json).deepCopy(); + } catch (Exception ex) { + log.error("ERROR: ", ex); + } + } + + return null; + } + private boolean checkReply(Response response) { + if (response != null) { + boolean statusCode = checkStatusCode(response.getStatus()); + if (!statusCode && response.hasEntity()) { + log.error("Failed request, HTTP error msg : " + response.readEntity(String.class)); + } + return statusCode; + } + log.error("Null reply from device"); + return false; + } + @Override + public boolean patch(UUID pncId, String request, ObjectNode payload, + String mediaType) { + request = discoverRootResource(pncId) + RESOURCE_PATH_PREFIX + + request; + return checkStatusCode(patch(pncId, request, payload, typeOfMediaType(mediaType))); + } + @Override + public int patch(UUID pncId, String request, ObjectNode payload, MediaType mediaType) { + + try { + log.debug("Url request {} ", getUrlString(pncId, request)); + HttpPatch httprequest = new HttpPatch(getUrlString(pncId, request)); + if (pncMap.get(pncId).username() != null) { + String pwd = pncMap.get(pncId).password() == null ? "" : COLON + pncMap.get(pncId).password(); + String userPassword = pncMap.get(pncId).username() + pwd; + String base64string = Base64.getEncoder().encodeToString(userPassword.getBytes(StandardCharsets.UTF_8)); + httprequest.addHeader(AUTHORIZATION_PROPERTY, BASIC_AUTH_PREFIX + base64string); + } + if (payload != null) { + StringEntity input = new StringEntity(payload.toString()); + input.setContentType(mediaType.toString()); + httprequest.setEntity(input); + } + CloseableHttpClient httpClient; + if (pncMap.containsKey(pncId) && pncMap.get(pncId).protocol().equals(HTTPS)) { + httpClient = getApacheSslBypassClient(); + } else { + httpClient = HttpClients.createDefault(); + } + return httpClient.execute(httprequest).getStatusLine().getStatusCode(); + } catch (IOException | NoSuchAlgorithmException | KeyManagementException | KeyStoreException e) { + log.error("Cannot do PATCH {} request on device {}", request, pncId, e); + } + return Response.Status.BAD_REQUEST.getStatusCode(); + } + private CloseableHttpClient getApacheSslBypassClient() throws NoSuchAlgorithmException, + KeyManagementException, KeyStoreException { + return HttpClients.custom(). + setHostnameVerifier(new AllowAllHostnameVerifier()). + setSslcontext(new SSLContextBuilder() + .loadTrustMaterial(null, (arg0, arg1) -> true) + .build()).build(); + } + @Override + public boolean delete(UUID pncId, String request, + String mediaType) { + request = discoverRootResource(pncId) + RESOURCE_PATH_PREFIX + + request; + return checkStatusCode(delete(pncId, request, typeOfMediaType(mediaType))); + } + @Override + public int delete(UUID pncId, String request, MediaType mediaType) { + + WebTarget wt = getWebTarget(pncId, request); + + Response response = wt.request(mediaType).delete(); + + return response.getStatus(); + } + @Override + public void enableNotifications(UUID pncId, String request, + String mediaType, + RestconfNotificationEventListener listener) { + + if (isNotificationEnabled(pncId)) { + log.warn("enableNotifications: already enabled on device: {}", pncId); + return; + } + + request = discoverRootResource(pncId) + NOTIFICATION_PATH_PREFIX + + request; + + addNotificationListener(pncId, listener); + + GetChunksRunnable runnable = new GetChunksRunnable(request, mediaType, + pncId); + runnableTable.put(pncId, runnable); + executor.execute(runnable); + } + + public void stopNotifications(UUID pncId) { + runnableTable.get(pncId).terminate(); + runnableTable.remove(pncId); + restconfNotificationListenerMap.remove(pncId); + log.debug("Stop sending notifications for device URI: " + pncId.toString()); + + } + + public class GetChunksRunnable implements Runnable { + private String request; + private String mediaType; + private UUID pnc; + + private volatile boolean running = true; + + public void terminate() { + running = false; + } + + /** + * @param request request + * @param mediaType media type + * @param pncId PNC identifier + */ + public GetChunksRunnable(String request, String mediaType, + UUID pncId) { + this.request = request; + this.mediaType = mediaType; + this.pnc = pncId; + } + + @Override + public void run() { + WebTarget wt = getWebTarget(pnc, request); + Response clientResp = wt.request(mediaType).get(); + Set<RestconfNotificationEventListener> listeners = + restconfNotificationListenerMap.get(pnc); + final ChunkedInput<String> chunkedInput = (ChunkedInput<String>) clientResp + .readEntity(new GenericType<ChunkedInput<String>>() { + }); + + String chunk; + // Note that the read() is a blocking operation and the invoking + // thread is blocked until a new chunk comes. Jersey implementation + // of this IO operation is in a way that it does not respond to + // interrupts. + while (running) { + chunk = chunkedInput.read(); + if (chunk != null) { + if (running) { + for (RestconfNotificationEventListener listener : listeners) { + listener.handleNotificationEvent(pnc, chunk); + } + } else { + log.trace("the requesting client is no more interested " + + "to receive such notifications."); + } + } else { + log.trace("The received notification chunk is null. do not continue any more."); + break; + } + } + log.trace("out of while loop -- end of run"); + } + } + + public String discoverRootResource(UUID pncId) { + // FIXME: send a GET command to the device to discover the root resource. + // The plan to fix this is for the Ibis release when the RESTCONF server and + // the RESTCONF client both support root resource discovery. + return ROOT_RESOURCE; + } + + @Override + public void addNotificationListener(UUID pncId, + RestconfNotificationEventListener listener) { + Set<RestconfNotificationEventListener> listeners = + restconfNotificationListenerMap.get(pncId); + if (listeners == null) { + listeners = new HashSet<>(); + } + + listeners.add(listener); + + this.restconfNotificationListenerMap.put(pncId, listeners); + } + + @Override + public void removeNotificationListener(UUID pncId, + RestconfNotificationEventListener listener) { + Set<RestconfNotificationEventListener> listeners = + restconfNotificationListenerMap.get(pncId); + if (listeners != null) { + listeners.remove(listener); + } + } + + public boolean isNotificationEnabled(UUID pncId) { + return runnableTable.containsKey(pncId); + } +} |