--- /dev/null
+use catalogdb;
+
+INSERT INTO `vnf_recipe` (`NF_ROLE`, `ACTION`, `VERSION_STR`, `DESCRIPTION`, `ORCHESTRATION_URI`, `RECIPE_TIMEOUT`)
+VALUES
+('GR-API-DEFAULT', 'recreateInstance', '1', 'Gr api recipe to recreate vnf', '/mso/async/services/WorkflowActionBB', 180);
\ No newline at end of file
* ================================================================================
* Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
* ================================================================================
- * Modifications Copyright 2018 IBM.
* 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
compareModel,
scaleInstance,
deactivateAndCloudDelete,
- scaleOut
+ scaleOut,
+ recreateInstance
}
compareModel,
scaleInstance,
deactivateAndCloudDelete,
- scaleOut
+ scaleOut,
+ recreateInstance
}
instanceIdMap.put("vnfInstanceId", vnfInstanceId);
return serviceInstances(request, Action.applyUpdatedConfig, instanceIdMap, version, requestId, getRequestUri(requestContext));
}
+
+ @POST
+ @Path("/{version:[vV][7]}/serviceInstances/{serviceInstanceId}/vnfs/{vnfInstanceId}/recreate")
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ @ApiOperation(value="Recreate VNF Instance",response=Response.class)
+ public Response recreateVnfInstance(String request, @PathParam("version") String version, @PathParam("serviceInstanceId") String serviceInstanceId,
+ @PathParam("vnfInstanceId") String vnfInstanceId, @Context ContainerRequestContext requestContext) throws ApiException {
+ String requestId = getRequestId(requestContext);
+ HashMap<String, String> instanceIdMap = new HashMap<>();
+ instanceIdMap.put("serviceInstanceId", serviceInstanceId);
+ instanceIdMap.put("vnfInstanceId", vnfInstanceId);
+ return serviceInstances(request, Action.recreateInstance, instanceIdMap, version, requestId, getRequestUri(requestContext));
+ }
@DELETE
if(requestScope.equalsIgnoreCase(ModelType.vfModule.name()) && (action == Action.deactivateAndCloudDelete || action == Action.scaleOut)){
throw new ValidationException("cloudConfiguration");
}
+ if(requestScope.equals(ModelType.vnf.name()) && action == Action.recreateInstance){
+ throw new ValidationException("cloudConfiguration", true);
+ }
}
if (cloudConfiguration == null && ((aLaCarteFlag != null && !aLaCarteFlag) && requestScope.equalsIgnoreCase (ModelType.service.name ()) && reqVersion < 5)) {
}
// modelCustomizationId or modelCustomizationName are required for VNF Replace
- if(requestParameters != null && reqVersion > 4 && requestScope.equalsIgnoreCase(ModelType.vnf.name()) && action == Action.replaceInstance) {
+ if(requestParameters != null && reqVersion > 4 && requestScope.equalsIgnoreCase(ModelType.vnf.name()) && action == Action.replaceInstance || action == Action.recreateInstance) {
if(!UUIDChecker.isValidUUID(modelInfo.getModelCustomizationId()) && modelInfo.getModelCustomizationName() == null) {
throw new ValidationException("modelCustomizationId or modelCustomizationName");
}
if(empty(modelInfo.getModelInvariantId()) && (requestScope.equalsIgnoreCase(ModelType.vfModule.name()) && action == Action.scaleOut)){
throw new ValidationException("modelInvariantId");
}
-
+ if(empty(modelInfo.getModelInvariantId()) && (requestScope.equalsIgnoreCase(ModelType.vnf.name()) && action == Action.recreateInstance)){
+ throw new ValidationException("modelInvariantId", true);
+ }
if (!empty (modelInfo.getModelInvariantId ()) && !UUIDChecker.isValidUUID (modelInfo.getModelInvariantId ())) {
throw new ValidationException ("modelInvariantId format");
}
if(reqVersion >= 4 && !(requestScope.equalsIgnoreCase(ModelType.configuration.name())) && empty (modelInfo.getModelName ()) && (action == Action.createInstance || action == Action.updateInstance ||
- action == Action.addRelationships || action == Action.removeRelationships || ((action == Action.deleteInstance || action == Action.scaleOut) && (requestScope.equalsIgnoreCase (ModelType.vfModule.name ()))))){
- throw new ValidationException ("modelName");
+ action == Action.addRelationships || action == Action.removeRelationships || action == Action.recreateInstance || ((action == Action.deleteInstance || action == Action.scaleOut) && (requestScope.equalsIgnoreCase (ModelType.vfModule.name ()))))){
+ throw new ValidationException ("modelName", true);
}
if (empty (modelInfo.getModelVersion ()) && !(requestScope.equalsIgnoreCase(ModelType.configuration.name())) &&
&& (action == Action.createInstance || action == Action.updateInstance || action == Action.addRelationships || action == Action.removeRelationships || action == Action.scaleOut))) {
throw new ValidationException ("modelVersion");
}
+
+ if(empty(modelInfo.getModelVersion()) && (requestScope.equalsIgnoreCase(ModelType.vnf.name()) && action == Action.recreateInstance)){
+ throw new ValidationException("modelVersion", true);
+ }
// is required for serviceInstance delete macro when aLaCarte=false in v4
if (reqVersion >= 4 && empty (modelInfo.getModelVersionId()) && (((aLaCarteFlag != null && !aLaCarteFlag) && requestScope.equalsIgnoreCase(ModelType.service.name()) && action == Action.deleteInstance) ||
(requestScope.equalsIgnoreCase(ModelType.configuration.name()) && (action == Action.activateInstance || action == Action.deactivateInstance))))) {
throw new ValidationException ("modelVersionId");
}
+ if(empty(modelInfo.getModelVersionId()) && (requestScope.equalsIgnoreCase(ModelType.vnf.name()) && action == Action.recreateInstance)){
+ throw new ValidationException("modelVersionId", true);
+ }
if(empty(modelInfo.getModelVersionId()) && (requestScope.equalsIgnoreCase(ModelType.vfModule.name()) && action == Action.scaleOut)){
throw new ValidationException("modelVersionId");
}
{"No valid cloudConfiguration is specified", mapper.readValue(inputStream("/CloudConfiguration/InPlaceSoftwareUpdateCloudConfiguration.json"), ServiceInstancesRequest.class), instanceIdMapTest, Action.inPlaceSoftwareUpdate, 6},
{"No valid cloudConfiguration is specified", mapper.readValue(inputStream("/CloudConfiguration/DeactivateAndCloudDeleteCloudConfiguration.json"), ServiceInstancesRequest.class), instanceIdMapTest, Action.deactivateAndCloudDelete, 7},
{"No valid cloudConfiguration is specified", mapper.readValue(inputStream("/CloudConfiguration/ScaleOutNoCloudConfig.json"), ServiceInstancesRequest.class), instanceIdMapTest, Action.scaleOut, 7},
+ {"No valid cloudConfiguration is specified", mapper.readValue(inputStream("/CloudConfiguration/VnfRecreateCloudConfig.json"), ServiceInstancesRequest.class), instanceIdMapTest, Action.recreateInstance, 7},
{"No valid lcpCloudRegionId is specified", mapper.readValue(inputStream("/CloudConfiguration/EmptyLcpCloudConfiguration.json"), ServiceInstancesRequest.class), instanceIdMapTest, Action.createInstance, 5},
{"No valid lcpCloudRegionId is specified", mapper.readValue(inputStream("/CloudConfiguration/InPlaceSoftwareUpdateCloudRegionId.json"), ServiceInstancesRequest.class), instanceIdMapTest, Action.inPlaceSoftwareUpdate, 6},
{"No valid tenantId is specified", mapper.readValue(inputStream("/CloudConfiguration/EmptyTenantId.json"), ServiceInstancesRequest.class), instanceIdMapTest, Action.createInstance, 5},
{"No valid modelCustomizationId or modelCustomizationName is specified", mapper.readValue(inputStream("/ModelInfo/ModelCustomization.json"), ServiceInstancesRequest.class), instanceIdMapTest, Action.replaceInstance, 6},
{"No valid modelCustomizationId or modelCustomizationName is specified", mapper.readValue(inputStream("/ModelInfo/ModelCustomization.json"), ServiceInstancesRequest.class), instanceIdMapTest, Action.updateInstance, 6},
{"No valid modelCustomizationId or modelCustomizationName is specified", mapper.readValue(inputStream("/ModelInfo/VnfModelCustomizationId.json"), ServiceInstancesRequest.class), instanceIdMapTest, Action.replaceInstance, 5},
+ {"No valid modelCustomizationId or modelCustomizationName is specified", mapper.readValue(inputStream("/ModelInfo/VnfRecreateNoModelCustomizationId.json"), ServiceInstancesRequest.class), instanceIdMapTest, Action.recreateInstance, 7},
{"No valid modelCustomizationId is specified", mapper.readValue(inputStream("/ModelInfo/ScaleOutNoModelCustomizationId.json"), ServiceInstancesRequest.class), instanceIdMapTest, Action.scaleOut, 7},
{"No valid model-info is specified", mapper.readValue(inputStream("/ModelInfo/ModelInfoNull.json"), ServiceInstancesRequest.class), instanceIdMapTest, Action.createInstance, 5},
{"No valid modelInvariantId format is specified", mapper.readValue(inputStream("/ModelInfo/InvalidModelInvariantId.json"), ServiceInstancesRequest.class), instanceIdMapTest, Action.createInstance, 2},
{"No valid modelInvariantId is specified", mapper.readValue(inputStream("/ModelInfo/v5DeactivateModelInvariantId.json"), ServiceInstancesRequest.class), instanceIdMapTest, Action.deactivateInstance, 5},
{"No valid modelInvariantId is specified", mapper.readValue(inputStream("/ModelInfo/v3UpdateNetworkBad.json"), ServiceInstancesRequest.class), instanceIdMapTest, Action.updateInstance, 4},
{"No valid modelInvariantId is specified", mapper.readValue(inputStream("/ModelInfo/ScaleOutNoModelInvariantId.json"), ServiceInstancesRequest.class), instanceIdMapTest, Action.scaleOut, 7},
+ {"No valid modelInvariantId is specified", mapper.readValue(inputStream("/ModelInfo/VnfRecreateModelInvariantId.json"), ServiceInstancesRequest.class), instanceIdMapTest, Action.recreateInstance, 7},
{"No valid modelName is specified", mapper.readValue(inputStream("/ModelInfo/VfModuleModelName.json"), ServiceInstancesRequest.class), instanceIdMapTest, Action.deleteInstance, 4},
{"No valid modelName is specified", mapper.readValue(inputStream("/ModelInfo/ScaleOutNoModelName.json"), ServiceInstancesRequest.class), instanceIdMapTest, Action.scaleOut, 7},
+ {"No valid modelName is specified", mapper.readValue(inputStream("/ModelInfo/VnfRecreateModelName.json"), ServiceInstancesRequest.class), instanceIdMapTest, Action.recreateInstance, 7},
{"No valid modelType is specified", mapper.readValue(inputStream("/ModelInfo/ModelTypeNull.json"), ServiceInstancesRequest.class), instanceIdMapTest, Action.createInstance, 5},
{"No valid modelVersion is specified", mapper.readValue(inputStream("/ModelInfo/ModelVersionService.json"), ServiceInstancesRequest.class), instanceIdMapTest, Action.updateInstance, 3},
{"No valid modelVersion is specified", mapper.readValue(inputStream("/ModelInfo/ModelVersionVfModule.json"), ServiceInstancesRequest.class), instanceIdMapTest, Action.createInstance, 3},
{"No valid modelVersion is specified", mapper.readValue(inputStream("/ModelInfo/ModelVersionServiceCreate.json"), ServiceInstancesRequest.class), instanceIdMapTest, Action.createInstance, 3},
{"No valid modelVersion is specified", mapper.readValue(inputStream("/ModelInfo/ScaleOutNoModelVersion.json"), ServiceInstancesRequest.class), instanceIdMapTest, Action.scaleOut, 7},
+ {"No valid modelVersion is specified", mapper.readValue(inputStream("/ModelInfo/VnfRecreateModelVersion.json"), ServiceInstancesRequest.class), instanceIdMapTest, Action.recreateInstance, 7},
{"No valid modelVersionId is specified", mapper.readValue(inputStream("/ModelInfo/ModelVersionId.json"), ServiceInstancesRequest.class), instanceIdMapTest, Action.createInstance, 5},
{"No valid modelVersionId is specified", mapper.readValue(inputStream("/ModelInfo/ConfigurationModelVersionId.json"), ServiceInstancesRequest.class), instanceIdMapTest, Action.activateInstance, 5},
{"No valid modelVersionId is specified", mapper.readValue(inputStream("/ModelInfo/v2ModelVersionId.json"), ServiceInstancesRequest.class), instanceIdMapTest, Action.activateInstance, 4},
{"No valid modelVersionId is specified", mapper.readValue(inputStream("/ModelInfo/ModelVersionIdCreate.json"), ServiceInstancesRequest.class), instanceIdMapTest, Action.createInstance, 5},
{"No valid modelVersionId is specified", mapper.readValue(inputStream("/ModelInfo/v5ActivateModelVersionId.json"), ServiceInstancesRequest.class), instanceIdMapTest, Action.activateInstance, 5},
{"No valid modelVersionId is specified", mapper.readValue(inputStream("/ModelInfo/ScaleOutNoModelVersionId.json"), ServiceInstancesRequest.class), instanceIdMapTest, Action.scaleOut, 7},
+ {"No valid modelVersionId is specified", mapper.readValue(inputStream("/ModelInfo/VnfRecreateModelVersionId.json"), ServiceInstancesRequest.class), instanceIdMapTest, Action.recreateInstance, 7},
//ValidationException for Platform and LineOfBusiness
{"No valid lineOfBusinessName is specified", mapper.readValue(inputStream("/PlatformAndLineOfBusiness/EmptyLineOfBusiness.json"), ServiceInstancesRequest.class), instanceIdMapTest, Action.createInstance, 5},
{"No valid platform is specified", mapper.readValue(inputStream("/PlatformAndLineOfBusiness/Platform.json"), ServiceInstancesRequest.class), instanceIdMapTest, Action.createInstance, 6},
assertThat(realResponse, sameBeanAs(expectedResponse).ignoring("requestReferences.requestId"));
}
@Test
+ public void recreateVnfInstance() throws IOException {
+ stubFor(post(urlPathEqualTo("/mso/async/services/WorkflowActionBB"))
+ .willReturn(aResponse().withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON)
+ .withBodyFile("Camunda/TestResponse.json").withStatus(org.apache.http.HttpStatus.SC_OK)));
+
+ stubFor(get(urlMatching(".*/vnfResourceCustomization/68dc9a92-214c-11e7-93ae-92361f002674"))
+ .willReturn(aResponse().withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON)
+ .withBody(getWiremockResponseForCatalogdb("vnfResourceCustomization_Response"))
+ .withStatus(org.apache.http.HttpStatus.SC_OK)));
+
+ stubFor(get(urlMatching(".*/vnfResourceCustomization/68dc9a92-214c-11e7-93ae-92361f002674/vnfResources"))
+ .willReturn(aResponse().withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON)
+ .withBody(getWiremockResponseForCatalogdb("vnfResources_Response.json"))
+ .withStatus(org.apache.http.HttpStatus.SC_OK)));
+
+ stubFor(get(urlMatching(".*/vnfRecipe/search/findFirstVnfRecipeByNfRoleAndAction[?]nfRole=GR-API-DEFAULT&action=recreateInstance"))
+ .willReturn(aResponse().withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON)
+ .withBody(getWiremockResponseForCatalogdb("vnfRecipe_ResponseWorkflowAction.json"))
+ .withStatus(org.apache.http.HttpStatus.SC_OK)));
+
+ //expected response
+ ServiceInstancesResponse expectedResponse = new ServiceInstancesResponse();
+ RequestReferences requestReferences = new RequestReferences();
+ requestReferences.setInstanceId("1882939");
+ expectedResponse.setRequestReferences(requestReferences);
+ uri = servInstanceuri + "v7" + "/serviceInstances/f7ce78bb-423b-11e7-93f8-0050569a7968/vnfs/ff305d54-75b4-431b-adb2-eb6b9e5ff000/recreate";
+ ResponseEntity<String> response = sendRequest(inputStream("/VnfRecreate.json"), uri, HttpMethod.POST);
+ logger.debug(response.getBody());
+ ObjectMapper mapper = new ObjectMapper();
+ mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+
+ assertEquals(Response.Status.ACCEPTED.getStatusCode(), response.getStatusCode().value());
+ ServiceInstancesResponse realResponse = mapper.readValue(response.getBody(), ServiceInstancesResponse.class);
+ assertThat(realResponse, sameBeanAs(expectedResponse).ignoring("requestReferences.requestId"));
+ }
+ @Test
public void updateVnfInstance() throws IOException {
stubFor(post(urlPathEqualTo("/mso/async/services/WorkflowActionBB"))
.willReturn(aResponse().withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON)
},
"requestInfo": {
"source": "VID",
- "requestorId": "az2016"
+ "requestorId": "xxxxxx"
}
}
}
\ No newline at end of file
--- /dev/null
+{
+ "requestDetails":{
+ "modelInfo":{
+ "modelType":"vnf",
+ "modelInvariantId":"ff5256d1-5a33-55df-13ab-12abad84e7ff",
+ "modelVersionId":"254583ad-b38c-498b-bdbd-b8de5e07541b",
+ "modelName":"vSAMP12",
+ "modelVersion":"2.0",
+ "modelCustomizationName":"vSAMP12 1",
+ "modelCustomizationId":"c539433a-84a6-4082-a12e-5c9b00c3b960"
+ },
+ "requestInfo":{
+ "source":"source",
+ "suppressRollback":false,
+ "requestorId":"xxxxxx"
+ },
+ "requestParameters":{
+ "rebuildVolumeGroups":false
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+{
+ "requestDetails":{
+ "modelInfo":{
+ "modelType":"vnf",
+ "modelVersionId":"254583ad-b38c-498b-bdbd-b8de5e07541b",
+ "modelName":"vSAMP12",
+ "modelVersion":"2.0",
+ "modelCustomizationName":"vSAMP12 1",
+ "modelCustomizationId":"c539433a-84a6-4082-a12e-5c9b00c3b960"
+ },
+ "cloudConfiguration":{
+ "lcpCloudRegionId":"mdt1",
+ "tenantId":"88a6ca3ee0394ade9403f075db23167e"
+ },
+ "requestInfo":{
+ "source":"source",
+ "suppressRollback":false,
+ "requestorId":"xxxxxx"
+ },
+ "requestParameters":{
+ "rebuildVolumeGroups":false
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+{
+ "requestDetails":{
+ "modelInfo":{
+ "modelType":"vnf",
+ "modelInvariantId":"ff5256d1-5a33-55df-13ab-12abad84e7ff",
+ "modelVersionId":"254583ad-b38c-498b-bdbd-b8de5e07541b",
+ "modelVersion":"2.0",
+ "modelCustomizationName":"vSAMP12 1",
+ "modelCustomizationId":"c539433a-84a6-4082-a12e-5c9b00c3b960"
+ },
+ "cloudConfiguration":{
+ "lcpCloudRegionId":"mdt1",
+ "tenantId":"88a6ca3ee0394ade9403f075db23167e"
+ },
+ "requestInfo":{
+ "source":"source",
+ "suppressRollback":false,
+ "requestorId":"xxxxxx"
+ },
+ "requestParameters":{
+ "rebuildVolumeGroups":false
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+{
+ "requestDetails":{
+ "modelInfo":{
+ "modelType":"vnf",
+ "modelInvariantId":"ff5256d1-5a33-55df-13ab-12abad84e7ff",
+ "modelVersionId":"254583ad-b38c-498b-bdbd-b8de5e07541b",
+ "modelName":"vSAMP12",
+ "modelCustomizationName":"vSAMP12 1",
+ "modelCustomizationId":"c539433a-84a6-4082-a12e-5c9b00c3b960"
+ },
+ "cloudConfiguration":{
+ "lcpCloudRegionId":"mdt1",
+ "tenantId":"88a6ca3ee0394ade9403f075db23167e"
+ },
+ "requestInfo":{
+ "source":"source",
+ "suppressRollback":false,
+ "requestorId":"xxxxxx"
+ },
+ "requestParameters":{
+ "rebuildVolumeGroups":false
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+{
+ "requestDetails":{
+ "modelInfo":{
+ "modelType":"vnf",
+ "modelInvariantId":"ff5256d1-5a33-55df-13ab-12abad84e7ff",
+ "modelName":"vSAMP12",
+ "modelVersion":"2.0",
+ "modelCustomizationName":"vSAMP12 1",
+ "modelCustomizationId":"c539433a-84a6-4082-a12e-5c9b00c3b960"
+ },
+ "cloudConfiguration":{
+ "lcpCloudRegionId":"mdt1",
+ "tenantId":"88a6ca3ee0394ade9403f075db23167e"
+ },
+ "requestInfo":{
+ "source":"source",
+ "suppressRollback":false,
+ "requestorId":"xxxxxx"
+ },
+ "requestParameters":{
+ "rebuildVolumeGroups":false
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+{
+ "requestDetails":{
+ "modelInfo":{
+ "modelType":"vnf",
+ "modelInvariantId":"ff5256d1-5a33-55df-13ab-12abad84e7ff",
+ "modelVersionId":"254583ad-b38c-498b-bdbd-b8de5e07541b",
+ "modelName":"vSAMP12",
+ "modelVersion":"2.0"
+ },
+ "cloudConfiguration":{
+ "lcpCloudRegionId":"mdt1",
+ "tenantId":"88a6ca3ee0394ade9403f075db23167e"
+ },
+ "requestInfo":{
+ "source":"source",
+ "suppressRollback":false,
+ "requestorId":"xxxxxx"
+ },
+ "requestParameters":{
+ "rebuildVolumeGroups":false
+ }
+ }
+}
\ No newline at end of file
},
"requestInfo": {
"source": "VID",
- "requestorId": "az2016"
+ "requestorId": "xxxxxx"
}
}
}
--- /dev/null
+{
+ "requestDetails":{
+ "modelInfo":{
+ "modelType":"vnf",
+ "modelInvariantId":"ff5256d1-5a33-55df-13ab-12abad84e7ff",
+ "modelVersionId":"254583ad-b38c-498b-bdbd-b8de5e07541b",
+ "modelName":"vSAMP12",
+ "modelVersion":"2.0",
+ "modelCustomizationName":"vSAMP12 1",
+ "modelCustomizationId":"c539433a-84a6-4082-a12e-5c9b00c3b960"
+ },
+ "cloudConfiguration":{
+ "lcpCloudRegionId":"mdt1",
+ "tenantId":"88a6ca3ee0394ade9403f075db23167e"
+ },
+ "requestInfo":{
+ "source":"source",
+ "suppressRollback":false,
+ "requestorId":"xxxxxx"
+ },
+ "requestParameters":{
+ "rebuildVolumeGroups":false
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+{
+ "id": 21,
+ "nfRole": "GR-API-DEFAULT",
+ "paramXsd": "",
+ "vfModuleId": "1882934",
+ "action": "recreateInstance",
+ "description": "gr-api-default for vnf recreate",
+ "orchestrationUri": "/mso/async/services/WorkflowActionBB",
+ "recipeTimeout": 180,
+ "serviceType": "GR-API-DEFAULT",
+ "created": "2016-06-03T04:44:10.000+0000",
+ "_links": {
+ "self": {
+ "href": "http://localhost:8090/vnfRecipe/21"
+ },
+ "vnfRecipe": {
+ "href": "http://localhost:8090/vnfRecipe/21"
+ }
+ }
+}
\ No newline at end of file