Changed to unmaintained
[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-2019 AT&T Intellectual Property. All rights reserved.
6  * ================================================================================
7  * Copyright (C) 2017 Amdocs
8  * ================================================================================
9  * Modifications (C) 2019 Ericsson
10  * =============================================================================
11  * Licensed under the Apache License, Version 2.0 (the "License");
12  * you may not use this file except in compliance with the License.
13  * You may obtain a copy of the License at
14  *
15  *      http://www.apache.org/licenses/LICENSE-2.0
16  *
17  * Unless required by applicable law or agreed to in writing, software
18  * distributed under the License is distributed on an "AS IS" BASIS,
19  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20  * See the License for the specific language governing permissions and
21  * limitations under the License.
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 = getRestClientInvoker(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         String msg;
138         try {
139             String leader = getLeaderNode();
140             if (Constants.SELF.equals(leader)) {
141                 logger.debug("Current node is a leader.");
142             } else {
143                 logger.debug("Attempting to load yang module on Leader: " + leader);
144                 String inputJson = createInputJson(yang, moduleName);
145                 RestClientInvoker remoteClient = getRemoteClient(leader);
146                 HttpResponse response = remoteClient.doPost(Constants.YANG_LOADER_PATH, inputJson);
147                 int httpCode = response.getStatusLine().getStatusCode();
148                 String respBody = IOUtils.toString(response.getEntity().getContent());
149                 if (httpCode < 200 || httpCode >= 300) {
150                     logger.debug("Error while loading yang module on leader. Response code: " + httpCode);
151                     processRestconfResponse(respBody);
152                 } else {
153                     logger.debug("Yang module successfully loaded on leader. Response code: " + httpCode);
154                 }
155             }
156         } catch (APPCException e) {
157             msg = "Error loading Yang on Leader. Error message: " + e.getMessage();
158             logger.error(msg);
159             throw new MDSALStoreException(msg, e);
160         } catch (IOException e) {
161             msg = "Error reading response from remote client. Error message: " + e.getMessage();
162             logger.error(msg);
163             throw new MDSALStoreException(msg, e);
164         }
165     }
166
167     private String createInputJson(String yang, String moduleName)  throws MDSALStoreException {
168         StoreYangInputBuilder builder = new StoreYangInputBuilder();
169         builder.setYang(yang).setModuleName(moduleName);
170         StoreYangInput input = builder.build();
171         try {
172             ObjectMapper objectMapper = new ObjectMapper();
173             objectMapper.addMixIn(StoreYangInput.class, MixIn.class);
174             String inputJson = objectMapper.writer().withRootName("input").writeValueAsString(input);
175             logger.debug("Input JSON: " + inputJson);
176             return inputJson;
177         } catch (JsonProcessingException e) {
178             String msg = String.format("Error creating JSON input using yang: %s. Error message: %s",
179                     yang, e.getMessage());
180             logger.error(msg);
181             throw new MDSALStoreException(msg, e);
182         }
183     }
184
185     private RestClientInvoker getRemoteClient(String leader) throws MDSALStoreException {
186         if (remoteClientMap.containsKey(leader)) {
187             return remoteClientMap.get(leader);
188         } else {
189             String msg;
190             Configuration configuration = ConfigurationFactory.getConfiguration();
191             Properties properties = configuration.getProperties();
192             if (properties != null) {
193                 try {
194                     URL configUrl =
195                             new URL(properties.getProperty(Constants.CONFIG_URL_PROPERTY,
196                                 Constants.CONFIG_URL_DEFAULT));
197                     String user = properties.getProperty(Constants.CONFIG_USER_PROPERTY);
198                     String password = properties.getProperty(Constants.CONFIG_PASS_PROPERTY);
199                     RestClientInvoker remoteClient =
200                             getRestClientInvoker(new URL(configUrl.getProtocol(), leader, configUrl.getPort(), ""));
201                     remoteClient.setAuthentication(user, password);
202                     remoteClientMap.put(leader, remoteClient);
203                     return remoteClient;
204                 } catch (MalformedURLException e) {
205                     msg = "Error initializing remote RestConf client: " + e.getMessage();
206                     logger.error(msg, e);
207                     throw new MDSALStoreException(msg, e);
208                 }
209             } else {
210                 msg = "Error initializing remote RestConf client. Could not read appc properties";
211                 logger.error(msg);
212                 throw new MDSALStoreException(msg);
213             }
214         }
215     }
216
217     abstract class MixIn {
218         @JsonIgnore
219         abstract Class<? extends DataContainer> getImplementedInterface(); // to be removed during serialization
220
221         @JsonValue
222         abstract java.lang.String getValue();
223
224         @JsonProperty("module-name")
225         abstract java.lang.String getModuleName();
226     }
227
228     @Override
229     public void storeJson(String module, String requestId, String configJson) throws MDSALStoreException {
230         if (configJson == null) {
231             throw new MDSALStoreException("Configuration JSON is empty or null");
232         }
233         logger.debug("Configuration JSON: " + configJson + "\n" + "module" + module);
234         try {
235             String path = requestFormatter.buildPath(module, org.onap.appc.Constants.YANG_BASE_CONTAINER,
236                     org.onap.appc.Constants.YANG_VNF_CONFIG_LIST, requestId, org.onap.appc.Constants.YANG_VNF_CONFIG);
237             logger.debug("Configuration Path: " + path);
238             HttpResponse response = client.doPut(path, configJson);
239             int httpCode = response.getStatusLine().getStatusCode();
240             String respBody = IOUtils.toString(response.getEntity().getContent());
241             if (httpCode < 200 || httpCode >= 300) {
242                 logger.debug("Error while storing configuration JSON to MD-SAL store. Response code: " + httpCode);
243                 processRestconfResponse(respBody);
244             } else {
245                 logger.debug("Configuration JSON stored to MD-SAL store successfully. Response code: " + httpCode);
246             }
247         } catch (IOException | APPCException e) {
248             logger.error("Error while storing configuration json. Error Message" + e.getMessage(), e);
249             throw new MDSALStoreException(e);
250         }
251     }
252
253     private void processRestconfResponse(String response) throws MDSALStoreException {
254         try {
255             JsonNode responseJson = mapper.readTree(response);
256             ArrayList<String> errorMessage = new ArrayList<>();
257             if (responseJson != null && responseJson.get("errors") != null) {
258                 JsonNode errors = responseJson.get("errors").get("error");
259                 for (Iterator<JsonNode> i = errors.elements(); i.hasNext(); ) {
260                     JsonNode error = i.next();
261                     errorMessage.add(error.get("error-message").textValue());
262                 }
263             }
264             logger.error("Failed to load config JSON to MD SAL store. " + errorMessage.toString());
265             throw new MDSALStoreException("Failed to load config JSON to MD SAL store. Error Message: "
266                     + errorMessage.toString());
267         } catch (IOException e) {
268             logger.error("Failed to process error response from RestConf: " + e.getMessage());
269             throw new MDSALStoreException("Failed to process RestConf response. Error Message: " + e.toString(), e);
270         }
271     }
272
273     private byte[] createBundleJar(String yang, String blueprint, BundleInfo bundleInfo) throws MDSALStoreException {
274
275         Manifest manifest = new Manifest();
276         manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, Constants.MANIFEST_VALUE_VERSION);
277         manifest.getMainAttributes().put(new Attributes.Name(Constants.MANIFEST_ATTR_BUNDLE_NAME),
278                 bundleInfo.getName());
279         manifest.getMainAttributes().put(new Attributes.Name(Constants.MANIFEST_ATTR_BUNDLE_SYMBOLIC_NAME),
280                 bundleInfo.getName());
281         manifest.getMainAttributes().put(new Attributes.Name(Constants.MANIFEST_ATTR_BUNDLE_DESCRIPTION),
282                 bundleInfo.getDescription());
283         manifest.getMainAttributes().put(new Attributes.Name(Constants.MANIFEST_ATTR_BUNDLE_MANIFEST_VERSION),
284                 Constants.MANIFEST_VALUE_BUNDLE_MAN_VERSION);
285         manifest.getMainAttributes().put(new Attributes.Name(Constants.MANIFEST_ATTR_BUNDLE_VERSION),
286                 String.valueOf(bundleInfo.getVersion()));
287         manifest.getMainAttributes().put(new Attributes.Name(Constants.MANIFEST_ATTR_BUNDLE_BLUEPRINT),
288                 Constants.MANIFEST_VALUE_BUNDLE_BLUEPRINT);
289
290         byte[] retunValue;
291
292         try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
293                 JarOutputStream jarOutputStream = new JarOutputStream(outputStream, manifest)) {
294             jarOutputStream.putNextEntry(new JarEntry("META-INF/yang/"));
295             jarOutputStream.putNextEntry(new JarEntry("META-INF/yang/" + bundleInfo.getName() + ".yang"));
296             jarOutputStream.write(yang.getBytes());
297             jarOutputStream.closeEntry();
298
299             jarOutputStream.putNextEntry(new JarEntry("OSGI-INF/blueprint/"));
300             jarOutputStream.putNextEntry(new JarEntry(Constants.MANIFEST_VALUE_BUNDLE_BLUEPRINT));
301             jarOutputStream.write(blueprint.getBytes());
302             jarOutputStream.closeEntry();
303             jarOutputStream.close();
304             retunValue = outputStream.toByteArray();
305         } catch (Exception e) {
306             logger.error("Error creating bundle jar: " + bundleInfo.getName() + ". Error message: " + e.getMessage());
307             throw new MDSALStoreException("Error creating bundle jar: " + bundleInfo.getName() + " " + e.getMessage(),
308                     e);
309         }
310         return retunValue;
311     }
312
313     private String getLeaderNode() throws MDSALStoreException {
314         try {
315             String shardName = String.format(Constants.SHARD_NAME_FORMAT, getNodeName());
316             HttpResponse response = client.doGet(String.format(Constants.GET_NODE_STATUS_PATH_FORMAT, shardName));
317             int httpCode = response.getStatusLine().getStatusCode();
318             String respBody = IOUtils.toString(response.getEntity().getContent());
319             logger.debug(String.format("Get node status returned Code: %s. Response: %s ", httpCode, respBody));
320             if (httpCode == 200 && mapper.readTree(respBody).get(Constants.JSON_RESPONSE_VALUE) != null) {
321                 JsonNode responseValue = mapper.readTree(respBody).get(Constants.JSON_RESPONSE_VALUE);
322                     String leaderShard = responseValue.get("Leader").asText();
323                     if (shardName.equals(leaderShard)) {
324                         logger.debug("Current node is leader.");
325                         return Constants.SELF;
326                     } else {
327                         String[] peers = responseValue.get("PeerAddresses").asText().split(",");
328                         for (String peer : peers) {
329                             if (peer.trim().startsWith(leaderShard)) {
330                                 String leader =
331                                         peer.substring(peer.indexOf('@') + 1, peer.indexOf(':', peer.indexOf('@')));
332                                 logger.debug(String.format("Node %s is a leader", leader));
333                                 return leader;
334                             }
335                         }
336                         logger.error("No Leader found for a cluster");
337                         throw new MDSALStoreException("No Leader found for a cluster");
338                     }
339             } else {
340                 logger.error("Error while retrieving leader node.");
341                 throw new MDSALStoreException("Error while retrieving leader node.");
342             }
343         } catch (IOException | APPCException e) {
344             logger.error(String.format("Error while retrieving leader Node. Error message: %s ", e.getMessage()), e);
345             throw new MDSALStoreException(e);
346         }
347     }
348
349     private String getNodeName() throws MDSALStoreException {
350         try {
351             HttpResponse response = client.doGet(Constants.GET_SHARD_LIST_PATH);
352             int httpCode = response.getStatusLine().getStatusCode();
353             String respBody = IOUtils.toString(response.getEntity().getContent());
354             logger.debug(String.format("Get shard list returned Code: %s. Response: %s ", httpCode, respBody));
355             if (httpCode == 200) {
356                 JsonNode responseValue = mapper.readTree(respBody).get(Constants.JSON_RESPONSE_VALUE);
357                 if (responseValue != null && responseValue.get(Constants.JSON_RESPONSE_MEMBER_NAME) != null) {
358                     String name = responseValue.get(Constants.JSON_RESPONSE_MEMBER_NAME).asText();
359                     logger.debug("Node name: " + name);
360                     return name;
361                 } else {
362                     String msg = String.format("Error while retrieving node name from response. Response body: %s.",
363                             respBody);
364                     logger.error(msg);
365                     throw new MDSALStoreException(msg);
366                 }
367             } else {
368                 String msg = String.format("Error while retrieving node name. Error code: %s. Error response: %s.",
369                         httpCode, respBody);
370                 logger.error(msg);
371                 throw new MDSALStoreException(msg);
372             }
373         } catch (IOException | APPCException e) {
374             logger.error("Error while getting node name: " + e.getMessage(), e);
375             throw new MDSALStoreException(e);
376         }
377     }
378
379     protected RestClientInvoker getRestClientInvoker(URL configUrl) throws MalformedURLException {
380         return new RestClientInvoker(configUrl);
381     }
382 }