X-Git-Url: https://gerrit.onap.org/r/gitweb?a=blobdiff_plain;f=appc-dg%2Fappc-dg-shared%2Fappc-dg-mdsal-store%2Fappc-dg-mdsal-bundle%2Fsrc%2Fmain%2Fjava%2Forg%2Fonap%2Fappc%2Fmdsal%2Fimpl%2FMDSALStoreImpl.java;fp=appc-dg%2Fappc-dg-shared%2Fappc-dg-mdsal-store%2Fappc-dg-mdsal-bundle%2Fsrc%2Fmain%2Fjava%2Forg%2Fonap%2Fappc%2Fmdsal%2Fimpl%2FMDSALStoreImpl.java;h=fcd315bf6be4f8756c13b1663f8424d57c9d7e81;hb=31540f8ba964a7d66ca04f1468db061abda5c7a1;hp=0000000000000000000000000000000000000000;hpb=36bcd566167f2f91c0e8e7a304fce5f6bc150776;p=appc.git diff --git a/appc-dg/appc-dg-shared/appc-dg-mdsal-store/appc-dg-mdsal-bundle/src/main/java/org/onap/appc/mdsal/impl/MDSALStoreImpl.java b/appc-dg/appc-dg-shared/appc-dg-mdsal-store/appc-dg-mdsal-bundle/src/main/java/org/onap/appc/mdsal/impl/MDSALStoreImpl.java new file mode 100644 index 000000000..fcd315bf6 --- /dev/null +++ b/appc-dg/appc-dg-shared/appc-dg-mdsal-store/appc-dg-mdsal-bundle/src/main/java/org/onap/appc/mdsal/impl/MDSALStoreImpl.java @@ -0,0 +1,354 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP : APPC + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Copyright (C) 2017 Amdocs + * ============================================================================= + * 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. + * + * ECOMP is a trademark and service mark of AT&T Intellectual Property. + * ============LICENSE_END========================================================= + */ + +package org.onap.appc.mdsal.impl; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonValue; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.io.IOUtils; +import org.apache.http.HttpResponse; +import org.opendaylight.yang.gen.v1.org.onap.appc.mdsal.store.rev170925.StoreYangInput; +import org.opendaylight.yang.gen.v1.org.onap.appc.mdsal.store.rev170925.StoreYangInputBuilder; +import org.opendaylight.yangtools.yang.binding.DataContainer; +import org.onap.appc.configuration.Configuration; +import org.onap.appc.configuration.ConfigurationFactory; +import org.onap.appc.exceptions.APPCException; +import org.onap.appc.mdsal.MDSALStore; +import org.onap.appc.mdsal.exception.MDSALStoreException; +import org.onap.appc.mdsal.objects.BundleInfo; +import com.att.eelf.configuration.EELFLogger; +import com.att.eelf.configuration.EELFManager; +import org.onap.appc.mdsal.operation.ConfigOperationRequestFormatter; +import org.onap.appc.rest.client.RestClientInvoker; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; +import org.osgi.framework.FrameworkUtil; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.*; +import java.util.jar.Attributes; +import java.util.jar.JarEntry; +import java.util.jar.JarOutputStream; +import java.util.jar.Manifest; + +/** + * Implementation of MDSALStore + */ +public class MDSALStoreImpl implements MDSALStore { + + private final EELFLogger logger = EELFManager.getInstance().getLogger(MDSALStoreImpl.class); + private RestClientInvoker client; + private ConfigOperationRequestFormatter requestFormatter = new ConfigOperationRequestFormatter(); + private ObjectMapper mapper = new ObjectMapper(); + private Map remoteClientMap = new HashMap<>(); + + MDSALStoreImpl() { + String configUrl = null; + String user = null; + String password = null; + Configuration configuration = ConfigurationFactory.getConfiguration(); + Properties properties = configuration.getProperties(); + if (properties != null) { + configUrl = properties.getProperty(Constants.CONFIG_URL_PROPERTY, Constants.CONFIG_URL_DEFAULT); + user = properties.getProperty(Constants.CONFIG_USER_PROPERTY); + password = properties.getProperty(Constants.CONFIG_PASS_PROPERTY); + } + if (configUrl != null) { + try { + client = new RestClientInvoker(new URL(configUrl)); + client.setAuthentication(user, password); + } catch (MalformedURLException e) { + logger.error("Error initializing RestConf client: " + e.getMessage(), e); + } + } + } + + + @Override + public boolean isModulePresent(String moduleName, Date revision) { + + if (logger.isDebugEnabled()) { + logger.debug("isModulePresent invoked with moduleName = " + moduleName + " , revision = " + revision); + } + + BundleContext bundleContext = FrameworkUtil.getBundle(this.getClass()).getBundleContext(); + /* + * SchemaContext interface of ODL provides APIs for querying details of yang modules + * loaded into MD-SAL store, but its limitation is, it only returns information about + * static yang modules loaded on server start up, it does not return information about + * the yang modules loaded dynamically. Due to this limitation, we are checking the + * presence of OSGI bundle instead of yang module. (Note: Assuming OSGI bundle is named + * with the yang module name). + */ + + Bundle bundle = bundleContext.getBundle(moduleName); + if (logger.isDebugEnabled()) { + logger.debug("isModulePresent returned = " + (bundle != null)); + } + return bundle != null; + } + + @Override + public void storeYangModule(String yang, BundleInfo bundleInfo) throws MDSALStoreException { + + BundleContext bundleContext = FrameworkUtil.getBundle(this.getClass()).getBundleContext(); + byte[] byteArray = createBundleJar(yang, Constants.BLUEPRINT, bundleInfo); + + try (ByteArrayInputStream inputStream = new ByteArrayInputStream(byteArray)) { + Bundle bundle = bundleContext.installBundle(bundleInfo.getLocation(), inputStream); + bundle.start(); + } catch (Exception e) { + logger.error(String.format("Error storing yang module: %s. Error message: %s.", yang, e.getMessage())); + throw new MDSALStoreException("Error storing yang module: " + yang + " " + e.getMessage(), e); + } + } + + @Override + public void storeYangModuleOnLeader(String yang, String moduleName) throws MDSALStoreException { + try { + String leader = getLeaderNode(); + if (Constants.SELF.equals(leader)){ + logger.debug("Current node is a leader."); + }else{ + logger.debug("Attempting to load yang module on Leader: " + leader ); + String inputJson = createInputJson(yang, moduleName); + RestClientInvoker remoteClient = getRemoteClient(leader); + HttpResponse response = remoteClient.doPost(Constants.YANG_LOADER_PATH, inputJson); + int httpCode = response.getStatusLine().getStatusCode(); + String respBody = IOUtils.toString(response.getEntity().getContent()); + if (httpCode < 200 || httpCode >= 300) { + logger.debug("Error while loading yang module on leader. Response code: " + httpCode); + processRestconfResponse(respBody); + } else { + logger.debug("Yang module successfully loaded on leader. Response code: " + httpCode); + } + } + } catch (APPCException e) { + logger.error("Error loading Yang on Leader. Error message: " + e.getMessage()); + throw new MDSALStoreException("Error loading Yang on Leader. Error message: " + e.getMessage(), e); + } catch (IOException e) { + logger.error("Error reading response from remote client. Error message: " + e.getMessage()); + throw new MDSALStoreException("Error reading response from remote client. Error message: " + e.getMessage(), e); + } + } + + private String createInputJson(String yang, String moduleName) throws MDSALStoreException { + StoreYangInputBuilder builder = new StoreYangInputBuilder(); + builder.setYang(yang).setModuleName(moduleName); + StoreYangInput input = builder.build(); + try { + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.addMixInAnnotations(StoreYangInput.class, MixIn.class); + String inputJson = objectMapper.writer().withRootName("input").writeValueAsString(input); + logger.debug("Input JSON :" + inputJson); + return inputJson; + } catch (JsonProcessingException e) { + logger.error(String.format("Error creating JSON input using yang: %s. Error message: %s",yang ,e.getMessage())); + throw new MDSALStoreException(String.format("Error creating JSON input using yang: %s. Error message: %s",yang ,e.getMessage()), e); + } + } + + private RestClientInvoker getRemoteClient(String leader) throws MDSALStoreException { + if (remoteClientMap.containsKey(leader)) { + return remoteClientMap.get(leader); + } else { + Configuration configuration = ConfigurationFactory.getConfiguration(); + Properties properties = configuration.getProperties(); + if (properties != null) { + try { + URL configUrl = new URL(properties.getProperty(Constants.CONFIG_URL_PROPERTY, Constants.CONFIG_URL_DEFAULT)); + String user = properties.getProperty(Constants.CONFIG_USER_PROPERTY); + String password = properties.getProperty(Constants.CONFIG_PASS_PROPERTY); + RestClientInvoker remoteClient = new RestClientInvoker(new URL(configUrl.getProtocol(), leader, configUrl.getPort(), "")); + remoteClient.setAuthentication(user, password); + remoteClientMap.put(leader, remoteClient); + return remoteClient; + } catch (MalformedURLException e) { + logger.error("Error initializing remote RestConf client: " + e.getMessage(), e); + throw new MDSALStoreException("Error initializing Remote RestConf client: " + e.getMessage(), e); + } + } else { + logger.error("Error initializing Remote RestConf client. Could not read appc properties"); + throw new MDSALStoreException("Error initializing Remote RestConf client. Could not read appc properties"); + } + } + } + + abstract class MixIn { + @JsonIgnore + abstract Class getImplementedInterface(); // to be removed during serialization + + @JsonValue + abstract java.lang.String getValue(); + + @JsonProperty("module-name") + abstract java.lang.String getModuleName(); + } + + @Override + public void storeJson(String module, String requestId, String configJson) throws MDSALStoreException { + if (configJson == null) { + throw new MDSALStoreException("Configuration JSON is empty or null"); + } + logger.debug("Configuration JSON: " + configJson + "\n" + "module" + module); + try { + String path = requestFormatter.buildPath(module, org.onap.appc.Constants.YANG_BASE_CONTAINER, + org.onap.appc.Constants.YANG_VNF_CONFIG_LIST, requestId, org.onap.appc.Constants.YANG_VNF_CONFIG); + logger.debug("Configuration Path : " + path); + HttpResponse response = client.doPut(path, configJson); + int httpCode = response.getStatusLine().getStatusCode(); + String respBody = IOUtils.toString(response.getEntity().getContent()); + if (httpCode < 200 || httpCode >= 300) { + logger.debug("Error while storing configuration JSON to MD-SAL store. Response code: " + httpCode); + processRestconfResponse(respBody); + } else { + logger.debug("Configuration JSON stored to MD-SAL store successfully. Response code: " + httpCode); + } + } catch (IOException | APPCException e) { + logger.error("Error while storing configuration json. Error Message" + e.getMessage(), e); + throw new MDSALStoreException(e); + } + } + + private void processRestconfResponse(String response) throws MDSALStoreException { + try { + JsonNode responseJson = mapper.readTree(response); + ArrayList errorMessage = new ArrayList<>(); + if (responseJson != null && responseJson.get("errors") != null) { + JsonNode errors = responseJson.get("errors").get("error"); + for (Iterator i = errors.elements(); i.hasNext(); ) { + JsonNode error = i.next(); + errorMessage.add(error.get("error-message").textValue()); + } + } + logger.error("Failed to load config JSON to MD SAL store. " + errorMessage.toString()); + throw new MDSALStoreException("Failed to load config JSON to MD SAL store. Error Message: " + errorMessage.toString()); + } catch (IOException e) { + logger.error("Failed to process error response from RestConf: " + e.getMessage()); + throw new MDSALStoreException("Failed to process RestConf response. Error Message: " + e.toString(), e); + } + } + + private byte[] createBundleJar(String yang, String blueprint, BundleInfo bundleInfo) throws MDSALStoreException { + + Manifest manifest = new Manifest(); + manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, Constants.MANIFEST_VALUE_VERSION); + manifest.getMainAttributes().put(new Attributes.Name(Constants.MANIFEST_ATTR_BUNDLE_NAME), bundleInfo.getName()); + manifest.getMainAttributes().put(new Attributes.Name(Constants.MANIFEST_ATTR_BUNDLE_SYMBOLIC_NAME), bundleInfo.getName()); + manifest.getMainAttributes().put(new Attributes.Name(Constants.MANIFEST_ATTR_BUNDLE_DESCRIPTION), bundleInfo.getDescription()); + manifest.getMainAttributes().put(new Attributes.Name(Constants.MANIFEST_ATTR_BUNDLE_MANIFEST_VERSION), Constants.MANIFEST_VALUE_BUNDLE_MAN_VERSION); + manifest.getMainAttributes().put(new Attributes.Name(Constants.MANIFEST_ATTR_BUNDLE_VERSION), String.valueOf(bundleInfo.getVersion())); + manifest.getMainAttributes().put(new Attributes.Name(Constants.MANIFEST_ATTR_BUNDLE_BLUEPRINT), Constants.MANIFEST_VALUE_BUNDLE_BLUEPRINT); + + byte[] retunValue; + + try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + JarOutputStream jarOutputStream = new JarOutputStream(outputStream, manifest)) { + jarOutputStream.putNextEntry(new JarEntry("META-INF/yang/")); + jarOutputStream.putNextEntry(new JarEntry("META-INF/yang/" + bundleInfo.getName() + ".yang")); + jarOutputStream.write(yang.getBytes()); + jarOutputStream.closeEntry(); + + jarOutputStream.putNextEntry(new JarEntry("OSGI-INF/blueprint/")); + jarOutputStream.putNextEntry(new JarEntry(Constants.MANIFEST_VALUE_BUNDLE_BLUEPRINT)); + jarOutputStream.write(blueprint.getBytes()); + jarOutputStream.closeEntry(); + jarOutputStream.close(); + retunValue = outputStream.toByteArray(); + } catch (Exception e) { + logger.error("Error creating bundle jar: " + bundleInfo.getName() + ". Error message: " + e.getMessage()); + throw new MDSALStoreException("Error creating bundle jar: " + bundleInfo.getName() + " " + e.getMessage(), e); + } + return retunValue; + } + + private String getLeaderNode() throws MDSALStoreException { + try { + String shardName = String.format(Constants.SHARD_NAME_FORMAT, getNodeName()); + HttpResponse response = client.doGet(String.format(Constants.GET_NODE_STATUS_PATH_FORMAT, shardName)); + int httpCode = response.getStatusLine().getStatusCode(); + String respBody = IOUtils.toString(response.getEntity().getContent()); + logger.debug(String.format("Get node status returned Code: %s. Response: %s ", httpCode, respBody)); + if (httpCode == 200 && mapper.readTree(respBody).get(Constants.JSON_RESPONSE_VALUE) !=null ) { + JsonNode responseValue = mapper.readTree(respBody).get(Constants.JSON_RESPONSE_VALUE); + String leaderShard = responseValue.get("Leader").asText(); + if (shardName.equals(leaderShard)) { + logger.debug("Current node is leader."); + return Constants.SELF; + } else { + String[] peers = responseValue.get("PeerAddresses").asText().split(","); + for (String peer : peers) { + if (peer.trim().startsWith(leaderShard)) { + String leader = peer.substring(peer.indexOf('@') + 1, peer.indexOf(':', peer.indexOf('@'))); + logger.debug(String.format("Node %s is a leader", leader)); + return leader; + } + } + logger.error("No Leader found for a cluster"); + throw new MDSALStoreException("No Leader found for a cluster"); + } + } else { + logger.error("Error while retrieving leader node."); + throw new MDSALStoreException("Error while retrieving leader node."); + } + } catch (IOException | APPCException e) { + logger.error(String.format("Error while retrieving leader Node. Error message : %s ", e.getMessage()), e); + throw new MDSALStoreException(e); + } + } + + private String getNodeName() throws MDSALStoreException { + try { + HttpResponse response = client.doGet(Constants.GET_SHARD_LIST_PATH); + int httpCode = response.getStatusLine().getStatusCode(); + String respBody = IOUtils.toString(response.getEntity().getContent()); + logger.debug(String.format("Get shard list returned Code: %s. Response: %s ", httpCode, respBody)); + if (httpCode == 200) { + JsonNode responseValue = mapper.readTree(respBody).get(Constants.JSON_RESPONSE_VALUE); + if (responseValue != null && responseValue.get(Constants.JSON_RESPONSE_MEMBER_NAME) != null) { + String name = responseValue.get(Constants.JSON_RESPONSE_MEMBER_NAME).asText(); + logger.debug("Node name : " + name); + return name; + }else{ + logger.error(String.format("Error while retrieving node name from response. Response body: %s.", respBody)); + throw new MDSALStoreException(String.format("Error while retrieving node name from response. Response body: %s.", respBody)); + } + } else { + logger.error(String.format("Error while retrieving node name. Error code: %s. Error response: %s.", httpCode, respBody)); + throw new MDSALStoreException(String.format("Error while retrieving node name. Error code: %s. Error response: %s.", httpCode, respBody)); + } + } catch (IOException | APPCException e) { + logger.error("Error while getting node name " + e.getMessage(), e); + throw new MDSALStoreException(e); + } + } +}