8b01ff1853a4a2b20539689918441657ab491555
[sdc.git] / catalog-be / src / main / java / org / openecomp / sdc / be / components / lifecycle / LifecycleBusinessLogic.java
1 /*-
2  * ============LICENSE_START=======================================================
3  * SDC
4  * ================================================================================
5  * Copyright (C) 2017 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  * Modifications copyright (c) 2019 Nokia
20  * ================================================================================
21  */
22 package org.openecomp.sdc.be.components.lifecycle;
23
24 import com.google.common.annotations.VisibleForTesting;
25 import fj.data.Either;
26 import java.util.HashMap;
27 import java.util.Map;
28 import javax.annotation.PostConstruct;
29 import org.openecomp.sdc.be.catalog.enums.ChangeTypeEnum;
30 import org.openecomp.sdc.be.components.impl.ComponentBusinessLogic;
31 import org.openecomp.sdc.be.components.impl.ProductBusinessLogic;
32 import org.openecomp.sdc.be.components.impl.ResourceBusinessLogic;
33 import org.openecomp.sdc.be.components.impl.ServiceBusinessLogic;
34 import org.openecomp.sdc.be.components.impl.exceptions.ByActionStatusComponentException;
35 import org.openecomp.sdc.be.components.impl.exceptions.ByResponseFormatComponentException;
36 import org.openecomp.sdc.be.components.impl.exceptions.ComponentException;
37 import org.openecomp.sdc.be.components.impl.version.VesionUpdateHandler;
38 import org.openecomp.sdc.be.components.lifecycle.LifecycleChangeInfoWithAction.LifecycleChanceActionEnum;
39 import org.openecomp.sdc.be.dao.api.ActionStatus;
40 import org.openecomp.sdc.be.dao.janusgraph.JanusGraphDao;
41 import org.openecomp.sdc.be.datatypes.enums.ComponentTypeEnum;
42 import org.openecomp.sdc.be.datatypes.enums.NodeTypeEnum;
43 import org.openecomp.sdc.be.facade.operations.CatalogOperation;
44 import org.openecomp.sdc.be.impl.ComponentsUtils;
45 import org.openecomp.sdc.be.model.Component;
46 import org.openecomp.sdc.be.model.LifeCycleTransitionEnum;
47 import org.openecomp.sdc.be.model.LifecycleStateEnum;
48 import org.openecomp.sdc.be.model.Resource;
49 import org.openecomp.sdc.be.model.User;
50 import org.openecomp.sdc.be.model.jsonjanusgraph.datamodel.ToscaElement;
51 import org.openecomp.sdc.be.model.jsonjanusgraph.operations.NodeTemplateOperation;
52 import org.openecomp.sdc.be.model.jsonjanusgraph.operations.ToscaElementLifecycleOperation;
53 import org.openecomp.sdc.be.model.jsonjanusgraph.operations.ToscaOperationFacade;
54 import org.openecomp.sdc.be.model.jsonjanusgraph.utils.ModelConverter;
55 import org.openecomp.sdc.be.model.operations.api.IGraphLockOperation;
56 import org.openecomp.sdc.be.model.operations.api.StorageOperationStatus;
57 import org.openecomp.sdc.be.resources.data.auditing.AuditingActionEnum;
58 import org.openecomp.sdc.be.resources.data.auditing.model.ResourceCommonInfo;
59 import org.openecomp.sdc.be.resources.data.auditing.model.ResourceVersionInfo;
60 import org.openecomp.sdc.common.api.Constants;
61 import org.openecomp.sdc.common.log.wrappers.Logger;
62 import org.openecomp.sdc.common.util.ValidationUtils;
63 import org.openecomp.sdc.exception.ResponseFormat;
64 import org.springframework.beans.factory.annotation.Autowired;
65 import org.springframework.context.annotation.Lazy;
66
67 @org.springframework.stereotype.Component("lifecycleBusinessLogic")
68 public class LifecycleBusinessLogic {
69
70     private static final String COMMENT = "comment";
71     private static final Logger log = Logger.getLogger(LifecycleBusinessLogic.class);
72     @Autowired
73     ToscaOperationFacade toscaOperationFacade;
74     @Autowired
75     NodeTemplateOperation nodeTemplateOperation;
76     @Autowired
77     CatalogOperation catalogOperations;
78     @Autowired
79     VesionUpdateHandler groupUpdateHandler;
80     @Autowired
81     private IGraphLockOperation graphLockOperation = null;
82     @Autowired
83     private JanusGraphDao janusGraphDao;
84     @javax.annotation.Resource
85     private ComponentsUtils componentUtils;
86     @javax.annotation.Resource
87     private ToscaElementLifecycleOperation lifecycleOperation;
88     @Autowired
89     @Lazy
90     private ServiceBusinessLogic serviceBusinessLogic;
91     @Autowired
92     @Lazy
93     private ResourceBusinessLogic resourceBusinessLogic;
94     @Autowired
95     @Lazy
96     private ProductBusinessLogic productBusinessLogic;
97     private Map<String, LifeCycleTransition> stateTransitions;
98
99     @PostConstruct
100     public void init() {
101         initStateOperations();
102     }
103
104     private void initStateOperations() {
105         stateTransitions = new HashMap<>();
106         LifeCycleTransition checkoutOp = new CheckoutTransition(componentUtils, lifecycleOperation, toscaOperationFacade, janusGraphDao);
107         stateTransitions.put(checkoutOp.getName().name(), checkoutOp);
108         UndoCheckoutTransition undoCheckoutOp = new UndoCheckoutTransition(componentUtils, lifecycleOperation, toscaOperationFacade, janusGraphDao);
109         undoCheckoutOp.setCatalogOperations(catalogOperations);
110         stateTransitions.put(undoCheckoutOp.getName().name(), undoCheckoutOp);
111         LifeCycleTransition checkinOp = new CheckinTransition(componentUtils, lifecycleOperation, toscaOperationFacade, janusGraphDao,
112             groupUpdateHandler);
113         stateTransitions.put(checkinOp.getName().name(), checkinOp);
114         CertificationChangeTransition successCertification = new CertificationChangeTransition(serviceBusinessLogic, LifeCycleTransitionEnum.CERTIFY,
115             componentUtils, lifecycleOperation, toscaOperationFacade, janusGraphDao);
116         successCertification.setNodeTemplateOperation(nodeTemplateOperation);
117         stateTransitions.put(successCertification.getName().name(), successCertification);
118     }
119
120     @VisibleForTesting
121     Map<String, LifeCycleTransition> getStartTransition() {
122         return stateTransitions;
123     }
124
125     // TODO: rhalili - should use changeComponentState when possible
126     public Either<Resource, ResponseFormat> changeState(String resourceId, User modifier, LifeCycleTransitionEnum transitionEnum,
127                                                         LifecycleChangeInfoWithAction changeInfo, boolean inTransaction, boolean needLock) {
128         return changeComponentState(ComponentTypeEnum.RESOURCE, resourceId, modifier, transitionEnum, changeInfo, inTransaction, needLock);
129     }
130
131     public <T extends Component> Either<T, ResponseFormat> changeComponentState(ComponentTypeEnum componentType, String componentId, User modifier,
132                                                                                 LifeCycleTransitionEnum transitionEnum,
133                                                                                 LifecycleChangeInfoWithAction changeInfo, boolean inTransaction,
134                                                                                 boolean needLock) {
135         LifeCycleTransition lifeCycleTransition = stateTransitions.get(transitionEnum.name());
136         if (lifeCycleTransition == null) {
137             log.debug("state operation is not valid. operations allowed are: {}", LifeCycleTransitionEnum.valuesAsString());
138             ResponseFormat error = componentUtils.getInvalidContentErrorAndAudit(modifier, componentId, AuditingActionEnum.CHECKOUT_RESOURCE);
139             return Either.right(error);
140         }
141         log.debug("get resource from graph");
142         ResponseFormat errorResponse;
143         Either<T, ResponseFormat> eitherResourceResponse = getComponentForChange(componentType, componentId, modifier, lifeCycleTransition,
144             changeInfo);
145         if (eitherResourceResponse.isRight()) {
146             return eitherResourceResponse;
147         }
148         T component = eitherResourceResponse.left().value();
149         String resourceCurrVersion = component.getVersion();
150         LifecycleStateEnum resourceCurrState = component.getLifecycleState();
151         // lock resource
152         if (!inTransaction && needLock) {
153             log.debug("lock component {}", componentId);
154             try {
155                 lockComponent(componentType, component);
156             } catch (ComponentException e) {
157                 errorResponse = e.getResponseFormat();
158                 componentUtils.auditComponent(errorResponse, modifier, component, lifeCycleTransition.getAuditingAction(),
159                     new ResourceCommonInfo(componentType.getValue()),
160                     ResourceVersionInfo.newBuilder().state(resourceCurrState.name()).version(resourceCurrVersion).build());
161                 log.error("lock component {} failed", componentId);
162                 return Either.right(errorResponse);
163             }
164             log.debug("after lock component {}", componentId);
165         }
166         try {
167             Either<String, ResponseFormat> commentValidationResult = validateComment(changeInfo, transitionEnum);
168             if (commentValidationResult.isRight()) {
169                 errorResponse = commentValidationResult.right().value();
170                 componentUtils.auditComponent(errorResponse, modifier, component, lifeCycleTransition.getAuditingAction(),
171                     new ResourceCommonInfo(componentType.getValue()),
172                     ResourceVersionInfo.newBuilder().state(resourceCurrState.name()).version(resourceCurrVersion).build(),
173                     changeInfo.getUserRemarks());
174                 return Either.right(errorResponse);
175             }
176             changeInfo.setUserRemarks(commentValidationResult.left().value());
177             log.debug("after validate component");
178             Either<Boolean, ResponseFormat> validateHighestVersion = validateHighestVersion(modifier, lifeCycleTransition, component,
179                 resourceCurrVersion, componentType);
180             if (validateHighestVersion.isRight()) {
181                 return Either.right(validateHighestVersion.right().value());
182             }
183             log.debug("after validate Highest Version");
184             final T oldComponent = component;
185             Either<T, ResponseFormat> checkedInComponentEither = checkInBeforeCertifyIfNeeded(componentType, modifier, transitionEnum, changeInfo,
186                 inTransaction, component);
187             if (checkedInComponentEither.isRight()) {
188                 return Either.right(checkedInComponentEither.right().value());
189             }
190             component = checkedInComponentEither.left().value();
191             return changeState(component, lifeCycleTransition, componentType, modifier, changeInfo, inTransaction).left()
192                 .bind(c -> updateCatalog(c, oldComponent, ChangeTypeEnum.LIFECYCLE));
193         } finally {
194             component.setUniqueId(componentId);
195             if (!inTransaction && needLock) {
196                 log.info("unlock component {}", componentId);
197                 NodeTypeEnum nodeType = componentType.getNodeType();
198                 log.info("During change state, another component {} has been created/updated", componentId);
199                 graphLockOperation.unlockComponent(componentId, nodeType);
200             }
201         }
202     }
203
204     private <T extends Component> Either<T, ResponseFormat> updateCatalog(T component, T oldComponent, ChangeTypeEnum changeStatus) {
205         log.debug("updateCatalog start");
206         T result = component == null ? oldComponent : component;
207         if (component != null) {
208             ActionStatus status = catalogOperations.updateCatalog(changeStatus, component);
209             if (status != ActionStatus.OK) {
210                 return Either.right(componentUtils.getResponseFormat(status));
211             }
212         }
213         return Either.left(result);
214     }
215
216     private <T extends Component> Either<T, ResponseFormat> checkInBeforeCertifyIfNeeded(ComponentTypeEnum componentType, User modifier,
217                                                                                          LifeCycleTransitionEnum transitionEnum,
218                                                                                          LifecycleChangeInfoWithAction changeInfo,
219                                                                                          boolean inTransaction, T component) {
220         LifecycleStateEnum oldState = component.getLifecycleState();
221         log.debug("Certification request for resource {} ", component.getUniqueId());
222         if (oldState == LifecycleStateEnum.NOT_CERTIFIED_CHECKOUT && transitionEnum == LifeCycleTransitionEnum.CERTIFY) {
223             log.debug("Resource {} is in Checkout state perform checkin", component.getUniqueId());
224             Either<T, ResponseFormat> actionResponse = changeState(component, stateTransitions.get(LifeCycleTransitionEnum.CHECKIN.name()),
225                 componentType, modifier, changeInfo, inTransaction);
226             if (actionResponse.isRight()) {
227                 log.debug("Failed to check in Resource {} error {}", component.getUniqueId(), actionResponse.right().value());
228             }
229             return actionResponse;
230         }
231         return Either.left(component);
232     }
233
234     private <T extends Component> Either<T, ResponseFormat> changeState(T component, LifeCycleTransition lifeCycleTransition,
235                                                                         ComponentTypeEnum componentType, User modifier,
236                                                                         LifecycleChangeInfoWithAction changeInfo, boolean inTransaction) {
237         ResponseFormat errorResponse;
238         LifecycleStateEnum oldState = component.getLifecycleState();
239         String resourceCurrVersion = component.getVersion();
240         ComponentBusinessLogic bl = getComponentBL(componentType);
241         Either<User, ResponseFormat> ownerResult = lifeCycleTransition.getComponentOwner(component, componentType);
242         if (ownerResult.isRight()) {
243             return Either.right(ownerResult.right().value());
244         }
245         User owner = ownerResult.left().value();
246         log.info("owner of resource {} is {}", component.getUniqueId(), owner.getUserId());
247         Either<Boolean, ResponseFormat> stateValidationResult = lifeCycleTransition
248             .validateBeforeTransition(component, componentType, modifier, owner, oldState, changeInfo);
249         if (stateValidationResult.isRight()) {
250             log.error("Failed to validateBeforeTransition");
251             errorResponse = stateValidationResult.right().value();
252             componentUtils.auditComponent(errorResponse, modifier, component, lifeCycleTransition.getAuditingAction(),
253                 new ResourceCommonInfo(componentType.getValue()),
254                 ResourceVersionInfo.newBuilder().version(resourceCurrVersion).state(oldState.name()).build(), changeInfo.getUserRemarks());
255             return Either.right(errorResponse);
256         }
257         Either<T, ResponseFormat> operationResult = lifeCycleTransition
258             .changeState(componentType, component, bl, modifier, owner, false, inTransaction);
259         if (operationResult.isRight()) {
260             errorResponse = operationResult.right().value();
261             log.info("audit before sending error response");
262             componentUtils.auditComponentAdmin(errorResponse, modifier, component, lifeCycleTransition.getAuditingAction(), componentType,
263                 ResourceVersionInfo.newBuilder().state(oldState.name()).version(resourceCurrVersion).build());
264             return Either.right(errorResponse);
265         }
266         Component resourceAfterOperation = operationResult.left().value() == null ? component : operationResult.left().value();
267         componentUtils.auditComponent(componentUtils.getResponseFormat(ActionStatus.OK), modifier, resourceAfterOperation,
268             lifeCycleTransition.getAuditingAction(), new ResourceCommonInfo(componentType.getValue()),
269             ResourceVersionInfo.newBuilder().state(oldState.name()).version(resourceCurrVersion).build(), changeInfo.getUserRemarks());
270         return operationResult;
271     }
272
273     private <T extends Component> Either<T, ResponseFormat> getComponentForChange(ComponentTypeEnum componentType, String componentId, User modifier,
274                                                                                   LifeCycleTransition lifeCycleTransition,
275                                                                                   LifecycleChangeInfoWithAction changeInfo) {
276         Either<T, StorageOperationStatus> eitherResourceResponse = toscaOperationFacade.getToscaElement(componentId);
277         ResponseFormat errorResponse;
278         if (eitherResourceResponse.isRight()) {
279             ActionStatus actionStatus = componentUtils.convertFromStorageResponse(eitherResourceResponse.right().value(), componentType);
280             errorResponse = componentUtils.getResponseFormat(actionStatus, Constants.EMPTY_STRING);
281             log.debug("audit before sending response");
282             componentUtils.auditComponent(errorResponse, modifier, lifeCycleTransition.getAuditingAction(),
283                 new ResourceCommonInfo(componentId, componentType.getValue()), changeInfo.getUserRemarks());
284             return Either.right(errorResponse);
285         }
286         return Either.left(eitherResourceResponse.left().value());
287     }
288
289     private Either<Boolean, ResponseFormat> validateHighestVersion(User modifier, LifeCycleTransition lifeCycleTransition, Component component,
290                                                                    String resourceCurrVersion, ComponentTypeEnum componentType) {
291         ResponseFormat errorResponse;
292         if (!component.isHighestVersion()) {
293             log.debug("Component version {} is not the last version of component {}",
294                 component.getComponentMetadataDefinition().getMetadataDataDefinition().getVersion(),
295                 component.getComponentMetadataDefinition().getMetadataDataDefinition().getName());
296             errorResponse = componentUtils.getResponseFormat(ActionStatus.COMPONENT_HAS_NEWER_VERSION,
297                 component.getComponentMetadataDefinition().getMetadataDataDefinition().getName(), componentType.getValue().toLowerCase());
298             componentUtils.auditComponentAdmin(errorResponse, modifier, component, lifeCycleTransition.getAuditingAction(), componentType,
299                 ResourceVersionInfo.newBuilder().state(component.getLifecycleState().name()).version(resourceCurrVersion).build());
300             return Either.right(errorResponse);
301         }
302         return Either.left(true);
303     }
304
305     private Boolean lockComponent(ComponentTypeEnum componentType, Component component) {
306         NodeTypeEnum nodeType = componentType.getNodeType();
307         StorageOperationStatus lockResourceStatus = graphLockOperation.lockComponent(component.getUniqueId(), nodeType);
308         if (lockResourceStatus.equals(StorageOperationStatus.OK)) {
309             return true;
310         } else {
311             ActionStatus actionStatus = componentUtils.convertFromStorageResponse(lockResourceStatus);
312             throw new ByActionStatusComponentException(actionStatus,
313                 component.getComponentMetadataDefinition().getMetadataDataDefinition().getName());
314         }
315     }
316
317     private Either<String, ResponseFormat> validateComment(LifecycleChangeInfoWithAction changeInfo, LifeCycleTransitionEnum transitionEnum) {
318         String comment = changeInfo.getUserRemarks();
319         if (LifeCycleTransitionEnum.CERTIFY == transitionEnum || LifeCycleTransitionEnum.CHECKIN == transitionEnum
320             // import?
321         ) {
322             if (!ValidationUtils.validateStringNotEmpty(comment)) {
323                 log.debug("user comment cannot be empty or null.");
324                 ResponseFormat errorResponse = componentUtils.getResponseFormat(ActionStatus.MISSING_DATA, COMMENT);
325                 return Either.right(errorResponse);
326             }
327             comment = ValidationUtils.removeNoneUtf8Chars(comment);
328             comment = ValidationUtils.removeHtmlTags(comment);
329             comment = ValidationUtils.normaliseWhitespace(comment);
330             comment = ValidationUtils.stripOctets(comment);
331             if (!ValidationUtils.validateLength(comment, ValidationUtils.COMMENT_MAX_LENGTH)) {
332                 log.debug("user comment exceeds limit.");
333                 return Either
334                     .right(componentUtils.getResponseFormat(ActionStatus.EXCEEDS_LIMIT, COMMENT, String.valueOf(ValidationUtils.COMMENT_MAX_LENGTH)));
335             }
336             if (!ValidationUtils.validateIsEnglish(comment)) {
337                 return Either.right(componentUtils.getResponseFormat(ActionStatus.INVALID_CONTENT));
338             }
339         }
340         return Either.left(comment);
341     }
342
343     private ComponentBusinessLogic getComponentBL(ComponentTypeEnum componentTypeEnum) {
344         ComponentBusinessLogic businessLogic;
345         switch (componentTypeEnum) {
346             case RESOURCE:
347                 businessLogic = this.resourceBusinessLogic;
348                 break;
349             case SERVICE:
350                 businessLogic = this.serviceBusinessLogic;
351                 break;
352             case PRODUCT:
353                 businessLogic = this.productBusinessLogic;
354                 break;
355             default:
356                 throw new IllegalArgumentException("Illegal component type:" + componentTypeEnum.getValue());
357         }
358         return businessLogic;
359     }
360
361     public Either<Component, ResponseFormat> getLatestComponentByUuid(ComponentTypeEnum componentTypeEnum, String uuid) {
362         Either<Component, StorageOperationStatus> latestVersionEither = toscaOperationFacade.getLatestComponentByUuid(uuid);
363         if (latestVersionEither.isRight()) {
364             return Either.right(componentUtils
365                 .getResponseFormat(componentUtils.convertFromStorageResponse(latestVersionEither.right().value(), componentTypeEnum), uuid));
366         }
367         Component latestComponent = latestVersionEither.left().value();
368         return Either.left(latestComponent);
369     }
370
371     /**
372      * Performs Force certification. Note that a Force certification is allowed for the first certification only, as only a state and a version is
373      * promoted due a Force certification, skipping other actions required if a previous certified version exists.
374      *
375      * @param resource
376      * @param user
377      * @param lifecycleChangeInfo
378      * @param inTransaction
379      * @param needLock
380      * @return
381      */
382     public Resource forceResourceCertification(Resource resource, User user, LifecycleChangeInfoWithAction lifecycleChangeInfo, boolean inTransaction,
383                                                boolean needLock) {
384         Resource result = null;
385         Either<ToscaElement, StorageOperationStatus> certifyResourceRes = null;
386         if (lifecycleChangeInfo.getAction() != LifecycleChanceActionEnum.CREATE_FROM_CSAR) {
387             log.debug("Force certification is not allowed for the action {}. ", lifecycleChangeInfo.getAction());
388             throw new ByActionStatusComponentException(ActionStatus.NOT_ALLOWED);
389         }
390         if (!isFirstCertification(resource.getVersion())) {
391             log.debug("Failed to perform a force certification of resource{}. Force certification is allowed for the first certification only. ",
392                 resource.getName());
393             throw new ByActionStatusComponentException(ActionStatus.NOT_ALLOWED);
394         }
395         // lock resource
396         if (!inTransaction && needLock) {
397             log.info("lock component {}", resource.getUniqueId());
398             lockComponent(resource.getComponentType(), resource);
399             log.info("after lock component {}", resource.getUniqueId());
400         }
401         try {
402             certifyResourceRes = lifecycleOperation
403                 .forceCerificationOfToscaElement(resource.getUniqueId(), user.getUserId(), user.getUserId(), resource.getVersion());
404             if (certifyResourceRes.isRight()) {
405                 StorageOperationStatus status = certifyResourceRes.right().value();
406                 log.debug("Failed to perform a force certification of resource {}. The status is {}. ", resource.getName(), status);
407                 throw new ByResponseFormatComponentException(
408                     componentUtils.getResponseFormatByResource(componentUtils.convertFromStorageResponse(status), resource));
409             }
410             result = ModelConverter.convertFromToscaElement(certifyResourceRes.left().value());
411             resource.setComponentMetadataDefinition(result.getComponentMetadataDefinition());
412         } finally {
413             log.info("unlock component {}", resource.getUniqueId());
414             if (!inTransaction) {
415                 if (result != null) {
416                     janusGraphDao.commit();
417                 } else {
418                     janusGraphDao.rollback();
419                 }
420                 if (needLock) {
421                     NodeTypeEnum nodeType = resource.getComponentType().getNodeType();
422                     log.info("During change state, another component {} has been created/updated", resource.getUniqueId());
423                     graphLockOperation.unlockComponent(resource.getUniqueId(), nodeType);
424                 }
425             }
426         }
427         return result;
428     }
429
430     public boolean isFirstCertification(String previousVersion) {
431         return previousVersion.split("\\.")[0].equals("0");
432     }
433 }