diff --git a/appserver/connectors/connectors-runtime/src/main/java/com/sun/enterprise/connectors/util/ConnectionPoolObjectsUtils.java b/appserver/connectors/connectors-runtime/src/main/java/com/sun/enterprise/connectors/util/ConnectionPoolObjectsUtils.java index 365ec0e6419..6b2d7dc71ed 100644 --- a/appserver/connectors/connectors-runtime/src/main/java/com/sun/enterprise/connectors/util/ConnectionPoolObjectsUtils.java +++ b/appserver/connectors/connectors-runtime/src/main/java/com/sun/enterprise/connectors/util/ConnectionPoolObjectsUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 Contributors to the Eclipse Foundation + * Copyright (c) 2022, 2025 Contributors to the Eclipse Foundation * Copyright (c) 1997, 2020 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the @@ -395,7 +395,9 @@ private static boolean toBoolean(Object prop, boolean defaultVal) { public static int getTransactionSupportFromRaXml(String rarName) throws ConnectorRuntimeException { ConnectorDescriptor descriptor = ConnectorRuntime.getRuntime().getConnectorDescriptor(rarName); - String txSupport = descriptor.getOutboundResourceAdapter().getTransSupport(); + String txSupport = descriptor == null || descriptor.getOutboundResourceAdapter() == null + ? null + : descriptor.getOutboundResourceAdapter().getTransSupport(); return parseTransactionSupportString(txSupport); } } diff --git a/appserver/connectors/connectors-runtime/src/main/java/com/sun/enterprise/resource/allocator/AbstractConnectorAllocator.java b/appserver/connectors/connectors-runtime/src/main/java/com/sun/enterprise/resource/allocator/AbstractConnectorAllocator.java index c6eb4b65bab..1a2e4e43158 100644 --- a/appserver/connectors/connectors-runtime/src/main/java/com/sun/enterprise/resource/allocator/AbstractConnectorAllocator.java +++ b/appserver/connectors/connectors-runtime/src/main/java/com/sun/enterprise/resource/allocator/AbstractConnectorAllocator.java @@ -52,7 +52,7 @@ * @author Sivakumar Thyagarajan */ public abstract class AbstractConnectorAllocator implements ResourceAllocator { - protected final static Logger LOG = LogDomains.getLogger(AbstractConnectorAllocator.class,LogDomains.RSR_LOGGER); + protected static final Logger LOG = LogDomains.getLogger(AbstractConnectorAllocator.class,LogDomains.RSR_LOGGER); protected PoolManager poolMgr; protected ResourceSpec spec; diff --git a/appserver/connectors/connectors-runtime/src/main/java/com/sun/enterprise/resource/allocator/LocalTxConnectorAllocator.java b/appserver/connectors/connectors-runtime/src/main/java/com/sun/enterprise/resource/allocator/LocalTxConnectorAllocator.java index d95e4f9bcb1..f85def83867 100644 --- a/appserver/connectors/connectors-runtime/src/main/java/com/sun/enterprise/resource/allocator/LocalTxConnectorAllocator.java +++ b/appserver/connectors/connectors-runtime/src/main/java/com/sun/enterprise/resource/allocator/LocalTxConnectorAllocator.java @@ -40,6 +40,8 @@ import javax.security.auth.Subject; import javax.transaction.xa.XAResource; +import static java.util.logging.Level.FINEST; + /** * LocalTransaction Connector Allocator, used for transaction level: * {@link com.sun.appserv.connectors.internal.api.ConnectorConstants#LOCAL_TRANSACTION_INT}. @@ -51,7 +53,7 @@ public class LocalTxConnectorAllocator extends AbstractConnectorAllocator { private static final String COMMIT = "COMMIT"; private static final String ROLLBACK = "ROLLBACK"; - private static String transactionCompletionMode = System + private static final String TX_COMPLETION_MODE = System .getProperty("com.sun.enterprise.in-progress-local-transaction.completion-mode"); private final boolean shareable; @@ -69,6 +71,10 @@ public LocalTxConnectorAllocator(PoolManager poolMgr, this.shareable = shareable; } + @Override + public boolean shareableWithinComponent() { + return shareable; + } @Override public ResourceHandle createResource() throws PoolingException { @@ -90,68 +96,56 @@ public ResourceHandle createResource() throws PoolingException { } @Override - public void fillInResourceObjects(ResourceHandle resource) - throws PoolingException { + public void fillInResourceObjects(ResourceHandle handle) throws PoolingException { try { - ManagedConnection mc = resource.getResource(); + ManagedConnection mc = handle.getResource(); Object con = mc.getConnection(subject, reqInfo); - ConnectorXAResource xares = (ConnectorXAResource) resource.getXAResource(); + ConnectorXAResource xares = (ConnectorXAResource) handle.getXAResource(); xares.setUserHandle(con); - resource.fillInResourceObjects(con, xares); + handle.fillInResourceObjects(con, xares); } catch (ResourceException ex) { throw new PoolingException(ex); } } @Override - public void destroyResource(ResourceHandle resource) - throws PoolingException { + public void destroyResource(ResourceHandle handle) throws PoolingException { try { - ManagedConnection mc = resource.getResource(); - XAResource xares = resource.getXAResource(); - forceTransactionCompletion(xares); - mc.destroy(); - LOG.finest("destroyResource for LocalTxConnectorAllocator done"); - + ManagedConnection connection = handle.getResource(); + XAResource xaResource = handle.getXAResource(); + forceTransactionCompletion(xaResource); + connection.destroy(); + LOG.log(FINEST, "Connection was destroyed: {0}", connection); } catch (Exception ex) { throw new PoolingException(ex); } } - @Override - public boolean shareableWithinComponent() { - return shareable; - } - private void forceTransactionCompletion(XAResource xares) throws SystemException { - if(transactionCompletionMode != null){ - if(xares instanceof ConnectorXAResource){ - ConnectorXAResource connectorXARes = (ConnectorXAResource)xares; - JavaEETransaction j2eetran = connectorXARes.getAssociatedTransaction(); - if(j2eetran != null && j2eetran.isLocalTx()){ - if(j2eetran.getStatus() == (Status.STATUS_ACTIVE)){ - try{ - if(transactionCompletionMode.equalsIgnoreCase(COMMIT)){ - if(LOG.isLoggable(Level.FINEST)){ - LOG.log(Level.FINEST,"Transaction Completion Mode for LocalTx resource is " + - "set as COMMIT, committing transaction"); - } - j2eetran.commit(); - }else if(transactionCompletionMode.equalsIgnoreCase(ROLLBACK)){ - if(LOG.isLoggable(Level.FINEST)){ - LOG.log(Level.FINEST,"Transaction Completion Mode for LocalTx resource is " + - "set as ROLLBACK, rolling back transaction"); - } - j2eetran.rollback(); - }else{ - LOG.log(Level.WARNING,"Unknown transaction completion mode, no action made"); - } - }catch(Exception e){ - LOG.log(Level.WARNING, "Failure while forcibily completing an incomplete, " + - "local transaction ", e); - } - } + private void forceTransactionCompletion(XAResource xaResource) throws SystemException { + if (TX_COMPLETION_MODE == null) { + return; + } + if (xaResource instanceof ConnectorXAResource) { + ConnectorXAResource connectorXARes = (ConnectorXAResource) xaResource; + JavaEETransaction j2eetran = connectorXARes.getAssociatedTransaction(); + if (j2eetran == null || !j2eetran.isLocalTx() || j2eetran.getStatus() != Status.STATUS_ACTIVE) { + return; + } + try { + if (COMMIT.equalsIgnoreCase(TX_COMPLETION_MODE)) { + LOG.log(FINEST, "Transaction Completion Mode for LocalTx resource is set as COMMIT," + + " committing transaction"); + j2eetran.commit(); + } else if (ROLLBACK.equalsIgnoreCase(TX_COMPLETION_MODE)) { + LOG.log(FINEST, "Transaction Completion Mode for LocalTx resource is set as ROLLBACK," + + " rolling back transaction"); + j2eetran.rollback(); + } else { + LOG.log(Level.WARNING, "Unknown transaction completion mode, no action made"); } + } catch (Exception e) { + LOG.log(Level.WARNING, "Failure while forcibily completing an incomplete, local transaction ", e); } } } diff --git a/appserver/connectors/connectors-runtime/src/main/java/com/sun/enterprise/resource/pool/AssocWithThreadResourcePool.java b/appserver/connectors/connectors-runtime/src/main/java/com/sun/enterprise/resource/pool/AssocWithThreadResourcePool.java index 0daa3abe621..fae1af2816d 100644 --- a/appserver/connectors/connectors-runtime/src/main/java/com/sun/enterprise/resource/pool/AssocWithThreadResourcePool.java +++ b/appserver/connectors/connectors-runtime/src/main/java/com/sun/enterprise/resource/pool/AssocWithThreadResourcePool.java @@ -199,15 +199,15 @@ protected synchronized void freeUnenlistedResource(ResourceHandle resourceHandle /** * destroys the resource * - * @param resourceHandle resource to be destroyed + * @param handle resource to be destroyed */ @Override - public void deleteResource(ResourceHandle resourceHandle) { + public void deleteResource(ResourceHandle handle) { try { - super.deleteResource(resourceHandle); + super.deleteResource(handle); } finally { - if (resourceHandle instanceof AssocWithThreadResourceHandle) { - ((AssocWithThreadResourceHandle) resourceHandle).setUnusable(); + if (handle instanceof AssocWithThreadResourceHandle) { + ((AssocWithThreadResourceHandle) handle).setUnusable(); } } } @@ -215,18 +215,18 @@ public void deleteResource(ResourceHandle resourceHandle) { /** * to associate a resource with the thread * - * @param h ResourceHandle + * @param handle ResourceHandle */ - private void setInThreadLocal(AssocWithThreadResourceHandle h) { - if (h == null) { + private void setInThreadLocal(AssocWithThreadResourceHandle handle) { + if (handle == null) { return; } - h.lock(); + handle.lock(); try { - h.setAssociated(true); - localResource.set(h); + handle.setAssociated(true); + localResource.set(handle); } finally { - h.unlock(); + handle.unlock(); } } @@ -250,7 +250,7 @@ private ResourceHandle resolvePossibleRemoval(ResourceHandle handle) { } } - private synchronized ResourceHandle searchFreeUnenlisted(ResourceAllocator alloc) { + private synchronized ResourceHandle searchFreeUnenlisted(ResourceAllocator allocator) { for (ResourceHandle handle : dataStructure.getAllResources()) { handle.lock(); try { @@ -262,7 +262,7 @@ private synchronized ResourceHandle searchFreeUnenlisted(ResourceAllocator alloc if (handle.getResourceState().isEnlisted() || handle.getResourceState().isBusy() || handle.hasConnectionErrorOccurred() || ((AssocWithThreadResourceHandle) handle).isUnusable() - || !matchConnection(handle, alloc)) { + || !matchConnection(handle, allocator)) { continue; } setResourceStateToBusy(handle); diff --git a/appserver/connectors/connectors-runtime/src/main/java/com/sun/enterprise/resource/pool/ConnectionPool.java b/appserver/connectors/connectors-runtime/src/main/java/com/sun/enterprise/resource/pool/ConnectionPool.java index d7b4ba6e077..58bbfa87aa1 100644 --- a/appserver/connectors/connectors-runtime/src/main/java/com/sun/enterprise/resource/pool/ConnectionPool.java +++ b/appserver/connectors/connectors-runtime/src/main/java/com/sun/enterprise/resource/pool/ConnectionPool.java @@ -31,13 +31,13 @@ import com.sun.enterprise.resource.pool.waitqueue.PoolWaitQueue; import com.sun.enterprise.resource.pool.waitqueue.PoolWaitQueueFactory; import com.sun.enterprise.transaction.api.JavaEETransaction; -import com.sun.logging.LogDomains; import jakarta.resource.ResourceException; import jakarta.resource.spi.ManagedConnection; import jakarta.resource.spi.RetryableUnavailableException; import jakarta.transaction.Transaction; +import java.lang.System.Logger; import java.util.ArrayList; import java.util.Hashtable; import java.util.Iterator; @@ -45,7 +45,6 @@ import java.util.Set; import java.util.Timer; import java.util.concurrent.locks.ReentrantLock; -import java.util.logging.Logger; import javax.naming.NamingException; @@ -53,11 +52,10 @@ import static com.sun.appserv.connectors.internal.spi.BadConnectionEventListener.POOL_RECONFIGURED_ERROR_CODE; import static com.sun.enterprise.connectors.service.ConnectorAdminServiceUtils.getReservePrefixedJNDINameForPool; -import static java.util.logging.Level.FINE; -import static java.util.logging.Level.FINEST; -import static java.util.logging.Level.INFO; -import static java.util.logging.Level.SEVERE; -import static java.util.logging.Level.WARNING; +import static java.lang.System.Logger.Level.DEBUG; +import static java.lang.System.Logger.Level.ERROR; +import static java.lang.System.Logger.Level.TRACE; +import static java.lang.System.Logger.Level.WARNING; /** * Connection Pool for Connector & JDBC resources
@@ -66,7 +64,7 @@ */ public class ConnectionPool implements ResourcePool, ConnectionLeakListener, ResourceHandler, PoolProperties { - private static final Logger LOG = LogDomains.getLogger(ConnectionPool.class, LogDomains.RSR_LOGGER); + private static final Logger LOG = System.getLogger(ConnectionPool.class.getName()); // pool life-cycle config properties /** @@ -256,7 +254,7 @@ public ConnectionPool(PoolInfo poolInfo, Hashtable env) throws PoolingExce poolTxHelper = new PoolTxHelper(this.poolInfo); gateway = ResourceGateway.getInstance(resourceGatewayClass); - LOG.log(FINE, "Connection Pool: {0}", this.poolInfo); + LOG.log(DEBUG, "Connection Pool: {0}", this.poolInfo); } protected void initializePoolWaitQueue() throws PoolingException { @@ -275,6 +273,19 @@ protected void initializeResourceSelectionStrategy() { // do nothing } + + /** + * Overridden in {@link AssocWithThreadResourcePool} to fetch the resource cached + * in the ThreadLocal. Here it simply returns null. + * + * @param spec the ResourceSpec used to locate the correct resource pool + * @param alloc ResourceAllocator to create a resource + * @return ResourceHandle resource from ThreadLocal + */ + protected ResourceHandle prefetch(ResourceSpec spec, ResourceAllocator alloc) { + return null; + } + private void setPoolConfiguration(Hashtable env) throws PoolingException { ConnectorConnectionPool poolResource = getPoolConfigurationFromJndi(env); @@ -321,14 +332,18 @@ protected ConnectorConnectionPool getPoolConfigurationFromJndi(Hashtable e } } - // This method does not need to be synchronized since all caller methods are, - // but it does not hurt. Just to be safe. - protected synchronized void initPool(ResourceAllocator allocator) throws PoolingException { + /** + * Synchronized initialization of the pool. + * + * @param resourceAllocator + * @throws PoolingException + */ + protected synchronized void ensurePoolInitialized(ResourceAllocator resourceAllocator) throws PoolingException { if (poolInitialized) { return; } - - this.allocator = allocator; + LOG.log(DEBUG, "Initializing pool {0} with resource allocator: {1}", poolInfo, resourceAllocator); + this.allocator = resourceAllocator; createResources(this.allocator, steadyPoolSize - dataStructure.getResourcesSize()); @@ -367,7 +382,7 @@ protected void scheduleResizerTask() { } resizerTaskTimer.scheduleAtFixedRate(resizerTask, idletime, idletime); - LOG.log(FINE, "Scheduled resizer task with the idle time {0} ms", idletime); + LOG.log(DEBUG, "Scheduled resizer task with the idle time {0} ms", idletime); } protected Resizer initializeResizer() { @@ -390,7 +405,7 @@ private void addResource(ResourceAllocator alloc) throws PoolingException { } } - LOG.log(FINE, "Pool: resource added"); + LOG.log(DEBUG, "Pool: resource added"); } /** @@ -427,181 +442,147 @@ protected void setResourceStateToBusy(ResourceHandle resourceHandle) { * max-connection-wait-time-in-millis has expired. */ @Override - public ResourceHandle getResource(ResourceSpec spec, ResourceAllocator alloc, Transaction transaction) throws PoolingException, RetryableUnavailableException { - // Note: this method should not be synchronized or the - // startTime would be incorrect for threads waiting to enter - - /* - * Here are all the comments for the method put together for easy reference. 1. // - Try to get a free resource. Note: - * internalGetResource() // will create a new resource if none is free and the max has // not been reached. // - If - * can't get one, get on the wait queue. // - Repeat this until maxWaitTime expires. // - If maxWaitTime == 0, repeat - * indefinitely. - * - * 2. //the doFailAllConnectionsProcessing method would already //have been invoked by now. //We simply go ahead and - * create a new resource here //from the allocator that we have and adjust the resources //list accordingly so as to not - * exceed the maxPoolSize ever //(i.e if steadyPoolSize == maxPoolSize ) ///Also since we are creating the resource out - * of the allocator //that we came into this method with, we need not worry about //matching - */ - ResourceHandle result = null; - - long startTime = System.currentTimeMillis(); - long elapsedWaitTime; - long remainingWaitTime = 0; + public ResourceHandle getResource(ResourceSpec spec, ResourceAllocator alloc, Transaction transaction) + throws PoolingException, RetryableUnavailableException { + ResourceHandle handle = queueForResource(spec, alloc, transaction); + if (handle != null) { + alloc.fillInResourceObjects(handle); + } + return handle; + } + private ResourceHandle queueForResource(ResourceSpec spec, ResourceAllocator alloc, Transaction transaction) + throws PoolingException, RetryableUnavailableException { + final long startTime = System.currentTimeMillis(); while (true) { - if (gateway.allowed()) { - // See comment #1 above - JavaEETransaction javaEETransaction = ((JavaEETransaction) transaction); - final Set resourcesSet = javaEETransaction == null ? null : javaEETransaction.getResources(poolInfo); - - // Allow when the pool is not blocked or at-least one resource is - // already obtained in the current transaction. - if (!blocked || (resourcesSet != null && !resourcesSet.isEmpty())) { - try { - result = internalGetResource(spec, alloc, transaction); - } finally { - gateway.acquiredResource(); - } - } - } - - if (result != null) { - // got one, return it + final ResourceHandle handle = tryToAcquireHandle(spec, alloc, transaction); + final long elapsedTime = System.currentTimeMillis() - startTime; + if (handle != null) { + gateway.acquiredResource(); if (poolLifeCycleListener != null) { - poolLifeCycleListener.connectionAcquired(result.getId()); - elapsedWaitTime = System.currentTimeMillis() - startTime; - poolLifeCycleListener.connectionRequestServed(elapsedWaitTime); - if (LOG.isLoggable(FINE)) { - LOG.log(FINE, - "Resource Pool: elapsed time (ms) to get connection for [" + spec + "] : " + elapsedWaitTime); - } + poolLifeCycleListener.connectionAcquired(handle.getId()); + poolLifeCycleListener.connectionRequestServed(elapsedTime); + LOG.log(DEBUG, "Elapsed time to get connection for {0}: {1} ms", spec, elapsedTime); } - // got one - seems we are not doing validation or matching - // return it - break; + return handle; } - // did not get a resource. - if (maxWaitTime > 0) { - elapsedWaitTime = System.currentTimeMillis() - startTime; - if (elapsedWaitTime < maxWaitTime) { - // time has not expired, determine remaining wait time. - remainingWaitTime = maxWaitTime - elapsedWaitTime; - } else if (!blocked) { - // wait time has expired - if (poolLifeCycleListener != null) { - poolLifeCycleListener.connectionTimedOut(); + final long remainingTime = checkRemainingTime(elapsedTime); + if (blocked) { + waitForReconfiguration(); + } + // add to wait-queue + final Object waitMonitor = new Object(); + if (poolLifeCycleListener != null) { + poolLifeCycleListener.connectionRequestQueued(); + } + synchronized (waitMonitor) { + waitQueue.addToQueue(waitMonitor); + try { + LOG.log(TRACE, "Getting on wait queue"); + waitMonitor.wait(remainingTime); + } catch (InterruptedException e) { + LOG.log(TRACE, "Waiting interrupted.", e); + Thread.currentThread().interrupt(); + } finally { + LOG.log(TRACE, "Removing wait monitor from queue: {0}", waitMonitor); + if (waitQueue.removeFromQueue(waitMonitor) && poolLifeCycleListener != null) { + poolLifeCycleListener.connectionRequestDequeued(); } - throw new PoolingException("No available resources and wait time " + maxWaitTime + " ms expired."); } } + } + } - if (!blocked) { - // add to wait-queue - Object waitMonitor = new Object(); - if (poolLifeCycleListener != null) { - poolLifeCycleListener.connectionRequestQueued(); - } - synchronized (waitMonitor) { - waitQueue.addToQueue(waitMonitor); - try { - LOG.log(FINE, "Resource Pool: getting on wait queue"); - waitMonitor.wait(remainingWaitTime); + private ResourceHandle tryToAcquireHandle(ResourceSpec spec, ResourceAllocator resourceAllocator, + Transaction transaction) throws PoolingException { + if (!gateway.allowed()) { + LOG.log(TRACE, "Returning null because: gateway not allowed."); + return null; + } + final JavaEETransaction jeeTransaction = ((JavaEETransaction) transaction); + final Set resources = jeeTransaction == null ? null : jeeTransaction.getResources(poolInfo); + if (blocked && (resources == null || resources.isEmpty())) { + LOG.log(TRACE, "Returning null because: blocked and no resources available using JEE Transaction."); + return null; + } + // Allow when the pool is not blocked or at-least one resource is + // already obtained in the current transaction. + return acquireHandle(spec, resourceAllocator, transaction); + } - } catch (InterruptedException ex) { - // Could be system shutdown. - Thread.currentThread().interrupt(); - break; - } - // Try to remove in case that the monitor has timed out. We don't expect the queue to grow to great numbers - // so the overhead for removing inexistant objects is low. - LOG.log(FINE, "removing wait monitor from queue: {0}", waitMonitor); + private ResourceHandle acquireHandle(ResourceSpec resourceSpec, ResourceAllocator resourceAllocator, + Transaction transaction) throws PoolingException { - if (waitQueue.removeFromQueue(waitMonitor)) { - if (poolLifeCycleListener != null) { - poolLifeCycleListener.connectionRequestDequeued(); - } - } - } - } else { - // Add to reconfig-wait-queue - Object reconfigWaitMonitor = new Object(); - synchronized (reconfigWaitMonitor) { - reconfigWaitQueue.addToQueue(reconfigWaitMonitor); - try { - if (reconfigWaitTime > 0) { - LOG.log(FINEST, "[DRC] getting into reconfig wait queue for time [{0}]", reconfigWaitTime); - reconfigWaitMonitor.wait(reconfigWaitTime); - } - } catch (InterruptedException ex) { - // Could be system shutdown. - Thread.currentThread().interrupt(); - break; - } + ensurePoolInitialized(resourceAllocator); - // Try to remove in case that the monitor has timed - // out. We don't expect the queue to grow to great numbers - // so the overhead for removing inexistent objects is low. - LOG.log(FINEST, "[DRC] removing wait monitor from reconfig-wait-queue: {0}", reconfigWaitMonitor); + LOG.log(DEBUG, "Acquiring handle for {0}", resourceSpec); - reconfigWaitQueue.removeFromQueue(reconfigWaitMonitor); + ResourceHandle handle = getResourceFromTransaction(transaction, resourceAllocator, resourceSpec); + if (handle != null) { + return handle; + } - LOG.log(FINEST, "[DRC] throwing Retryable-Unavailable-Exception"); - RetryableUnavailableException rue = new RetryableUnavailableException( - "Pool Reconfigured, Connection Factory can retry the lookup"); - rue.setErrorCode(POOL_RECONFIGURED_ERROR_CODE); + handle = prefetch(resourceSpec, resourceAllocator); + if (handle != null) { + return handle; + } - throw rue; - } + // We didn't get a connection that is already enlisted in the current transaction (if any). + handle = getUnenlistedResource(resourceSpec, resourceAllocator); + if (handle != null) { + handle.getResourceState().incrementUsageCount(); + if (poolLifeCycleListener != null) { + poolLifeCycleListener.connectionUsed(handle.getId()); + poolLifeCycleListener.decrementNumConnFree(); } } - - alloc.fillInResourceObjects(result); - return result; + return handle; } - /** - * Overridden in AssocWithThreadResourcePool to fetch the resource cached in the ThreadLocal In ConnectionPool this - * simply returns null. - * - * @param spec the ResourceSpec used to locate the correct resource pool - * @param alloc ResourceAllocator to create a resource - * @return ResourceHandle resource from ThreadLocal - */ - protected ResourceHandle prefetch(ResourceSpec spec, ResourceAllocator alloc) { - return null; + private void waitForReconfiguration() throws RetryableUnavailableException { + // Add to reconfig-wait-queue + final Object reconfigWaitMonitor = new Object(); + synchronized (reconfigWaitMonitor) { + reconfigWaitQueue.addToQueue(reconfigWaitMonitor); + try { + if (reconfigWaitTime > 0) { + LOG.log(TRACE, "Getting into reconfig wait queue for {0} ms", reconfigWaitTime); + reconfigWaitMonitor.wait(reconfigWaitTime); + } + } catch (InterruptedException e) { + // http thread can time out, system shutdown, etc. + LOG.log(TRACE, "Waiting interrupted.", e); + Thread.currentThread().interrupt(); + return; + } finally { + // Wait could time out or could be notified + LOG.log(TRACE, "Removing wait monitor from reconfig-wait-queue: {0}", reconfigWaitMonitor); + reconfigWaitQueue.removeFromQueue(reconfigWaitMonitor); + } + throw new RetryableUnavailableException( + "Pool Reconfigured, Connection Factory can retry the lookup", POOL_RECONFIGURED_ERROR_CODE); + } } - - protected ResourceHandle internalGetResource(ResourceSpec resourceSpec, ResourceAllocator resourceAllocator, - Transaction transaction) throws PoolingException { - if (!poolInitialized) { - initPool(resourceAllocator); + private long checkRemainingTime(final long elapsedTime) throws PoolingException { + if (maxWaitTime <= 0) { + return 0L; } - - ResourceHandle resourceHandle = getResourceFromTransaction(transaction, resourceAllocator, resourceSpec); - if (resourceHandle != null) { - return resourceHandle; + if (elapsedTime < maxWaitTime) { + // time has not expired, determine remaining wait time. + return maxWaitTime - elapsedTime; } - - resourceHandle = prefetch(resourceSpec, resourceAllocator); - if (resourceHandle != null) { - return resourceHandle; + if (blocked) { + return 0L; } - - // We didnt get a connection that is already enlisted in the current transaction (if any). - resourceHandle = getUnenlistedResource(resourceSpec, resourceAllocator); - if (resourceHandle != null) { - resourceHandle.getResourceState().incrementUsageCount(); - if (poolLifeCycleListener != null) { - poolLifeCycleListener.connectionUsed(resourceHandle.getId()); - // Decrement numConnFree - poolLifeCycleListener.decrementNumConnFree(); - } + // Wait time has expired + if (poolLifeCycleListener != null) { + poolLifeCycleListener.connectionTimedOut(); } - return resourceHandle; + throw new PoolingException("No available resources and wait time " + maxWaitTime + " ms expired."); } /** @@ -622,18 +603,16 @@ private ResourceHandle getResourceFromTransaction(Transaction transaction, Resou try { javaEETransaction = (JavaEETransaction) transaction; } catch (ClassCastException e) { - LOG.log(SEVERE, - "Pool: getResource : transaction is not JavaEETransaction but a " + transaction.getClass().getName(), - e); + LOG.log(ERROR, "Transaction is not JavaEETransaction but a " + transaction.getClass(), e); return null; } // case 1. look for free and enlisted in same tx - final Set set = javaEETransaction.getResources(poolInfo); - if (set == null) { + final Set resources = javaEETransaction.getResources(poolInfo); + if (resources == null) { return null; } - final Iterator iter = set.iterator(); + final Iterator iter = resources.iterator(); while (iter.hasNext()) { ResourceHandle resourceHandle = (ResourceHandle) iter.next(); if (resourceHandle.hasConnectionErrorOccurred()) { @@ -866,13 +845,8 @@ private ResourceHandle getUnenlistedResource(ResourceAllocator resourceAllocator resourceFromPool = resourceHandle; break; } - - // Matching, but not shareable. To be returned to the pool. - freeResources.add(resourceHandle); - } else { - // Not matching. To be returned to the pool. - freeResources.add(resourceHandle); } + freeResources.add(resourceHandle); } } finally { // Return all unmatched, free resources @@ -888,20 +862,17 @@ private ResourceHandle getUnenlistedResource(ResourceAllocator resourceAllocator freeResources.clear(); } - if (resourceFromPool != null) { - // Set state to Busy - setResourceStateToBusy(resourceFromPool); - } else { + if (resourceFromPool == null) { // Set state to Busy via resizePoolAndGetNewResource call resourceFromPool = resizePoolAndGetNewResource(resourceAllocator); + } else { + // Set state to Busy + setResourceStateToBusy(resourceFromPool); } // Resource from the pool must be marked busy when it is returned from the pool if (resourceHandle != null) { makeSureResourceIsBusy(resourceHandle); - } - - if (resourceHandle != null) { // Not expecting an enlisted resource to be returned from the pool makeSureResourceIsNotEnlisted(resourceHandle); } @@ -1035,7 +1006,7 @@ private int purgeResources(int quantity) { int totalResourcesRemoved = 0; int freeResourcesCount = dataStructure.getFreeListSize(); int resourcesCount = (freeResourcesCount >= quantity) ? quantity : freeResourcesCount; - LOG.log(FINE, "Purging resources of size: {0}", resourcesCount); + LOG.log(DEBUG, "Purging resources of size: {0}", resourcesCount); for (int i = resourcesCount - 1; i >= 0; i--) { ResourceHandle resource = dataStructure.getResource(); @@ -1100,7 +1071,7 @@ protected ResourceHandle createSingleResource(ResourceAllocator resourceAllocato count++; ResourceHandle resourceHandle = resourceAllocator.createResource(); long now = System.currentTimeMillis(); - LOG.log(FINE, + LOG.log(DEBUG, () -> "Time taken to create a single resource: " + resourceHandle.getResourceSpec().getResourceId() + " and adding to the pool: " + (now - startTime) + " ms."); if (connectionValidationRequired || validateAtmostEveryIdleSecs) { @@ -1179,7 +1150,7 @@ public void deleteResource(ResourceHandle resourceHandle) { @Override public void resourceClosed(ResourceHandle handle) throws IllegalStateException { - LOG.log(FINE, "Resource was closed, processing handle: {0}", handle); + LOG.log(DEBUG, "Resource was closed, processing handle: {0}", handle); ResourceState state = handle.getResourceState(); if (!state.isBusy()) { @@ -1205,7 +1176,7 @@ public void resourceClosed(ResourceHandle handle) throws IllegalStateException { } // Note handle might already be altered by another thread before it is logged! - LOG.log(FINE, "Resource was freed after its closure: {0}", handle); + LOG.log(DEBUG, "Resource was freed after its closure: {0}", handle); } /** @@ -1215,8 +1186,8 @@ public void resourceClosed(ResourceHandle handle) throws IllegalStateException { */ protected void performMaxConnectionUsageOperation(ResourceHandle handle) { dataStructure.removeResource(handle); - LOG.log(INFO, "Destroying connection {0} since it has reached the maximum usage of: {1}", - new Object[] {handle.getId(), handle.getResourceState().getUsageCount()}); + LOG.log(DEBUG, "Destroying connection {0} since it has reached the maximum usage of: {1}", handle.getId(), + handle.getResourceState().getUsageCount()); if (poolLifeCycleListener != null) { poolLifeCycleListener.decrementConnectionUsed(handle.getId()); @@ -1240,7 +1211,7 @@ protected void performMaxConnectionUsageOperation(ResourceHandle handle) { protected void freeUnenlistedResource(ResourceHandle resourceHandle) { // TODO: There is no validation here at all that the resourceHandle.state is already set to unenlisted - LOG.log(FINE, "freeUnenlistedResource handle: {0}", resourceHandle); + LOG.log(DEBUG, "freeUnenlistedResource handle: {0}", resourceHandle); try { getResourceFromPoolAndFreeResourceMethodsLock.lock(); if (cleanupResource(resourceHandle)) { @@ -1298,7 +1269,7 @@ protected boolean cleanupResource(ResourceHandle resource) { @Override public void resourceErrorOccurred(ResourceHandle resourceHandle) throws IllegalStateException { - LOG.log(FINE, "Resource error occurred: {0}", resourceHandle); + LOG.log(DEBUG, "Resource error occurred: {0}", resourceHandle); if (failAllConnections) { // TODO: leakDetector is not updated and isBusy state of this resource is not updated correctly: possible bug. // leakDetector should be updated in the doFailAllConnectionsProcessing method. The resource can be updated here. @@ -1345,7 +1316,7 @@ public void resourceErrorOccurred(ResourceHandle resourceHandle) throws IllegalS } private void doFailAllConnectionsProcessing() { - LOG.log(FINE, "doFailAllConnectionsProcessing()"); + LOG.log(DEBUG, "doFailAllConnectionsProcessing()"); cancelResizerTask(); if (poolLifeCycleListener != null) { poolLifeCycleListener.connectionValidationFailed(dataStructure.getResourcesSize()); @@ -1357,10 +1328,10 @@ private void doFailAllConnectionsProcessing() { try { createResources(allocator, steadyPoolSize); - LOG.log(FINE, "Successfully created new resources."); + LOG.log(DEBUG, "Successfully created new resources."); } catch (PoolingException pe) { // Ignore and hope the resizer does its stuff - LOG.log(FINE, "Could not create " + steadyPoolSize + " resources.", pe); + LOG.log(DEBUG, "Could not create " + steadyPoolSize + " resources.", pe); } scheduleResizerTask(); } @@ -1445,10 +1416,10 @@ protected void notifyWaitingThreads() { } } if (waitMonitor == null) { - LOG.log(FINE, "Wait monitor is null"); + LOG.log(DEBUG, "Wait monitor is null"); } else { synchronized (waitMonitor) { - LOG.log(FINE, "Notifying wait monitor: {0}", waitMonitor); + LOG.log(DEBUG, "Notifying wait monitor: {0}", waitMonitor); waitMonitor.notifyAll(); } } @@ -1462,13 +1433,13 @@ private void incrementNumConnFailedValidation() { @Override public void emptyPool() { - LOG.log(FINE, "Emptying pool {0}", poolInfo.getName()); + LOG.log(DEBUG, "Emptying pool {0}", poolInfo.getName()); dataStructure.removeAll(); } @Override public void emptyFreeConnectionsInPool() { - LOG.log(FINE, "Emptying free connections in the pool {0}", poolInfo.getName()); + LOG.log(DEBUG, "Emptying free connections in the pool {0}", poolInfo.getName()); // TODO this is not completely thread safe, between getResource and removeResource // the dataStructure can be altered by other threads @@ -1518,7 +1489,7 @@ public long getReconfigWaitTime() { @Override public synchronized boolean flushConnectionPool() throws PoolingException { - LOG.log(FINE, "Flushing Connection Pool {0}", poolInfo); + LOG.log(DEBUG, "Flushing Connection Pool {0}", poolInfo); if (!poolInitialized) { throw new PoolingException( @@ -1529,76 +1500,71 @@ public synchronized boolean flushConnectionPool() throws PoolingException { dataStructure.removeAll(); scheduleResizerTask(); increaseSteadyPoolSize(steadyPoolSize); - LOG.log(FINE, "Flush Connection Pool done"); + LOG.log(DEBUG, "Flush Connection Pool done"); return true; } @Override public synchronized void reconfigurePool(ConnectorConnectionPool poolResource) throws PoolingException { - int _idleTime = Integer.parseInt(poolResource.getIdleTimeoutInSeconds()) * 1000; + final int idleTimeParam = Integer.parseInt(poolResource.getIdleTimeoutInSeconds()) * 1000; if (poolInitialized) { - if (_idleTime != idletime && _idleTime != 0) { - idletime = _idleTime; + if (idleTimeParam != this.idletime && idleTimeParam != 0) { + this.idletime = idleTimeParam; scheduleResizerTask(); } - if (_idleTime == 0) { + if (idleTimeParam == 0) { // resizerTask.cancel(); cancelResizerTask(); } } - idletime = _idleTime; - - resizeQuantity = Integer.parseInt(poolResource.getPoolResizeQuantity()); - - maxWaitTime = Integer.parseInt(poolResource.getMaxWaitTimeInMillis()); - // Make sure it's not negative. - if (maxWaitTime < 0) { - maxWaitTime = 0; + this.idletime = idleTimeParam; + this.resizeQuantity = Integer.parseInt(poolResource.getPoolResizeQuantity()); + this.maxWaitTime = Integer.parseInt(poolResource.getMaxWaitTimeInMillis()); + if (this.maxWaitTime < 0) { + this.maxWaitTime = 0; } - connectionValidationRequired = poolResource.isIsConnectionValidationRequired(); - failAllConnections = poolResource.isFailAllConnections(); + this.connectionValidationRequired = poolResource.isIsConnectionValidationRequired(); + this.failAllConnections = poolResource.isFailAllConnections(); setAdvancedPoolConfiguration(poolResource); - int _maxPoolSize = Integer.parseInt(poolResource.getMaxPoolSize()); - int oldMaxPoolSize = maxPoolSize; + final int maxPoolSizeParam = Integer.parseInt(poolResource.getMaxPoolSize()); + final int oldMaxPoolSize = this.maxPoolSize; - if (_maxPoolSize < steadyPoolSize) { + if (maxPoolSizeParam < this.steadyPoolSize) { // should not happen, admin must throw exception when this condition happens. // as a precaution set max pool size to steady pool size - maxPoolSize = steadyPoolSize; + this.maxPoolSize = this.steadyPoolSize; } else { - maxPoolSize = _maxPoolSize; + this.maxPoolSize = maxPoolSizeParam; } - if (oldMaxPoolSize != maxPoolSize) { - dataStructure.setMaxSize(maxPoolSize); + if (oldMaxPoolSize != this.maxPoolSize) { + this.dataStructure.setMaxSize(this.maxPoolSize); } - int _steadyPoolSize = Integer.parseInt(poolResource.getSteadyPoolSize()); - int oldSteadyPoolSize = steadyPoolSize; - - if (_steadyPoolSize > maxPoolSize) { + final int steadyPoolSizeParam = Integer.parseInt(poolResource.getSteadyPoolSize()); + final int oldSteadyPoolSize = this.steadyPoolSize; + if (steadyPoolSizeParam > this.maxPoolSize) { // should not happen, admin must throw exception when this condition happens. // as a precaution set steady pool size to max pool size - steadyPoolSize = maxPoolSize; + this.steadyPoolSize = this.maxPoolSize; } else { - steadyPoolSize = _steadyPoolSize; + this.steadyPoolSize = steadyPoolSizeParam; } - if (poolInitialized) { + if (this.poolInitialized) { // In this case we need to kill extra connections in the pool - // For the case where the value is increased, we need not - // do anything - // num resources to kill is decided by the resources in the pool. + // For the case where the value is increased, we need not do anything + // + // The number resources to kill is decided by the resources in the pool. // if we have less than current maxPoolSize resources, we need to // kill less. - int toKill = dataStructure.getResourcesSize() - maxPoolSize; - + final int toKill = this.dataStructure.getResourcesSize() - this.maxPoolSize; if (toKill > 0) { killExtraResources(toKill); } } - reconfigureSteadyPoolSize(oldSteadyPoolSize, _steadyPoolSize); + reconfigureSteadyPoolSize(oldSteadyPoolSize, steadyPoolSizeParam); } protected void reconfigureSteadyPoolSize(int oldSteadyPoolSize, int newSteadyPoolSize) throws PoolingException { @@ -1684,7 +1650,7 @@ public final PoolInfo getPoolInfo() { @Override public synchronized void cancelResizerTask() { - LOG.log(FINE, "Cancelling resizer task."); + LOG.log(DEBUG, "Cancelling resizer task."); if (resizerTask != null) { resizerTask.cancel(); } @@ -1729,7 +1695,7 @@ public void setMaxPoolSize(int size) { killExtraResources(toKill); } catch (Exception re) { // ignore for now - LOG.log(FINE, "setMaxPoolSize:: killExtraResources throws exception!", re); + LOG.log(DEBUG, "setMaxPoolSize:: killExtraResources throws exception!", re); } } } @@ -1765,7 +1731,7 @@ public void reclaimConnection(ResourceHandle handle) { // Entity beans when used in bean managed transaction will face an issue since connections // are destroyed during reclaim. // Stateful session beans will work fine. - LOG.log(INFO, + LOG.log(WARNING, "Reclaiming the leaked connection of pool [{0}] and destroying it so as to avoid both" + " the application that leaked the connection and any other request that can potentially acquire" + " the same connection from the pool end up using the connection at the same time", diff --git a/appserver/connectors/connectors-runtime/src/main/java/com/sun/enterprise/resource/pool/PoolManagerImpl.java b/appserver/connectors/connectors-runtime/src/main/java/com/sun/enterprise/resource/pool/PoolManagerImpl.java index af682d14fc8..efec5e8fbcc 100644 --- a/appserver/connectors/connectors-runtime/src/main/java/com/sun/enterprise/resource/pool/PoolManagerImpl.java +++ b/appserver/connectors/connectors-runtime/src/main/java/com/sun/enterprise/resource/pool/PoolManagerImpl.java @@ -139,21 +139,19 @@ void createAndInitPool(final PoolInfo poolInfo, PoolType poolType, Hashtable "PoolManagerImpl.getResource START, resourceSpec=" + resourceSpec + "\nresourceAllocator=" + + resourceAllocator + "\nclientSecurityInfo=" + clientSecurityInfo); + final Transaction transaction = resourceAllocator.isTransactional() + ? getResourceManager(resourceSpec).getTransaction() + : null; + + final ResourceHandle resourceHandle = getResourceFromPool(resourceSpec, resourceAllocator, clientSecurityInfo, transaction); + LOG.log(FINE, () -> "PoolManagerImpl.getResource handle=" + resourceHandle + ", poolStatus" + + getPoolStatus(resourceSpec.getPoolInfo())); + if (resourceHandle == null) { + throw new PoolingException("No resource handle available for " + resourceSpec.getPoolInfo()); } if (!resourceHandle.supportsLazyAssociation()) { resourceSpec.setLazyAssociatable(false); @@ -168,7 +166,6 @@ public Object getResource(ResourceSpec resourceSpec, ResourceAllocator resourceA managedConnection.associateConnection(connection); } catch (ResourceException e) { putbackDirectToPool(resourceHandle, resourceSpec.getPoolInfo()); - throw new PoolingException(e.getMessage(), e); } } @@ -188,7 +185,7 @@ public Object getResource(ResourceSpec resourceSpec, ResourceAllocator resourceA try { if (resourceHandle.getResourceState().isEnlisted()) { - LOG.log(FINE, "getResource - DO NOT ENLIST because it is already Enlisted, resource=" + resourceHandle); + LOG.log(FINE, "getResource - DO NOT ENLIST because it is already Enlisted, resource={0}", resourceHandle); } else { // The spec being used here is the spec with the updated lazy enlistment info. @@ -211,9 +208,7 @@ public Object getResource(ResourceSpec resourceSpec, ResourceAllocator resourceA } Object result = resourceHandle.getUserConnection(); - if (LOG.isLoggable(FINE)) { - LOG.log(FINE, "PoolManagerImpl.getResource END, resourceHandle=" + resourceHandle); - } + LOG.log(FINE, "PoolManagerImpl.getResource END, resourceHandle={0}", resourceHandle); return result; } diff --git a/appserver/connectors/connectors-runtime/src/main/java/com/sun/enterprise/resource/pool/UnpooledResource.java b/appserver/connectors/connectors-runtime/src/main/java/com/sun/enterprise/resource/pool/UnpooledResource.java index a983d56ffa4..3b4eb119ed3 100644 --- a/appserver/connectors/connectors-runtime/src/main/java/com/sun/enterprise/resource/pool/UnpooledResource.java +++ b/appserver/connectors/connectors-runtime/src/main/java/com/sun/enterprise/resource/pool/UnpooledResource.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2023 Contributors to the Eclipse Foundation + * Copyright (c) 2022, 2025 Contributors to the Eclipse Foundation * Copyright (c) 1997, 2020 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the @@ -46,7 +46,7 @@ public UnpooledResource(PoolInfo poolInfo, Hashtable env) throws PoolingExceptio } @Override - protected synchronized void initPool(ResourceAllocator allocator) throws PoolingException { + protected synchronized void ensurePoolInitialized(ResourceAllocator allocator) throws PoolingException { if (poolInitialized) { return; } diff --git a/appserver/tests/admin/ssh-cluster/pom.xml b/appserver/tests/admin/ssh-cluster/pom.xml index 8c5f8d0b39f..5bf5112111e 100644 --- a/appserver/tests/admin/ssh-cluster/pom.xml +++ b/appserver/tests/admin/ssh-cluster/pom.xml @@ -51,7 +51,7 @@ org.testcontainers - junit-jupiter + testcontainers-junit-jupiter org.glassfish.main.distributions diff --git a/appserver/tests/jdbc/pom.xml b/appserver/tests/jdbc/pom.xml index e54bae65feb..0e8ffc67e6c 100755 --- a/appserver/tests/jdbc/pom.xml +++ b/appserver/tests/jdbc/pom.xml @@ -40,6 +40,12 @@ glassfish-jul-extension test + + org.glassfish.main.extras + glassfish-embedded-all + ${glassfish.version} + test + org.junit.jupiter junit-jupiter-engine @@ -67,11 +73,11 @@ org.testcontainers - junit-jupiter + testcontainers-junit-jupiter org.testcontainers - postgresql + testcontainers-postgresql org.jboss.shrinkwrap.resolver @@ -162,28 +168,4 @@ provided - - - - - maven-dependency-plugin - - - prepare-gf-and-db-driver.zip - - copy-dependencies - - generate-resources - - ${project.build.testOutputDirectory} - org.glassfish.main.distributions,org.postgresql - glassfish,postgresql - zip,jar - true - - - - - - diff --git a/appserver/tests/jdbc/src/main/java/org/glassfish/main/test/jdbc/pool/war/GlassFishUserRestEndpoint.java b/appserver/tests/jdbc/src/main/java/org/glassfish/main/test/jdbc/pool/war/GlassFishUserRestEndpoint.java index b4cc61540d5..9935a4b3122 100644 --- a/appserver/tests/jdbc/src/main/java/org/glassfish/main/test/jdbc/pool/war/GlassFishUserRestEndpoint.java +++ b/appserver/tests/jdbc/src/main/java/org/glassfish/main/test/jdbc/pool/war/GlassFishUserRestEndpoint.java @@ -39,19 +39,14 @@ public class GlassFishUserRestEndpoint { @Transactional(TxType.REQUIRES_NEW) public void create(User user) { em.persist(user); -// try { -// Thread.sleep(50L); -// } catch (InterruptedException e) { -// Thread.currentThread().interrupt(); -// } } @GET @Path("/list") - @Transactional(TxType.NOT_SUPPORTED) + @Transactional(TxType.SUPPORTS) public List list() { - return em.createQuery("select u from User u", User.class).setMaxResults(100).getResultList(); + return em.createQuery("select u from User u order by u.name", User.class).setMaxResults(100).getResultList(); } diff --git a/appserver/tests/jdbc/src/main/resources/jdbc/pool/war/persistence.xml b/appserver/tests/jdbc/src/main/resources/jdbc/pool/war/persistence.xml index 2d82f5eba22..65a6b5c62c9 100644 --- a/appserver/tests/jdbc/src/main/resources/jdbc/pool/war/persistence.xml +++ b/appserver/tests/jdbc/src/main/resources/jdbc/pool/war/persistence.xml @@ -8,6 +8,8 @@ jdbc/dsPoolA + + diff --git a/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/jdbc/jta/JtaTimeoutLoggingITest.java b/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/jdbc/jta/JtaTimeoutLoggingITest.java index 0d4ccdaf75e..cb4fcb64b1f 100644 --- a/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/jdbc/jta/JtaTimeoutLoggingITest.java +++ b/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/jdbc/jta/JtaTimeoutLoggingITest.java @@ -35,7 +35,7 @@ import org.glassfish.main.jul.record.GlassFishLogRecord; import org.glassfish.main.test.jdbc.jta.timeout.war.AsynchronousTimeoutingJob; import org.glassfish.main.test.jdbc.jta.timeout.war.SlowJpaPartitioner; -import org.glassfish.main.test.perf.util.DockerTestEnvironment; +import org.glassfish.main.test.perf.server.DockerTestEnvironment; import org.glassfish.tests.utils.junit.TestLoggingExtension; import org.hamcrest.Matcher; import org.hamcrest.Matchers; @@ -52,9 +52,6 @@ import static jakarta.transaction.Status.STATUS_MARKED_ROLLBACK; import static org.glassfish.main.test.jdbc.jta.timeout.war.SlowJpaPartitioner.TIMEOUT_IN_SECONDS; -import static org.glassfish.main.test.perf.util.DockerTestEnvironment.asadmin; -import static org.glassfish.main.test.perf.util.DockerTestEnvironment.deploy; -import static org.glassfish.main.test.perf.util.DockerTestEnvironment.undeploy; import static org.glassfish.tests.utils.junit.matcher.WaitForExecutable.waitFor; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.MatcherAssert.assertThat; @@ -102,16 +99,18 @@ public class JtaTimeoutLoggingITest { private static final String APPNAME = "jtaTimeout"; private static boolean dockerAvailable; + + private static DockerTestEnvironment environment; private static LogCollectorHandler domainLog; private static WebTarget wsEndpoint; - @BeforeAll public static void init() throws Exception { dockerAvailable = DockerClientFactory.instance().isDockerAvailable(); assumeTrue(dockerAvailable, "Docker is not available on this environment"); - final java.util.logging.Logger logger = DockerTestEnvironment.getDomainLogger(); + environment = DockerTestEnvironment.getInstance(); + final java.util.logging.Logger logger = environment.getDomainLogger(); assertNotNull(logger, "domain logger was not found"); final Filter filter = event -> { final Predicate predicate = msgPart -> { @@ -123,9 +122,10 @@ public static void init() throws Exception { domainLog = new LogCollectorHandler(logger, 100_000, 10); domainLog.setFilter(filter); - asadmin("set", "configs.config.server-config.transaction-service.timeout-in-seconds=" + TIMEOUT_IN_SECONDS); - asadmin("restart-domain", "domain1"); - wsEndpoint = deploy(APPNAME, getArchiveToDeploy()); + environment.asadmin("set", + "configs.config.server-config.transaction-service.timeout-in-seconds=" + TIMEOUT_IN_SECONDS); + environment.asadmin("restart-domain", "domain1"); + wsEndpoint = environment.deploy(APPNAME, getArchiveToDeploy()); } @@ -143,8 +143,8 @@ public static void cleanup() throws Exception { return; } domainLog.close(); - undeploy(APPNAME); - asadmin("set", "configs.config.server-config.transaction-service.timeout-in-seconds=0"); + environment.undeploy(APPNAME); + environment.asadmin("set", "configs.config.server-config.transaction-service.timeout-in-seconds=0"); } diff --git a/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/jdbc/pool/JdbcPoolInjectionsIT.java b/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/jdbc/pool/JdbcPoolInjectionsIT.java index 61100dd8dfe..a943b2550e7 100644 --- a/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/jdbc/pool/JdbcPoolInjectionsIT.java +++ b/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/jdbc/pool/JdbcPoolInjectionsIT.java @@ -24,7 +24,7 @@ import org.glassfish.main.test.jdbc.pool.war.DataSourceDefinitionBean; import org.glassfish.main.test.jdbc.pool.war.JdbcDsName; import org.glassfish.main.test.jdbc.pool.war.RestAppConfig; -import org.glassfish.main.test.perf.util.DockerTestEnvironment; +import org.glassfish.main.test.perf.server.DockerTestEnvironment; import org.glassfish.tests.utils.junit.TestLoggingExtension; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; @@ -32,8 +32,6 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.testcontainers.DockerClientFactory; -import static org.glassfish.main.test.perf.util.DockerTestEnvironment.deploy; -import static org.glassfish.main.test.perf.util.DockerTestEnvironment.undeploy; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assumptions.assumeTrue; @@ -42,6 +40,7 @@ public class JdbcPoolInjectionsIT { private static final String APPNAME = "dspools"; private static boolean dockerAvailable; + private static DockerTestEnvironment environment; private static WebTarget wsEndpoint; @@ -49,7 +48,8 @@ public class JdbcPoolInjectionsIT { public static void init() throws Exception { dockerAvailable = DockerClientFactory.instance().isDockerAvailable(); assumeTrue(dockerAvailable, "Docker is not available on this environment"); - wsEndpoint = deploy(APPNAME, DataSourceDefinitionBean.class, JdbcDsName.class, RestAppConfig.class); + environment = DockerTestEnvironment.getInstance(); + wsEndpoint = environment.deploy(APPNAME, DataSourceDefinitionBean.class, JdbcDsName.class, RestAppConfig.class); } @AfterAll @@ -57,8 +57,8 @@ public static void cleanup() throws Exception { if (!dockerAvailable) { return; } - undeploy(APPNAME); - DockerTestEnvironment.reinitializeDatabase(); + environment.undeploy(APPNAME); + environment.reinitializeDatabase(); } @Test diff --git a/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/jdbc/pool/JdbcPoolPerformanceIT.java b/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/jdbc/pool/JdbcPoolPerformanceIT.java index f22b8998a29..4d5f1fdca35 100644 --- a/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/jdbc/pool/JdbcPoolPerformanceIT.java +++ b/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/jdbc/pool/JdbcPoolPerformanceIT.java @@ -19,151 +19,137 @@ import jakarta.ws.rs.client.WebTarget; import java.lang.System.Logger; -import java.net.URI; +import java.time.Duration; import java.util.Collection; -import java.util.List; -import java.util.concurrent.TimeUnit; -import org.apache.commons.lang3.RandomStringUtils; import org.glassfish.main.test.jdbc.pool.war.GlassFishUserRestEndpoint; import org.glassfish.main.test.jdbc.pool.war.RestAppConfig; import org.glassfish.main.test.jdbc.pool.war.User; -import org.glassfish.main.test.perf.util.DockerTestEnvironment; -import org.glassfish.main.test.perf.util.UserRestClient; +import org.glassfish.main.test.perf.benchmark.Environment; +import org.glassfish.main.test.perf.benchmark.RestBenchmark; +import org.glassfish.main.test.perf.embedded.DockerTestEnvironmentWithEmbedded; +import org.glassfish.main.test.perf.rest.UserRestClient; +import org.glassfish.main.test.perf.server.DockerTestEnvironment; import org.glassfish.tests.utils.junit.TestLoggingExtension; import org.jboss.shrinkwrap.api.ShrinkWrap; import org.jboss.shrinkwrap.api.spec.WebArchive; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; import org.junit.jupiter.api.condition.EnabledIfSystemProperty; import org.junit.jupiter.api.extension.ExtendWith; -import org.openjdk.jmh.annotations.Benchmark; -import org.openjdk.jmh.annotations.Mode; -import org.openjdk.jmh.annotations.Scope; -import org.openjdk.jmh.annotations.State; import org.openjdk.jmh.results.Result; import org.openjdk.jmh.results.RunResult; import org.openjdk.jmh.runner.Runner; -import org.openjdk.jmh.runner.options.ChainedOptionsBuilder; import org.openjdk.jmh.runner.options.Options; -import org.openjdk.jmh.runner.options.OptionsBuilder; import org.testcontainers.DockerClientFactory; import static java.lang.Math.min; import static java.lang.System.Logger.Level.INFO; -import static org.glassfish.main.test.jdbc.pool.JdbcPoolPerformanceIT.RestClientProvider.SYS_PROPERTY_ENDPOINT; -import static org.glassfish.main.test.perf.util.DockerTestEnvironment.LIMIT_JDBC; -import static org.glassfish.main.test.perf.util.DockerTestEnvironment.asadmin; -import static org.glassfish.main.test.perf.util.DockerTestEnvironment.asadminMonitor; -import static org.glassfish.main.test.perf.util.DockerTestEnvironment.deploy; -import static org.glassfish.main.test.perf.util.DockerTestEnvironment.undeploy; -import static org.glassfish.main.test.perf.util.GlassFishContainer.LIMIT_HTTP_THREADS; -import static org.hamcrest.CoreMatchers.allOf; +import static org.glassfish.main.test.perf.benchmark.BenchmarkLimits.LIMIT_JMH_THREADS; +import static org.glassfish.main.test.perf.benchmark.BenchmarkLimits.LIMIT_POOL_HTTP_THREADS; +import static org.glassfish.main.test.perf.benchmark.BenchmarkLimits.LIMIT_POOL_JDBC; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.greaterThan; import static org.hamcrest.Matchers.hasSize; -import static org.hamcrest.Matchers.lessThanOrEqualTo; +import static org.hamcrest.Matchers.lessThan; import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assumptions.assumeTrue; -import static org.openjdk.jmh.runner.options.TimeValue.seconds; + +/** + * + * mvn clean install -pl :jdbc-tests -Dit.test=JdbcPoolPerformanceIT -DenableHWDependentTests=true -Dit.embedded=true -Dit.disconnectDatabase=false + * + */ @EnabledIfSystemProperty( named = "enableHWDependentTests", matches = "true", disabledReason = "Test depends on hardware performance") @ExtendWith(TestLoggingExtension.class) +@TestMethodOrder(MethodOrderer.MethodName.class) public class JdbcPoolPerformanceIT { private static final Logger LOG = System.getLogger(JdbcPoolPerformanceIT.class.getName()); private static final String APPNAME = "perf"; - private static final int LIMIT_JMH_THREADS = 500; - private static boolean dockerAvailable; + private static final boolean DOCKER_AVAILABLE = DockerClientFactory.instance().isDockerAvailable(); + private static final boolean EMBEDDED = Boolean.getBoolean("it.embedded"); + private static final boolean DISCONNECT_DB = Boolean.getBoolean("it.disconnectDatabase"); + private static final int CONN_MAX = min(LIMIT_JMH_THREADS, min(LIMIT_POOL_HTTP_THREADS, LIMIT_POOL_JDBC)); + + private static Environment env; private static WebTarget wsEndpoint; @BeforeAll public static void init() throws Exception { - dockerAvailable = DockerClientFactory.instance().isDockerAvailable(); - assumeTrue(dockerAvailable, "Docker is not available on this environment"); - wsEndpoint = deploy(APPNAME, getArchiveToDeploy()); + assumeTrue(DOCKER_AVAILABLE, "Docker is not available on this environment"); + WebArchive war = getArchiveToDeploy(); + env = EMBEDDED ? new DockerTestEnvironmentWithEmbedded() : new DockerTestEnvironment(); + wsEndpoint = env.start(APPNAME, war); } @AfterAll public static void cleanup() throws Exception { - if (!dockerAvailable) { + if (!DOCKER_AVAILABLE) { return; } - undeploy(APPNAME); - DockerTestEnvironment.reinitializeDatabase(); + env.stop(); } @Test - public void testHeavyLoad() throws Exception { - Options options = createOptions(); -// Thread networkIssue = new Thread(() -> { -// try { -// Thread.sleep(10); -// DockerTestEnvironment.disconnectDatabase(2); -// } catch (InterruptedException e) { -// Thread.currentThread().interrupt(); -// } -// }); -// networkIssue.start(); + public void createUser() throws Exception { + Options options = RestBenchmark.createOptions(wsEndpoint.getUri(), "createUser"); + if (DISCONNECT_DB) { + env.disconnectDatabase(Duration.ofSeconds(2L), Duration.ofSeconds(5)); + } Collection results = new Runner(options).run(); - // Print all results - for tuning. - asadmin("get", "--monitor", "*"); assertThat(results, hasSize(1)); - Result primaryResult = results.iterator().next().getPrimaryResult(); - long usersCreated = new UserRestClient(wsEndpoint).count(); - int jdbcConnAcquired = asadminMonitor("server.resources.domain-pool-A.numconnacquired-count"); - int jdbcConnReleased = asadminMonitor("server.resources.domain-pool-A.numconnreleased-count"); - int jdbcConnCreated = asadminMonitor("server.resources.domain-pool-A.numconncreated-count"); - int jdbcConnCurrent = asadminMonitor("server.resources.domain-pool-A.perf.numconnused-current"); - int jdbcConnFree = asadminMonitor("server.resources.domain-pool-A.numconnfree-current"); - int jdbcMaxUsed = asadminMonitor("server.resources.domain-pool-A.perf.numconnused-highwatermark"); - int limit = min(LIMIT_JMH_THREADS, min(LIMIT_HTTP_THREADS, LIMIT_JDBC)); - double jmhThroughput = primaryResult.getScore(); - LOG.log(INFO, () -> "Results:" - + "\nJMH throughput: " + jmhThroughput - + "\nusersCreated: " + usersCreated - + "\njdbcConnAcquired: " + jdbcConnAcquired - + "\njdbcConnReleased: " + jdbcConnReleased - + "\njdbcConnCreated: " + jdbcConnCreated - + "\njdbcConnCurrent: " + jdbcConnCurrent - + "\njdbcConnFree: " + jdbcConnFree - + "\njdbcMaxUsed: " + jdbcMaxUsed - ); + PerformanceTestResult resultCreate = new PerformanceTestResult(results.iterator().next().getPrimaryResult()); + LOG.log(INFO, () -> "Results(create): " + resultCreate); + if (!EMBEDDED) { + assertAll( + () -> assertThat("conn released==acquired", resultCreate.jdbcConnAcquired, equalTo(resultCreate.jdbcConnReleased)), + () -> assertThat("conn acquired", resultCreate.jdbcConnAcquired, greaterThan(CONN_MAX)), + () -> assertThat("conn created", resultCreate.jdbcConnCreated, equalTo(CONN_MAX)), + () -> assertThat("conn used now", resultCreate.jdbcConnCurrent, equalTo(0)), + () -> assertThat("conn usable now", resultCreate.jdbcConnFree, equalTo(CONN_MAX)), + () -> assertThat("conn highwatermark", resultCreate.jdbcMaxUsed, equalTo(CONN_MAX)) + ); + } assertAll( - () -> assertThat("JMH throughput", jmhThroughput, greaterThan(1_000_000d)), - () -> assertThat("Records created", usersCreated, greaterThan(170_000L)), - () -> assertThat("conn released==acquired", jdbcConnAcquired, equalTo(jdbcConnReleased)), - () -> assertThat("conn acquired", jdbcConnAcquired, greaterThan(limit)), - () -> assertThat("conn created", jdbcConnCreated, equalTo(limit)), - () -> assertThat("conn used now", jdbcConnCurrent, equalTo(0)), - () -> assertThat("conn usable now", jdbcConnFree, equalTo(limit)), - () -> assertThat("conn highwatermark", jdbcMaxUsed, equalTo(limit)) + () -> assertThat("Average Time (ms)", resultCreate.avgTime, lessThan(100d)), + () -> assertThat("Records created", resultCreate.usersCreated, greaterThan(150_000L)) ); } - private Options createOptions() { - ChainedOptionsBuilder builder = new OptionsBuilder().include(getClass().getName() + ".*"); - builder.shouldFailOnError(true); - builder.warmupIterations(0); - builder.timeUnit(TimeUnit.MILLISECONDS).mode(Mode.Throughput); - builder.detectJvmArgs().jvmArgsAppend("-D" + SYS_PROPERTY_ENDPOINT + "=" + wsEndpoint.getUri()); - builder.forks(1).threads(LIMIT_JMH_THREADS); - builder.operationsPerInvocation(1_000_000).measurementTime(seconds(5)).timeout(seconds(30)); - return builder.build(); - } + @Test + public void listUsers() throws Exception { + Options options = RestBenchmark.createOptions(wsEndpoint.getUri(), "listUsers"); + if (DISCONNECT_DB) { + env.disconnectDatabase(Duration.ofSeconds(2L), Duration.ofSeconds(5)); + } - @Benchmark - public void meanResponseTimeBenchmark(RestClientProvider clientProvider) throws Exception { - User user = new User(RandomStringUtils.insecure().nextAlphabetic(32)); - UserRestClient client = clientProvider.getClient(); - client.create(user); - List users = client.list(); - assertThat(users, hasSize(allOf(greaterThan(0), lessThanOrEqualTo(100)))); + Collection results = new Runner(options).run(); + assertThat(results, hasSize(1)); + PerformanceTestResult resultList = new PerformanceTestResult(results.iterator().next().getPrimaryResult()); + LOG.log(INFO, () -> "Results(list): " + resultList); + if (!EMBEDDED) { + assertAll( + () -> assertThat("conn released==acquired", resultList.jdbcConnAcquired, equalTo(resultList.jdbcConnReleased)), + () -> assertThat("conn acquired", resultList.jdbcConnAcquired, greaterThan(CONN_MAX)), + () -> assertThat("conn created", resultList.jdbcConnCreated, equalTo(CONN_MAX)), + () -> assertThat("conn used now", resultList.jdbcConnCurrent, equalTo(0)), + () -> assertThat("conn usable now", resultList.jdbcConnFree, equalTo(CONN_MAX)), + () -> assertThat("conn highwatermark", resultList.jdbcMaxUsed, equalTo(CONN_MAX)) + ); + } + assertAll( + () -> assertThat("Average Time (ms)", resultList.avgTime, lessThan(2000d)), + () -> assertThat("Records created", resultList.usersCreated, equalTo(resultList.usersCreated)) + ); } @@ -174,14 +160,46 @@ private static WebArchive getArchiveToDeploy() throws Exception { ; } - @State(Scope.Benchmark) - public static class RestClientProvider { - public static final String SYS_PROPERTY_ENDPOINT = "endpoint"; - private static final UserRestClient CLIENT = new UserRestClient( - URI.create(System.getProperty(SYS_PROPERTY_ENDPOINT)), false); + private static class PerformanceTestResult { + long usersCreated; + int jdbcConnAcquired; + int jdbcConnReleased; + int jdbcConnCreated; + int jdbcConnCurrent; + int jdbcConnFree; + int jdbcMaxUsed; + double avgTime; + + PerformanceTestResult(Result result) { + usersCreated = new UserRestClient(wsEndpoint).count(); + jdbcConnAcquired = asadminMonitor("server.resources.domain-pool-A.numconnacquired-count"); + jdbcConnReleased = asadminMonitor("server.resources.domain-pool-A.numconnreleased-count"); + jdbcConnCreated = asadminMonitor("server.resources.domain-pool-A.numconncreated-count"); + jdbcConnCurrent = asadminMonitor("server.resources.domain-pool-A.perf.numconnused-current"); + jdbcConnFree = asadminMonitor("server.resources.domain-pool-A.numconnfree-current"); + jdbcMaxUsed = asadminMonitor("server.resources.domain-pool-A.perf.numconnused-highwatermark"); + avgTime = result.getScore(); + } + + + @Override + public String toString() { + return "\nAverage Time (ms): " + avgTime + + "\nusersCreated: " + usersCreated + + "\njdbcConnAcquired: " + jdbcConnAcquired + + "\njdbcConnReleased: " + jdbcConnReleased + + "\njdbcConnCreated: " + jdbcConnCreated + + "\njdbcConnCurrent: " + jdbcConnCurrent + + "\njdbcConnFree: " + jdbcConnFree + + "\njdbcMaxUsed: " + jdbcMaxUsed; + } - public UserRestClient getClient() { - return CLIENT; + private int asadminMonitor(String string) { + if (env instanceof DockerTestEnvironmentWithEmbedded) { + return 0; + } + DockerTestEnvironment gfEnv = (DockerTestEnvironment) env; + return gfEnv.asadminMonitor(string); } } } diff --git a/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/perf/benchmark/BenchmarkLimits.java b/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/perf/benchmark/BenchmarkLimits.java new file mode 100644 index 00000000000..4dcc0e682fd --- /dev/null +++ b/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/perf/benchmark/BenchmarkLimits.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2025 Eclipse Foundation and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.main.test.perf.benchmark; + + +/** + * Configuration of several critical values affecting performance and stability. + */ +public final class BenchmarkLimits { + + public static final int LIMIT_JMH_THREADS = 500; + public static final int LIMIT_POOL_HTTP_THREADS = 1000; + // FIXME: 4 reproduces the JDBC Pool error - pool contains just unusable connections! + public static final int LIMIT_HTTP_REQUEST_TIMEOUT = 10; + + public static final int LIMIT_DBSERVER_CONNECTION_COUNT = 500; + public static final int LIMIT_POOL_JDBC = 300; + public static final int LIMIT_POOL_EJB = 500; + public static final boolean ENABLE_CONNECTION_VALIDATION = false; + + public static final int MEM_MAX_APP_OS = 4; + public static final int MEM_MAX_APP_HEAP = 3; + public static final int MEM_MAX_DB_OS = 4; + public static final int MEM_MAX_DB_SHARED = 4; + + public static final String SERVER_LOG_LEVEL = "INFO"; + + private BenchmarkLimits() { + // hidden + } +} diff --git a/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/perf/benchmark/Environment.java b/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/perf/benchmark/Environment.java new file mode 100644 index 00000000000..89146fbcd23 --- /dev/null +++ b/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/perf/benchmark/Environment.java @@ -0,0 +1,209 @@ +/* + * Copyright (c) 2025 Eclipse Foundation and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.main.test.perf.benchmark; + +import com.github.database.rider.core.api.connection.ConnectionHolder; +import com.github.database.rider.core.api.dataset.DataSetExecutor; +import com.github.database.rider.core.dataset.DataSetExecutorImpl; +import com.github.dockerjava.api.DockerClient; +import com.github.dockerjava.api.model.HostConfig; +import com.github.dockerjava.api.model.Ulimit; + +import jakarta.ws.rs.client.WebTarget; + +import java.lang.System.Logger; +import java.time.Duration; + +import org.jboss.shrinkwrap.api.spec.WebArchive; +import org.postgresql.ds.PGSimpleDataSource; +import org.testcontainers.DockerClientFactory; +import org.testcontainers.containers.Network; +import org.testcontainers.containers.PostgreSQLContainer; + +import static java.lang.System.Logger.Level.INFO; +import static java.lang.System.Logger.Level.TRACE; +import static java.lang.System.Logger.Level.WARNING; +import static org.glassfish.main.test.perf.benchmark.BenchmarkLimits.LIMIT_DBSERVER_CONNECTION_COUNT; +import static org.glassfish.main.test.perf.benchmark.BenchmarkLimits.MEM_MAX_DB_OS; +import static org.glassfish.main.test.perf.benchmark.BenchmarkLimits.MEM_MAX_DB_SHARED; +import static org.junit.jupiter.api.Assumptions.assumeTrue; + +/** + * The environment of the test, database server, application server, network. + */ +public abstract class Environment { + private static final Logger LOG = System.getLogger(Environment.class.getName()); + private static final Logger LOG_DB = System.getLogger("DB"); + + private final Network network = Network.newNetwork(); + + @SuppressWarnings("resource") + private final PostgreSQLContainer database = new PostgreSQLContainer<>("postgres:17.6") + .withNetwork(network).withDatabaseName("testdb").withExposedPorts(5432).withCreateContainerCmdModifier(cmd -> { + cmd.withHostName("tc-testdb"); + cmd.withAttachStderr(true); + cmd.withAttachStdout(true); + final HostConfig hostConfig = cmd.getHostConfig(); + hostConfig.withMemory(MEM_MAX_DB_OS * 1024 * 1024 * 1024L); + hostConfig.withMemorySwappiness(0L); + hostConfig.withUlimits(new Ulimit[] {new Ulimit("nofile", 4096L, 8192L)}); + hostConfig.withShmSize(MEM_MAX_DB_SHARED * 1024 * 1024 * 1024L); + }) + .withLogConsumer(o -> LOG_DB.log(INFO, o.getUtf8StringWithoutLineEnding())) + .withCommand("postgres", + "-c", "log_statement=none", + "-c", "log_destination=stderr", + "-c", "max_connections=" + LIMIT_DBSERVER_CONNECTION_COUNT + ); + + private ConnectionHolder connectionHolder; + private DataSetExecutor dsExecutor; + + /** @return Docker network */ + public Network getNetwork() { + return network; + } + + /** + * @return database container + */ + public PostgreSQLContainer getDatabase() { + return database; + } + + /** + * Start the environment - network and database, children may start more containers. + * Then deploys the application. + * @param appName + * + * @param war + * @return endpoint of the application. + */ + public WebTarget start(String appName, WebArchive war) { + start(); + return deploy(appName, war); + } + + /** + * Start the environment - network and database + */ + public void start() { + assumeTrue(DockerClientFactory.instance().isDockerAvailable(), "Docker is not available on this environment"); + PostgreSQLContainer db = getDatabase(); + db.start(); + + final PGSimpleDataSource dataSource = new PGSimpleDataSource(); + dataSource.setDatabaseName(db.getDatabaseName()); + dataSource.setServerNames(new String[] {db.getHost()}); + dataSource.setUrl(db.getJdbcUrl()); + dataSource.setUser(db.getUsername()); + dataSource.setPassword(db.getPassword()); + dataSource.setConnectTimeout(60); + connectionHolder = dataSource::getConnection; + dsExecutor = DataSetExecutorImpl.instance("executor", connectionHolder); + reinitializeDatabase(); + } + + /** + * Stops all virtual computers, and destroys the virtual network. + */ + public void stop() { + PostgreSQLContainer db = getDatabase(); + if (db.isRunning()) { + closeSilently(db); + } + closeSilently(getNetwork()); + } + + /** + * Deploys the war file under the given application name + * + * @param appName + * @param war + * @return endpoint + */ + public abstract WebTarget deploy(String appName, WebArchive war); + + /** + * Undeploys the war file registered under the given application name. + * + * @param appname + */ + public abstract void undeploy(String appname); + + /** + * Disconnects the database for the duration. Then reconnects. + * + * @param delay + * @param duration + */ + public void disconnectDatabase(Duration delay, Duration duration) { + Thread networkIssue = new Thread(() -> { + try { + Thread.sleep(delay.toMillis()); + disconnectDatabase(duration); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + }); + networkIssue.start(); + } + + /** + * Disconnects the database for the duration. Then reconnects. + * + * @param duration + */ + public void disconnectDatabase(Duration duration) { + DockerClient client = DockerClientFactory.instance().client(); + LOG.log(INFO, "Disconnecting database from network!"); + client.disconnectFromNetworkCmd().withNetworkId(network.getId()).withContainerId(database.getContainerId()).exec(); + try { + Thread.sleep(duration.toMillis()); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + client.connectToNetworkCmd().withNetworkId(network.getId()).withContainerId(database.getContainerId()).exec(); + LOG.log(INFO, "Database reconnected to the network!"); + } + + /** + * Drop all data and recreate just those existing before the test. + */ + public void reinitializeDatabase() { + dsExecutor.executeScript("initSchema.sql"); + } + + /** + * Closes the {@link AutoCloseable} - if it throws exception, the exception is just logged. + * Useful for situation where we are closing whole environment and we don't plan any reaction + * to possible exceptions. + * + * @param closeable + */ + protected static void closeSilently(final AutoCloseable closeable) { + LOG.log(TRACE, "closeSilently(closeable={0})", closeable); + if (closeable == null) { + return; + } + try { + closeable.close(); + } catch (final Exception e) { + LOG.log(WARNING, "Close method caused an exception.", e); + } + } +} diff --git a/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/perf/benchmark/RestBenchmark.java b/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/perf/benchmark/RestBenchmark.java new file mode 100644 index 00000000000..983969d639f --- /dev/null +++ b/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/perf/benchmark/RestBenchmark.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2025 Eclipse Foundation and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.main.test.perf.benchmark; + +import java.net.URI; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import org.apache.commons.lang3.RandomStringUtils; +import org.glassfish.main.test.jdbc.pool.war.User; +import org.glassfish.main.test.perf.rest.UserRestClient; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.runner.options.ChainedOptionsBuilder; +import org.openjdk.jmh.runner.options.Options; +import org.openjdk.jmh.runner.options.OptionsBuilder; + +import static org.glassfish.main.test.perf.benchmark.BenchmarkLimits.LIMIT_JMH_THREADS; +import static org.glassfish.main.test.perf.benchmark.RestBenchmark.RestClientProvider.SYS_PROPERTY_ENDPOINT; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasSize; +import static org.openjdk.jmh.runner.options.TimeValue.seconds; + +/** + * + */ +public class RestBenchmark { + + public static Options createOptions(URI wsEndpoint, String benchmark) { + ChainedOptionsBuilder builder = new OptionsBuilder().include(RestBenchmark.class.getName() + '.' + benchmark); + builder.shouldFailOnError(true); + builder.warmupIterations(1).warmupTime(seconds(5L)); + builder.timeUnit(TimeUnit.MILLISECONDS).mode(Mode.AverageTime); + builder.detectJvmArgs().jvmArgsAppend("-D" + SYS_PROPERTY_ENDPOINT + "=" + wsEndpoint); + builder.forks(1).threads(LIMIT_JMH_THREADS); + builder + .measurementIterations(2).measurementTime(seconds(10)) + .timeout(seconds(10)); + return builder.build(); + } + + // Don't forget benchmarks are executed alphabetically! + + @Benchmark + public void createUser(RestClientProvider clientProvider) throws Exception { + User user = new User(RandomStringUtils.insecure().nextAlphabetic(32)); + UserRestClient client = clientProvider.getClient(); + client.create(user); + } + + @Benchmark + public void listUsers(RestClientProvider clientProvider) throws Exception { + UserRestClient client = clientProvider.getClient(); + List users = client.list(); + assertThat(users, hasSize(equalTo(100))); + } + + @State(Scope.Benchmark) + public static class RestClientProvider { + public static final String SYS_PROPERTY_ENDPOINT = "endpoint"; + private static final UserRestClient CLIENT = new UserRestClient( + URI.create(System.getProperty(SYS_PROPERTY_ENDPOINT)), false); + + public UserRestClient getClient() { + return CLIENT; + } + } +} diff --git a/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/perf/embedded/DockerTestEnvironmentWithEmbedded.java b/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/perf/embedded/DockerTestEnvironmentWithEmbedded.java new file mode 100644 index 00000000000..65146ab2c46 --- /dev/null +++ b/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/perf/embedded/DockerTestEnvironmentWithEmbedded.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2025 Contributors to the Eclipse Foundation. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.main.test.perf.embedded; + +import jakarta.ws.rs.client.WebTarget; + +import java.io.File; +import java.io.IOException; +import java.lang.System.Logger; + +import org.glassfish.main.test.perf.benchmark.Environment; +import org.jboss.shrinkwrap.api.exporter.ZipExporter; +import org.jboss.shrinkwrap.api.spec.WebArchive; + +import static java.lang.System.Logger.Level.INFO; +import static org.glassfish.main.test.perf.embedded.EmbeddedGlassFishContainer.WAR_FILE; +import static org.testcontainers.utility.MountableFile.forHostPath; + +/** + * Environment of Eclipse GlassFish, Derby and Postgress SQL databases. + */ +public class DockerTestEnvironmentWithEmbedded extends Environment { + + private static final Logger LOG = System.getLogger(DockerTestEnvironmentWithEmbedded.class.getName()); + + private final EmbeddedGlassFishContainer app; + + /** + * Creates network, databases and application server, but doesn't start them. + */ + public DockerTestEnvironmentWithEmbedded() { + app = new EmbeddedGlassFishContainer(getNetwork(), "admin", "A", getDatabase()); + Thread hook = new Thread(this::stop); + Runtime.getRuntime().addShutdownHook(hook); + } + + + @Override + public WebTarget start(String appName, WebArchive war) { + super.start(); + undeploy(appName); + return deploy(appName, war); + } + + @Override + public WebTarget deploy(String appName, WebArchive war) { + app.withCopyFileToContainer(forHostPath(toFile("webapp", war).getPath()), WAR_FILE); + app.start(); + return app.getRestClient(""); + } + + @Override + public void undeploy(String appname) { + if (app.isRunning()) { + app.stop(); + } + } + + @Override + public void stop() { + if (app.isRunning()) { + closeSilently(app); + } + super.stop(); + } + + + private static File toFile(final String appName, final WebArchive war) { + LOG.log(INFO, () -> war.toString(true)); + File warFile; + try { + warFile = File.createTempFile(appName, ".war"); + } catch (IOException e) { + throw new RuntimeException(e); + } + war.as(ZipExporter.class).exportTo(warFile, true); + return warFile; + } +} diff --git a/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/perf/embedded/EmbeddedGlassFishContainer.java b/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/perf/embedded/EmbeddedGlassFishContainer.java new file mode 100644 index 00000000000..bc4a294daa9 --- /dev/null +++ b/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/perf/embedded/EmbeddedGlassFishContainer.java @@ -0,0 +1,192 @@ +/* + * Copyright (c) 2024, 2025 Contributors to the Eclipse Foundation. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.main.test.perf.embedded; + +import com.github.dockerjava.api.model.HostConfig; +import com.github.dockerjava.api.model.Ulimit; + +import jakarta.ws.rs.client.WebTarget; + +import java.io.FileOutputStream; +import java.io.IOException; +import java.net.URI; +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.Duration; +import java.util.Properties; +import java.util.logging.Level; + +import org.glassfish.main.test.perf.rest.RestClientUtilities; +import org.glassfish.tests.utils.junit.JUnitSystem; +import org.jboss.shrinkwrap.resolver.api.maven.Maven; +import org.jboss.shrinkwrap.resolver.api.maven.PomEquippedResolveStage; +import org.postgresql.ds.PGSimpleDataSource; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.Network; +import org.testcontainers.containers.PostgreSQLContainer; +import org.testcontainers.containers.wait.strategy.Wait; + +import static org.glassfish.main.jul.cfg.GlassFishLoggingConstants.CLASS_LOG_MANAGER_GLASSFISH; +import static org.glassfish.main.jul.cfg.GlassFishLoggingConstants.JVM_OPT_LOGGING_CFG_DEFAULT_LEVEL; +import static org.glassfish.main.jul.cfg.GlassFishLoggingConstants.JVM_OPT_LOGGING_CFG_USE_DEFAULTS; +import static org.glassfish.main.jul.cfg.GlassFishLoggingConstants.JVM_OPT_LOGGING_MANAGER; +import static org.glassfish.main.test.jdbc.pool.war.JdbcDsName.JDBC_DS_POOL_A; +import static org.glassfish.main.test.perf.benchmark.BenchmarkLimits.ENABLE_CONNECTION_VALIDATION; +import static org.glassfish.main.test.perf.benchmark.BenchmarkLimits.LIMIT_HTTP_REQUEST_TIMEOUT; +import static org.glassfish.main.test.perf.benchmark.BenchmarkLimits.LIMIT_POOL_EJB; +import static org.glassfish.main.test.perf.benchmark.BenchmarkLimits.LIMIT_POOL_HTTP_THREADS; +import static org.glassfish.main.test.perf.benchmark.BenchmarkLimits.LIMIT_POOL_JDBC; +import static org.glassfish.main.test.perf.benchmark.BenchmarkLimits.MEM_MAX_APP_HEAP; +import static org.glassfish.main.test.perf.benchmark.BenchmarkLimits.MEM_MAX_APP_OS; +import static org.glassfish.main.test.perf.benchmark.BenchmarkLimits.SERVER_LOG_LEVEL; +import static org.testcontainers.utility.MountableFile.forHostPath; + +/** + * Simple Embedded GlassFish container + */ +public class EmbeddedGlassFishContainer extends GenericContainer { + + private static final java.util.logging.Logger LOG_GF = java.util.logging.Logger.getLogger("EGF"); + + public static final String WAR_FILE = "/tmp.war"; + + /** + * Creates preconfigured container with GlassFish. + * + * @param network + * @param hostname + * @param logPrefix + */ + public EmbeddedGlassFishContainer(Network network, String hostname, String logPrefix, PostgreSQLContainer db) { + super("eclipse-temurin:" + Runtime.version().feature()); + PomEquippedResolveStage resolver = Maven.resolver() + .loadPomFromFile(JUnitSystem.detectBasedir().resolve("pom.xml").toFile()); + Path jarFile = resolver.resolve("org.glassfish.main.extras:glassfish-embedded-all") + .withoutTransitivity().asSingleFile().toPath(); + Path jdbcDriverFile = resolver.resolve("org.postgresql:postgresql") + .withoutTransitivity().asSingleFile().toPath(); + final String poolName = "domain-pool-" + JDBC_DS_POOL_A.charAt(JDBC_DS_POOL_A.length() - 1); + Path properties = generateProperties(db, poolName); + withNetwork(network) + .withCopyFileToContainer(forHostPath(jarFile), "/embedded-glassfish.jar") + .withCopyFileToContainer(forHostPath(jdbcDriverFile), "/postgresql.jar") + .withCopyFileToContainer(forHostPath(properties), "/gf-cfg.properties") + .withEnv("TZ", "UTC").withEnv("LC_ALL", "en_US.UTF-8") + .withStartupAttempts(1) + .withStartupTimeout(Duration.ofSeconds(30L)) + .withCreateContainerCmdModifier(cmd -> { + cmd.withEntrypoint("/bin/sh", "-c"); + cmd.withCmd(getCommandAdmin()); + cmd.withHostName(hostname); + cmd.withAttachStderr(true); + cmd.withAttachStdout(true); + final HostConfig hostConfig = cmd.getHostConfig(); + hostConfig.withMemory(MEM_MAX_APP_OS * 1024 * 1024 * 1024L); + hostConfig.withMemorySwappiness(0L); + hostConfig.withUlimits(new Ulimit[] {new Ulimit("nofile", 4096L, 8192L)}); + }) + .withLogConsumer(o -> LOG_GF.log(Level.INFO, o.getUtf8StringWithoutLineEnding())) + .withExposedPorts(8080) + .waitingFor(Wait.forLogMessage(".*########### GLASSFISH STARTED ###########.*", 1) + .withStartupTimeout(Duration.ofMinutes(5L))); + } + + /** + * @return JUL Logger to allow attaching handlers + */ + public java.util.logging.Logger getLogger() { + return LOG_GF; + } + + /** + * @param context + * @return HTTP REST client following redirects, logging requests and responses + */ + public WebTarget getRestClient(String context) { + return RestClientUtilities.getWebTarget(getHttpEndPoint(context), true); + } + + private URI getHttpEndPoint(String context) { + return URI.create("http://" + getHost() + ":" + getMappedPort(8080) + "/" + context); + } + + private static Path generateProperties(PostgreSQLContainer db, final String poolName) { + try { + Path properties = Files.createTempFile("gf-cfg", "properties"); + Properties cfg = new Properties(); + cfg.setProperty("command.0.addLibrary", "add-library " + "/postgresql.jar"); + cfg.setProperty("command.1.createPool", "create-jdbc-connection-pool" +// + " --ping" + + " --restype javax.sql.DataSource" + + " --datasourceclassname " + PGSimpleDataSource.class.getName() + + " --steadypoolsize 0 --maxpoolsize " + LIMIT_POOL_JDBC + + " --validationmethod auto-commit" + + " --isconnectvalidatereq " + ENABLE_CONNECTION_VALIDATION + " --failconnection true" // + + " --property user=" + db.getUsername() + ":password=" + db.getPassword() // + + ":DatabaseName=" + db.getDatabaseName() // + + ":ServerName=tc-testdb:port=" + 5432 + ":connectTimeout=10 " // + + poolName + ); + cfg.setProperty("command.2.createResource", + "create-jdbc-resource --connectionpoolid " + poolName + ' ' + JDBC_DS_POOL_A); +// cfg.setProperty("command.3.get", "get *"); + + cfg.setProperty("configs.config.server-config.ejb-container.max-pool-size", + Integer.toString(LIMIT_POOL_EJB)); + cfg.setProperty( + "configs.config.server-config.thread-pools.thread-pool.http-thread-pool.max-thread-pool-size", + Integer.toString(LIMIT_POOL_HTTP_THREADS)); + cfg.setProperty( + "configs.config.server-config.monitoring-service.module-monitoring-levels.jdbc-connection-pool", + "HIGH"); + cfg.setProperty( + "configs.config.server-config.network-config.protocols.protocol.https-listener.http.request-timeout-seconds", + Integer.toString(LIMIT_HTTP_REQUEST_TIMEOUT)); + + try (FileOutputStream outputStream = new FileOutputStream(properties.toFile())) { + cfg.store(outputStream, null); + } + return properties; + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + + private static String getCommandAdmin() { + final StringBuilder command = new StringBuilder(); + command.append("echo \"***************** Starting Embedded GlassFish *****************\""); + command.append(" && set -x && set -e"); + command.append(" && export LANG=\"en_US.UTF-8\"").append(" && export LANGUAGE=\"en_US.UTF-8\""); + command.append(" && (env | sort) && locale"); + command.append(" && ulimit -a"); + command.append(" && cat /etc/hosts && cat /etc/resolv.conf"); + command.append(" && hostname"); + command.append(" && java -version"); + command.append(" && mkdir -p /opt"); + command.append(" && cd /opt"); + command.append(" && cat /gf-cfg.properties"); + command.append(" && java ") + .append(" -D").append(JVM_OPT_LOGGING_MANAGER).append('=').append(CLASS_LOG_MANAGER_GLASSFISH) + .append(" -D").append(JVM_OPT_LOGGING_CFG_USE_DEFAULTS).append('=').append(true) + .append(" -D").append(JVM_OPT_LOGGING_CFG_DEFAULT_LEVEL).append('=').append(SERVER_LOG_LEVEL) + .append(" -Xmx").append(MEM_MAX_APP_HEAP).append('g') + .append(" -jar /embedded-glassfish.jar --properties=").append("/gf-cfg.properties") + .append(" --httpPort=8080 ") + .append(WAR_FILE); + return command.toString(); + } +} diff --git a/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/perf/util/LoggingResponseFilter.java b/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/perf/rest/LoggingResponseFilter.java similarity index 86% rename from appserver/tests/jdbc/src/test/java/org/glassfish/main/test/perf/util/LoggingResponseFilter.java rename to appserver/tests/jdbc/src/test/java/org/glassfish/main/test/perf/rest/LoggingResponseFilter.java index 432d272095d..f9ae861b93a 100644 --- a/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/perf/util/LoggingResponseFilter.java +++ b/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/perf/rest/LoggingResponseFilter.java @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 */ -package org.glassfish.main.test.perf.util; +package org.glassfish.main.test.perf.rest; import jakarta.ws.rs.client.ClientRequestContext; import jakarta.ws.rs.client.ClientResponseContext; @@ -22,9 +22,9 @@ import java.lang.System.Logger; -import org.testcontainers.shaded.org.apache.commons.lang3.builder.ReflectionToStringBuilder; +import org.apache.commons.lang3.builder.ReflectionToStringBuilder; -import static java.lang.System.Logger.Level.INFO; +import static java.lang.System.Logger.Level.DEBUG; /** * Logs REST communication when response comes. @@ -36,10 +36,10 @@ public class LoggingResponseFilter implements ClientResponseFilter { @Override public void filter(final ClientRequestContext requestContext, final ClientResponseContext responseContext) { - if (!LOG.isLoggable(INFO)) { + if (!LOG.isLoggable(DEBUG)) { return; } - LOG.log(INFO, "filter(requestContext, responseContext);" // + LOG.log(DEBUG, "filter(requestContext, responseContext);" // + "\nrequestContext: {0}" + "\nrequest headers: {1}" + "\nrequest cookies: {2}" diff --git a/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/perf/util/RestClientUtilities.java b/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/perf/rest/RestClientUtilities.java similarity index 97% rename from appserver/tests/jdbc/src/test/java/org/glassfish/main/test/perf/util/RestClientUtilities.java rename to appserver/tests/jdbc/src/test/java/org/glassfish/main/test/perf/rest/RestClientUtilities.java index bcde743c306..182f20632d4 100644 --- a/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/perf/util/RestClientUtilities.java +++ b/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/perf/rest/RestClientUtilities.java @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 */ -package org.glassfish.main.test.perf.util; +package org.glassfish.main.test.perf.rest; import com.fasterxml.jackson.databind.ObjectMapper; diff --git a/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/perf/util/UserRestClient.java b/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/perf/rest/UserRestClient.java similarity index 98% rename from appserver/tests/jdbc/src/test/java/org/glassfish/main/test/perf/util/UserRestClient.java rename to appserver/tests/jdbc/src/test/java/org/glassfish/main/test/perf/rest/UserRestClient.java index 56004c20e05..cbc23547712 100644 --- a/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/perf/util/UserRestClient.java +++ b/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/perf/rest/UserRestClient.java @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 */ -package org.glassfish.main.test.perf.util; +package org.glassfish.main.test.perf.rest; import jakarta.ws.rs.client.Entity; import jakarta.ws.rs.client.WebTarget; diff --git a/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/perf/server/DockerTestEnvironment.java b/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/perf/server/DockerTestEnvironment.java new file mode 100644 index 00000000000..e6851ac4507 --- /dev/null +++ b/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/perf/server/DockerTestEnvironment.java @@ -0,0 +1,239 @@ +/* + * Copyright (c) 2025 Contributors to the Eclipse Foundation. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.main.test.perf.server; + +import jakarta.ws.rs.client.WebTarget; + +import java.io.File; +import java.io.IOException; +import java.lang.System.Logger; + +import org.glassfish.main.test.perf.benchmark.Environment; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.exporter.ZipExporter; +import org.jboss.shrinkwrap.api.spec.WebArchive; +import org.postgresql.ds.PGSimpleDataSource; +import org.testcontainers.containers.Container.ExecResult; +import org.testcontainers.containers.PostgreSQLContainer; + +import static java.lang.System.Logger.Level.INFO; +import static org.glassfish.main.test.jdbc.pool.war.JdbcDsName.JDBC_DS_POOL_A; +import static org.glassfish.main.test.jdbc.pool.war.JdbcDsName.JDBC_DS_POOL_B; +import static org.glassfish.main.test.perf.benchmark.BenchmarkLimits.ENABLE_CONNECTION_VALIDATION; +import static org.glassfish.main.test.perf.benchmark.BenchmarkLimits.LIMIT_POOL_JDBC; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.stringContainsInOrder; +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * Environment of Eclipse GlassFish server, Derby and PostgreSQL databases. + */ +public class DockerTestEnvironment extends Environment { + + private static final Logger LOG = System.getLogger(DockerTestEnvironment.class.getName()); + + private static DockerTestEnvironment instance; + + private final GlassFishContainer appServer; + + /** + * Creates network, databases and application server, but doesn't start them. + */ + public DockerTestEnvironment() { + appServer = new GlassFishContainer(getNetwork(), "admin", "A"); + Thread hook = new Thread(this::stop); + Runtime.getRuntime().addShutdownHook(hook); + } + + /** + * @return Logger printing GlassFish server.log file + */ + public java.util.logging.Logger getDomainLogger() { + return appServer.getLogger(); + } + + /** + * GlassFish's asadmin get -m [key] execution + * + * @param key monitoring property key + * @return int value + */ + public int asadminMonitor(String key) { + return appServer.asadminGetInt(key); + } + + /** + * GlassFish's asadmin script execution. + * + * @param commandName + * @param arguments + * @return result of the command + */ + public ExecResult asadmin(String commandName, String... arguments) { + return appServer.asadmin(commandName, arguments); + } + + /** + * Start the environment - start Postgress database, and GlassFish's domain1. + * Create JDBC pool and resources. + * Start Derby (comes with GlassFish) + */ + @Override + public void start() { + super.start(); + PostgreSQLContainer db = getDatabase(); + appServer.start(); + for (String jndiName : new String[] {JDBC_DS_POOL_A, JDBC_DS_POOL_B}) { + final String poolName = "domain-pool-" + jndiName.charAt(jndiName.length() - 1); + assertEquals(0, appServer.asadmin("create-jdbc-connection-pool", "--ping", + "--restype", "javax.sql.DataSource", // + "--datasourceclassname", PGSimpleDataSource.class.getName(), // + "--steadypoolsize", "0", "--maxpoolsize", Integer.toString(LIMIT_POOL_JDBC), // + "--validationmethod", "auto-commit", // + "--isconnectvalidatereq", Boolean.toString(ENABLE_CONNECTION_VALIDATION), "--failconnection", "true", // + "--property", "user=" + db.getUsername() + ":password=" + db.getPassword() // + + ":DatabaseName=" + db.getDatabaseName() // + + ":ServerName=tc-testdb:port=" + 5432 + ":connectTimeout=10" // + , // + poolName).getExitCode()); + assertEquals(0, + appServer.asadmin("create-jdbc-resource", "--connectionpoolid", poolName, jndiName).getExitCode()); + } + assertEquals(0, appServer.asadmin("get", "resources.jdbc-connection-pool.*").getExitCode()); + assertEquals(0, appServer.asadmin("start-database").getExitCode()); + + final String respListPools = appServer.asadmin("list-jdbc-connection-pools").getStdout(); + assertThat("list-jdbc-connection-pools response", respListPools, + stringContainsInOrder("__TimerPool", "DerbyPool", "domain-pool-A", "domain-pool-B")); + } + + + /** + * Stops all virtual computers, and destroys the virtual network. + */ + @Override + public void stop() { + LOG.log(INFO, "Closing docker containers ..."); + if (appServer.isRunning()) { + appServer.asadmin("stop-domain", "--kill"); + closeSilently(appServer); + } + super.stop(); + } + + + /** + * Create and deploy the war file. + * + * @param appName name and application context + * @param classes classes included to the war file. + * @return endpoint of the application - it is expected that the context root is the application + * name. + */ + public WebTarget deploy(String appName, Class... classes) { + File warFile = getArchiveToDeploy(appName, classes); + return deploy(appName, warFile); + } + + /** + * Create and deploy the war file. + * + * @param appName name and application context + * @param pkgs packages included to the war file. + * @return endpoint of the application - it is expected that the context root is the application + * name. + */ + public WebTarget deploy(String appName, Package... pkgs) { + File warFile = getArchiveToDeploy(appName, pkgs); + return deploy(appName, warFile); + } + + /** + * Deploy the war file. + * + * @param appName name and application context + * @param war the war file to be deployed. + * @return endpoint of the application - it is expected that the context root is the application + * name. + */ + @Override + public WebTarget deploy(String appName, WebArchive war) { + File warFile = toFile(appName, war); + return deploy(appName, warFile); + } + + + /** + * Deploy the war file. + * + * @param appName name and application context + * @param warFile the war file to be deployed. + * @return endpoint of the application - it is expected that the context root is the application + * name. + */ + public WebTarget deploy(String appName, File warFile) { + return appServer.deploy(appName, warFile); + } + + /** + * Undeploy the web application + * + * @param appName + */ + @Override + public void undeploy(String appName) { + final ExecResult result = appServer.asadmin("undeploy", appName); + assertEquals(0, result.getExitCode(), "Undeploy exit code"); + } + + /** + * Creates the environment once, then provides always the same instance. + * + * @return {@link DockerTestEnvironment} + */ + public static synchronized DockerTestEnvironment getInstance() { + if (instance == null) { + instance = new DockerTestEnvironment(); + instance.start(); + } + return instance; + } + + + private static File getArchiveToDeploy(String appName, Class... classes) { + final WebArchive war = ShrinkWrap.create(WebArchive.class).addClasses(classes); + LOG.log(INFO, war.toString(true)); + return toFile(appName, war); + } + + private static File getArchiveToDeploy(String appName, Package... pkg) { + final WebArchive war = ShrinkWrap.create(WebArchive.class).addPackages(true, pkg); + return toFile(appName, war); + } + + private static File toFile(final String appName, final WebArchive war) { + LOG.log(INFO, () -> war.toString(true)); + File warFile; + try { + warFile = File.createTempFile(appName, ".war"); + } catch (IOException e) { + throw new RuntimeException(e); + } + war.as(ZipExporter.class).exportTo(warFile, true); + return warFile; + } +} diff --git a/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/perf/util/GlassFishContainer.java b/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/perf/server/GlassFishContainer.java similarity index 66% rename from appserver/tests/jdbc/src/test/java/org/glassfish/main/test/perf/util/GlassFishContainer.java rename to appserver/tests/jdbc/src/test/java/org/glassfish/main/test/perf/server/GlassFishContainer.java index f9e42aa649c..defbc2776b4 100644 --- a/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/perf/util/GlassFishContainer.java +++ b/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/perf/server/GlassFishContainer.java @@ -14,13 +14,14 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 */ -package org.glassfish.main.test.perf.util; +package org.glassfish.main.test.perf.server; import com.github.dockerjava.api.model.HostConfig; import com.github.dockerjava.api.model.Ulimit; import jakarta.ws.rs.client.WebTarget; +import java.io.File; import java.io.IOException; import java.lang.System.Logger; import java.net.URI; @@ -30,22 +31,31 @@ import java.util.List; import java.util.logging.Level; +import org.glassfish.main.test.perf.rest.RestClientUtilities; +import org.glassfish.tests.utils.junit.JUnitSystem; +import org.jboss.shrinkwrap.resolver.api.maven.Maven; +import org.jboss.shrinkwrap.resolver.api.maven.PomEquippedResolveStage; import org.testcontainers.containers.GenericContainer; import org.testcontainers.containers.Network; import org.testcontainers.containers.wait.strategy.Wait; -import org.testcontainers.utility.MountableFile; import static java.lang.System.Logger.Level.ERROR; import static java.lang.System.Logger.Level.INFO; +import static org.glassfish.main.test.perf.benchmark.BenchmarkLimits.LIMIT_HTTP_REQUEST_TIMEOUT; +import static org.glassfish.main.test.perf.benchmark.BenchmarkLimits.LIMIT_POOL_EJB; +import static org.glassfish.main.test.perf.benchmark.BenchmarkLimits.LIMIT_POOL_HTTP_THREADS; +import static org.glassfish.main.test.perf.benchmark.BenchmarkLimits.MEM_MAX_APP_HEAP; +import static org.glassfish.main.test.perf.benchmark.BenchmarkLimits.MEM_MAX_APP_OS; +import static org.glassfish.main.test.perf.benchmark.BenchmarkLimits.SERVER_LOG_LEVEL; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.stringContainsInOrder; +import static org.testcontainers.utility.MountableFile.forHostPath; /** * Simple GlassFish container based on domain1. */ public class GlassFishContainer extends GenericContainer { - public static final int LIMIT_HTTP_THREADS = 1000; - private static final int LIMIT_HTTP_REQUEST_TIMEOUT = 5; - private static final Logger LOG = System.getLogger(GlassFishContainer.class.getName()); private static final java.util.logging.Logger LOG_GF = java.util.logging.Logger.getLogger("GF"); @@ -61,15 +71,21 @@ public class GlassFishContainer extends GenericContainer { /** * Creates preconfigured container with GlassFish. * - * @param glassFishZip GlassFish zip file. * @param network * @param hostname * @param logPrefix */ - public GlassFishContainer(MountableFile glassFishZip, Network network, String hostname, String logPrefix) { + public GlassFishContainer(Network network, String hostname, String logPrefix) { super("eclipse-temurin:" + Runtime.version().feature()); + PomEquippedResolveStage resolver = Maven.resolver() + .loadPomFromFile(JUnitSystem.detectBasedir().resolve("pom.xml").toFile()); + Path zipFile = resolver.resolve("org.glassfish.main.distributions:glassfish:zip:?") + .withoutTransitivity().asSingleFile().toPath(); + Path jdbcDriverFile = resolver.resolve("org.postgresql:postgresql:jar:?") + .withoutTransitivity().asSingleFile().toPath(); withNetwork(network) - .withCopyFileToContainer(glassFishZip, "/glassfish.zip") + .withCopyFileToContainer(forHostPath(zipFile), "/glassfish.zip") + .withCopyFileToContainer(forHostPath(jdbcDriverFile), "/postgresql.jar") .withEnv("TZ", "UTC").withEnv("LC_ALL", "en_US.UTF-8") .withStartupAttempts(1) .withStartupTimeout(Duration.ofSeconds(30L)) @@ -80,7 +96,7 @@ public GlassFishContainer(MountableFile glassFishZip, Network network, String ho cmd.withAttachStderr(true); cmd.withAttachStdout(true); final HostConfig hostConfig = cmd.getHostConfig(); - hostConfig.withMemory(8 * 1024 * 1024 * 1024L); + hostConfig.withMemory(MEM_MAX_APP_OS * 1024 * 1024 * 1024L); hostConfig.withMemorySwappiness(0L); hostConfig.withUlimits(new Ulimit[] {new Ulimit("nofile", 4096L, 8192L)}); }) @@ -90,26 +106,47 @@ public GlassFishContainer(MountableFile glassFishZip, Network network, String ho Wait.forLogMessage(".*Total startup time including CLI.*", 1).withStartupTimeout(Duration.ofMinutes(5L))); } + /** - * Mount provided JDBC drivers and install them to domain1 when starting the container. - * - * @param jdbcDriverJars - * @return this + * @return JUL Logger to allow attaching handlers */ - public GlassFishContainer withJdbcDrivers(MountableFile... jdbcDriverJars) { - for (MountableFile jdbcDriverFile : jdbcDriverJars) { - withCopyFileToContainer(jdbcDriverFile, - "/" + Path.of(jdbcDriverFile.getFilesystemPath()).getFileName().toString()); - } - return this; + public java.util.logging.Logger getLogger() { + return LOG_GF; } /** - * @return JUL Logger to allow attaching handlers + * Deploy the war file. + * + * @param appName name and application context + * @param warFile the war file to be deployed. + * @return endpoint of the application - it is expected that the context root is the application + * name. */ - public java.util.logging.Logger getLogger() { - return LOG_GF; + public WebTarget deploy(String appName, File warFile) { + final String warFileInContainer = "/tmp.war"; + copyFileToContainer(forHostPath(warFile.toPath()), warFileInContainer); + try { + final ExecResult result = asadmin("deploy", "--contextroot", appName, "--name", appName, + warFileInContainer); + assertThat("deploy response", result.getStdout(), + stringContainsInOrder("Application deployed with name " + appName)); + return getRestClient(appName); + } finally { + try { + execInContainer("rm", warFileInContainer); + } catch (UnsupportedOperationException | IOException | InterruptedException e) { + throw new RuntimeException(e); + } + } + } + + + @Override + public void stop() { + // Print all results - for tuning. + asadmin("get", "--monitor", "*"); + super.stop(); } @@ -179,17 +216,28 @@ private static String getCommandAdmin() { command.append(" && cd ../glassfish/bin && chmod +x asadmin appclient startserv stopserv"); command.append(" && mv /*.jar ").append(PATH_DOCKER_GF_DOMAIN.resolve("lib")); command.append(" && ").append(PATH_DOCKER_ASADMIN).append(" start-domain ").append("domain1"); - command.append(" && ").append(PATH_DOCKER_ASADMIN).append(" set-log-levels ").append("org.postgresql.level=FINEST"); - command.append(" && ").append(PATH_DOCKER_ASADMIN).append(" set ").append("configs.config.server-config.thread-pools.thread-pool.http-thread-pool.max-thread-pool-size=" + LIMIT_HTTP_THREADS); - command.append(" && ").append(PATH_DOCKER_ASADMIN).append(" set ").append("configs.config.server-config.ejb-container.max-pool-size=900"); + // FIXME: We would have to go over all loggers + command.append(" && ").append(PATH_DOCKER_ASADMIN).append(" set-log-levels ").append("org.postgresql.level=").append(SERVER_LOG_LEVEL); + command.append(" && ").append(PATH_DOCKER_ASADMIN).append(" set-log-levels ") + .append("org.glassfish.main.jul.handler.GlassFishLogHandler.level=").append(SERVER_LOG_LEVEL); + command.append(" && ").append(PATH_DOCKER_ASADMIN).append(" set ") + .append("configs.config.server-config.ejb-container.max-pool-size=").append(LIMIT_POOL_EJB); + command.append(" && ").append(PATH_DOCKER_ASADMIN).append(" set ") + .append("configs.config.server-config.thread-pools.thread-pool.http-thread-pool.max-thread-pool-size=") + .append(LIMIT_POOL_HTTP_THREADS); // Monitoring takes around 10% of throughput - command.append(" && ").append(PATH_DOCKER_ASADMIN).append(" set ").append("configs.config.server-config.monitoring-service.module-monitoring-levels.jdbc-connection-pool=HIGH"); + command.append(" && ").append(PATH_DOCKER_ASADMIN).append(" set ").append( + "configs.config.server-config.monitoring-service.module-monitoring-levels.jdbc-connection-pool=HIGH"); + command.append(" && ").append(PATH_DOCKER_ASADMIN).append(" set ").append( + "configs.config.server-config.network-config.protocols.protocol.http-listener-1.http.request-timeout-seconds=") + .append(LIMIT_HTTP_REQUEST_TIMEOUT); // Does thread dumps periodically, takes much more // command.append(" && ").append(PATH_DOCKER_ASADMIN).append(" set ").append("configs.config.server-config.monitoring-service.module-monitoring-levels.jvm=HIGH"); // FIXME: Does not work, see https://github.com/eclipse-ee4j/glassfish/issues/25701 // command.append(" && ").append(PATH_DOCKER_ASADMIN).append(" set ").append("configs.config.server-config.monitoring-service.module-monitoring-levels.thread-pool=HIGH"); command.append(" && ").append(PATH_DOCKER_ASADMIN).append(" delete-jvm-options ").append("-Xmx512m"); - command.append(" && ").append(PATH_DOCKER_ASADMIN).append(" create-jvm-options ").append("-Xmx2g"); + command.append(" && ").append(PATH_DOCKER_ASADMIN).append(" create-jvm-options ").append("-Xmx") + .append(MEM_MAX_APP_HEAP).append('g'); command.append(" && ").append(PATH_DOCKER_ASADMIN).append(" restart-domain ").append("domain1"); command.append(" && tail -n 10000 -F ").append(PATH_DOCKER_GF_DOMAIN1_SERVER_LOG); return command.toString(); diff --git a/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/perf/util/DockerTestEnvironment.java b/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/perf/util/DockerTestEnvironment.java deleted file mode 100644 index 1141508d4fc..00000000000 --- a/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/perf/util/DockerTestEnvironment.java +++ /dev/null @@ -1,313 +0,0 @@ -/* - * Copyright (c) 2025 Contributors to the Eclipse Foundation. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0, which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the - * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, - * version 2 with the GNU Classpath Exception, which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - */ - -package org.glassfish.main.test.perf.util; - -import com.github.database.rider.core.api.connection.ConnectionHolder; -import com.github.database.rider.core.api.dataset.DataSetExecutor; -import com.github.database.rider.core.dataset.DataSetExecutorImpl; -import com.github.dockerjava.api.DockerClient; - -import jakarta.ws.rs.client.WebTarget; - -import java.io.File; -import java.io.IOException; -import java.lang.System.Logger; - -import org.jboss.shrinkwrap.api.ShrinkWrap; -import org.jboss.shrinkwrap.api.exporter.ZipExporter; -import org.jboss.shrinkwrap.api.spec.WebArchive; -import org.postgresql.ds.PGSimpleDataSource; -import org.testcontainers.DockerClientFactory; -import org.testcontainers.containers.Container.ExecResult; -import org.testcontainers.containers.Network; -import org.testcontainers.containers.PostgreSQLContainer; -import org.testcontainers.utility.MountableFile; - -import static java.lang.System.Logger.Level.INFO; -import static java.lang.System.Logger.Level.TRACE; -import static java.lang.System.Logger.Level.WARNING; -import static org.glassfish.main.test.jdbc.pool.war.JdbcDsName.JDBC_DS_POOL_A; -import static org.glassfish.main.test.jdbc.pool.war.JdbcDsName.JDBC_DS_POOL_B; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.stringContainsInOrder; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assumptions.assumeTrue; -import static org.testcontainers.utility.MountableFile.forClasspathResource; - -/** - * Environment of Eclipse GlassFish, Derby and Postgress SQL databases. - */ -public class DockerTestEnvironment { - public static final int LIMIT_DB = 300; - public static final int LIMIT_JDBC = 200; - - private static final Logger LOG = System.getLogger(DockerTestEnvironment.class.getName()); - private static final Logger LOG_DB = System.getLogger("DB"); - - /** Docker network */ - private static final Network NET = Network.newNetwork(); - - @SuppressWarnings("resource") - private static final PostgreSQLContainer DATABASE = new PostgreSQLContainer<>("postgres:17.6") - .withNetwork(NET).withDatabaseName("testdb").withExposedPorts(5432).withCreateContainerCmdModifier(cmd -> { - cmd.withHostName("tc-testdb"); - cmd.withAttachStderr(true); - cmd.withAttachStdout(true); - }) - .withLogConsumer(o -> LOG_DB.log(INFO, o.getUtf8StringWithoutLineEnding())) - .withCommand("postgres", - "-c", "log_statement=none", - "-c", "log_destination=stderr", - "-c", "max_connections=" + LIMIT_DB - ); - - @SuppressWarnings("resource") - private static final GlassFishContainer AS_DOMAIN = new GlassFishContainer(forClasspathResource("/glassfish.zip"), - NET, "admin", "A").withJdbcDrivers(forClasspathResource("/postgresql.jar")); - - static { - Thread hook = new Thread(DockerTestEnvironment::stop); - Runtime.getRuntime().addShutdownHook(hook); - start(); - } - - private static ConnectionHolder connectionHolder; - private static DataSetExecutor dsExecutor; - - /** - * @return Logger printing GlassFish server.log file - */ - public static java.util.logging.Logger getDomainLogger() { - return AS_DOMAIN.getLogger(); - } - - /** - * GlassFish's asadmin get -m [key] execution - * - * @param key monitoring property key - * @return int value - */ - public static int asadminMonitor(String key) { - return AS_DOMAIN.asadminGetInt(key); - } - - /** - * GlassFish's asadmin script execution. - * - * @param commandName - * @param arguments - * @return result of the command - */ - public static ExecResult asadmin(String commandName, String... arguments) { - return AS_DOMAIN.asadmin(commandName, arguments); - } - - /** - * Start the environment - start Postgress database, and GlassFish's domain1. - * Create JDBC pool and resources. - * Start Derby (comes with GlassFish) - */ - public static void start() { - assumeTrue(DockerClientFactory.instance().isDockerAvailable(), "Docker is not available on this environment"); - DATABASE.start(); - AS_DOMAIN.start(); - final PGSimpleDataSource dataSource = new PGSimpleDataSource(); - dataSource.setDatabaseName(DATABASE.getDatabaseName()); - dataSource.setServerNames(new String[] {DATABASE.getHost()}); - dataSource.setUrl(DATABASE.getJdbcUrl()); - dataSource.setUser(DATABASE.getUsername()); - dataSource.setPassword(DATABASE.getPassword()); - dataSource.setConnectTimeout(60); - connectionHolder = dataSource::getConnection; - dsExecutor = DataSetExecutorImpl.instance("executor", connectionHolder); - reinitializeDatabase(); - - for (String jndiName : new String[] {JDBC_DS_POOL_A, JDBC_DS_POOL_B}) { - final String poolName = "domain-pool-" + jndiName.charAt(jndiName.length() - 1); - assertEquals(0, AS_DOMAIN.asadmin("create-jdbc-connection-pool", "--ping", - "--restype", "javax.sql.DataSource", // - "--datasourceclassname", PGSimpleDataSource.class.getName(), // - "--steadypoolsize", "0", "--maxpoolsize", Integer.toString(LIMIT_JDBC), // - "--validationmethod", "auto-commit", // -// "--isconnectvalidatereq", "false", "--failconnection", "false", // - "--isconnectvalidatereq", "true", "--failconnection", "true", // - "--property", "user=" + DATABASE.getUsername() + ":password=" + DATABASE.getPassword() // - + ":DatabaseName=" + DATABASE.getDatabaseName() // - + ":ServerName=tc-testdb:port=" + 5432 + ":connectTimeout=10" // - , // - poolName).getExitCode()); - assertEquals(0, - AS_DOMAIN.asadmin("create-jdbc-resource", "--connectionpoolid", poolName, jndiName).getExitCode()); - } - - assertEquals(0, AS_DOMAIN.asadmin("start-database").getExitCode()); - - final String respListPools = AS_DOMAIN.asadmin("list-jdbc-connection-pools").getStdout(); - assertThat("list-jdbc-connection-pools response", respListPools, - stringContainsInOrder("__TimerPool", "DerbyPool", "domain-pool-A", "domain-pool-B")); - } - - - public static void disconnectDatabase(int seconds) { - DockerClient client = DockerClientFactory.instance().client(); - LOG.log(INFO, "Disconnecting database from network!"); - client.disconnectFromNetworkCmd().withNetworkId(NET.getId()).withContainerId(DATABASE.getContainerId()).exec(); - try { - Thread.sleep(seconds * 1000L); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - client.connectToNetworkCmd().withNetworkId(NET.getId()).withContainerId(DATABASE.getContainerId()).exec(); - LOG.log(INFO, "Database reconnected to the network!"); - } - - - /** - * Stops all virtual computers, and destroys the virtual network. - */ - public static void stop() { - LOG.log(INFO, "Closing docker containers ..."); - if (AS_DOMAIN.isRunning()) { - AS_DOMAIN.asadmin("stop-domain", "--kill"); - closeSilently(AS_DOMAIN); - } - if (DATABASE.isRunning()) { - closeSilently(DATABASE); - } - closeSilently(NET); - } - - - /** - * Drop all data and recreate just those existing before the test. - */ - public static void reinitializeDatabase() { - dsExecutor.executeScript("initSchema.sql"); - } - - /** - * Create and deploy the war file. - * - * @param appName name and application context - * @param classes classes included to the war file. - * @return endpoint of the application - it is expected that the context root is the application - * name. - */ - public static WebTarget deploy(String appName, Class... classes) { - File warFile = getArchiveToDeploy(appName, classes); - return deploy(appName, warFile); - } - - /** - * Create and deploy the war file. - * - * @param appName name and application context - * @param pkgs packages included to the war file. - * @return endpoint of the application - it is expected that the context root is the application - * name. - */ - public static WebTarget deploy(String appName, Package... pkgs) { - File warFile = getArchiveToDeploy(appName, pkgs); - return deploy(appName, warFile); - } - - /** - * Deploy the war file. - * - * @param appName name and application context - * @param war the war file to be deployed. - * @return endpoint of the application - it is expected that the context root is the application - * name. - */ - public static WebTarget deploy(String appName, WebArchive war) { - File warFile = toFile(appName, war); - return deploy(appName, warFile); - } - - /** - * Deploy the war file. - * - * @param appName name and application context - * @param warFile the war file to be deployed. - * @return endpoint of the application - it is expected that the context root is the application - * name. - */ - public static WebTarget deploy(String appName, File warFile) { - final String warFileInContainer = "/tmp.war"; - AS_DOMAIN.copyFileToContainer(MountableFile.forHostPath(warFile.toPath()), warFileInContainer); - try { - final ExecResult result = AS_DOMAIN.asadmin("deploy", "--contextroot", appName, "--name", appName, - warFileInContainer); - assertThat("deploy response", result.getStdout(), - stringContainsInOrder("Application deployed with name " + appName)); - return AS_DOMAIN.getRestClient(appName); - } finally { - try { - AS_DOMAIN.execInContainer("rm", warFileInContainer); - } catch (UnsupportedOperationException | IOException | InterruptedException e) { - throw new RuntimeException(e); - } - } - } - - /** - * Undeploy the web application - * - * @param appName - */ - public static void undeploy(String appName) { - final ExecResult result = AS_DOMAIN.asadmin("undeploy", appName); - assertEquals(0, result.getExitCode(), "Undeploy exit code"); - } - - private static File getArchiveToDeploy(String appName, Class... classes) { - final WebArchive war = ShrinkWrap.create(WebArchive.class).addClasses(classes); - LOG.log(INFO, war.toString(true)); - return toFile(appName, war); - } - - private static File getArchiveToDeploy(String appName, Package... pkg) { - final WebArchive war = ShrinkWrap.create(WebArchive.class).addPackages(true, pkg); - return toFile(appName, war); - } - - private static File toFile(final String appName, final WebArchive war) { - LOG.log(INFO, () -> war.toString(true)); - File warFile; - try { - warFile = File.createTempFile(appName, ".war"); - } catch (IOException e) { - throw new RuntimeException(e); - } - war.as(ZipExporter.class).exportTo(warFile, true); - return warFile; - } - - - // TODO: Move to jdke - private static void closeSilently(final AutoCloseable closeable) { - LOG.log(TRACE, "closeSilently(closeable={0})", closeable); - if (closeable == null) { - return; - } - try { - closeable.close(); - } catch (final Exception e) { - LOG.log(WARNING, "Close method caused an exception.", e); - } - } -} diff --git a/appserver/tests/jdbc/src/test/resources/initSchema.sql b/appserver/tests/jdbc/src/test/resources/initSchema.sql index a78807c30f5..d1391fc0305 100644 --- a/appserver/tests/jdbc/src/test/resources/initSchema.sql +++ b/appserver/tests/jdbc/src/test/resources/initSchema.sql @@ -18,6 +18,8 @@ CREATE TABLE IF NOT EXISTS "GlassFishUser" ( PRIMARY KEY (id) ); +CREATE INDEX IF NOT EXISTS "GlassFishUserName" ON "GlassFishUser" USING BTREE ("name"); + DELETE FROM "JEEVersion" WHERE 1=1; DELETE FROM "Brand" WHERE 1=1; DELETE FROm "GlassFishUser" WHERE 1=1; diff --git a/appserver/tests/pom.xml b/appserver/tests/pom.xml index ecd15fa626f..7e3eac44d1a 100755 --- a/appserver/tests/pom.xml +++ b/appserver/tests/pom.xml @@ -35,7 +35,7 @@ GlassFish Tests - 1.21.3 + 2.0.1 @@ -48,13 +48,13 @@ org.testcontainers - junit-jupiter + testcontainers-junit-jupiter ${testcontainers.version} test org.testcontainers - postgresql + testcontainers-postgresql ${testcontainers.version} test diff --git a/nucleus/core/bootstrap/src/main/java/com/sun/enterprise/glassfish/bootstrap/cfg/ServerFiles.java b/nucleus/core/bootstrap/src/main/java/com/sun/enterprise/glassfish/bootstrap/cfg/ServerFiles.java index ee80eefdc6b..db43df9cec9 100644 --- a/nucleus/core/bootstrap/src/main/java/com/sun/enterprise/glassfish/bootstrap/cfg/ServerFiles.java +++ b/nucleus/core/bootstrap/src/main/java/com/sun/enterprise/glassfish/bootstrap/cfg/ServerFiles.java @@ -95,7 +95,15 @@ public static File detectInstallRoot() { // glassfish/lib/bootstrap/glassfish.jar File bootstrapFile = findBootstrapFile(); // glassfish/ - return bootstrapFile.getParentFile().getParentFile().getParentFile(); + // Defensive code for embedded. + File parent = bootstrapFile.getParentFile(); + for (int i = 0; i < 2; i++) { + if (parent == null) { + return null; + } + parent = parent.getParentFile(); + } + return parent; } diff --git a/nucleus/core/kernel/src/main/java/org/glassfish/runnablejar/commandline/Option.java b/nucleus/core/kernel/src/main/java/org/glassfish/runnablejar/commandline/Option.java index cc5ee94d060..f09742853c1 100644 --- a/nucleus/core/kernel/src/main/java/org/glassfish/runnablejar/commandline/Option.java +++ b/nucleus/core/kernel/src/main/java/org/glassfish/runnablejar/commandline/Option.java @@ -63,6 +63,7 @@ public enum Option { + " The property \"properties\" can also be defined in this file, pointing to another file." + " In that case, properties will be loaded also from that file.") { + @Override public void handle(String value, Arguments arguments) { loadPropertiesFromFile(value, arguments); } @@ -133,7 +134,7 @@ public void handle(String fileName, Arguments arguments) { LogManager.getLogManager().readConfiguration(logConfig); } } catch (IOException | NullPointerException ex) { - logger.log(WARNING, ex, () -> "Could not open properties file " + fileName); + LOG.log(WARNING, ex, () -> "Could not open properties file " + fileName); } } }, @@ -195,21 +196,21 @@ public boolean handlesValue(String value) { }; - protected static final Logger logger = Logger.getLogger(Option.class.getName()); + private static final Logger LOG = Logger.getLogger(Option.class.getName()); private String mainName; private Set aliases; private String helpText; private String usage; - private Option(String mainName, Set aliases, String usage, String helpText) { + Option(String mainName, Set aliases, String usage, String helpText) { this.mainName = mainName; this.aliases = aliases; this.usage = usage; this.helpText = helpText; } - private Option(String mainName, String usage, String helpText) { + Option(String mainName, String usage, String helpText) { this(mainName, Set.of(), usage, helpText); } @@ -286,11 +287,11 @@ protected void loadPropertiesFromFile(String fileName, Arguments arguments) { try { arguments.setOption(key, value); } catch (UnknownPropertyException e) { - logger.log(Level.WARNING, e, () -> "Invalid property '" + e.getKey() + "' in file " + fileName); + LOG.log(Level.WARNING, e, () -> "Invalid property '" + e.getKey() + "' in file " + fileName); } }); } catch (IOException e) { - logger.log(Level.WARNING, e, () -> "Could not read properties from file " + fileName + " - " + e.getMessage()); + LOG.log(Level.WARNING, e, () -> "Could not read properties from file " + fileName + " - " + e.getMessage()); } } @@ -301,7 +302,7 @@ protected void loadApplicationsFromDirectory(String directoryName, Arguments arg arguments.deployables.add(appFile.getAbsolutePath()); } } else { - logger.log(WARNING, () -> "The path specified with the " + this.getUsage() + " option is not a directory: " + directoryName); + LOG.log(WARNING, () -> "The path specified with the " + this.getUsage() + " option is not a directory: " + directoryName); } }