Merge "Add controller which create error reports"
authorWojciech Sliwka <wojciech.sliwka@nokia.com>
Mon, 8 Jul 2019 07:00:44 +0000 (07:00 +0000)
committerGerrit Code Review <gerrit@onap.org>
Mon, 8 Jul 2019 07:00:44 +0000 (07:00 +0000)
19 files changed:
vid-app-common/src/main/java/org/onap/vid/aai/AaiClientInterface.java
vid-app-common/src/main/java/org/onap/vid/aai/AaiOverTLSClientInterface.java
vid-app-common/src/main/java/org/onap/vid/controller/ErrorReportController.java [new file with mode: 0644]
vid-app-common/src/main/java/org/onap/vid/controller/ProbeController.java
vid-app-common/src/main/java/org/onap/vid/model/GitRepositoryState.java
vid-app-common/src/main/java/org/onap/vid/model/Service.java
vid-app-common/src/main/java/org/onap/vid/model/errorReport/ReportCreationParameters.java [new file with mode: 0644]
vid-app-common/src/main/java/org/onap/vid/mso/MsoBusinessLogic.java
vid-app-common/src/main/java/org/onap/vid/reports/BasicReportGenerator.java [new file with mode: 0644]
vid-app-common/src/main/java/org/onap/vid/reports/DeploymentReportGenerator.java [new file with mode: 0644]
vid-app-common/src/main/java/org/onap/vid/reports/ReportGenerator.java [new file with mode: 0644]
vid-app-common/src/main/java/org/onap/vid/scheduler/SchedulerService.java
vid-app-common/src/main/java/org/onap/vid/services/ProbeInterface.java [moved from vid-app-common/src/main/java/org/onap/vid/controller/ProbeInterface.java with 97% similarity]
vid-app-common/src/main/java/org/onap/vid/services/ProbeService.java [new file with mode: 0644]
vid-app-common/src/main/java/org/onap/vid/services/VidService.java
vid-app-common/src/test/java/org/onap/vid/controller/ErrorReportControllerTest.java [new file with mode: 0644]
vid-app-common/src/test/java/org/onap/vid/reports/BasicReportGeneratorTest.java [new file with mode: 0644]
vid-app-common/src/test/java/org/onap/vid/reports/DeploymentReportGeneratorTest.java [new file with mode: 0644]
vid-app-common/src/test/java/org/onap/vid/services/ProbeServiceTest.java [new file with mode: 0644]

index 3f91464..5f69b87 100644 (file)
@@ -31,7 +31,7 @@ import org.onap.vid.aai.model.AaiGetTenatns.GetTenantsResponse;
 import org.onap.vid.aai.model.PortDetailsTranslator;
 import org.onap.vid.aai.model.Properties;
 import org.onap.vid.aai.model.ResourceType;
-import org.onap.vid.controller.ProbeInterface;
+import org.onap.vid.services.ProbeInterface;
 import org.onap.vid.model.SubscriberList;
 /**
  * Created by Oren on 7/4/17.
index 4cc9589..d2c208e 100644 (file)
@@ -23,7 +23,7 @@ package org.onap.vid.aai;
 import io.joshworks.restclient.http.HttpResponse;
 import org.onap.portalsdk.core.util.SystemProperties;
 import org.onap.vid.aai.model.ResourceType;
-import org.onap.vid.controller.ProbeInterface;
+import org.onap.vid.services.ProbeInterface;
 import org.onap.vid.model.SubscriberList;
 
 public interface AaiOverTLSClientInterface extends ProbeInterface {
diff --git a/vid-app-common/src/main/java/org/onap/vid/controller/ErrorReportController.java b/vid-app-common/src/main/java/org/onap/vid/controller/ErrorReportController.java
new file mode 100644 (file)
index 0000000..38a69fa
--- /dev/null
@@ -0,0 +1,81 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * VID
+ * ================================================================================
+ * Copyright (C) 2019 Nokia Intellectual Property. All rights reserved.
+ * ================================================================================
+ * 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.vid.controller;
+
+import org.jetbrains.annotations.NotNull;
+import org.onap.portalsdk.core.controller.RestrictedBaseController;
+import org.onap.portalsdk.core.controller.UnRestrictedBaseController;
+import org.onap.vid.model.errorReport.ReportCreationParameters;
+import org.onap.vid.reports.ReportGenerator;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.BinaryOperator;
+
+@RestController
+public class ErrorReportController extends RestrictedBaseController {
+
+       private final List<ReportGenerator> providers;
+
+       @Autowired
+       public ErrorReportController(List<ReportGenerator> reportGenerators) {
+               providers = reportGenerators;
+       }
+
+       @PostMapping(value = "error-report")
+       public Map<String, Object> getErrorReport(HttpServletRequest request,
+                                                 @RequestBody ReportCreationParameters creationParameters) {
+               return generateReportsData(request, creationParameters);
+       }
+
+       @NotNull
+       HashMap<String, Object> generateReportsData(HttpServletRequest request, ReportCreationParameters creationParameters) {
+               return providers
+                               .stream()
+                               .filter(provider -> provider.canGenerate(creationParameters))
+                               .map(provider -> provider.apply(request, creationParameters))
+                               .map(Map::entrySet)
+                               .flatMap(Collection::parallelStream)
+                               .reduce(new HashMap<>(), this::putAndGet, concatenateMap());
+       }
+
+       @NotNull
+       private HashMap<String, Object> putAndGet(HashMap<String, Object> map, Map.Entry<String, Object> entry) {
+               map.put(entry.getKey(), entry.getValue());
+               return map;
+       }
+
+       @NotNull
+       private BinaryOperator<HashMap<String, Object>> concatenateMap() {
+               return (map1, map2) -> {
+                       map1.putAll(map2);
+                       return map1;
+               };
+       }
+
+}
\ No newline at end of file
index c181c6f..26a8add 100644 (file)
@@ -22,27 +22,27 @@ package org.onap.vid.controller;
 
 import org.onap.portalsdk.core.controller.RestrictedBaseController;
 import org.onap.vid.model.probes.ExternalComponentStatus;
+import org.onap.vid.services.ProbeService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 
 import java.util.List;
-import java.util.stream.Collectors;
 
 @RestController
 @RequestMapping("probe")
 public class ProbeController extends RestrictedBaseController {
 
-    private final List<ProbeInterface> probes;
+    final private ProbeService probeService;
 
     @Autowired
-    public ProbeController(List<ProbeInterface> probes) {
-        this.probes = probes;
+    public ProbeController(ProbeService probeService) {
+        this.probeService = probeService;
     }
 
     @GetMapping
     public List<ExternalComponentStatus> getProbe() {
-        return probes.stream().map(ProbeInterface::probeComponent).collect(Collectors.toList());
+        return probeService.getProbe();
     }
 }
index f7c56b7..a6419ef 100644 (file)
@@ -7,9 +7,9 @@
  * 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.
@@ -24,25 +24,34 @@ import java.util.Properties;
 
 public class GitRepositoryState {
 
-    private final String commitId;
-    private final String commitMessageShort;
-    private final String commitTime;
+       public static final GitRepositoryState EMPTY = new GitRepositoryState("", "", "");
 
-    public GitRepositoryState(Properties properties) {
-        this.commitId = String.valueOf(properties.get("git.commit.id"));
-        this.commitMessageShort = String.valueOf(properties.get("git.commit.message.short"));
-        this.commitTime = String.valueOf(properties.get("git.commit.time"));
-    }
+       private final String commitId;
+       private final String commitMessageShort;
+       private final String commitTime;
 
-    public String getCommitId() {
-        return commitId;
-    }
+       public GitRepositoryState(Properties properties) {
+               this(String.valueOf(String.valueOf(properties.get("git.commit.id"))),
+                               String.valueOf(properties.get("git.commit.message.short")),
+                               String.valueOf(properties.get("git.commit.time"))
+               );
+       }
 
-    public String getCommitMessageShort() {
-        return commitMessageShort;
-    }
+       private GitRepositoryState(String commitId, String commitMessageShort, String commitTime) {
+               this.commitId = commitId;
+               this.commitMessageShort = commitMessageShort;
+               this.commitTime = commitTime;
+       }
 
-    public String getCommitTime() {
-        return commitTime;
-    }
+       public String getCommitId() {
+               return commitId;
+       }
+
+       public String getCommitMessageShort() {
+               return commitMessageShort;
+       }
+
+       public String getCommitTime() {
+               return commitTime;
+       }
 }
index bb6c92e..0168283 100644 (file)
@@ -33,19 +33,19 @@ public class Service {
 
        /** The uuid. */
        private String uuid;
-       
+
        /** The invariant uuid. */
        private String invariantUuid;
-       
+
        /** The name. */
        private String name;
-       
+
        /** The version. */
        private String version;
-       
+
        /** The tosca model URL. */
        private String toscaModelURL;
-       
+
        /** The category. */
        private String category;
 
@@ -54,10 +54,10 @@ public class Service {
 
        /** The Service Role */
        private String serviceRole;
-       
+
        /** The description. */
        private String description;
-       
+
        /** The service ecomp naming flag */
        private String serviceEcompNaming;
 
@@ -77,7 +77,7 @@ public class Service {
        public String getUuid() {
                return uuid;
        }
-       
+
        /**
         * Gets the invariant uuid.
         *
@@ -86,7 +86,7 @@ public class Service {
        public String getInvariantUuid() {
                return invariantUuid;
        }
-       
+
        /**
         * Gets the name.
         *
@@ -95,7 +95,7 @@ public class Service {
        public String getName() {
                return name;
        }
-       
+
        /**
         * Gets the version.
         *
@@ -104,7 +104,7 @@ public class Service {
        public String getVersion() {
                return version;
        }
-       
+
        /**
         * Gets the tosca model URL.
         *
@@ -113,7 +113,7 @@ public class Service {
        public String getToscaModelURL() {
                return toscaModelURL;
        }
-       
+
        /**
         * Gets the category.
         *
@@ -122,7 +122,7 @@ public class Service {
        public String getCategory() {
                return category;
        }
-       
+
        /**
         * Gets the description.
         *
@@ -131,7 +131,7 @@ public class Service {
        public String getDescription() {
                return description;
        }
-       
+
        /**
         * Gets the inputs.
         *
@@ -161,7 +161,7 @@ public class Service {
        public void setUuid(String uuid) {
                this.uuid = uuid;
        }
-       
+
        /**
         * Sets the invariant uuid.
         *
@@ -170,7 +170,7 @@ public class Service {
        public void setInvariantUuid(String invariantUuid) {
                this.invariantUuid = invariantUuid;
        }
-       
+
        /**
         * Sets the name.
         *
@@ -179,7 +179,7 @@ public class Service {
        public void setName(String name) {
                this.name = name;
        }
-       
+
        /**
         * Sets the version.
         *
@@ -188,7 +188,7 @@ public class Service {
        public void setVersion(String version) {
                this.version = version;
        }
-       
+
        /**
         * Sets the tosca model URL.
         *
@@ -197,7 +197,7 @@ public class Service {
        public void setToscaModelURL(String toscaModelURL) {
                this.toscaModelURL = toscaModelURL;
        }
-       
+
        /**
         * Sets the category.
         *
@@ -206,7 +206,7 @@ public class Service {
        public void setCategory(String category) {
                this.category = category;
        }
-       
+
        /**
         * Sets the description.
         *
@@ -215,7 +215,7 @@ public class Service {
        public void setDescription(String description) {
                this.description = description;
        }
-       
+
        /**
         * Sets the inputs.
         *
@@ -239,7 +239,7 @@ public class Service {
        public int hashCode() {
                return UUID.fromString(getUuid()).hashCode();
        }
-       
+
        /* (non-Javadoc)
         * @see java.lang.Object#equals(java.lang.Object)
         */
@@ -247,9 +247,9 @@ public class Service {
        public boolean equals(Object o) {
                if (o == this) return true;
                if (!(o instanceof Service)) return false;
-               
+
                final Service service = (Service) o;
-               
+
                return (service.getUuid().equals(getUuid()));
        }
 
diff --git a/vid-app-common/src/main/java/org/onap/vid/model/errorReport/ReportCreationParameters.java b/vid-app-common/src/main/java/org/onap/vid/model/errorReport/ReportCreationParameters.java
new file mode 100644 (file)
index 0000000..babbf89
--- /dev/null
@@ -0,0 +1,48 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * VID
+ * ================================================================================
+ * Copyright (C) 2019 Nokia Intellectual Property. All rights reserved.
+ * ================================================================================
+ * 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.vid.model.errorReport;
+
+public class ReportCreationParameters {
+       private String requestId;
+       private String serviceUuid;
+
+       public ReportCreationParameters() {}
+
+       public ReportCreationParameters(String requestId, String serviceUuid) {
+               this.requestId = requestId;
+               this.serviceUuid = serviceUuid;
+       }
+
+       public String getRequestId() {
+               return requestId;
+       }
+
+       public String getServiceUuid() {
+               return serviceUuid;
+       }
+
+       public void setRequestId(String requestId) {
+               this.requestId = requestId;
+       }
+
+       public void setServiceUuid(String serviceUuid) {
+               this.serviceUuid = serviceUuid;
+       }
+}
\ No newline at end of file
index ed64d20..cc32315 100644 (file)
@@ -24,7 +24,7 @@ package org.onap.vid.mso;
 import org.onap.vid.changeManagement.RequestDetailsWrapper;
 import org.onap.vid.changeManagement.WorkflowRequestDetail;
 import org.onap.vid.controller.OperationalEnvironmentController;
-import org.onap.vid.controller.ProbeInterface;
+import org.onap.vid.services.ProbeInterface;
 import org.onap.vid.model.SOWorkflowList;
 import org.onap.vid.model.SoftDeleteRequest;
 import org.onap.vid.mso.model.OperationalEnvironmentActivateInfo;
diff --git a/vid-app-common/src/main/java/org/onap/vid/reports/BasicReportGenerator.java b/vid-app-common/src/main/java/org/onap/vid/reports/BasicReportGenerator.java
new file mode 100644 (file)
index 0000000..00f8077
--- /dev/null
@@ -0,0 +1,110 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * VID
+ * ================================================================================
+ * Copyright (C) 2019 Nokia Intellectual Property. All rights reserved.
+ * ================================================================================
+ * 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.vid.reports;
+
+import com.google.common.collect.ImmutableMap;
+import io.joshworks.restclient.http.HttpResponse;
+import org.onap.vid.controller.ControllersUtils;
+import org.onap.vid.model.GitRepositoryState;
+import org.onap.vid.model.SubscriberList;
+import org.onap.vid.model.errorReport.ReportCreationParameters;
+import org.onap.vid.model.probes.ExternalComponentStatus;
+import org.onap.vid.services.AaiService;
+import org.onap.vid.services.ProbeService;
+import org.onap.vid.utils.SystemPropertiesWrapper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import javax.servlet.http.HttpServletRequest;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+
+import static org.onap.portalsdk.core.util.SystemProperties.ECOMP_REQUEST_ID;
+
+@Component
+public class BasicReportGenerator implements ReportGenerator {
+
+       private static final String GIT_PROPERTIES_FILENAME = "git.properties";
+       private final AaiService aaiService;
+       private final SystemPropertiesWrapper systemPropertiesWrapper;
+       private final ProbeService probeService;
+
+       @Autowired
+       public BasicReportGenerator(AaiService aaiService, SystemPropertiesWrapper systemPropertiesWrapper,
+                                   ProbeService probeService) {
+               this.aaiService = aaiService;
+               this.systemPropertiesWrapper = systemPropertiesWrapper;
+               this.probeService = probeService;
+       }
+
+       @Override
+       public Map<String, Object> apply(HttpServletRequest request, ReportCreationParameters creationParameters) {
+               return ImmutableMap.<String, Object>builder()
+                               .put("X-ECOMP-RequestID", request.getHeader(ECOMP_REQUEST_ID))
+                               .put("aaiFullSubscriberList", getFullSubscriberList())
+                               .put("userID", getUserIDFromSystemProperties(request))
+                               .put("commitInfo", getCommitInfoFromGitProperties())
+                               .put("probeInfo", getProbe())
+                               .build();
+       }
+
+       @Override
+       public boolean canGenerate(ReportCreationParameters creationParameters) {
+               return true;
+       }
+
+       private GitRepositoryState getCommitInfoFromGitProperties() {
+               GitRepositoryState gitRepositoryState;
+               try (InputStream resourceAsStream = getClass().getClassLoader().getResourceAsStream(GIT_PROPERTIES_FILENAME)) {
+                       Properties properties = new Properties();
+                       properties.load(resourceAsStream);
+                       gitRepositoryState = new GitRepositoryState(properties);
+               } catch (IOException e) {
+                       gitRepositoryState = GitRepositoryState.EMPTY;
+               }
+               return gitRepositoryState;
+       }
+
+       String getUserIDFromSystemProperties(HttpServletRequest request) {
+               return new ControllersUtils(systemPropertiesWrapper).extractUserId(request);
+       }
+
+       List<ExternalComponentStatus> getProbe() {
+               return probeService.getProbe();
+       }
+
+       ImmutableMap<String, Object> getFullSubscriberList() {
+               ImmutableMap<String, Object> fullSubscriberList;
+               try {
+                       HttpResponse<SubscriberList> fullSubscriberListResponse = aaiService.getFullSubscriberList();
+                       fullSubscriberList = ImmutableMap.<String, Object>builder()
+                                       .put("status", fullSubscriberListResponse.getStatus())
+                                       .put("body", fullSubscriberListResponse.getBody())
+                                       .put("headers", fullSubscriberListResponse.getHeaders())
+                                       .build();
+               } catch (Exception e) {
+                       fullSubscriberList = ImmutableMap.of("exception", e.toString());
+               }
+               return fullSubscriberList;
+       }
+}
diff --git a/vid-app-common/src/main/java/org/onap/vid/reports/DeploymentReportGenerator.java b/vid-app-common/src/main/java/org/onap/vid/reports/DeploymentReportGenerator.java
new file mode 100644 (file)
index 0000000..da84526
--- /dev/null
@@ -0,0 +1,85 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * VID
+ * ================================================================================
+ * Copyright (C) 2019 Nokia Intellectual Property. All rights reserved.
+ * ================================================================================
+ * 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.vid.reports;
+
+import com.google.common.collect.ImmutableMap;
+import org.onap.vid.asdc.AsdcCatalogException;
+import org.onap.vid.model.errorReport.ReportCreationParameters;
+import org.onap.vid.mso.MsoBusinessLogic;
+import org.onap.vid.mso.MsoResponseWrapper;
+import org.onap.vid.services.VidService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.HashMap;
+import java.util.Map;
+
+@Service
+public class DeploymentReportGenerator implements ReportGenerator {
+
+       private final VidService vidService;
+       private final MsoBusinessLogic msoBusinessLogic;
+
+       @Autowired
+       public DeploymentReportGenerator(VidService vidService, MsoBusinessLogic msoBusinessLogic) {
+               this.vidService = vidService;
+               this.msoBusinessLogic = msoBusinessLogic;
+       }
+
+       @Override
+       public Map<String, Object> apply(HttpServletRequest request, ReportCreationParameters creationParameters) {
+               return ImmutableMap.<String, Object>builder()
+                               .put("serviceInstanceInfo", getOrchestrationRequestFromMso(creationParameters.getRequestId()))
+                               .put("serviceDetails", getServiceDetails(creationParameters.getServiceUuid()))
+                               .build();
+       }
+
+       @Override
+       public boolean canGenerate(ReportCreationParameters creationParameters) {
+               return creationParameters.getRequestId() != null && creationParameters.getServiceUuid() != null;
+       }
+
+       MsoResponseWrapper getOrchestrationRequestFromMso(String requestId) {
+               return msoBusinessLogic.getOrchestrationRequest(requestId);
+       }
+
+       Map<String, Object> getServiceDetails(String serviceUuid) {
+               Map<String, Object> serviceDetails;
+               try {
+                       org.onap.vid.model.Service serviceModel = vidService.getService(serviceUuid).getService();
+                       serviceDetails = ImmutableMap.of("details", serviceModel);
+               } catch (AsdcCatalogException | NullPointerException e) {
+                       serviceDetails = generateServiceDetailsExceptionResponse(serviceUuid, e);
+               }
+               return serviceDetails;
+       }
+
+       Map<String, Object> generateServiceDetailsExceptionResponse(String serviceUuid, Exception e) {
+               Map<String, Object> result = new HashMap<>();
+               if (e.getClass() == NullPointerException.class) {
+                       result.put("message", "Service details for given uuid were not found");
+               }
+               result.put("exception", e.toString());
+               result.put("serviceUuid", serviceUuid);
+               return result;
+       }
+}
diff --git a/vid-app-common/src/main/java/org/onap/vid/reports/ReportGenerator.java b/vid-app-common/src/main/java/org/onap/vid/reports/ReportGenerator.java
new file mode 100644 (file)
index 0000000..ccc2901
--- /dev/null
@@ -0,0 +1,32 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * VID
+ * ================================================================================
+ * Copyright (C) 2019 Nokia Intellectual Property. All rights reserved.
+ * ================================================================================
+ * 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.vid.reports;
+
+import org.onap.vid.model.errorReport.ReportCreationParameters;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.Map;
+import java.util.function.BiFunction;
+
+public interface ReportGenerator extends BiFunction<HttpServletRequest, ReportCreationParameters, Map<String, Object>>{
+
+       boolean canGenerate(ReportCreationParameters creationParameters);
+
+}
index 643cd22..c74321d 100644 (file)
@@ -20,7 +20,7 @@
 
 package org.onap.vid.scheduler;
 
-import org.onap.vid.controller.ProbeInterface;
+import org.onap.vid.services.ProbeInterface;
 
 public interface SchedulerService  extends ProbeInterface {
 }
@@ -17,7 +17,7 @@
  * limitations under the License.
  * ============LICENSE_END=========================================================
  */
-package org.onap.vid.controller;
+package org.onap.vid.services;
 
 import org.onap.vid.model.probes.ExternalComponentStatus;
 
diff --git a/vid-app-common/src/main/java/org/onap/vid/services/ProbeService.java b/vid-app-common/src/main/java/org/onap/vid/services/ProbeService.java
new file mode 100644 (file)
index 0000000..b2062d5
--- /dev/null
@@ -0,0 +1,42 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * VID
+ * ================================================================================
+ * Copyright (C) 2019 Nokia Intellectual Property. All rights reserved.
+ * ================================================================================
+ * 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.vid.services;
+
+import org.onap.vid.model.probes.ExternalComponentStatus;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+@Service
+public class ProbeService {
+
+       private final List<ProbeInterface> probes;
+
+       @Autowired
+       public ProbeService(List<ProbeInterface> probes) {
+               this.probes = probes;
+       }
+
+       public List<ExternalComponentStatus> getProbe(){
+               return probes.stream().map(ProbeInterface::probeComponent).collect(Collectors.toList());
+       }
+}
index 18d8398..f7bc1f2 100644 (file)
@@ -22,7 +22,6 @@
 package org.onap.vid.services;
 
 import org.onap.vid.asdc.AsdcCatalogException;
-import org.onap.vid.controller.ProbeInterface;
 import org.onap.vid.model.ServiceModel;
 
 public interface VidService extends ProbeInterface {
diff --git a/vid-app-common/src/test/java/org/onap/vid/controller/ErrorReportControllerTest.java b/vid-app-common/src/test/java/org/onap/vid/controller/ErrorReportControllerTest.java
new file mode 100644 (file)
index 0000000..1f52f5a
--- /dev/null
@@ -0,0 +1,115 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * VID
+ * ================================================================================
+ * Copyright (C) 2019 Nokia Intellectual Property. All rights reserved.
+ * ================================================================================
+ * 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.vid.controller;
+
+import com.google.common.collect.ImmutableMap;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.onap.vid.model.errorReport.ReportCreationParameters;
+import org.onap.vid.reports.BasicReportGenerator;
+import org.onap.vid.reports.DeploymentReportGenerator;
+
+import javax.servlet.http.HttpServletRequest;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+@RunWith(MockitoJUnitRunner.class)
+public class ErrorReportControllerTest {
+
+       private BasicReportGenerator basicReportGenerator;
+       private DeploymentReportGenerator deploymentReportGenerator;
+       private HttpServletRequest httpServletRequest;
+
+       private ErrorReportController errorReportController;
+
+       @Before
+       public void setUp() {
+               basicReportGenerator = mock(BasicReportGenerator.class);
+               deploymentReportGenerator = mock(DeploymentReportGenerator.class);
+               httpServletRequest = mock(HttpServletRequest.class);
+
+               errorReportController
+                               = new ErrorReportController(Arrays.asList(basicReportGenerator, deploymentReportGenerator));
+       }
+
+       @Test
+       public void shouldGenerateBasicReportDataWhenNoParameters() {
+               //given
+               ReportCreationParameters parameters = new ReportCreationParameters();
+
+               ImmutableMap<String, Object> expectedReport = ImmutableMap.<String, Object>builder()
+                                               .put("X-ECOMP-RequestID", "request_id")
+                                               .put("X-ECOMP-RequestID1", "request_id1")
+                                               .put("X-ECOMP-RequestID2", "request_id2")
+                                               .put("X-ECOMP-RequestID3", "request_id3")
+                                               .build();
+
+               when(basicReportGenerator.apply(httpServletRequest, parameters)).thenReturn(expectedReport);
+               when(basicReportGenerator.canGenerate(parameters)).thenReturn(true);
+               when(deploymentReportGenerator.canGenerate(parameters)).thenReturn(false);
+
+               //when
+               Map<String, Object> actualReport = errorReportController.generateReportsData(httpServletRequest, parameters);
+
+               //then
+               assertThat(actualReport).isEqualTo(expectedReport);
+       }
+
+       @Test
+       public void shouldGenerateDeploymentReportDataWhenSpecificParameters() {
+               //given
+               ReportCreationParameters parameters =
+                               new ReportCreationParameters("request_id", "service_uuid");
+
+               ImmutableMap<String, Object> basicReport = ImmutableMap.<String, Object>builder()
+                               .put("X-ECOMP-RequestID", "request_id")
+                               .put("X-ECOMP-RequestID1", "request_id1")
+                               .build();
+
+               ImmutableMap<String, Object> extendedReport = ImmutableMap.<String, Object>builder()
+                               .put("serviceInstanceInfo", "serviceInstanceInfoVal")
+                               .put("serviceInstanceInfo1", "serviceInstanceInfoVal1")
+                               .build();
+
+               HashMap<String, Object> expectedReport = new HashMap<>(basicReport);
+               expectedReport.putAll(extendedReport);
+
+               when(basicReportGenerator.apply(httpServletRequest, parameters)).thenReturn(basicReport);
+               when(deploymentReportGenerator.apply(httpServletRequest, parameters)).thenReturn(extendedReport);
+               when(basicReportGenerator.canGenerate(parameters)).thenReturn(true);
+               when(deploymentReportGenerator.canGenerate(parameters)).thenReturn(true);
+
+               //when
+               Map<String, Object> actualReport = errorReportController.generateReportsData(httpServletRequest, parameters);
+
+               //then
+               assertThat(actualReport).isEqualTo(expectedReport);
+       }
+}
\ No newline at end of file
diff --git a/vid-app-common/src/test/java/org/onap/vid/reports/BasicReportGeneratorTest.java b/vid-app-common/src/test/java/org/onap/vid/reports/BasicReportGeneratorTest.java
new file mode 100644 (file)
index 0000000..d34646d
--- /dev/null
@@ -0,0 +1,165 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * VID
+ * ================================================================================
+ * Copyright (C) 2019 Nokia Intellectual Property. All rights reserved.
+ * ================================================================================
+ * 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.vid.reports;
+
+import com.google.common.collect.ImmutableMap;
+import io.joshworks.restclient.http.Headers;
+import io.joshworks.restclient.http.HttpResponse;
+import io.joshworks.restclient.http.exceptions.RestClientException;
+import nu.xom.jaxen.util.SingletonList;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.onap.portalsdk.core.domain.User;
+import org.onap.portalsdk.core.util.SystemProperties;
+import org.onap.vid.aai.AaiClient;
+import org.onap.vid.model.SubscriberList;
+import org.onap.vid.model.errorReport.ReportCreationParameters;
+import org.onap.vid.model.probes.ExternalComponentStatus;
+import org.onap.vid.model.probes.StatusMetadata;
+import org.onap.vid.roles.RoleProvider;
+import org.onap.vid.scheduler.SchedulerService;
+import org.onap.vid.services.AaiService;
+import org.onap.vid.services.ProbeService;
+import org.onap.vid.utils.SystemPropertiesWrapper;
+import org.springframework.http.HttpStatus;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+@RunWith(MockitoJUnitRunner.class)
+public class BasicReportGeneratorTest {
+
+       private static final String USER_ATTRIBUTE = "userAttribute";
+       private ReportCreationParameters creationParameters = new ReportCreationParameters();
+
+       @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+       private HttpServletRequest request;
+       @Mock
+       private HttpResponse<SubscriberList> subscriberListResponse;
+       @Mock
+       private ProbeService probeService;
+
+       @Mock
+       private AaiClient aaiClient;
+       @Mock
+       private SchedulerService schedulerService;
+       @Mock
+       private AaiService aaiService;
+       @Mock
+       private RoleProvider roleProvider;
+       @Mock
+       private SystemPropertiesWrapper systemPropertiesWrapper;
+
+       @InjectMocks
+       private BasicReportGenerator basicReportGenerator;
+
+       @Test
+       public void shouldAlwaysReturnTrueAsConditionOfGeneration() {
+               //given
+               //when
+               //then
+               assertThat(basicReportGenerator.canGenerate(creationParameters)).isTrue();
+       }
+
+       @Test
+       public void shouldGetUserIDFromSystemProperties() {
+               //given
+               final String expectedUserID = "user_id";
+               User user = new User();
+               user.setLoginId(expectedUserID);
+
+               when(systemPropertiesWrapper.getProperty(SystemProperties.USER_ATTRIBUTE_NAME)).thenReturn(USER_ATTRIBUTE);
+               when(request.getSession().getAttribute(USER_ATTRIBUTE)).thenReturn(user);
+
+               //when
+               String actualUserID = basicReportGenerator.getUserIDFromSystemProperties(request);
+
+               //then
+               assertThat(actualUserID).isEqualTo(expectedUserID);
+       }
+
+       @Test
+       public void shouldGetProbes() {
+               //given
+               ExternalComponentStatus status = new ExternalComponentStatus(
+                                               ExternalComponentStatus.Component.MSO,
+                                               true,
+                                               mock(StatusMetadata.class));
+               List<ExternalComponentStatus> expectedProbes = Collections.singletonList(status);
+
+               when(probeService.getProbe()).thenReturn(expectedProbes);
+
+               //when
+               List<ExternalComponentStatus> actualProbes = basicReportGenerator.getProbe();
+
+               //then
+               assertThat(actualProbes).isEqualTo(expectedProbes);
+       }
+
+       @Test
+       public void shouldGetFullSubscriberList() {
+               //given
+               SubscriberList subscriberList = new SubscriberList();
+               Headers headers = new Headers();
+               int status = HttpStatus.OK.value();
+
+               ImmutableMap<String, Object> expectedSubscriberList = ImmutableMap.<String, Object>builder()
+                               .put("status", status)
+                               .put("body", subscriberList)
+                               .put("headers", headers)
+                               .build();
+
+               when(aaiService.getFullSubscriberList()).thenReturn(subscriberListResponse);
+               when(subscriberListResponse.getStatus()).thenReturn(status);
+               when(subscriberListResponse.getBody()).thenReturn(subscriberList);
+               when(subscriberListResponse.getHeaders()).thenReturn(headers);
+
+               //when
+               ImmutableMap<String, Object> actualSubscriberList = basicReportGenerator.getFullSubscriberList();
+
+               //then
+               assertThat(actualSubscriberList).isEqualTo(expectedSubscriberList);
+       }
+
+       @Test
+       public void shouldReturnExceptionInfoWhileGettingFullSubscriberList() {
+               //given
+               RestClientException expectedException = mock(RestClientException.class);
+               ImmutableMap<String, Object> expectedResult = ImmutableMap.of("exception", expectedException.toString());
+
+               when(aaiService.getFullSubscriberList()).thenThrow(expectedException);
+
+               //when
+               ImmutableMap<String, Object> actualResult = basicReportGenerator.getFullSubscriberList();
+
+               //then
+               assertThat(actualResult).isEqualTo(expectedResult);
+       }
+}
\ No newline at end of file
diff --git a/vid-app-common/src/test/java/org/onap/vid/reports/DeploymentReportGeneratorTest.java b/vid-app-common/src/test/java/org/onap/vid/reports/DeploymentReportGeneratorTest.java
new file mode 100644 (file)
index 0000000..fa6e832
--- /dev/null
@@ -0,0 +1,120 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * VID
+ * ================================================================================
+ * Copyright (C) 2019 Nokia Intellectual Property. All rights reserved.
+ * ================================================================================
+ * 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.vid.reports;
+
+import com.google.common.collect.ImmutableMap;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.onap.vid.asdc.AsdcCatalogException;
+import org.onap.vid.model.Service;
+import org.onap.vid.model.ServiceModel;
+import org.onap.vid.model.errorReport.ReportCreationParameters;
+import org.onap.vid.mso.MsoBusinessLogic;
+import org.onap.vid.mso.MsoResponseWrapper;
+import org.onap.vid.services.VidService;
+
+import java.util.Map;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+@RunWith(MockitoJUnitRunner.class)
+public class DeploymentReportGeneratorTest {
+
+       @Mock
+       private MsoBusinessLogic msoBusinessLogic;
+       @Mock
+       private VidService vidService;
+
+       @InjectMocks
+       private DeploymentReportGenerator deploymentReportGenerator;
+
+       @Test
+       public void shouldReturnTrueIfConditionsOfGenerationAreFulfilled() {
+               //given
+               ReportCreationParameters parameters = new ReportCreationParameters();
+               parameters.setRequestId("request_id");
+               parameters.setServiceUuid("service_uuid");
+
+               //when
+               boolean actualResult = deploymentReportGenerator.canGenerate(parameters);
+
+               //then
+               assertThat(actualResult).isTrue();
+       }
+
+       @Test
+       public void shouldGetOrchestrationRequestFromMso() {
+               //given
+               final String requestId = "request_id";
+               MsoResponseWrapper expectedOrchestrationRequest = mock(MsoResponseWrapper.class);
+
+               when(msoBusinessLogic.getOrchestrationRequest(requestId)).thenReturn(expectedOrchestrationRequest);
+
+               //when
+               MsoResponseWrapper actualOrchestrationRequest =
+                               deploymentReportGenerator.getOrchestrationRequestFromMso(requestId);
+
+               //then
+               assertThat(actualOrchestrationRequest).isEqualTo(expectedOrchestrationRequest);
+       }
+
+       @Test
+       public void shouldGetServiceDetails() throws AsdcCatalogException {
+               //given
+               final String SERVICE_UUID = "service_uuid";
+               ServiceModel serviceModel = mock(ServiceModel.class);
+               Service expectedService = new Service();
+               ImmutableMap<String, Object> expectedServiceDetails = ImmutableMap.of("details", expectedService);
+
+               when(vidService.getService(SERVICE_UUID)).thenReturn(serviceModel);
+               when(serviceModel.getService()).thenReturn(expectedService);
+
+               //when
+               Map<String, Object> actualServiceDetails = deploymentReportGenerator.getServiceDetails(SERVICE_UUID);
+
+               //then
+               assertThat(actualServiceDetails).isEqualTo(expectedServiceDetails);
+       }
+
+       @Test
+       public void shouldGetNullPointerExceptionInfoInMapWhenWrongUuidIsGiven() {
+               //given
+               final String NOT_EXISTING_UUID = "8ad001d0-1111-2222-3333-123412341234";
+               NullPointerException expectedException = new NullPointerException("msg");
+
+               Map<String, Object> expectedResult = ImmutableMap.<String, Object>builder()
+                               .put("message", "Service details for given uuid were not found")
+                               .put("exception", expectedException.toString())
+                               .put("serviceUuid", NOT_EXISTING_UUID)
+                               .build();
+
+               //when
+               Map<String, Object> actualResult =
+                               deploymentReportGenerator.generateServiceDetailsExceptionResponse(NOT_EXISTING_UUID, expectedException);
+
+               //then
+               assertThat(actualResult).isEqualTo(expectedResult);
+       }
+}
\ No newline at end of file
diff --git a/vid-app-common/src/test/java/org/onap/vid/services/ProbeServiceTest.java b/vid-app-common/src/test/java/org/onap/vid/services/ProbeServiceTest.java
new file mode 100644 (file)
index 0000000..39b2df3
--- /dev/null
@@ -0,0 +1,96 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * VID
+ * ================================================================================
+ * Copyright (C) 2019 Nokia Intellectual Property. All rights reserved.
+ * ================================================================================
+ * 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.vid.services;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.onap.vid.aai.AaiClient;
+import org.onap.vid.aai.AaiOverTLSClient;
+import org.onap.vid.model.probes.ExternalComponentStatus;
+import org.onap.vid.model.probes.StatusMetadata;
+import org.onap.vid.mso.MsoBusinessLogic;
+import org.onap.vid.scheduler.SchedulerService;
+
+import javax.inject.Inject;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+@RunWith(MockitoJUnitRunner.class)
+public class ProbeServiceTest {
+
+       private AaiClient aaiClient;
+       private AaiOverTLSClient newAaiClient;
+       private SchedulerService schedulerService;
+       private MsoBusinessLogic msoBusinessLogic;
+       private VidService vidService;
+       private List<ProbeInterface> probeInterfaces;
+       private ProbeService probeService;
+
+       @Before
+       public void setUp() throws Exception {
+               aaiClient = mock(AaiClient.class);
+               newAaiClient = mock(AaiOverTLSClient.class);
+               schedulerService = mock(SchedulerService.class);
+               msoBusinessLogic = mock(MsoBusinessLogic.class);
+               vidService = mock(VidService.class);
+
+               probeInterfaces = Arrays.asList(aaiClient, newAaiClient, schedulerService, msoBusinessLogic, vidService);
+               probeService = new ProbeService(probeInterfaces);
+       }
+
+       @Test
+       public void shouldGetProbes() {
+               //given
+               StatusMetadata statusMetadata = mock(StatusMetadata.class);
+
+               ExternalComponentStatus statusAai =
+                               new ExternalComponentStatus(ExternalComponentStatus.Component.AAI, true, statusMetadata);
+               ExternalComponentStatus statusScheduler =
+                               new ExternalComponentStatus(ExternalComponentStatus.Component.SCHEDULER, false, statusMetadata);
+               ExternalComponentStatus statusMso =
+                               new ExternalComponentStatus(ExternalComponentStatus.Component.MSO, true, statusMetadata);
+               ExternalComponentStatus statusSdc =
+                               new ExternalComponentStatus(ExternalComponentStatus.Component.SDC, false, statusMetadata);
+
+               List<ExternalComponentStatus> expectedStatuses =
+                               Arrays.asList(statusAai, statusAai, statusScheduler, statusMso, statusSdc);
+
+               when(aaiClient.probeComponent()).thenReturn(statusAai);
+               when(newAaiClient.probeComponent()).thenReturn(statusAai);
+               when(schedulerService.probeComponent()).thenReturn(statusScheduler);
+               when(msoBusinessLogic.probeComponent()).thenReturn(statusMso);
+               when(vidService.probeComponent()).thenReturn(statusSdc);
+
+               //when
+               List<ExternalComponentStatus> actualStatuses = probeService.getProbe();
+
+               //then
+               assertThat(actualStatuses).isEqualTo(expectedStatuses);
+       }
+}
\ No newline at end of file