/** * Copyright (C) 2015 Orange * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * http://www.apache.org/licenses/LICENSE-2.0 * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.francetelecom.clara.cloud.logicalmodel; import com.francetelecom.clara.cloud.commons.*; import com.francetelecom.clara.cloud.commons.jaxb.AnyTypeAdapter; import com.francetelecom.clara.cloud.logicalmodel.InvalidConfigServiceException.ErrorType; import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.persistence.*; import javax.validation.Valid; import javax.validation.constraints.Max; import javax.validation.constraints.Min; import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlElementWrapper; import javax.xml.bind.annotation.XmlIDREF; import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; import java.util.*; @Entity @Table(name = "PROCESSING_NODE") //@MappedSuperclass @XmlJavaTypeAdapter(AnyTypeAdapter.class) public abstract class ProcessingNode extends LogicalModelItem { private static Logger logger = LoggerFactory.getLogger(ProcessingNode.class.getName()); private static final long serialVersionUID = 1L; public static final int MAX_CONFIG_SET_ENTRIES_PER_EXEC_NODE = 300; /** * logical deployment. */ @XmlIDREF @XmlElement(name = "logicalDeploymentRef") @ManyToOne @NotNull @GuiMapping(status = GuiMapping.StatusType.NA) protected LogicalDeployment logicalDeployment; /** * Logical Services used by the Current Node Cluster (relation). Use an * association Entity. */ @XmlElementWrapper @XmlElement(name = "logicalNodeServiceAssociation") @OneToMany(cascade = {CascadeType.ALL}, mappedBy = "processingNode", orphanRemoval = true) @GuiMapping @Valid private List<LogicalNodeServiceAssociation> logicalNodeServiceAssociations = new ArrayList<LogicalNodeServiceAssociation>(); /** * The application artefact that is hosted in this container (packaged as an EAR). */ @Embedded @GuiMapping @Valid MavenReference softwareReference; /** * Expect the Architect to provided a non pre existing artifact * If this is activated, existence control will be skipped, and a default artifact * will be provided "on the fly" @ activation time */ @GuiMapping boolean optionalSoftwareReference = false; private static final String[] EXCLUDED_EQUALS_FIELDS = EqualsUtils.mergeExcludedFieldLists(LogicalModelItem.EXCLUDED_EQUALS_FIELDS, new String[]{"logicalDeployment", "logicalNodeServiceAssociations"}); /** * Minimum non persistent disk in Mega bytes (1048576 bytes) that should be allocated to this execution node * This should be considered as a hint for disk sizing of production environment */ @GuiMapping(status = GuiMapping.StatusType.SUPPORTED, functional = false) @Min(768) @Max(100000) int minDiskMbHint = 1024; /** * Minimum memory in Mega bytes (1048576 bytes) that should be allocated to this execution node * This should be considered as a hint for memory sizing of production environment */ @GuiMapping(status = GuiMapping.StatusType.SUPPORTED, functional = false) @Min(1) @Max(10000) int minMemoryMbHint = 128; /** * Memory per session in KB (1024 bytes) * This should be considered as a hint for memory sizing of production environment */ @GuiMapping(status = GuiMapping.StatusType.SKIPPED, functional = false) @Min(1) @Max(10000) int memoryKbPerActiveSessionHint = 1024; public boolean isOptionalSoftwareReference() { return optionalSoftwareReference; } public void setOptionalSoftwareReference(boolean optionalSoftwareReference) { this.optionalSoftwareReference = optionalSoftwareReference; } /** * A custom Icon Url icon which can be null. * This is used to display own user icon */ @GuiMapping(status = GuiMapping.StatusType.SUPPORTED, functional = false) @Size(min = 0, max = 255) String iconUrl; protected ProcessingNode() { super(); } public ProcessingNode(String label, LogicalDeployment logicalDeployment) { super(UUIDUtils.generateUUID("en"), label); this.logicalDeployment = logicalDeployment; for (ProcessingNode executionNode : logicalDeployment.listProcessingNodes()) { Validate.isTrue(!executionNode.getLabel().equals(logicalDeployment), "ExecutionNode label expected to be unique, found duplicate:" + logicalDeployment); } this.logicalDeployment.processingNodes.add(this); } /** * Logical Node destructor : remove current logical execution node from * logical deployment list */ public void discard() { this.logicalDeployment.processingNodes.remove(this); } /** * Set the ClusterNode uses given Service Service must be on same deployment * <p> * An association persistent entity is created * * @param service */ public void addLogicalServiceUsage(LogicalService service, LogicalServiceAccessTypeEnum accessType) { if (service.logicalDeployment != this.logicalDeployment) throw new TechnicalException("Node Cluster is not in the same deployment " + logicalDeployment); LogicalNodeServiceAssociation association = new LogicalNodeServiceAssociation(this, service); association.setAccessType(accessType); this.logicalNodeServiceAssociations.add(association); // reverse relationship (LogicalService => ExecutionNode) service.logicalNodeServiceAssociations.add(association); } /** * Remove an association * <p> * An association persistent entity is deleted * * @param logicalNodeServiceAssociation the association to delete */ public void removeLogicalServiceUsage(LogicalNodeServiceAssociation logicalNodeServiceAssociation) { // remove reverse relationship (if not, we get a // javax.persistence.EntityNotFoundException: // deleted entity passed to persist) logicalNodeServiceAssociation.getLogicalService().logicalNodeServiceAssociations.remove(logicalNodeServiceAssociation); int index = 0; boolean found = false; for (LogicalNodeServiceAssociation assoc : this.logicalNodeServiceAssociations) { if (assoc.equalsDeep(logicalNodeServiceAssociation)) { found = true; break; } index++; } if (found) { this.logicalNodeServiceAssociations.remove(index); } } /** * Remove a list of associations. * <p> * one or more association persistent entity are deleted * * @param logicalNodeServiceAssociationList the list of */ public void removeAllLogicalServiceUsage(List<LogicalNodeServiceAssociation> logicalNodeServiceAssociationList) { // NB : we can't call removeLogicalServiceUsage for each list item // because it throws a ConcurrentModificationException. // remove all reverse relationship for (LogicalNodeServiceAssociation assoc : logicalNodeServiceAssociationList) { assoc.getLogicalService().logicalNodeServiceAssociations .remove(assoc); } // remove all assoc this.logicalNodeServiceAssociations .removeAll(logicalNodeServiceAssociationList); } /** * List the Logical Services used by the ClusterNode * <p> * Retrieves all LogicalService through the LogicalNodeService association * Entity * * @return */ public List<LogicalService> listLogicalServices() { return listLogicalServices(null); } /** * List the Logical Services of a specific class. * <p> * Retrieves all {@link LogicalService} that is connected to the current ExecNode through the {@link LogicalNodeServiceAssociation} * * @param filteredClass The class of the logicalService to restrict to (e.g. LogicalWebGUIService.class), or null * to not perform any filtering. Note this may be a superclass or an implementing interface. * @return a non-null, possibly empty list */ public <E extends LogicalService> List<E> listLogicalServices(Class<E> filteredClass) { List<E> services = new ArrayList<E>(); for (LogicalNodeServiceAssociation association : this.logicalNodeServiceAssociations) { LogicalService logicalService = association.logicalService; boolean includeThisClass = false; if (filteredClass == null) { includeThisClass = true; } else { includeThisClass = (filteredClass.isInstance(logicalService)); } if (includeThisClass) { services.add((E) logicalService); } } return Collections.unmodifiableList(services); } /** * List all association to used Logical services * * @return */ public List<LogicalNodeServiceAssociation> listLogicalServicesAssociations() { return Collections .unmodifiableList(this.logicalNodeServiceAssociations); } public MavenReference getSoftwareReference() { return softwareReference; } public void setSoftwareReference(MavenReference softwareReference) { this.softwareReference = softwareReference; } public int getMinDiskMbHint() { return minDiskMbHint; } public void setMinDiskMbHint(int minDiskMbHint) { this.minDiskMbHint = minDiskMbHint; } public int getMinMemoryMbHint() { return minMemoryMbHint; } public void setMinMemoryMbHint(int minMemoryMbHint) { this.minMemoryMbHint = minMemoryMbHint; } public int getMemoryKbPerActiveSessionHint() { return memoryKbPerActiveSessionHint; } public void setMemoryKbPerActiveSessionHint(int memoryKbPerActiveSessionHint) { this.memoryKbPerActiveSessionHint = memoryKbPerActiveSessionHint; } public String getIconUrl() { return iconUrl; } public void setIconUrl(String iconUrl) { this.iconUrl = iconUrl; } @Override public boolean equals(Object obj) { //See http://commons.apache.org/lang/api-2.5/org/apache/commons/lang/builder/HashCodeBuilder.html return EqualsBuilder.reflectionEquals(this, obj, EXCLUDED_EQUALS_FIELDS); } @Override public int hashCode() { return HashCodeBuilder.reflectionHashCode(this, EXCLUDED_EQUALS_FIELDS); } /** * Utility method to apply consistency checking on the overall model * */ public void checkConsistency() throws InvalidConfigServiceException { Properties mergedProperties = getMergedConfigServicesProperties(); //TODO: add a debug trace on merged properties ? } /** * Utility method to return a merged list of {@link LogicalConfigService} content in a {@link Properties} format. * * @return a @{link Properties} instance with {@link String} as keys and * @throws InvalidConfigServiceException if some of the {@link LogicalConfigService} associated to this object were * not valid */ public Properties getMergedConfigServicesProperties() throws InvalidConfigServiceException { List<LogicalConfigService> logicalConfigServices = listLogicalServices(LogicalConfigService.class); Properties mergedProperties = new Properties(); Set<String> duplicates = new HashSet<String>(); StringBuffer collisions = new StringBuffer(); for (LogicalConfigService logicalConfigService : logicalConfigServices) { //Check overlap in key names among the subscribed config services logicalConfigService.mergeAndCheckForDuplicateKeys(mergedProperties, duplicates, collisions); } if (mergedProperties.size() > MAX_CONFIG_SET_ENTRIES_PER_EXEC_NODE) { InvalidConfigServiceException invalidConfigServiceException = new InvalidConfigServiceException("Too many Config entries for ExecutionNode=" + this.getLabel()); invalidConfigServiceException.setType(ErrorType.TOO_MANY_ENTRIES); invalidConfigServiceException.setEntryCount(mergedProperties.size()); invalidConfigServiceException.setMaxEntryCount(MAX_CONFIG_SET_ENTRIES_PER_EXEC_NODE); invalidConfigServiceException.setImpactedElementName(getLabel()); throw invalidConfigServiceException; } if (collisions.length() > 0) { InvalidConfigServiceException invalidConfigServiceException = new InvalidConfigServiceException("Collision for ExecutionNode=" + this.getLabel() + " collision=" + collisions.toString()); invalidConfigServiceException.setType(ErrorType.DUPLICATE_KEYS); invalidConfigServiceException.getDuplicateKeys().addAll(duplicates); invalidConfigServiceException.setImpactedElementName(getLabel()); throw invalidConfigServiceException; } return mergedProperties; } protected void setLogicalDeployment(LogicalDeployment logicalDeployment) { this.logicalDeployment = logicalDeployment; } public LogicalDeployment getLogicalDeployment() { return logicalDeployment; } @Override protected boolean isFieldExcludedFromToString(String fieldName) { return isFieldExcludedFromToString(fieldName, ProcessingNode.EXCLUDED_EQUALS_FIELDS); } /** * Utility method to check that each Service associated to an ExecutionNode is indeed also pointing to this node. * This is used with asserts. * * @return true if symetry is indeed respected, false otherwise. */ public boolean isAssociationsSymmetryRespected() { for (LogicalNodeServiceAssociation association : logicalNodeServiceAssociations) { //For the current service, check it point back to us boolean currentExecNodeFound = false; final LogicalService logicalService = association.getLogicalService(); final List<LogicalNodeServiceAssociation> logicalNodeServiceAssociations1 = logicalService.listLogicalServicesAssociations(); for (LogicalNodeServiceAssociation logicalNodeServiceAssociation : logicalNodeServiceAssociations1) { if (logicalNodeServiceAssociation.getProcessingNode() == this) { currentExecNodeFound = true; } } if (!currentExecNodeFound) { logger.error("associations between execution nodes and services should be symmetric, did not find pointer back to:" + this + " from:" + logicalService); return false; } } return true; } }