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