Write async instantiations request-summary to DB
[vid.git] / vid-app-common / src / main / java / org / onap / vid / services / AsyncInstantiationBusinessLogicImpl.java
1 /*-
2  * ============LICENSE_START=======================================================
3  * VID
4  * ================================================================================
5  * Copyright (C) 2017 - 2019 AT&T Intellectual Property. All rights reserved.
6  * ================================================================================
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  * 
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  * 
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  * ============LICENSE_END=========================================================
19  */
20
21 package org.onap.vid.services;
22
23 import static com.google.common.collect.Streams.concat;
24 import static java.util.function.Function.identity;
25 import static java.util.stream.Collectors.counting;
26 import static java.util.stream.Collectors.groupingBy;
27 import static java.util.stream.Stream.empty;
28 import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
29 import static org.apache.commons.lang3.StringUtils.isNotEmpty;
30 import static org.onap.vid.controller.MsoController.SVC_INSTANCE_ID;
31 import static org.onap.vid.controller.MsoController.VNF_INSTANCE_ID;
32 import static org.onap.vid.utils.KotlinUtilsKt.JACKSON_OBJECT_MAPPER;
33
34 import com.google.common.collect.ImmutableMap;
35 import java.io.IOException;
36 import java.time.ZonedDateTime;
37 import java.util.ArrayList;
38 import java.util.Calendar;
39 import java.util.Date;
40 import java.util.List;
41 import java.util.Map;
42 import java.util.Objects;
43 import java.util.UUID;
44 import java.util.function.Consumer;
45 import java.util.stream.Stream;
46 import org.hibernate.SessionFactory;
47 import org.onap.portalsdk.core.logging.logic.EELFLoggerDelegate;
48 import org.onap.vid.aai.AaiClientInterface;
49 import org.onap.vid.aai.model.ResourceType;
50 import org.onap.vid.dal.AsyncInstantiationRepository;
51 import org.onap.vid.exceptions.DbFailureUncheckedException;
52 import org.onap.vid.exceptions.GenericUncheckedException;
53 import org.onap.vid.exceptions.MaxRetriesException;
54 import org.onap.vid.exceptions.OperationNotAllowedException;
55 import org.onap.vid.job.Job;
56 import org.onap.vid.job.Job.JobStatus;
57 import org.onap.vid.job.JobAdapter;
58 import org.onap.vid.job.JobType;
59 import org.onap.vid.job.JobsBrokerService;
60 import org.onap.vid.job.impl.JobSharedData;
61 import org.onap.vid.model.Action;
62 import org.onap.vid.model.NameCounter;
63 import org.onap.vid.model.ResourceInfo;
64 import org.onap.vid.model.ServiceInfo;
65 import org.onap.vid.model.ServiceInfo.ServiceAction;
66 import org.onap.vid.model.serviceInstantiation.BaseResource;
67 import org.onap.vid.model.serviceInstantiation.ServiceInstantiation;
68 import org.onap.vid.model.serviceInstantiation.VfModule;
69 import org.onap.vid.mso.MsoBusinessLogicImpl;
70 import org.onap.vid.mso.MsoProperties;
71 import org.onap.vid.mso.MsoUtil;
72 import org.onap.vid.mso.RestObject;
73 import org.onap.vid.mso.model.ModelInfo;
74 import org.onap.vid.mso.rest.AsyncRequestStatus;
75 import org.onap.vid.mso.rest.RequestStatus;
76 import org.onap.vid.properties.Features;
77 import org.onap.vid.utils.DaoUtils;
78 import org.onap.vid.utils.TimeUtils;
79 import org.springframework.beans.factory.annotation.Autowired;
80 import org.springframework.stereotype.Service;
81 import org.togglz.core.manager.FeatureManager;
82
83 @Service
84 public class AsyncInstantiationBusinessLogicImpl implements
85         AsyncInstantiationBusinessLogic {
86
87     private static final int MAX_RETRIES_GETTING_COUNTER = 100;
88     private static final int MAX_RETRIES_GETTING_FREE_NAME_FROM_AAI = 10000;
89     public static final String NAME_FOR_CHECK_AAI_STATUS = "NAME_FOR_CHECK_AAI_STATUS";
90
91     private final JobAdapter jobAdapter;
92
93     private final JobsBrokerService jobService;
94
95     private final CloudOwnerService cloudOwnerService;
96
97     private final AsyncInstantiationRepository asyncInstantiationRepository;
98
99     private SessionFactory sessionFactory;
100
101     private AaiClientInterface aaiClient;
102
103     private FeatureManager featureManager;
104
105     private AuditService auditService;
106
107
108     private int maxRetriesGettingFreeNameFromAai = MAX_RETRIES_GETTING_FREE_NAME_FROM_AAI;
109
110     private static final EELFLoggerDelegate logger = EELFLoggerDelegate.getLogger(AsyncInstantiationBusinessLogicImpl.class);
111     private Map<String, JobStatus> msoStateToJobStatusMap = ImmutableMap.<String, JobStatus>builder()
112             .put("inprogress", JobStatus.IN_PROGRESS)
113             .put("failed", JobStatus.FAILED)
114             .put("pause", JobStatus.PAUSE)
115             .put("paused", JobStatus.PAUSE)
116             .put("complete", JobStatus.COMPLETED)
117             .put("pending", JobStatus.IN_PROGRESS)
118             .put("pendingmanualtask", JobStatus.PAUSE)
119             .put("unlocked", JobStatus.IN_PROGRESS)
120             .put("aborted", JobStatus.COMPLETED_WITH_ERRORS)
121             .put("rolledback", JobStatus.FAILED)
122             .put("rolledbacktoassigned", JobStatus.FAILED)
123             .put("rolledbacktocreated", JobStatus.FAILED)
124             .build();
125
126
127     @Autowired
128     public AsyncInstantiationBusinessLogicImpl(JobAdapter jobAdapter,
129                                                JobsBrokerService jobService,
130                                                SessionFactory sessionFactory,
131                                                AaiClientInterface aaiClient,
132                                                FeatureManager featureManager,
133                                                CloudOwnerService cloudOwnerService, AsyncInstantiationRepository asyncInstantiationRepository,
134                                                AuditService auditService) {
135         this.jobAdapter = jobAdapter;
136         this.jobService = jobService;
137         this.sessionFactory = sessionFactory;
138         this.aaiClient = aaiClient;
139         this.featureManager = featureManager;
140         this.cloudOwnerService = cloudOwnerService;
141         this.asyncInstantiationRepository = asyncInstantiationRepository;
142         this.auditService = auditService;
143     }
144
145     @Override
146     public List<ServiceInfo> getAllServicesInfo() {
147         return asyncInstantiationRepository.getAllServicesInfo();
148     }
149
150     JobType getJobType(ServiceInstantiation request) {
151         if (request.isALaCarte()) {
152             switch (defaultIfNull(request.getAction(), Action.Create)) {
153                 case Delete:
154                     return JobType.ALaCarteService;
155                 case None:
156                     return JobType.ALaCarteService;
157                 default:
158                     return JobType.ALaCarteServiceInstantiation;
159             }
160         } else {
161             return JobType.MacroServiceInstantiation;
162         }
163     }
164
165     @Override
166     public List<UUID> pushBulkJob(ServiceInstantiation request, String userId) {
167
168         List<UUID> uuids = new ArrayList<>();
169         Date createdBulkDate = Calendar.getInstance().getTime();
170         int bulkSize = request.getBulkSize();
171         UUID templateId = UUID.randomUUID();
172         for (int i = 0; i < bulkSize; i++) {
173             ServiceInstantiation requestPerJob = prepareServiceToBeUnique(request);
174             ServiceInfo.ServiceAction serviceAction = getAction(requestPerJob);
175             JobType jobType = getJobType(requestPerJob);
176             final String optimisticUniqueServiceInstanceName = bulkSize>1 ? //only bulk with more than 1 service need to get multiple names
177                     getOptimisticUniqueServiceInstanceName(requestPerJob.getInstanceName()) : requestPerJob.getInstanceName();
178             Job job = jobAdapter.createServiceInstantiationJob(jobType, requestPerJob, templateId, userId, request.getTestApi(), optimisticUniqueServiceInstanceName, i);
179             UUID jobId = job.getUuid();
180
181             asyncInstantiationRepository.saveServiceInfo(createServiceInfo(
182                 userId, requestPerJob, jobId, templateId, createdBulkDate,
183                 optimisticUniqueServiceInstanceName, serviceAction,
184                 requestSummaryOrNull(requestPerJob)));
185             asyncInstantiationRepository.addJobRequest(jobId, requestPerJob);
186             auditService.auditVidStatus(jobId, job.getStatus());
187             uuids.add(jobId);
188
189             jobService.add(job);
190         }
191         return uuids;
192     }
193
194     Map<String, Long> requestSummaryOrNull(ServiceInstantiation request) {
195         if (!featureManager.isActive(Features.FLAG_2004_CREATE_ANOTHER_INSTANCE_FROM_TEMPLATE)) {
196             return null;
197         }
198
199         if (getAction(request) != ServiceAction.INSTANTIATE) {
200             return null;
201         }
202
203         return summarizedChildrenMap(request);
204     }
205
206     public Map<String, Long> summarizedChildrenMap(ServiceInstantiation serviceInstantiation){
207         Stream<String> existingTypesStream =
208             allDeepChildResources(serviceInstantiation)
209                 .map(this::getModelTypes)
210                 .flatMap(identity());
211
212         Map<String, Long> existingTypesCounters =
213             existingTypesStream.collect(groupingBy(identity(), counting()));
214
215         return existingTypesCounters;
216     }
217
218     private Stream<String> getModelTypes(BaseResource resource) {
219         return concat(
220             Stream.of(resource)
221                 .map(BaseResource::getModelInfo)
222                 .filter(Objects::nonNull)
223                 .map(ModelInfo::getModelType),
224             streamVolumeGroups(resource)
225         );
226     }
227
228     private Stream<String> streamVolumeGroups(BaseResource resource) {
229         return hasVolumeGroup(resource)
230             ? Stream.of("volumeGroup")
231             : empty();
232     }
233
234     private boolean hasVolumeGroup(BaseResource resource) {
235         return
236             resource instanceof VfModule
237             && isNotEmpty(((VfModule) resource).getVolumeGroupInstanceName());
238     }
239
240     private Stream<BaseResource> allDeepChildResources(BaseResource resource) {
241         return resource
242             .getChildren()
243             .stream()
244             .map(it -> concat(Stream.of(it), allDeepChildResources(it)))
245             .flatMap(identity());
246     }
247
248     private ServiceInfo.ServiceAction getAction(ServiceInstantiation request) {
249         if (request.getAction() == null) {
250             //throw new GenericUncheckedException("Required 'action' field not provided at service level");
251             return Action.Create.getServiceAction();
252         }
253         return request.getAction().getServiceAction();
254     }
255
256
257     private String getOptimisticUniqueServiceInstanceName(String instanceName) {
258         return isNotEmpty(instanceName) ? getUniqueNameFromDbOnly(instanceName) : instanceName;
259     }
260
261     protected ServiceInfo createServiceInfo(String userId, ServiceInstantiation serviceInstantiation, UUID jobId,
262         UUID templateId, Date createdBulkDate, String optimisticUniqueServiceInstanceName,
263         ServiceInfo.ServiceAction serviceAction, Map<String, Long> requestSummary) {
264         return new ServiceInfo(
265                 userId,
266                 serviceInstantiation.isALaCarte(),
267                 Job.JobStatus.PENDING, serviceInstantiation.isPause(), jobId, templateId,
268                 serviceInstantiation.getOwningEntityId(),
269                 serviceInstantiation.getOwningEntityName(),
270                 serviceInstantiation.getProjectName(),
271                 serviceInstantiation.getAicZoneId(),
272                 serviceInstantiation.getAicZoneName(),
273                 serviceInstantiation.getTenantId(),
274                 serviceInstantiation.getTenantName(),
275                 serviceInstantiation.getLcpCloudRegionId(),
276                 null,
277                 serviceInstantiation.getSubscriptionServiceType(),
278                 serviceInstantiation.getSubscriberName(),
279                 serviceInstantiation.getGlobalSubscriberId(),
280                 serviceInstantiation.getInstanceId(),
281                 optimisticUniqueServiceInstanceName,
282                 serviceInstantiation.getModelInfo().getModelVersionId(),
283                 serviceInstantiation.getModelInfo().getModelName(),
284                 serviceInstantiation.getModelInfo().getModelVersion(),
285                 createdBulkDate,
286                 serviceAction,
287                 false,
288                 requestSummary);
289     }
290
291     @Override
292     public boolean isPartOfBulk(UUID jobId) {
293         if (jobId == null) {
294             return false;
295     }
296         ServiceInfo serviceInfo = asyncInstantiationRepository.getServiceInfoByJobId(jobId);
297         UUID templateId = serviceInfo.getTemplateId();
298         if (templateId != null) {
299             return getNumberOfJobsInBulk(templateId) > 1;
300     }
301         return false;
302
303     }
304
305     private int getNumberOfJobsInBulk(UUID templateId) {
306         String hqlSelectJob = "from JobDaoImpl where templateId = :templateId";
307         return DaoUtils.tryWithSessionAndTransaction(sessionFactory, session ->
308             session.createQuery(hqlSelectJob)
309                     .setText("templateId", templateId.toString())
310                     .list()
311                     .size()
312         );
313     }
314
315     @Override
316     public String getServiceInstantiationPath(ServiceInstantiation serviceInstantiationRequest) {
317         //in case pause flag is true - use assign , else - use create.
318         return MsoBusinessLogicImpl.validateEndpointPath(
319                 serviceInstantiationRequest.isPause() ?
320                         MsoProperties.MSO_REST_API_SERVICE_INSTANCE_ASSIGN : MsoProperties.MSO_RESTAPI_SERVICE_INSTANCE
321         );
322     }
323
324     @Override
325     public String getServiceDeletionPath(String serviceInstanceId) {
326         return MsoBusinessLogicImpl.validateEndpointPath( MsoProperties.MSO_RESTAPI_SERVICE_INSTANCE)  + "/" + serviceInstanceId;
327     }
328
329     @Override
330     public String getVnfInstantiationPath(String serviceInstanceId) {
331         return MsoBusinessLogicImpl.validateEndpointPath(MsoProperties.MSO_REST_API_VNF_INSTANCE).
332                 replaceFirst(SVC_INSTANCE_ID, serviceInstanceId);
333     }
334
335     @Override
336     public String getVnfDeletionPath(String serviceInstanceId, String vnfInstanceId) {
337         return (MsoBusinessLogicImpl.validateEndpointPath(MsoProperties.MSO_REST_API_VNF_INSTANCE)
338                 + '/' + vnfInstanceId)
339                 .replaceFirst(SVC_INSTANCE_ID, serviceInstanceId).replaceFirst(VNF_INSTANCE_ID, vnfInstanceId);
340     }
341
342     @Override
343     public String getNetworkInstantiationPath(String serviceInstanceId) {
344         return MsoBusinessLogicImpl.validateEndpointPath(MsoProperties.MSO_REST_API_NETWORK_INSTANCE).
345                 replaceFirst(SVC_INSTANCE_ID, serviceInstanceId);
346     }
347
348     @Override
349     public String getVfmoduleInstantiationPath(String serviceInstanceId, String vnfInstanceId) {
350         return MsoBusinessLogicImpl.validateEndpointPath(MsoProperties.MSO_REST_API_VF_MODULE_INSTANCE)
351                 .replaceFirst(SVC_INSTANCE_ID, serviceInstanceId)
352                 .replaceFirst(VNF_INSTANCE_ID, vnfInstanceId);
353     }
354
355     @Override
356     public String getVfModuleReplacePath(String serviceInstanceId, String vnfInstanceId, String vfModuleInstanceId)
357     {
358         return MsoBusinessLogicImpl.validateEndpointPath(MsoProperties.MSO_REST_API_VF_MODULE_INSTANCE)
359                 .replaceFirst(SVC_INSTANCE_ID, serviceInstanceId)
360                 .replaceFirst(VNF_INSTANCE_ID, vnfInstanceId)
361                 + "/" + vfModuleInstanceId
362                 + "/replace";
363     }
364
365     @Override
366     public String getVfModuleDeletePath(String serviceInstanceId, String vnfInstanceId, String vfModuleInstanceId) {
367         return MsoBusinessLogicImpl.validateEndpointPath(MsoProperties.MSO_REST_API_VF_MODULE_INSTANCE)
368                 .replaceFirst(SVC_INSTANCE_ID, serviceInstanceId)
369                 .replaceFirst(VNF_INSTANCE_ID, vnfInstanceId)
370                 + "/" + vfModuleInstanceId;
371     }
372
373     @Override
374     public String getVolumeGroupInstantiationPath(String serviceInstanceId, String vnfInstanceId) {
375         return MsoBusinessLogicImpl.validateEndpointPath(MsoProperties.MSO_REST_API_VOLUME_GROUP_INSTANCE)
376                 .replaceFirst(SVC_INSTANCE_ID, serviceInstanceId)
377                 .replaceFirst(VNF_INSTANCE_ID, vnfInstanceId);
378     }
379
380     @Override
381     public String getInstanceGroupInstantiationPath() {
382         return MsoBusinessLogicImpl.validateEndpointPath(MsoProperties.MSO_REST_API_INSTANCE_GROUP);
383     }
384
385     @Override
386     public String getInstanceGroupMemberInstantiationPath(String vnfGroupInstanceId) {
387         return MsoBusinessLogicImpl.validateEndpointPath(MsoProperties.MSO_REST_API_INSTANCE_GROUP)
388                 + '/' + vnfGroupInstanceId + "/addMembers";
389     }
390
391     @Override
392     public String getInstanceGroupDeletePath(String instanceGroupId) {
393         return MsoBusinessLogicImpl.validateEndpointPath(MsoProperties.MSO_REST_API_INSTANCE_GROUP)
394                 + '/' + instanceGroupId;
395     }
396
397     @Override
398     public String getInstanceGroupMemberDeletePath(String vnfGroupInstanceId){
399         return MsoBusinessLogicImpl.validateEndpointPath(MsoProperties.MSO_REST_API_INSTANCE_GROUP)
400                 + '/' + vnfGroupInstanceId + "/removeMembers";
401     }
402
403     @Override
404     public String getNetworkDeletePath(String serviceInstanceId, String networkInstanceId) {
405         return (MsoBusinessLogicImpl.validateEndpointPath(MsoProperties.MSO_REST_API_NETWORK_INSTANCE)
406                 + "/" + networkInstanceId)
407                 .replaceFirst(SVC_INSTANCE_ID, serviceInstanceId);
408     }
409
410     @Override
411     public String getResumeRequestPath(String requestId) {
412         return MsoBusinessLogicImpl.validateEndpointPath("mso.restapi.resume.orc.req")
413                 .replaceFirst("<request_id>", requestId);
414     }
415
416     @Override
417     public String getOrchestrationRequestsPath() {
418         return MsoBusinessLogicImpl.validateEndpointPath(MsoProperties.MSO_REST_API_GET_ORC_REQ);
419     }
420
421     @Override
422     public ServiceInfo updateServiceInfo(UUID jobUUID, Consumer<ServiceInfo> serviceUpdater) {
423         ServiceInfo serviceInfo = asyncInstantiationRepository.getServiceInfoByJobId(jobUUID);
424         serviceUpdater.accept(serviceInfo);
425         asyncInstantiationRepository.saveServiceInfo(serviceInfo);
426         return serviceInfo;
427     }
428
429     @Override
430     public ServiceInfo updateServiceInfoAndAuditStatus(UUID jobUuid, JobStatus jobStatus) {
431         auditService.auditVidStatus(jobUuid, jobStatus);
432         return updateServiceInfo(jobUuid, x -> setServiceInfoStatus(x, jobStatus));
433     }
434
435     private boolean isRetryEnabledForStatus(JobStatus jobStatus) {
436         return featureManager.isActive(Features.FLAG_1902_RETRY_JOB) &&
437                 (jobStatus==JobStatus.COMPLETED_WITH_ERRORS || jobStatus==JobStatus.FAILED);
438     }
439
440     private void setServiceInfoStatus(ServiceInfo serviceInfo, JobStatus jobStatus) {
441         serviceInfo.setJobStatus(jobStatus);
442         serviceInfo.setStatusModifiedDate(new Date());
443         serviceInfo.setRetryEnabled(isRetryEnabledForStatus(jobStatus));
444     }
445
446     public Job.JobStatus calcStatus(AsyncRequestStatus asyncRequestStatus) {
447         String msoRequestState = asyncRequestStatus.request.requestStatus.getRequestState().toLowerCase().replaceAll("[^a-z]+", "");
448         JobStatus jobStatus = msoStateToJobStatusMap.get(msoRequestState);
449         return (jobStatus != null ? jobStatus : JobStatus.IN_PROGRESS);
450     }
451
452     @Override
453     public void handleFailedInstantiation(UUID jobUUID) {
454         ServiceInfo serviceInfo = asyncInstantiationRepository.getServiceInfoByJobId(jobUUID);
455         List<ServiceInfo> serviceInfoList = asyncInstantiationRepository.getServiceInfoByTemplateIdAndJobStatus(serviceInfo.getTemplateId(), JobStatus.PENDING);
456         serviceInfoList.forEach(si -> updateServiceInfoAndAuditStatus(si.getJobId(), JobStatus.STOPPED));
457     }
458
459     @Override
460     public void deleteJob(UUID jobId) {
461         jobService.delete(jobId);
462         Date now = new Date();
463         updateServiceInfo(jobId, x -> x.setDeletedAt(now));
464     }
465
466     @Override
467     public void hideServiceInfo(UUID jobUUID) {
468         ServiceInfo serviceInfo = asyncInstantiationRepository.getServiceInfoByJobId(jobUUID);
469         if (!serviceInfo.getJobStatus().isFinal()) {
470             String message = String.format("jobId %s: Service status does not allow hide service, status = %s",
471                     serviceInfo.getJobId(),
472                     serviceInfo.getJobStatus());
473             logger.error(EELFLoggerDelegate.errorLogger, message);
474             throw new OperationNotAllowedException(message);
475         }
476         serviceInfo.setHidden(true);
477         asyncInstantiationRepository.saveServiceInfo(serviceInfo);
478     }
479
480     @Override
481     public int getCounterForName(String name) {
482
483         String hqlSelectNC = "from NameCounter where name = :name";
484         String hqlUpdateCounter = "update NameCounter set counter = :newCounter " +
485                 "where name= :name " +
486                 "and counter= :prevCounter";
487
488         Integer counter = null;
489         GenericUncheckedException lastException = null;
490         for (int i = 0; i< MAX_RETRIES_GETTING_COUNTER && counter==null; i++) {
491             try {
492                 counter = calcCounter(name, hqlSelectNC, hqlUpdateCounter);
493             }
494             catch (GenericUncheckedException exception) {
495                 lastException = exception; //do nothing, we will try again in the loop
496             }
497         }
498
499         if (counter!=null) {
500             return counter;
501         }
502
503         throw lastException!=null ? new DbFailureUncheckedException(lastException) :
504                 new DbFailureUncheckedException("Failed to get counter for "+name+" due to unknown error");
505
506     }
507
508     private Integer calcCounter(String name, String hqlSelectNC, String hqlUpdateCounter) {
509         Integer counter;
510         counter = DaoUtils.tryWithSessionAndTransaction(sessionFactory, session -> {
511             NameCounter nameCounter = (NameCounter) session.createQuery(hqlSelectNC)
512                     .setText("name", name)
513                     .uniqueResult();
514             if (nameCounter != null) {
515                 int updatedRows = session.createQuery(hqlUpdateCounter)
516                         .setText("name", nameCounter.getName())
517                         .setInteger("prevCounter", nameCounter.getCounter())
518                         .setInteger("newCounter", nameCounter.getCounter() + 1)
519                         .executeUpdate();
520                 if (updatedRows == 1) {
521                     return nameCounter.getCounter() + 1;
522                 }
523             } else {
524                 Object nameAsId = session.save(new NameCounter(name));
525                 //if save success
526                 if (nameAsId != null) {
527                     return 0;
528                 }
529             }
530             //in case of failure return null, in order to continue the loop
531             return null;
532         });
533         return counter;
534     }
535
536     @Override
537     public int getMaxRetriesGettingFreeNameFromAai() {
538         return maxRetriesGettingFreeNameFromAai;
539     }
540
541     @Override
542     public void setMaxRetriesGettingFreeNameFromAai(int maxRetriesGettingFreeNameFromAai) {
543         this.maxRetriesGettingFreeNameFromAai = maxRetriesGettingFreeNameFromAai;
544     }
545
546     @Override
547     public String getUniqueName(String name, ResourceType resourceType) {
548         //check that name aai response well before increasing counter from DB
549         //Prevents unnecessary increasing of the counter while AAI doesn't response
550         isNameFreeInAai(NAME_FOR_CHECK_AAI_STATUS, resourceType);
551
552         for (int i=0; i<getMaxRetriesGettingFreeNameFromAai(); i++) {
553             String newName = getUniqueNameFromDbOnly(name);
554             if (isNameFreeInAai(newName, resourceType)) {
555                 return newName;
556             }
557         }
558
559         throw new MaxRetriesException("can't find unused name for "+name, getMaxRetriesGettingFreeNameFromAai());
560     }
561
562     @Override
563     public ServiceInstantiation prepareServiceToBeUnique(ServiceInstantiation serviceInstantiation) {
564         try {
565             ServiceInstantiation clonedServiceInstantiation = JACKSON_OBJECT_MAPPER.readValue(
566                     JACKSON_OBJECT_MAPPER.writeValueAsBytes(serviceInstantiation), ServiceInstantiation.class);
567             clonedServiceInstantiation.setBulkSize(1);
568             return replaceAllTrackById(clonedServiceInstantiation);
569         } catch (IOException e) {
570             throw new GenericUncheckedException(e);
571         }
572
573     }
574
575     private<T extends BaseResource> T replaceAllTrackById(T resource) {
576         resource.setTrackById(UUID.randomUUID().toString());
577         resource.getChildren().forEach(this::replaceAllTrackById);
578         return resource;
579     }
580
581     @Override
582     public List<UUID> retryJob(ServiceInstantiation request, UUID jobId, String userId ) {
583         updateServiceInfo(jobId, si->si.setRetryEnabled(false));
584         return pushBulkJob(request, userId);
585     }
586
587     @Override
588     public List<UUID> retryJob(UUID jobId, String userId) {
589         ServiceInstantiation serviceInstantiationRequest = asyncInstantiationRepository.getJobRequest(jobId);
590         enrichBulkForRetry(serviceInstantiationRequest, jobId);
591
592         try {
593             logger.debug(EELFLoggerDelegate.debugLogger, "retry ServiceInstantiation request: "+
594                     JACKSON_OBJECT_MAPPER.writeValueAsString(serviceInstantiationRequest));
595         } catch (Exception e) {
596             logger.error(EELFLoggerDelegate.errorLogger, "failed to log retry of ServiceInstantiation request ", e);
597         }
598         return retryJob(serviceInstantiationRequest, jobId, userId);
599     }
600
601     @Override
602     public ServiceInstantiation getBulkForRetry(UUID jobId) {
603          return enrichBulkForRetry( asyncInstantiationRepository.getJobRequest(jobId), jobId);
604     }
605
606     @Override
607     public void addResourceInfo(JobSharedData sharedData, Job.JobStatus jobStatus, String instanceId) {
608         String trackById = ((BaseResource) sharedData.getRequest()).getTrackById();
609         ResourceInfo resourceInfo = new ResourceInfo(trackById, sharedData.getRootJobId(), instanceId, jobStatus, null);
610         asyncInstantiationRepository.saveResourceInfo(resourceInfo);
611     }
612
613     @Override
614     public void addFailedResourceInfo(JobSharedData sharedData, RestObject msoResponse) {
615         String trackById = ((BaseResource) sharedData.getRequest()).getTrackById();
616         String errorMessage = MsoUtil.formatExceptionAdditionalInfo(msoResponse.getStatusCode(), msoResponse.getRaw());
617         AsyncRequestStatus asyncRequestStatus = convertMessageToAsyncRequestStatus(errorMessage);
618         ResourceInfo resourceInfo = new ResourceInfo(trackById, sharedData.getRootJobId(), null, JobStatus.FAILED, asyncRequestStatus);
619
620         asyncInstantiationRepository.saveResourceInfo(resourceInfo);
621     }
622
623     @Override
624     public void updateResourceInfo(JobSharedData sharedData, JobStatus jobStatus, AsyncRequestStatus message) {
625         ResourceInfo resourceInfo = asyncInstantiationRepository.getResourceInfoByTrackId(((BaseResource) sharedData.getRequest()).getTrackById());
626         resourceInfo.setJobStatus(jobStatus);
627         if (jobStatus.isFailure()) {
628             resourceInfo.setErrorMessage(message);
629         }
630         asyncInstantiationRepository.saveResourceInfo(resourceInfo);
631     }
632
633     public AsyncRequestStatus convertMessageToAsyncRequestStatus(String message) {
634         RequestStatus requestStatus = new RequestStatus("FAILED", message, TimeUtils.zonedDateTimeToString(ZonedDateTime.now()));
635         AsyncRequestStatus.Request request = new AsyncRequestStatus.Request(requestStatus);
636         return new AsyncRequestStatus(request);
637     }
638
639     protected String getUniqueNameFromDbOnly(String name) {
640         int counter = getCounterForName(name);
641         return formatNameAndCounter(name, counter);
642     }
643
644     //the method is protected so we can call it in the UT
645     protected String formatNameAndCounter(String name, int counter) {
646         return counter==0 ? name : name + "_" + String.format("%03d", counter);
647     }
648
649     private boolean isNameFreeInAai(String name, ResourceType resourceType){
650         return !aaiClient.isNodeTypeExistsByName(name, resourceType);
651     }
652
653     @Override
654     public ServiceInstantiation enrichBulkForRetry(ServiceInstantiation serviceInstantiation, UUID jobId){
655         Map<String, ResourceInfo> resourceInfoByTrackId = asyncInstantiationRepository.getResourceInfoByRootJobId(jobId);
656
657         return setResourceStatus(resourceInfoByTrackId, serviceInstantiation);
658     }
659
660     protected String readStatusMsg(ResourceInfo resourceInfo){
661         if(resourceInfo!=null && resourceInfo.getErrorMessage()!=null && resourceInfo.getErrorMessage().request != null &&resourceInfo.getErrorMessage().request.requestStatus != null ) {
662             return resourceInfo.getErrorMessage().request.requestStatus.getStatusMessage();
663         }
664         return null;
665     }
666
667     private<T extends BaseResource> T setResourceStatus(Map<String, ResourceInfo> resourceInfoByTrackId, T resource) {
668         ResourceInfo resourceInfo = resourceInfoByTrackId.get(resource.getTrackById());
669         if(resourceInfo != null) {
670             boolean failed = resourceInfo.getJobStatus().isFailure();
671             resource.setIsFailed(failed);
672             resource.setStatusMessage(readStatusMsg(resourceInfo));
673             if (!failed) {
674                 // if(resource.getAction().equals(Action.Delete)){
675                 // TODO not yet implemented- completed after delete should remove the node
676                 resource.setAction(Action.None);
677                 resource.setInstanceId(resourceInfo.getInstanceId());
678             }
679         }
680         resource.getChildren().forEach(child -> setResourceStatus(resourceInfoByTrackId, child));
681         return resource;
682     }
683
684 }