Add cloud logging service feature#1847
Conversation
# Conflicts: # multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/backup-existing-app.bpmn # multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/deploy-app.bpmn # multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/stop-dependent-modules.bpmn # multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/undeploy-app.bpmn
20788df to
7220532
Compare
c1abde6 to
bb7fc04
Compare
| import org.cloudfoundry.multiapps.controller.core.auditlogging.model.ConfigurationChangeActions; | ||
| import org.cloudfoundry.multiapps.controller.persistence.model.LoggingConfiguration; | ||
|
|
||
| public class CloudLoggingServiceConfigurationAuditLog { |
There was a problem hiding this comment.
Because SAP Cloud logging service is an internal proprietary service it should not be exposed in the open source. The whole logic related to the service must be move in the internal project.
Move every SAP related thing to the internal project and only leave the what is actually open source in this PR
There was a problem hiding this comment.
At least audit logging part maybe can be moved in internal project? SAP Cloud Logging is internal service but API endpoints are public, right?
There was a problem hiding this comment.
I don't see any changes here.
| InputStream serverCaStream = getCredentialInputStream(loggingConfiguration.getServerCa()); | ||
| InputStream clientCertStream = getCredentialInputStream(loggingConfiguration.getClientCert()); | ||
| InputStream clientKeyStream = getCredentialInputStream(loggingConfiguration.getClientKey()); |
There was a problem hiding this comment.
are these streams closed?
IvanBorislavovDimitrov
left a comment
There was a problem hiding this comment.
Additional comments on OperationLogsExporter not yet covered in the earlier review.
|
| import org.cloudfoundry.multiapps.controller.core.auditlogging.model.ConfigurationChangeActions; | ||
| import org.cloudfoundry.multiapps.controller.persistence.model.LoggingConfiguration; | ||
|
|
||
| public class CloudLoggingServiceConfigurationAuditLog { |
There was a problem hiding this comment.
At least audit logging part maybe can be moved in internal project? SAP Cloud Logging is internal service but API endpoints are public, right?
| <dependency> | ||
| <groupId>io.projectreactor.netty</groupId> | ||
| <artifactId>reactor-netty-http</artifactId> | ||
| <version>${reactor-netty.version}</version> | ||
| </dependency> | ||
| <dependency> | ||
| <groupId>io.projectreactor.netty</groupId> | ||
| <artifactId>reactor-netty-core</artifactId> | ||
| <version>${reactor-netty.version}</version> | ||
| </dependency> |
There was a problem hiding this comment.
Why these dependencies are required? WebClient provided by spring webflux isn't it enough?
There was a problem hiding this comment.
I use HttpClient in CloudLoggingServiceHttpClient which required these dependencies
70685f6 to
9558829
Compare
| return executor; | ||
| } | ||
|
|
||
| @Bean("cloudLoggingServiceAsyncExecutor") |
There was a problem hiding this comment.
Where is this cloudLoggingServiceAsyncExecutor used?
| auditLog = new CloudLoggingServiceConfigurationAuditLog(auditLoggingFacade); | ||
| } | ||
|
|
||
| // --- logCreateLoggingConfiguration --- |
There was a problem hiding this comment.
remove this?
| assertEquals(NAMESPACE, identifiers.get("namespace")); | ||
| } | ||
|
|
||
| // --- logDeleteLoggingConfiguration --- |
There was a problem hiding this comment.
remove these kinds of comments
| @NamedQuery(name = LoggingConfigurationDto.NamedQueries.FIND_BY_MTA_SPACE_ID_NAMESPACE, query = "SELECT c FROM LoggingConfigurationDto c WHERE c.mtaSpace = :mtaSpace AND c.mtaId = :mtaId AND c.namespace = :namespace") | ||
| @NamedQuery(name = LoggingConfigurationDto.NamedQueries.FIND_BY_MTA_SPACE_ID_NULL_NAMESPACE, query = "SELECT c FROM LoggingConfigurationDto c WHERE c.mtaSpace = :mtaSpace AND c.mtaId = :mtaId AND c.namespace IS NULL") | ||
| @NamedQuery(name = LoggingConfigurationDto.NamedQueries.FIND_ALL_BY_MTA_SPACE_ID, query = "SELECT c FROM LoggingConfigurationDto c WHERE c.mtaSpaceId = :mtaSpaceId") |
There was a problem hiding this comment.
Can this queries be created by chaining the methods?
| @Column(name = TableColumnNames.CLOUD_LOGGING_NAMESPACE) | ||
| private String namespace; | ||
|
|
||
| @Column(name = TableColumnNames.CLOUD_LOGGING_SERVICE_INSTANCE_NAME, nullable = false, length = 50) |
There was a problem hiding this comment.
Let's remove this length enforcements? Same for others
| private static final long MAX_LIMIT_REQUEST_SIZE_BYTES = 3 * 1024 * 1024 + 512 * 1024; | ||
|
|
||
| private static final Map<String, WebClient> clientCache = Collections.synchronizedMap( | ||
| new LinkedHashMap<>(CLIENT_CACHE_MAX_SIZE, 0.75f, true) { |
There was a problem hiding this comment.
Can we use our impl of CachedMap?
There was a problem hiding this comment.
Its in the core package so it is not accessible
|
|
||
| INFO, WARN, DEBUG, ERROR, TRACE; | ||
|
|
||
| private static final Map<LogLevel, List<LogLevel>> logLevelLoggingType = setupLogLevels(); |
| sendLogsToCloudLoggingService(externalOperationLogEntryBatches, cloudLoggingServiceWebClient, loggingConfiguration); | ||
| } | ||
|
|
||
| public List<OperationLogEntry> getUnsendProcessLogs(LoggingConfiguration loggingConfiguration) { |
There was a problem hiding this comment.
this class does too many things and should be split
There was a problem hiding this comment.
still applicable?
| // NOTE: extractMessage strips one trailing character after .trim() — likely a bug | ||
| // (the -1 was probably meant to handle a trailing newline that .trim() already removes). | ||
| // The test pins current behavior; flag for follow-up. |
There was a problem hiding this comment.
What is this?
| if (areCloudLoggingParametersInvalid(serviceInstanceName, serviceKeyName)) { | ||
| return handleMissingServiceKey(isFailSafe, null); | ||
| } | ||
| CloudControllerClient client = calculateExternalLoggingServiceConfiguration(destinationOrg, destinationSpace); | ||
| try { | ||
| CloudServiceKey loggingServiceKey = client.getServiceKey(serviceInstanceName, serviceKeyName); | ||
| if (loggingServiceKey != null) { | ||
| return loggingServiceKey; | ||
| } |
There was a problem hiding this comment.
These methods return null or throw exception
| super(entityManagerFactory); | ||
| } | ||
|
|
||
| public void storeCloudLoggingServiceConfiguration(LoggingConfiguration loggingConfiguration) { |
There was a problem hiding this comment.
is this method needed, as it just invokes add(loggingConfiguration)?
| public LoggingConfiguration getCloudLoggingServiceConfiguration(String mtaSpace, String mtaId, String namespace) { | ||
| try (EntityManager manager = createEntityManager()) { | ||
| return findByMtaSpaceIdAndNamespace(manager, mtaSpace, mtaId, namespace).getResultStream() | ||
| .findFirst() | ||
| .map(mapper::fromDto) | ||
| .orElse(null); | ||
| } | ||
| } | ||
|
|
||
| public void deleteCloudLoggingServiceConfiguration(String id) { | ||
| try (EntityManager manager = createEntityManager()) { | ||
| manager.getTransaction() | ||
| .begin(); | ||
| LoggingConfigurationDto dto = manager.find(LoggingConfigurationDto.class, id); | ||
| if (dto != null) { | ||
| manager.remove(dto); | ||
| } | ||
| manager.getTransaction() | ||
| .commit(); | ||
| } |
There was a problem hiding this comment.
Instead of having so many methods, can we remove these and reuse the pattern from other services like:
public AccessTokenQuery createQuery() {
return new AccessTokenQueryImpl(createEntityManager(), accessTokenMapper);
}
return accessTokenService.createQuery()
.userGuid(userGuid)
.orderByExpiresAt(OrderDirection.DESCENDING)
.list();
| import org.springframework.web.reactive.function.client.WebClient; | ||
|
|
||
| @Named("cloudLoggingServiceWebClientCache") | ||
| public class CloudLoggingServiceWebClientCache { |
There was a problem hiding this comment.
Instead of implementing a new cache. Can you reuse the CachedMap from our code to store the web clients?
| LoggingConfiguration loggingConfiguration = cloudLoggingServiceConfigurationService.getCloudLoggingServiceConfiguration( | ||
| context.getVariable(Variables.SPACE_NAME), context.getVariable(Variables.MTA_ID), context.getVariable(Variables.MTA_NAMESPACE)); | ||
|
|
||
| if (loggingConfiguration != null) { |
There was a problem hiding this comment.
as we are trying to get the cloud logging configuration, I guess it is ok to log that it is being fetched. Can we remove the null check here?
| sendLogsToCloudLoggingService(externalOperationLogEntryBatches, cloudLoggingServiceWebClient, loggingConfiguration); | ||
| } | ||
|
|
||
| public List<OperationLogEntry> getUnsendProcessLogs(LoggingConfiguration loggingConfiguration) { |
There was a problem hiding this comment.
still applicable?
| } | ||
| } | ||
|
|
||
| public void removeClientFromCache(String operationId) { |
There was a problem hiding this comment.
what happens with processes that are not in final state? like ERROR?
| private final CloudLoggingServiceWebClientCache webClientCache; | ||
|
|
||
| public CloudLoggingServiceHttpClient() { | ||
| this(new CloudLoggingServiceWebClientFactory(), new CloudLoggingServiceWebClientCache()); |
There was a problem hiding this comment.
these classes are beans and still created with new?
There was a problem hiding this comment.
This is for the unit tests
| CloudLoggingServiceUtil.logErrorOrThrowExceptionBasedOnFailSafe(loggingConfiguration, LOGGER, | ||
| Messages.FAILED_TO_SEND_LOG_MESSAGE_TO_CLS); | ||
| } | ||
| } catch (RuntimeException e) { |
There was a problem hiding this comment.
still applicable?
| import org.slf4j.LoggerFactory; | ||
|
|
||
| @Named("operationLogsExporter") | ||
| public class OperationLogsExporter { |
There was a problem hiding this comment.
Split this class in multiple classes because it does too many things.
| import org.cloudfoundry.multiapps.controller.core.Messages; | ||
| import org.cloudfoundry.multiapps.controller.core.auditlogging.model.AuditLogConfiguration; | ||
| import org.cloudfoundry.multiapps.controller.core.auditlogging.model.ConfigurationChangeActions; | ||
| import org.cloudfoundry.multiapps.controller.persistence.model.LoggingConfiguration; |
There was a problem hiding this comment.
I think it will be better if we move this class in the internal project (to be extended in the internal project)
| import org.cloudfoundry.multiapps.controller.core.auditlogging.model.ConfigurationChangeActions; | ||
| import org.cloudfoundry.multiapps.controller.persistence.model.LoggingConfiguration; | ||
|
|
||
| public class CloudLoggingServiceConfigurationAuditLog { |
There was a problem hiding this comment.
I don't see any changes here.
| private CloudLoggingServiceConfigurationService cloudLoggingServiceConfigurationService; | ||
| @Mock | ||
| private CloudLoggingServiceConfigurationAuditLog cloudLoggingServiceConfigurationAuditLog; | ||
| @InjectMocks |
There was a problem hiding this comment.
This annotation can be removed now when mocks are injected through the constructor.
| super(entityManagerFactory); | ||
| } | ||
|
|
||
| public void storeCloudLoggingServiceConfiguration(LoggingConfiguration loggingConfiguration) { |
There was a problem hiding this comment.
I think we can skip this method and just invoke directly add method.
| } | ||
| } | ||
|
|
||
| public void updateCloudLoggingServiceConfiguration(LoggingConfiguration loggingConfiguration) { |
There was a problem hiding this comment.
Why don't use standard update method from base class?
|
|
||
| public List<LoggingConfiguration> getAllCloudLoggingServiceConfigurationsFromSpace(String spaceId) { | ||
| try (EntityManager manager = createEntityManager()) { | ||
| return manager.createQuery("SELECT c FROM LoggingConfigurationDto c WHERE c.mtaSpaceId = :mtaSpaceId", |
There was a problem hiding this comment.
Why still is use pure SQL query when you can add criteria buiilder class to filter based on different fields. Take a look at OperationQueryImpl class how it is implemented and used.
| .trim(); | ||
| if (trimmed.isEmpty()) { | ||
| return message; | ||
| } else { |
There was a problem hiding this comment.
else part can be removed and just return the value without any else block
| public class CloudLoggingServiceWebClientCache { | ||
|
|
||
| private static final Logger LOGGER = LoggerFactory.getLogger(CloudLoggingServiceWebClientCache.class); | ||
| private final Map<String, WebClient> CLIENT_CACHE = Collections.synchronizedMap(new HashMap<>()); |
There was a problem hiding this comment.
Why don't reuse org.cloudfoundry.multiapps.controller.core.model.CachedMap . I have some memories that long time ago we have using Collections.synchronizedMap but there is big performance degradation. ConcurrentHashMap provided by Spring is another recommended map used for concurrency.
|
|
||
| @Test | ||
| void testDeleteConfiguration_nonExistentIdDoesNotThrow() { | ||
| service.deleteCloudLoggingServiceConfiguration("nonexistent-id"); |
There was a problem hiding this comment.
still missing assertion
| converter = new CloudLoggingServiceMessageConverter(); | ||
| } | ||
|
|
||
| // --- extractLogName --- |
| flowableFacade.setVariableInParentProcessXSA(execution, variableName, value); | ||
| } | ||
|
|
||
| protected boolean hasSuperExecution(DelegateExecution execution) { |
There was a problem hiding this comment.
Check method isRootProcess in the same class. I think it's doing the same thing.




No description provided.