2 * ============LICENSE_START=======================================================
4 * ================================================================================
5 * Copyright (C) 2017-2019 AT&T Intellectual Property. All rights reserved.
6 * ================================================================================
7 * Copyright (C) 2017 Amdocs
8 * ================================================================================
9 * Modifications Copyright (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
15 * http://www.apache.org/licenses/LICENSE-2.0
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=========================================================
25 package org.onap.appc.requesthandler.impl;
27 import com.att.eelf.i18n.EELFResourceManager;
28 import com.fasterxml.jackson.databind.JsonNode;
29 import com.fasterxml.jackson.databind.ObjectMapper;
30 import com.fasterxml.jackson.databind.node.ObjectNode;
32 import org.apache.commons.io.IOUtils;
33 import org.apache.commons.lang.ObjectUtils;
34 import org.apache.commons.lang.StringUtils;
35 import org.apache.http.HttpResponse;
36 import org.onap.appc.domainmodel.lcm.Flags;
37 import org.onap.appc.domainmodel.lcm.RequestContext;
38 import org.onap.appc.domainmodel.lcm.RuntimeContext;
39 import org.onap.appc.domainmodel.lcm.TransactionRecord;
40 import org.onap.appc.domainmodel.lcm.VNFContext;
41 import org.onap.appc.domainmodel.lcm.VNFOperation;
42 import org.onap.appc.exceptions.APPCException;
43 import org.onap.appc.executor.objects.LCMCommandStatus;
44 import org.onap.appc.executor.objects.Params;
45 import org.onap.appc.i18n.Msg;
46 import org.onap.appc.lockmanager.api.LockException;
47 import org.onap.appc.lockmanager.api.LockManager;
48 import org.onap.appc.logging.LoggingConstants;
49 import org.onap.appc.logging.LoggingUtils;
50 import org.onap.appc.requesthandler.exceptions.DGWorkflowNotFoundException;
51 import org.onap.appc.requesthandler.exceptions.LCMOperationsDisabledException;
52 import org.onap.appc.requesthandler.exceptions.MissingVNFDataInAAIException;
53 import org.onap.appc.requesthandler.exceptions.RequestValidationException;
54 import org.onap.appc.requesthandler.exceptions.VNFNotFoundException;
55 import org.onap.appc.requesthandler.exceptions.WorkflowNotFoundException;
56 import org.onap.appc.requesthandler.model.ActionIdentifierModel;
57 import org.onap.appc.requesthandler.model.Input;
58 import org.onap.appc.requesthandler.model.Request;
59 import org.onap.appc.requesthandler.model.RequestData;
60 import org.onap.appc.requesthandler.model.RequestModel;
61 import org.onap.appc.requesthandler.model.ScopeOverlapModel;
62 import org.onap.appc.rest.client.RestClientInvoker;
63 import org.onap.appc.validationpolicy.RequestValidationPolicy;
64 import org.onap.appc.validationpolicy.executors.RuleExecutor;
65 import org.onap.appc.validationpolicy.objects.RuleResult;
66 import org.onap.appc.workflow.WorkFlowManager;
67 import org.onap.appc.workflow.objects.WorkflowExistsOutput;
68 import org.onap.appc.workflow.objects.WorkflowRequest;
69 import org.onap.ccsdk.sli.core.sli.SvcLogicContext;
70 import org.onap.ccsdk.sli.core.sli.SvcLogicException;
71 import org.onap.ccsdk.sli.core.sli.SvcLogicResource;
72 import org.onap.ccsdk.sli.adaptors.aai.AAIService;
73 import org.osgi.framework.BundleContext;
74 import org.osgi.framework.FrameworkUtil;
75 import org.osgi.framework.ServiceReference;
77 import java.io.IOException;
78 import java.net.MalformedURLException;
80 import java.util.ArrayList;
81 import java.util.Date;
82 import java.util.List;
83 import java.util.Properties;
84 import java.util.stream.Collectors;
86 public class RequestValidatorImpl extends AbstractRequestValidatorImpl {
88 private WorkFlowManager workflowManager;
89 private LockManager lockManager;
90 private AAIService aaiService;
91 private RequestValidationPolicy requestValidationPolicy;
92 private RestClientInvoker client;
94 private int transactionWindowInterval=0;
96 static final String SCOPE_OVERLAP_ENDPOINT = "appc.LCM.scopeOverlap.endpoint";
97 static final String ODL_USER = "appc.LCM.provider.user";
98 static final String ODL_PASS = "appc.LCM.provider.pass";
99 static final String TRANSACTION_WINDOW_HOURS = "appc.inProgressTransactionWindow.hours";
101 public void initialize() throws APPCException {
102 logger.info("Initializing RequestValidatorImpl.");
103 String endpoint = null;
106 String transactionWindow = null;
107 Properties properties = configuration.getProperties();
108 if (properties != null) {
109 endpoint = properties.getProperty(SCOPE_OVERLAP_ENDPOINT);
110 user = properties.getProperty(ODL_USER);
111 pass = properties.getProperty(ODL_PASS);
112 transactionWindow = properties.getProperty(TRANSACTION_WINDOW_HOURS);
114 if (endpoint == null) {
115 String message = "End point is not defined for scope over lap service in appc.properties.";
116 logger.error(message);
117 // TODO throw following exception (and remove the "return") when
118 // entry in appc.properties file is made for scope overlap service
120 // and remove @Ignore in unit tests:
121 // testInitializeWithNullConfigProps,
122 // testInitializeWithoutEndpointProp
123 // throw new APPCException(message);
127 if (StringUtils.isNotBlank(transactionWindow)) {
128 logger.info("RequestValidatorImpl::TransactionWindow defined !!!");
130 transactionWindowInterval = Integer.parseInt(transactionWindow);
132 catch (NumberFormatException e) {
133 String message = "RequestValidatorImpl:::Error parsing transaction window interval!";
134 logger.error(message, e);
135 throw new APPCException(message);
140 URL url = new URL(endpoint);
141 client = new RestClientInvoker(url);
142 client.setAuthentication(user, pass);
143 path = url.getPath();
145 } catch (MalformedURLException e) {
146 String message = "Invalid endpoint " + endpoint;
147 logger.error(message, e);
148 // TODO throw following exception when entry in appc.properties file
149 // is made for scope overlap service endpoint
150 // and remove @Ignore in unit test:
151 // testInitializeWithMalFormatEndpoint
152 // throw new APPCException(message);
156 private void getAAIservice() {
157 BundleContext bctx = FrameworkUtil.getBundle(AAIService.class).getBundleContext();
158 // Get AAIadapter reference
159 ServiceReference sref = bctx.getServiceReference(AAIService.class.getName());
161 logger.info("AAIService from bundlecontext");
162 aaiService = (AAIService) bctx.getService(sref);
165 logger.error("Cannot find service reference for org.onap.ccsdk.sli.adaptors.aai.AAIService");
170 public void setLockManager(LockManager lockManager) {
171 this.lockManager = lockManager;
174 public void setClient(RestClientInvoker client) {
175 this.client = client;
178 public void setWorkflowManager(WorkFlowManager workflowManager) {
179 this.workflowManager = workflowManager;
182 public void setRequestValidationPolicy(RequestValidationPolicy requestValidationPolicy) {
183 this.requestValidationPolicy = requestValidationPolicy;
186 public void validateRequest(RuntimeContext runtimeContext) throws Exception {
187 if (logger.isTraceEnabled()) {
189 "Entering to validateRequest with RequestHandlerInput = " + ObjectUtils.toString(runtimeContext));
191 if (!lcmStateManager.isLCMOperationEnabled()) {
192 LoggingUtils.logErrorMessage(LoggingConstants.TargetNames.REQUEST_VALIDATOR,
193 EELFResourceManager.format(Msg.LCM_OPERATIONS_DISABLED), this.getClass().getCanonicalName());
194 throw new LCMOperationsDisabledException("APPC LCM operations have been administratively disabled");
198 validateInput(runtimeContext);
199 String vnfId = runtimeContext.getRequestContext().getActionIdentifiers().getVnfId();
200 VNFContext vnfContext = queryAAI(vnfId);
201 runtimeContext.setVnfContext(vnfContext);
202 runtimeContext.getTransactionRecord().setTargetType(vnfContext.getType());
204 VNFOperation operation = runtimeContext.getRequestContext().getAction();
205 if (operation.isBuiltIn()) {
209 validateVNFLock(runtimeContext);
210 checkWorkflowExists(vnfContext, runtimeContext.getRequestContext());
212 if (runtimeContext.getRequestContext().getCommonHeader().getFlags().isForce()) {
216 List<TransactionRecord> inProgressTransactionsAll = transactionRecorder
217 .getInProgressRequests(runtimeContext.getTransactionRecord(), 0);
218 List<TransactionRecord> inProgressTransactions = transactionRecorder
219 .getInProgressRequests(runtimeContext.getTransactionRecord(), transactionWindowInterval);
220 long inProgressTransactionsAllCount = inProgressTransactionsAll == null ? 0 : inProgressTransactionsAll.size();
221 long inProgressTransactionsRelevant = inProgressTransactions == null ? 0 : inProgressTransactions.size();
222 logger.debug("In progress requests " + inProgressTransactions.toString());
224 if (inProgressTransactionsRelevant == 0) { //No need for further checks
228 if (logger.isInfoEnabled()) {
229 logger.info(logInProgressTransactions(inProgressTransactions, inProgressTransactionsAllCount,
230 inProgressTransactionsRelevant));
233 Long exclusiveRequestCount = inProgressTransactions.stream()
234 .filter(record -> record.getMode().equals(Flags.Mode.EXCLUSIVE.name())).count();
235 if (exclusiveRequestCount > 0) {
236 String message = "Request rejected - Existing request in progress with exclusive mode for VNF: " + vnfId;
237 throw new RequestValidationException(message, LCMCommandStatus.EXLCUSIVE_REQUEST_IN_PROGRESS,
238 (new Params()).addParam("errorMsg", message));
241 Boolean scopeOverLap = checkScopeOverLap(runtimeContext.getRequestContext(), inProgressTransactions);
242 logger.debug("Scope overlap " + scopeOverLap);
244 List<VNFOperation> inProgressActions = inProgressTransactions.stream().map(TransactionRecord::getOperation)
245 .collect(Collectors.toList());
247 RuleExecutor ruleExecutor = requestValidationPolicy.getInProgressRuleExecutor();
248 RuleResult result = ruleExecutor.executeRule(operation.name(), inProgressActions);
249 logger.debug("Policy validation result " + result);
250 if (RuleResult.REJECT == result) {
251 String message = "Request rejected as per the request validation policy";
252 throw new RequestValidationException(message, LCMCommandStatus.POLICY_VALIDATION_FAILURE,
253 (new Params()).addParam("errorMsg", message));
258 private void validateVNFLock(RuntimeContext runtimeContext) throws LockException {
259 String vnfId = runtimeContext.getRequestContext().getActionIdentifiers().getVnfId();
260 String lockOwner = lockManager.getLockOwner(vnfId);
261 logger.debug("Current lock owner is " + lockOwner + " for vnf " + vnfId);
262 if (lockOwner != null
263 && !lockOwner.equals(runtimeContext.getRequestContext().getCommonHeader().getRequestId())) {
264 String message = new StringBuilder("VNF : ").append(vnfId).append(" is locked by request id :")
265 .append(lockOwner).toString();
266 throw new LockException(message);
271 * Do not remove this method, this is actual method for invoking scope
272 * overlap service When the service becomes available, its dummy
273 * implementation should be removed and this implementation should be used.
275 private Boolean checkScopeOverLap(RequestContext requestContext, List<TransactionRecord> inProgressTransactions)
276 throws APPCException {
277 Boolean scopeOverlap = null;
279 JsonNode inputJSON = convertToJsonInput(requestContext, inProgressTransactions);
280 logger.debug("Input to scope overlap service " + inputJSON.toString());
281 HttpResponse response = client.doPost(path, inputJSON.toString());
282 int httpCode = response.getStatusLine().getStatusCode();
283 if (httpCode < 200 || httpCode >= 300) {
284 logger.debug("http error code " + httpCode);
285 throw new APPCException("Exception occurred on invoking check scope overlap api");
287 String respBody = IOUtils.toString(response.getEntity().getContent());
288 logger.debug("response body " + respBody);
289 ObjectMapper mapper = new ObjectMapper();
290 JsonNode outputJSON = mapper.readTree(respBody);
291 scopeOverlap = readScopeOverlap(outputJSON);
292 } catch (IOException e) {
293 String message = "Error accessing check scope overlap service";
294 logger.error(message, e);
295 throw new APPCException(message);
300 private Boolean readScopeOverlap(JsonNode outputJSON) throws APPCException {
301 logger.debug("Response JSON " + outputJSON.toString());
302 String message = "Error reading response JSON from scope overlap service ";
303 JsonNode outputNode = outputJSON.get("output");
304 JsonNode statusNode = outputNode.get("status");
305 if (statusNode == null) {
306 throw new APPCException(message);
309 if (null == statusNode.get("message"))
310 throw new APPCException(message + "Status message is null.");
311 String responseStatusMessage = statusNode.get("message").textValue();
313 if (null == statusNode.get("code"))
314 throw new APPCException(message + "Status code is null.");
315 String code = statusNode.get("code").textValue();
317 JsonNode responseInfoNode = outputNode.get("response-info");
318 JsonNode blockNode = responseInfoNode.get("block");
319 String requestOverlapValue = null;
321 if (null != blockNode)
322 requestOverlapValue = blockNode.textValue();
324 logger.debug("Response JSON " + requestOverlapValue);
326 if (code.equals("400")) {
327 if(null==requestOverlapValue)
328 throw new APPCException("Response code is 400 but requestOverlapValue is null ");
329 if (requestOverlapValue.contains("true")) {
331 } else if (requestOverlapValue.contains("false")) {
332 return Boolean.FALSE;
334 throw new APPCException(
335 message + "requestOverlap value is other than True and False, it is " + requestOverlapValue);
337 } else if (code.equals("401")) {
338 throw new APPCException(message + responseStatusMessage);
340 throw new APPCException(message + "Status code is neither 400 nor 401, it is " + code);
345 private JsonNode convertToJsonInput(RequestContext requestContext, List<TransactionRecord> inProgressTransactions) {
346 ObjectMapper objectMapper = new ObjectMapper();
347 ScopeOverlapModel scopeOverlapModel = getScopeOverlapModel(requestContext, inProgressTransactions);
348 // Added for change in interface for action level
350 JsonNode jsonObject = objectMapper.valueToTree(scopeOverlapModel);
355 public ScopeOverlapModel getScopeOverlapModel(RequestContext requestContext,
356 List<TransactionRecord> inProgressTransactions) {
357 ScopeOverlapModel scopeOverlapModel = new ScopeOverlapModel();
358 RequestData requestData = new RequestData();
360 List<RequestModel> inProgressRequests = new ArrayList<>();
361 RequestModel requestModel = new RequestModel();
362 ActionIdentifierModel actionIdentifierModel = extractActionIdentifierModel(requestContext);
363 requestModel.setAction(requestContext.getAction().toString());
364 requestModel.setActionIdentifier(actionIdentifierModel);
366 if (requestModel.getActionIdentifier().getVnfId() != null) {
367 requestData.setVnfID(requestModel.getActionIdentifier().getVnfId());
370 if (requestModel.getActionIdentifier().getVnfcName() != null
371 || requestModel.getActionIdentifier().getVfModuleId() != null
372 || requestModel.getActionIdentifier().getVserverId() != null) {
374 requestModel.getActionIdentifier().setVnfId(null);
377 requestData.setCurrentRequest(requestModel);
379 for (TransactionRecord record : inProgressTransactions) {
380 RequestModel request = new RequestModel();
381 ActionIdentifierModel actionIdentifier = new ActionIdentifierModel();
383 actionIdentifier.setServiceInstanceId(record.getServiceInstanceId());
384 actionIdentifier.setVnfId(record.getTargetId());
385 actionIdentifier.setVnfcName(record.getVnfcName());
386 actionIdentifier.setVfModuleId(record.getVfModuleId());
387 actionIdentifier.setVserverId(record.getVserverId());
389 request.setAction(record.getOperation().name());
390 request.setActionIdentifier(actionIdentifier);
391 if (request.getActionIdentifier().getVnfcName() != null
392 || request.getActionIdentifier().getVfModuleId() != null
393 || request.getActionIdentifier().getVserverId() != null) {
395 request.getActionIdentifier().setVnfId(null);
397 request.setTargetId(record.getTargetId());
398 inProgressRequests.add(request);
401 requestData.setInProgressRequests(inProgressRequests);
403 Request request = new Request();
405 Date date = new Date();
406 request.setRequestID("RequestId-ScopeOverlap " + date.toString());
407 request.setAction("isScopeOverlap");
408 ObjectMapper objectMapper = new ObjectMapper();
409 JsonNode json = objectMapper.valueToTree(requestData);
410 request.setRequestData(json.toString());
411 Input input = new Input();
412 input.setRequest(request);
413 scopeOverlapModel.setInput(input);
415 return scopeOverlapModel;
418 private ActionIdentifierModel extractActionIdentifierModel(RequestContext requestContext) {
419 ActionIdentifierModel actionIdentifierModel = new ActionIdentifierModel();
420 actionIdentifierModel.setServiceInstanceId(requestContext.getActionIdentifiers().getServiceInstanceId());
421 actionIdentifierModel.setVnfId(requestContext.getActionIdentifiers().getVnfId());
422 actionIdentifierModel.setVnfcName(requestContext.getActionIdentifiers().getVnfcName());
423 actionIdentifierModel.setVfModuleId(requestContext.getActionIdentifiers().getVfModuleId());
424 actionIdentifierModel.setVserverId(requestContext.getActionIdentifiers().getVserverId());
425 return actionIdentifierModel;
428 private VNFContext queryAAI(String vnfId) throws VNFNotFoundException, MissingVNFDataInAAIException {
429 SvcLogicContext ctx = new SvcLogicContext();
430 ctx = getVnfdata(vnfId, "vnf", ctx);
432 VNFContext vnfContext = new VNFContext();
433 populateVnfContext(vnfContext, ctx);
438 private SvcLogicContext getVnfdata(String vnf_id, String prefix, SvcLogicContext ctx) throws VNFNotFoundException {
439 if (logger.isTraceEnabled()) {
440 logger.trace("Entering to getVnfdata with vnfid = " + ObjectUtils.toString(vnf_id) + ", prefix = "
441 + ObjectUtils.toString(prefix) + ", SvcLogicContext" + ObjectUtils.toString(ctx));
443 String key = "vnf-id = '" + vnf_id + "'";
444 logger.debug("inside getVnfdata=== " + key);
446 Date beginTimestamp = new Date();
447 SvcLogicResource.QueryStatus response = aaiService.query("generic-vnf", false, null, key, prefix, null,
449 Date endTimestamp = new Date();
450 String status = SvcLogicResource.QueryStatus.SUCCESS.equals(response)
451 ? LoggingConstants.StatusCodes.COMPLETE : LoggingConstants.StatusCodes.ERROR;
452 LoggingUtils.logMetricsMessage(beginTimestamp.toInstant(), endTimestamp.toInstant(),
453 LoggingConstants.TargetNames.AAI, LoggingConstants.TargetServiceNames.AAIServiceNames.QUERY, status,
454 "", response.name(), this.getClass().getCanonicalName());
455 if (SvcLogicResource.QueryStatus.NOT_FOUND.equals(response)) {
456 throw new VNFNotFoundException("VNF not found for vnf_id = " + vnf_id, vnf_id);
457 } else if (SvcLogicResource.QueryStatus.FAILURE.equals(response)) {
458 throw new RuntimeException("Error Querying AAI with vnfID = " + vnf_id);
460 logger.info("AAIResponse: " + response.toString());
461 } catch (SvcLogicException e) {
463 LoggingUtils.logErrorMessage(LoggingConstants.TargetServiceNames.AAIServiceNames.GET_VNF_DATA,
464 "Error in getVnfdata" + e, this.getClass().getCanonicalName());
466 throw new RuntimeException(e);
468 if (logger.isTraceEnabled()) {
469 logger.trace("Exiting from getVnfdata with (SvcLogicContext = " + ObjectUtils.toString(ctx) + ")");
474 private void populateVnfContext(VNFContext vnfContext, SvcLogicContext ctx) throws MissingVNFDataInAAIException {
475 String vnfType = ctx.getAttribute("vnf.vnf-type");
476 if (StringUtils.isEmpty(vnfType)) {
477 throw new MissingVNFDataInAAIException("vnf-type", ctx.getAttribute("vnf.vnf-id"));
479 vnfContext.setType(vnfType);
480 vnfContext.setId(ctx.getAttribute("vnf.vnf-id"));
483 private void checkWorkflowExists(VNFContext vnfContext, RequestContext requestContext)
484 throws WorkflowNotFoundException, DGWorkflowNotFoundException {
485 WorkflowExistsOutput workflowExistsOutput = workflowManager
486 .workflowExists(getWorkflowQueryParams(vnfContext, requestContext));
487 if (!workflowExistsOutput.isMappingExist()) {
488 if (logger.isDebugEnabled()) {
489 logger.debug("WorkflowManager : Workflow mapping not found for vnfType = " + vnfContext.getType()
490 + ", version = " + vnfContext.getVersion() + ", command = "
491 + requestContext.getAction().name());
493 LoggingUtils.logErrorMessage(LoggingConstants.TargetNames.WORKFLOW_MANAGER, EELFResourceManager
494 .format(Msg.APPC_WORKFLOW_NOT_FOUND, vnfContext.getType(), requestContext.getAction().name()),
495 this.getClass().getCanonicalName());
496 throw new WorkflowNotFoundException(
497 "Workflow mapping not found for vnfType = " + vnfContext.getType() + ", command = "
498 + requestContext.getAction().name(),
499 vnfContext.getType(), requestContext.getAction().name());
501 if (!workflowExistsOutput.isDgExist()) {
502 if (logger.isDebugEnabled()) {
503 logger.debug("WorkflowManager : DG Workflow not found for vnfType = " + vnfContext.getType()
504 + ", version = " + vnfContext.getVersion() + ", command = " + requestContext.getAction().name()
505 + " " + workflowExistsOutput);
507 LoggingUtils.logErrorMessage(LoggingConstants.TargetNames.WORKFLOW_MANAGER, EELFResourceManager
508 .format(Msg.APPC_WORKFLOW_NOT_FOUND, vnfContext.getType(), requestContext.getAction().name()),
509 this.getClass().getCanonicalName());
510 throw new DGWorkflowNotFoundException(
511 "Workflow not found for vnfType = " + vnfContext.getType() + ", command = "
512 + requestContext.getAction().name(),
513 workflowExistsOutput.getWorkflowModule(), workflowExistsOutput.getWorkflowName(),
514 workflowExistsOutput.getWorkflowVersion(), vnfContext.getType(), requestContext.getAction().name());
518 private WorkflowRequest getWorkflowQueryParams(VNFContext vnfContext, RequestContext requestContext) {
519 WorkflowRequest workflowRequest = new WorkflowRequest();
520 workflowRequest.setVnfContext(vnfContext);
521 workflowRequest.setRequestContext(requestContext);
522 if (logger.isTraceEnabled()) {
523 logger.trace("Exiting from getWorkflowQueryParams with (WorkflowRequest = "
524 + ObjectUtils.toString(workflowRequest) + ")");
526 return workflowRequest;
529 public String logInProgressTransactions(List<TransactionRecord> inProgressTransactions,
530 long inProgressTransactionsAllCount, long inProgressTransactionsRelevant) {
531 if (inProgressTransactionsAllCount > inProgressTransactionsRelevant) {
532 logger.info("Found Stale Transactions! Ignoring Stale Transactions for target, only considering "
533 + "transactions within the last " + transactionWindowInterval + " hours as transactions in-progress");
536 for (TransactionRecord tr: inProgressTransactions) {
537 logMsg = ("In Progress transaction for Target ID - " + tr.getTargetId()
538 + " in state " + tr.getRequestState()
539 + " with Start time " + tr.getStartTime().toString()
540 + " for more than configurable time period " + transactionWindowInterval
541 + " hours [transaction details - Request ID - " + tr.getTransactionId()
542 + ", Service Instance Id - " + tr.getServiceInstanceId()
543 + ", Vserver_id - " + tr.getVserverId()
544 + ", VNFC_name - " + tr.getVnfcName()
545 + ", VF module Id - " + tr.getVfModuleId()
546 + " Start time " + tr.getStartTime().toString()