Add junit coverage to RequestInfoBuilder class
[appc.git] / appc-dg / appc-dg-shared / appc-dg-mdsal-store / appc-dg-mdsal-bundle / src / main / java / org / onap / appc / mdsal / impl / MDSALStoreImpl.java
1 /*-
2  * ============LICENSE_START=======================================================
3  * ONAP : APPC
4  * ================================================================================
5  * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
6  * ================================================================================
7  * Copyright (C) 2017 Amdocs
8  * =============================================================================
9  * Licensed under the Apache License, Version 2.0 (the "License");
10  * you may not use this file except in compliance with the License.
11  * You may obtain a copy of the License at
12  * 
13  *      http://www.apache.org/licenses/LICENSE-2.0
14  * 
15  * Unless required by applicable law or agreed to in writing, software
16  * distributed under the License is distributed on an "AS IS" BASIS,
17  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18  * See the License for the specific language governing permissions and
19  * limitations under the License.
20  * 
21  * ECOMP is a trademark and service mark of AT&T Intellectual Property.
22  * ============LICENSE_END=========================================================
23  */
24
25 package org.onap.appc.mdsal.impl;
26
27 import com.fasterxml.jackson.annotation.JsonIgnore;
28 import com.fasterxml.jackson.annotation.JsonProperty;
29 import com.fasterxml.jackson.annotation.JsonValue;
30 import com.fasterxml.jackson.core.JsonProcessingException;
31 import com.fasterxml.jackson.databind.JsonNode;
32 import com.fasterxml.jackson.databind.ObjectMapper;
33 import org.apache.commons.io.IOUtils;
34 import org.apache.http.HttpResponse;
35 import org.opendaylight.yang.gen.v1.org.onap.appc.mdsal.store.rev170925.StoreYangInput;
36 import org.opendaylight.yang.gen.v1.org.onap.appc.mdsal.store.rev170925.StoreYangInputBuilder;
37 import org.opendaylight.yangtools.yang.binding.DataContainer;
38 import org.onap.appc.configuration.Configuration;
39 import org.onap.appc.configuration.ConfigurationFactory;
40 import org.onap.appc.exceptions.APPCException;
41 import org.onap.appc.mdsal.MDSALStore;
42 import org.onap.appc.mdsal.exception.MDSALStoreException;
43 import org.onap.appc.mdsal.objects.BundleInfo;
44 import com.att.eelf.configuration.EELFLogger;
45 import com.att.eelf.configuration.EELFManager;
46 import org.onap.appc.mdsal.operation.ConfigOperationRequestFormatter;
47 import org.onap.appc.rest.client.RestClientInvoker;
48 import org.osgi.framework.Bundle;
49 import org.osgi.framework.BundleContext;
50 import org.osgi.framework.FrameworkUtil;
51
52 import java.io.ByteArrayInputStream;
53 import java.io.ByteArrayOutputStream;
54 import java.io.IOException;
55 import java.net.MalformedURLException;
56 import java.net.URL;
57 import java.util.*;
58 import java.util.jar.Attributes;
59 import java.util.jar.JarEntry;
60 import java.util.jar.JarOutputStream;
61 import java.util.jar.Manifest;
62
63 /**
64  * Implementation of MDSALStore
65  */
66 public class MDSALStoreImpl implements MDSALStore {
67
68     private final EELFLogger logger = EELFManager.getInstance().getLogger(MDSALStoreImpl.class);
69     private RestClientInvoker client;
70     private ConfigOperationRequestFormatter requestFormatter = new ConfigOperationRequestFormatter();
71     private ObjectMapper mapper = new ObjectMapper();
72     private Map<String, RestClientInvoker> remoteClientMap = new HashMap<>();
73
74     MDSALStoreImpl() {
75         String configUrl = null;
76         String user = null;
77         String password = null;
78         Configuration configuration = ConfigurationFactory.getConfiguration();
79         Properties properties = configuration.getProperties();
80         if (properties != null) {
81             configUrl = properties.getProperty(Constants.CONFIG_URL_PROPERTY, Constants.CONFIG_URL_DEFAULT);
82             user = properties.getProperty(Constants.CONFIG_USER_PROPERTY);
83             password = properties.getProperty(Constants.CONFIG_PASS_PROPERTY);
84         }
85         if (configUrl != null) {
86             try {
87                 client = new RestClientInvoker(new URL(configUrl));
88                 client.setAuthentication(user, password);
89             } catch (MalformedURLException e) {
90                 logger.error("Error initializing RestConf client: " + e.getMessage(), e);
91             }
92         }
93     }
94
95
96     @Override
97     public boolean isModulePresent(String moduleName, Date revision) {
98
99         if (logger.isDebugEnabled()) {
100             logger.debug("isModulePresent invoked with moduleName = " + moduleName + " , revision = " + revision);
101         }
102
103         BundleContext bundleContext = FrameworkUtil.getBundle(this.getClass()).getBundleContext();
104         /*
105          * SchemaContext interface of ODL provides APIs for querying details of yang modules
106          * loaded into MD-SAL store, but its limitation is, it only returns information about
107          * static yang modules loaded on server start up, it does not return information about
108          * the yang modules loaded dynamically. Due to this limitation, we are checking the
109          * presence of OSGI bundle instead of yang module. (Note: Assuming OSGI bundle is named
110          * with the yang module name).
111          */
112
113         Bundle bundle = bundleContext.getBundle(moduleName);
114         if (logger.isDebugEnabled()) {
115             logger.debug("isModulePresent returned = " + (bundle != null));
116         }
117         return bundle != null;
118     }
119
120     @Override
121     public void storeYangModule(String yang, BundleInfo bundleInfo) throws MDSALStoreException {
122
123         BundleContext bundleContext = FrameworkUtil.getBundle(this.getClass()).getBundleContext();
124         byte[] byteArray = createBundleJar(yang, Constants.BLUEPRINT, bundleInfo);
125
126         try (ByteArrayInputStream inputStream = new ByteArrayInputStream(byteArray)) {
127             Bundle bundle = bundleContext.installBundle(bundleInfo.getLocation(), inputStream);
128             bundle.start();
129         } catch (Exception e) {
130             logger.error(String.format("Error storing yang module: %s. Error message: %s.", yang, e.getMessage()));
131             throw new MDSALStoreException("Error storing yang module: " + yang + " " + e.getMessage(), e);
132         }
133     }
134
135     @Override
136     public void storeYangModuleOnLeader(String yang, String moduleName) throws MDSALStoreException {
137         try {
138             String leader = getLeaderNode();
139             if (Constants.SELF.equals(leader)){
140                 logger.debug("Current node is a leader.");
141             }else{
142                 logger.debug("Attempting to load yang module on Leader: " + leader );
143                 String inputJson = createInputJson(yang, moduleName);
144                 RestClientInvoker remoteClient = getRemoteClient(leader);
145                 HttpResponse response = remoteClient.doPost(Constants.YANG_LOADER_PATH, inputJson);
146                 int httpCode = response.getStatusLine().getStatusCode();
147                 String respBody = IOUtils.toString(response.getEntity().getContent());
148                 if (httpCode < 200 || httpCode >= 300) {
149                     logger.debug("Error while loading yang module on leader. Response code: " + httpCode);
150                     processRestconfResponse(respBody);
151                 } else {
152                     logger.debug("Yang module successfully loaded on leader. Response code: " + httpCode);
153                 }
154             }
155         } catch (APPCException e) {
156             logger.error("Error loading Yang on Leader. Error message: " + e.getMessage());
157             throw new MDSALStoreException("Error loading Yang on Leader. Error message: " + e.getMessage(), e);
158         } catch (IOException e) {
159             logger.error("Error reading response from remote client. Error message: " + e.getMessage());
160             throw new MDSALStoreException("Error reading response from remote client. Error message: " + e.getMessage(), e);
161         }
162     }
163
164     private String createInputJson(String yang, String moduleName)  throws MDSALStoreException {
165         StoreYangInputBuilder builder = new StoreYangInputBuilder();
166         builder.setYang(yang).setModuleName(moduleName);
167         StoreYangInput input = builder.build();
168         try {
169             ObjectMapper objectMapper = new ObjectMapper();
170             objectMapper.addMixInAnnotations(StoreYangInput.class, MixIn.class);
171             String inputJson = objectMapper.writer().withRootName("input").writeValueAsString(input);
172             logger.debug("Input JSON :" + inputJson);
173             return inputJson;
174         } catch (JsonProcessingException e) {
175             logger.error(String.format("Error creating JSON input using yang: %s. Error message: %s",yang ,e.getMessage()));
176             throw new MDSALStoreException(String.format("Error creating JSON input using yang: %s. Error message: %s",yang ,e.getMessage()), e);
177         }
178     }
179
180     private RestClientInvoker getRemoteClient(String leader) throws MDSALStoreException {
181         if (remoteClientMap.containsKey(leader)) {
182             return remoteClientMap.get(leader);
183         } else {
184             Configuration configuration = ConfigurationFactory.getConfiguration();
185             Properties properties = configuration.getProperties();
186             if (properties != null) {
187                 try {
188                     URL configUrl = new URL(properties.getProperty(Constants.CONFIG_URL_PROPERTY, Constants.CONFIG_URL_DEFAULT));
189                     String user = properties.getProperty(Constants.CONFIG_USER_PROPERTY);
190                     String password = properties.getProperty(Constants.CONFIG_PASS_PROPERTY);
191                     RestClientInvoker remoteClient = new RestClientInvoker(new URL(configUrl.getProtocol(), leader, configUrl.getPort(), ""));
192                     remoteClient.setAuthentication(user, password);
193                     remoteClientMap.put(leader, remoteClient);
194                     return remoteClient;
195                 } catch (MalformedURLException e) {
196                     logger.error("Error initializing remote RestConf client: " + e.getMessage(), e);
197                     throw new MDSALStoreException("Error initializing Remote RestConf client: " + e.getMessage(), e);
198                 }
199             } else {
200                 logger.error("Error initializing Remote RestConf client. Could not read appc properties");
201                 throw new MDSALStoreException("Error initializing Remote RestConf client. Could not read appc properties");
202             }
203         }
204     }
205
206     abstract class MixIn {
207         @JsonIgnore
208         abstract Class<? extends DataContainer> getImplementedInterface(); // to be removed during serialization
209
210         @JsonValue
211         abstract java.lang.String getValue();
212
213         @JsonProperty("module-name")
214         abstract java.lang.String getModuleName();
215     }
216
217     @Override
218     public void storeJson(String module, String requestId, String configJson) throws MDSALStoreException {
219         if (configJson == null) {
220             throw new MDSALStoreException("Configuration JSON is empty or null");
221         }
222         logger.debug("Configuration JSON: " + configJson + "\n" + "module" + module);
223         try {
224             String path = requestFormatter.buildPath(module, org.onap.appc.Constants.YANG_BASE_CONTAINER,
225                     org.onap.appc.Constants.YANG_VNF_CONFIG_LIST, requestId, org.onap.appc.Constants.YANG_VNF_CONFIG);
226             logger.debug("Configuration Path : " + path);
227             HttpResponse response = client.doPut(path, configJson);
228             int httpCode = response.getStatusLine().getStatusCode();
229             String respBody = IOUtils.toString(response.getEntity().getContent());
230             if (httpCode < 200 || httpCode >= 300) {
231                 logger.debug("Error while storing configuration JSON to MD-SAL store. Response code: " + httpCode);
232                 processRestconfResponse(respBody);
233             } else {
234                 logger.debug("Configuration JSON stored to MD-SAL store successfully. Response code: " + httpCode);
235             }
236         } catch (IOException | APPCException e) {
237             logger.error("Error while storing configuration json. Error Message" + e.getMessage(), e);
238             throw new MDSALStoreException(e);
239         }
240     }
241
242     private void processRestconfResponse(String response) throws MDSALStoreException {
243         try {
244             JsonNode responseJson = mapper.readTree(response);
245             ArrayList<String> errorMessage = new ArrayList<>();
246             if (responseJson != null && responseJson.get("errors") != null) {
247                 JsonNode errors = responseJson.get("errors").get("error");
248                 for (Iterator<JsonNode> i = errors.elements(); i.hasNext(); ) {
249                     JsonNode error = i.next();
250                     errorMessage.add(error.get("error-message").textValue());
251                 }
252             }
253             logger.error("Failed to load config JSON to MD SAL store. " + errorMessage.toString());
254             throw new MDSALStoreException("Failed to load config JSON to MD SAL store. Error Message: " + errorMessage.toString());
255         } catch (IOException e) {
256             logger.error("Failed to process error response from RestConf: " + e.getMessage());
257             throw new MDSALStoreException("Failed to process RestConf response. Error Message: " + e.toString(), e);
258         }
259     }
260
261     private byte[] createBundleJar(String yang, String blueprint, BundleInfo bundleInfo) throws MDSALStoreException {
262
263         Manifest manifest = new Manifest();
264         manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, Constants.MANIFEST_VALUE_VERSION);
265         manifest.getMainAttributes().put(new Attributes.Name(Constants.MANIFEST_ATTR_BUNDLE_NAME), bundleInfo.getName());
266         manifest.getMainAttributes().put(new Attributes.Name(Constants.MANIFEST_ATTR_BUNDLE_SYMBOLIC_NAME), bundleInfo.getName());
267         manifest.getMainAttributes().put(new Attributes.Name(Constants.MANIFEST_ATTR_BUNDLE_DESCRIPTION), bundleInfo.getDescription());
268         manifest.getMainAttributes().put(new Attributes.Name(Constants.MANIFEST_ATTR_BUNDLE_MANIFEST_VERSION), Constants.MANIFEST_VALUE_BUNDLE_MAN_VERSION);
269         manifest.getMainAttributes().put(new Attributes.Name(Constants.MANIFEST_ATTR_BUNDLE_VERSION), String.valueOf(bundleInfo.getVersion()));
270         manifest.getMainAttributes().put(new Attributes.Name(Constants.MANIFEST_ATTR_BUNDLE_BLUEPRINT), Constants.MANIFEST_VALUE_BUNDLE_BLUEPRINT);
271
272         byte[] retunValue;
273
274         try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
275              JarOutputStream jarOutputStream = new JarOutputStream(outputStream, manifest)) {
276             jarOutputStream.putNextEntry(new JarEntry("META-INF/yang/"));
277             jarOutputStream.putNextEntry(new JarEntry("META-INF/yang/" + bundleInfo.getName() + ".yang"));
278             jarOutputStream.write(yang.getBytes());
279             jarOutputStream.closeEntry();
280
281             jarOutputStream.putNextEntry(new JarEntry("OSGI-INF/blueprint/"));
282             jarOutputStream.putNextEntry(new JarEntry(Constants.MANIFEST_VALUE_BUNDLE_BLUEPRINT));
283             jarOutputStream.write(blueprint.getBytes());
284             jarOutputStream.closeEntry();
285             jarOutputStream.close();
286             retunValue = outputStream.toByteArray();
287         } catch (Exception e) {
288             logger.error("Error creating bundle jar: " + bundleInfo.getName() + ". Error message: " + e.getMessage());
289             throw new MDSALStoreException("Error creating bundle jar: " + bundleInfo.getName() + " " + e.getMessage(), e);
290         }
291         return retunValue;
292     }
293
294     private String getLeaderNode() throws MDSALStoreException {
295         try {
296             String shardName = String.format(Constants.SHARD_NAME_FORMAT, getNodeName());
297             HttpResponse response = client.doGet(String.format(Constants.GET_NODE_STATUS_PATH_FORMAT, shardName));
298             int httpCode = response.getStatusLine().getStatusCode();
299             String respBody = IOUtils.toString(response.getEntity().getContent());
300             logger.debug(String.format("Get node status returned Code: %s. Response: %s ", httpCode, respBody));
301             if (httpCode == 200 && mapper.readTree(respBody).get(Constants.JSON_RESPONSE_VALUE) !=null ) {
302                 JsonNode responseValue = mapper.readTree(respBody).get(Constants.JSON_RESPONSE_VALUE);
303                     String leaderShard = responseValue.get("Leader").asText();
304                     if (shardName.equals(leaderShard)) {
305                         logger.debug("Current node is leader.");
306                         return Constants.SELF;
307                     } else {
308                         String[] peers = responseValue.get("PeerAddresses").asText().split(",");
309                         for (String peer : peers) {
310                             if (peer.trim().startsWith(leaderShard)) {
311                                 String leader = peer.substring(peer.indexOf('@') + 1, peer.indexOf(':', peer.indexOf('@')));
312                                 logger.debug(String.format("Node %s is a leader", leader));
313                                 return leader;
314                             }
315                         }
316                         logger.error("No Leader found for a cluster");
317                         throw new MDSALStoreException("No Leader found for a cluster");
318                     }
319             } else {
320                 logger.error("Error while retrieving leader node.");
321                 throw new MDSALStoreException("Error while retrieving leader node.");
322             }
323         } catch (IOException | APPCException e) {
324             logger.error(String.format("Error while retrieving leader Node. Error message : %s ", e.getMessage()), e);
325             throw new MDSALStoreException(e);
326         }
327     }
328
329     private String getNodeName() throws MDSALStoreException {
330         try {
331             HttpResponse response = client.doGet(Constants.GET_SHARD_LIST_PATH);
332             int httpCode = response.getStatusLine().getStatusCode();
333             String respBody = IOUtils.toString(response.getEntity().getContent());
334             logger.debug(String.format("Get shard list returned Code: %s. Response: %s ", httpCode, respBody));
335             if (httpCode == 200) {
336                 JsonNode responseValue = mapper.readTree(respBody).get(Constants.JSON_RESPONSE_VALUE);
337                 if (responseValue != null && responseValue.get(Constants.JSON_RESPONSE_MEMBER_NAME) != null) {
338                     String name = responseValue.get(Constants.JSON_RESPONSE_MEMBER_NAME).asText();
339                     logger.debug("Node name : " + name);
340                     return name;
341                 }else{
342                     logger.error(String.format("Error while retrieving node name from response. Response body: %s.", respBody));
343                     throw new MDSALStoreException(String.format("Error while retrieving node name from response. Response body: %s.", respBody));
344                 }
345             } else {
346                 logger.error(String.format("Error while retrieving node name. Error code: %s. Error response: %s.", httpCode, respBody));
347                 throw new MDSALStoreException(String.format("Error while retrieving node name. Error code: %s. Error response: %s.", httpCode, respBody));
348             }
349         } catch (IOException | APPCException e) {
350             logger.error("Error while getting node name " + e.getMessage(), e);
351             throw new MDSALStoreException(e);
352         }
353     }
354 }