mirror of
https://github.com/corda/corda.git
synced 2024-12-19 21:17:58 +00:00
CORDA-1099: Orchestrated clean shutdown from Shell (#2831)
This commit is contained in:
parent
c964e50696
commit
7a077e76f0
@ -658,10 +658,25 @@ public static final class net.corda.core.contracts.UniqueIdentifier$Companion ex
|
||||
@org.jetbrains.annotations.NotNull public abstract List getServiceFlows()
|
||||
@org.jetbrains.annotations.NotNull public abstract List getServices()
|
||||
##
|
||||
@net.corda.core.DoNotImplement public interface net.corda.core.cordapp.CordappConfig
|
||||
public abstract boolean exists(String)
|
||||
@org.jetbrains.annotations.NotNull public abstract Object get(String)
|
||||
public abstract boolean getBoolean(String)
|
||||
public abstract double getDouble(String)
|
||||
public abstract float getFloat(String)
|
||||
public abstract int getInt(String)
|
||||
public abstract long getLong(String)
|
||||
@org.jetbrains.annotations.NotNull public abstract Number getNumber(String)
|
||||
@org.jetbrains.annotations.NotNull public abstract String getString(String)
|
||||
##
|
||||
public final class net.corda.core.cordapp.CordappConfigException extends java.lang.Exception
|
||||
public <init>(String, Throwable)
|
||||
##
|
||||
public final class net.corda.core.cordapp.CordappContext extends java.lang.Object
|
||||
public <init>(net.corda.core.cordapp.Cordapp, net.corda.core.crypto.SecureHash, ClassLoader, net.corda.core.cordapp.CordappConfig)
|
||||
@org.jetbrains.annotations.Nullable public final net.corda.core.crypto.SecureHash getAttachmentId()
|
||||
@org.jetbrains.annotations.NotNull public final ClassLoader getClassLoader()
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.core.cordapp.CordappConfig getConfig()
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.core.cordapp.Cordapp getCordapp()
|
||||
##
|
||||
@net.corda.core.DoNotImplement public interface net.corda.core.cordapp.CordappProvider
|
||||
@ -963,6 +978,8 @@ public static final class net.corda.core.crypto.PartialMerkleTree$Companion exte
|
||||
@kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public static final net.corda.core.crypto.SecureHash$SHA256 sha256Twice(byte[])
|
||||
@org.jetbrains.annotations.NotNull public String toString()
|
||||
public static final net.corda.core.crypto.SecureHash$Companion Companion
|
||||
@org.jetbrains.annotations.NotNull public static final net.corda.core.crypto.SecureHash$SHA256 allOnesHash
|
||||
@org.jetbrains.annotations.NotNull public static final net.corda.core.crypto.SecureHash$SHA256 zeroHash
|
||||
##
|
||||
public static final class net.corda.core.crypto.SecureHash$Companion extends java.lang.Object
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash$SHA256 getAllOnesHash()
|
||||
@ -1368,6 +1385,15 @@ public static final class net.corda.core.flows.NotarisationRequest$Companion ext
|
||||
public int hashCode()
|
||||
public String toString()
|
||||
##
|
||||
@net.corda.core.serialization.CordaSerializable public final class net.corda.core.flows.NotarisationResponse extends java.lang.Object
|
||||
public <init>(List)
|
||||
@org.jetbrains.annotations.NotNull public final List component1()
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.core.flows.NotarisationResponse copy(List)
|
||||
public boolean equals(Object)
|
||||
@org.jetbrains.annotations.NotNull public final List getSignatures()
|
||||
public int hashCode()
|
||||
public String toString()
|
||||
##
|
||||
@net.corda.core.flows.InitiatingFlow public final class net.corda.core.flows.NotaryChangeFlow extends net.corda.core.flows.AbstractStateReplacementFlow$Instigator
|
||||
public <init>(net.corda.core.contracts.StateAndRef, net.corda.core.identity.Party, net.corda.core.utilities.ProgressTracker)
|
||||
@org.jetbrains.annotations.NotNull protected net.corda.core.flows.AbstractStateReplacementFlow$UpgradeTx assembleTx()
|
||||
@ -1375,10 +1401,12 @@ public static final class net.corda.core.flows.NotarisationRequest$Companion ext
|
||||
@net.corda.core.serialization.CordaSerializable public abstract class net.corda.core.flows.NotaryError extends java.lang.Object
|
||||
##
|
||||
@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.flows.NotaryError$Conflict extends net.corda.core.flows.NotaryError
|
||||
public <init>(net.corda.core.crypto.SecureHash, Map)
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash component1()
|
||||
@org.jetbrains.annotations.NotNull public final Map component2()
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.core.flows.NotaryError$Conflict copy(net.corda.core.crypto.SecureHash, Map)
|
||||
public boolean equals(Object)
|
||||
@org.jetbrains.annotations.NotNull public final Map getConsumedStates()
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash getTxId()
|
||||
public int hashCode()
|
||||
@org.jetbrains.annotations.NotNull public String toString()
|
||||
@ -1431,6 +1459,7 @@ public static final class net.corda.core.flows.NotaryError$TimeWindowInvalid$Com
|
||||
@net.corda.core.serialization.CordaSerializable public final class net.corda.core.flows.NotaryException extends net.corda.core.flows.FlowException
|
||||
public <init>(net.corda.core.flows.NotaryError, net.corda.core.crypto.SecureHash)
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.core.flows.NotaryError getError()
|
||||
@org.jetbrains.annotations.Nullable public final net.corda.core.crypto.SecureHash getTxId()
|
||||
##
|
||||
public final class net.corda.core.flows.NotaryFlow extends java.lang.Object
|
||||
public <init>()
|
||||
@ -1462,6 +1491,10 @@ public abstract static class net.corda.core.flows.NotaryFlow$Service extends net
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.core.node.services.TrustedAuthorityNotaryService getService()
|
||||
@co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull protected abstract net.corda.core.flows.TransactionParts validateRequest(net.corda.core.flows.NotarisationPayload)
|
||||
##
|
||||
@net.corda.core.serialization.CordaSerializable public final class net.corda.core.flows.NotaryInternalException extends net.corda.core.flows.FlowException
|
||||
public <init>(net.corda.core.flows.NotaryError)
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.core.flows.NotaryError getError()
|
||||
##
|
||||
public final class net.corda.core.flows.ReceiveStateAndRefFlow extends net.corda.core.flows.FlowLogic
|
||||
public <init>(net.corda.core.flows.FlowSession)
|
||||
@co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull public List call()
|
||||
@ -1514,6 +1547,15 @@ public @interface net.corda.core.flows.StartableByRPC
|
||||
##
|
||||
public @interface net.corda.core.flows.StartableByService
|
||||
##
|
||||
@net.corda.core.serialization.CordaSerializable public final class net.corda.core.flows.StateConsumptionDetails extends java.lang.Object
|
||||
public <init>(net.corda.core.crypto.SecureHash)
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash component1()
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.core.flows.StateConsumptionDetails copy(net.corda.core.crypto.SecureHash)
|
||||
public boolean equals(Object)
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash getHashOfTransactionId()
|
||||
public int hashCode()
|
||||
public String toString()
|
||||
##
|
||||
@net.corda.core.serialization.CordaSerializable public final class net.corda.core.flows.StateMachineRunId extends java.lang.Object
|
||||
public <init>(UUID)
|
||||
@org.jetbrains.annotations.NotNull public final UUID component1()
|
||||
@ -1658,6 +1700,7 @@ public final class net.corda.core.identity.IdentityUtils extends java.lang.Objec
|
||||
@org.jetbrains.annotations.NotNull public abstract List queryAttachments(net.corda.core.node.services.vault.AttachmentQueryCriteria, net.corda.core.node.services.vault.AttachmentSort)
|
||||
@org.jetbrains.annotations.NotNull public abstract List registeredFlows()
|
||||
public abstract void setFlowsDrainingModeEnabled(boolean)
|
||||
public abstract void shutdown()
|
||||
@net.corda.core.messaging.RPCReturnsObservables @org.jetbrains.annotations.NotNull public abstract net.corda.core.messaging.FlowHandle startFlowDynamic(Class, Object...)
|
||||
@net.corda.core.messaging.RPCReturnsObservables @org.jetbrains.annotations.NotNull public abstract net.corda.core.messaging.FlowProgressHandle startTrackedFlowDynamic(Class, Object...)
|
||||
@net.corda.core.messaging.RPCReturnsObservables @org.jetbrains.annotations.NotNull public abstract net.corda.core.messaging.DataFeed stateMachineRecordedTransactionMappingFeed()
|
||||
@ -1681,6 +1724,7 @@ public final class net.corda.core.identity.IdentityUtils extends java.lang.Objec
|
||||
@org.jetbrains.annotations.Nullable public abstract net.corda.core.identity.Party wellKnownPartyFromX500Name(net.corda.core.identity.CordaX500Name)
|
||||
##
|
||||
public final class net.corda.core.messaging.CordaRPCOpsKt extends java.lang.Object
|
||||
@org.jetbrains.annotations.NotNull public static final net.corda.core.messaging.DataFeed pendingFlowsCount(net.corda.core.messaging.CordaRPCOps)
|
||||
##
|
||||
@net.corda.core.serialization.CordaSerializable public final class net.corda.core.messaging.DataFeed extends java.lang.Object
|
||||
public <init>(Object, rx.Observable)
|
||||
@ -1880,6 +1924,7 @@ public @interface net.corda.core.messaging.RPCReturnsObservables
|
||||
@org.jetbrains.annotations.NotNull public abstract net.corda.core.crypto.TransactionSignature createSignature(net.corda.core.transactions.FilteredTransaction, java.security.PublicKey)
|
||||
@org.jetbrains.annotations.NotNull public abstract net.corda.core.crypto.TransactionSignature createSignature(net.corda.core.transactions.SignedTransaction)
|
||||
@org.jetbrains.annotations.NotNull public abstract net.corda.core.crypto.TransactionSignature createSignature(net.corda.core.transactions.SignedTransaction, java.security.PublicKey)
|
||||
@org.jetbrains.annotations.NotNull public abstract net.corda.core.cordapp.CordappContext getAppContext()
|
||||
@org.jetbrains.annotations.NotNull public abstract java.time.Clock getClock()
|
||||
@org.jetbrains.annotations.NotNull public abstract net.corda.core.node.services.ContractUpgradeService getContractUpgradeService()
|
||||
@org.jetbrains.annotations.NotNull public abstract net.corda.core.node.services.KeyManagementService getKeyManagementService()
|
||||
@ -2856,6 +2901,8 @@ public interface net.corda.core.schemas.StatePersistable
|
||||
public interface net.corda.core.serialization.ClassWhitelist
|
||||
public abstract boolean hasListed(Class)
|
||||
##
|
||||
public @interface net.corda.core.serialization.ConstructorForDeserialization
|
||||
##
|
||||
public @interface net.corda.core.serialization.CordaSerializable
|
||||
##
|
||||
public @interface net.corda.core.serialization.CordaSerializationTransformEnumDefault
|
||||
@ -2875,6 +2922,9 @@ public @interface net.corda.core.serialization.CordaSerializationTransformRename
|
||||
public @interface net.corda.core.serialization.DeprecatedConstructorForDeserialization
|
||||
public abstract int version()
|
||||
##
|
||||
@net.corda.core.DoNotImplement public interface net.corda.core.serialization.EncodingWhitelist
|
||||
public abstract boolean acceptEncoding(net.corda.core.serialization.SerializationEncoding)
|
||||
##
|
||||
@net.corda.core.serialization.CordaSerializable public final class net.corda.core.serialization.MissingAttachmentsException extends net.corda.core.CordaException
|
||||
public <init>(List)
|
||||
@org.jetbrains.annotations.NotNull public final List getIds()
|
||||
@ -2895,6 +2945,8 @@ public final class net.corda.core.serialization.SerializationAPIKt extends java.
|
||||
##
|
||||
@net.corda.core.DoNotImplement public interface net.corda.core.serialization.SerializationContext
|
||||
@org.jetbrains.annotations.NotNull public abstract ClassLoader getDeserializationClassLoader()
|
||||
@org.jetbrains.annotations.Nullable public abstract net.corda.core.serialization.SerializationEncoding getEncoding()
|
||||
@org.jetbrains.annotations.NotNull public abstract net.corda.core.serialization.EncodingWhitelist getEncodingWhitelist()
|
||||
public abstract boolean getObjectReferencesEnabled()
|
||||
@org.jetbrains.annotations.NotNull public abstract net.corda.core.utilities.ByteSequence getPreferredSerializationVersion()
|
||||
@org.jetbrains.annotations.NotNull public abstract Map getProperties()
|
||||
@ -2902,6 +2954,7 @@ public final class net.corda.core.serialization.SerializationAPIKt extends java.
|
||||
@org.jetbrains.annotations.NotNull public abstract net.corda.core.serialization.ClassWhitelist getWhitelist()
|
||||
@org.jetbrains.annotations.NotNull public abstract net.corda.core.serialization.SerializationContext withAttachmentsClassLoader(List)
|
||||
@org.jetbrains.annotations.NotNull public abstract net.corda.core.serialization.SerializationContext withClassLoader(ClassLoader)
|
||||
@org.jetbrains.annotations.NotNull public abstract net.corda.core.serialization.SerializationContext withEncoding(net.corda.core.serialization.SerializationEncoding)
|
||||
@org.jetbrains.annotations.NotNull public abstract net.corda.core.serialization.SerializationContext withPreferredSerializationVersion(net.corda.core.utilities.ByteSequence)
|
||||
@org.jetbrains.annotations.NotNull public abstract net.corda.core.serialization.SerializationContext withProperty(Object, Object)
|
||||
@org.jetbrains.annotations.NotNull public abstract net.corda.core.serialization.SerializationContext withWhitelisted(Class)
|
||||
@ -2925,6 +2978,8 @@ public final class net.corda.core.serialization.SerializationDefaults extends ja
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.core.serialization.SerializationContext getSTORAGE_CONTEXT()
|
||||
public static final net.corda.core.serialization.SerializationDefaults INSTANCE
|
||||
##
|
||||
@net.corda.core.DoNotImplement public interface net.corda.core.serialization.SerializationEncoding
|
||||
##
|
||||
public abstract class net.corda.core.serialization.SerializationFactory extends java.lang.Object
|
||||
public <init>()
|
||||
public final Object asCurrent(kotlin.jvm.functions.Function1)
|
||||
@ -3004,13 +3059,20 @@ public static final class net.corda.core.serialization.SingletonSerializationTok
|
||||
@org.jetbrains.annotations.NotNull public final Map component2()
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.core.transactions.ContractUpgradeFilteredTransaction copy(Map, Map)
|
||||
public boolean equals(Object)
|
||||
@org.jetbrains.annotations.NotNull public final Map getHiddenComponents()
|
||||
@org.jetbrains.annotations.NotNull public net.corda.core.crypto.SecureHash getId()
|
||||
@org.jetbrains.annotations.NotNull public List getInputs()
|
||||
@org.jetbrains.annotations.NotNull public net.corda.core.identity.Party getNotary()
|
||||
@org.jetbrains.annotations.NotNull public List getOutputs()
|
||||
@org.jetbrains.annotations.NotNull public final Map getVisibleComponents()
|
||||
public int hashCode()
|
||||
public String toString()
|
||||
##
|
||||
@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.transactions.ContractUpgradeFilteredTransaction$FilteredComponent extends java.lang.Object
|
||||
public <init>(net.corda.core.utilities.OpaqueBytes, net.corda.core.crypto.SecureHash)
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.core.utilities.OpaqueBytes getComponent()
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash getNonce()
|
||||
##
|
||||
@net.corda.core.DoNotImplement public final class net.corda.core.transactions.ContractUpgradeLedgerTransaction extends net.corda.core.transactions.FullTransaction implements net.corda.core.transactions.TransactionWithSignatures
|
||||
public <init>(List, net.corda.core.identity.Party, net.corda.core.contracts.Attachment, String, net.corda.core.contracts.Attachment, net.corda.core.crypto.SecureHash, net.corda.core.contracts.PrivacySalt, List, net.corda.core.node.NetworkParameters)
|
||||
public void checkSignaturesAreValid()
|
||||
@ -3055,12 +3117,18 @@ public static final class net.corda.core.serialization.SingletonSerializationTok
|
||||
@org.jetbrains.annotations.NotNull public net.corda.core.identity.Party getNotary()
|
||||
@org.jetbrains.annotations.NotNull public List getOutputs()
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.core.contracts.PrivacySalt getPrivacySalt()
|
||||
@org.jetbrains.annotations.NotNull public final List getSerializedComponents()
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash getUpgradedContractAttachmentId()
|
||||
@org.jetbrains.annotations.NotNull public final String getUpgradedContractClassName()
|
||||
public int hashCode()
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.core.transactions.ContractUpgradeLedgerTransaction resolve(net.corda.core.node.ServicesForResolution, List)
|
||||
public String toString()
|
||||
##
|
||||
public static final class net.corda.core.transactions.ContractUpgradeWireTransaction$Component extends java.lang.Enum
|
||||
protected <init>(String, int)
|
||||
public static net.corda.core.transactions.ContractUpgradeWireTransaction$Component valueOf(String)
|
||||
public static net.corda.core.transactions.ContractUpgradeWireTransaction$Component[] values()
|
||||
##
|
||||
@net.corda.core.DoNotImplement @net.corda.core.serialization.CordaSerializable public abstract class net.corda.core.transactions.CoreTransaction extends net.corda.core.transactions.BaseTransaction
|
||||
public <init>()
|
||||
@org.jetbrains.annotations.NotNull public abstract List getInputs()
|
||||
@ -3192,6 +3260,7 @@ public static final class net.corda.core.transactions.LedgerTransaction$InOutGro
|
||||
##
|
||||
@net.corda.core.DoNotImplement @net.corda.core.serialization.CordaSerializable public final class net.corda.core.transactions.NotaryChangeWireTransaction extends net.corda.core.transactions.CoreTransaction
|
||||
public <init>(List)
|
||||
@kotlin.Deprecated public <init>(List, net.corda.core.identity.Party, net.corda.core.identity.Party)
|
||||
@org.jetbrains.annotations.NotNull public final List component1()
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.core.transactions.NotaryChangeWireTransaction copy(List)
|
||||
public boolean equals(Object)
|
||||
@ -3200,11 +3269,17 @@ public static final class net.corda.core.transactions.LedgerTransaction$InOutGro
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.core.identity.Party getNewNotary()
|
||||
@org.jetbrains.annotations.NotNull public net.corda.core.identity.Party getNotary()
|
||||
@org.jetbrains.annotations.NotNull public List getOutputs()
|
||||
@org.jetbrains.annotations.NotNull public final List getSerializedComponents()
|
||||
public int hashCode()
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.core.transactions.NotaryChangeLedgerTransaction resolve(net.corda.core.node.ServiceHub, List)
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.core.transactions.NotaryChangeLedgerTransaction resolve(net.corda.core.node.ServicesForResolution, List)
|
||||
public String toString()
|
||||
##
|
||||
public static final class net.corda.core.transactions.NotaryChangeWireTransaction$Component extends java.lang.Enum
|
||||
protected <init>(String, int)
|
||||
public static net.corda.core.transactions.NotaryChangeWireTransaction$Component valueOf(String)
|
||||
public static net.corda.core.transactions.NotaryChangeWireTransaction$Component[] values()
|
||||
##
|
||||
@net.corda.core.DoNotImplement @net.corda.core.serialization.CordaSerializable public final class net.corda.core.transactions.SignedTransaction extends java.lang.Object implements net.corda.core.transactions.TransactionWithSignatures
|
||||
public <init>(net.corda.core.serialization.SerializedBytes, List)
|
||||
public <init>(net.corda.core.transactions.CoreTransaction, List)
|
||||
@ -3348,6 +3423,7 @@ public final class net.corda.core.utilities.ByteArrays extends java.lang.Object
|
||||
@net.corda.core.serialization.CordaSerializable public abstract class net.corda.core.utilities.ByteSequence extends java.lang.Object implements java.lang.Comparable
|
||||
public int compareTo(net.corda.core.utilities.ByteSequence)
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.core.utilities.ByteSequence copy()
|
||||
@org.jetbrains.annotations.NotNull public final byte[] copyBytes()
|
||||
public boolean equals(Object)
|
||||
@org.jetbrains.annotations.NotNull public abstract byte[] getBytes()
|
||||
public final int getOffset()
|
||||
@ -3357,9 +3433,12 @@ public final class net.corda.core.utilities.ByteArrays extends java.lang.Object
|
||||
@kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public static final net.corda.core.utilities.ByteSequence of(byte[], int)
|
||||
@kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public static final net.corda.core.utilities.ByteSequence of(byte[], int, int)
|
||||
@org.jetbrains.annotations.NotNull public final java.io.ByteArrayInputStream open()
|
||||
@org.jetbrains.annotations.NotNull public final java.nio.ByteBuffer putTo(java.nio.ByteBuffer)
|
||||
@org.jetbrains.annotations.NotNull public final java.nio.ByteBuffer slice(int, int)
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.core.utilities.ByteSequence subSequence(int, int)
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.core.utilities.ByteSequence take(int)
|
||||
@org.jetbrains.annotations.NotNull public String toString()
|
||||
public final void writeTo(java.io.OutputStream)
|
||||
public static final net.corda.core.utilities.ByteSequence$Companion Companion
|
||||
##
|
||||
public static final class net.corda.core.utilities.ByteSequence$Companion extends java.lang.Object
|
||||
@ -4126,6 +4205,7 @@ public class net.corda.testing.node.MockServices extends java.lang.Object implem
|
||||
@org.jetbrains.annotations.NotNull public net.corda.core.crypto.TransactionSignature createSignature(net.corda.core.transactions.FilteredTransaction, java.security.PublicKey)
|
||||
@org.jetbrains.annotations.NotNull public net.corda.core.crypto.TransactionSignature createSignature(net.corda.core.transactions.SignedTransaction)
|
||||
@org.jetbrains.annotations.NotNull public net.corda.core.crypto.TransactionSignature createSignature(net.corda.core.transactions.SignedTransaction, java.security.PublicKey)
|
||||
@org.jetbrains.annotations.NotNull public net.corda.core.cordapp.CordappContext getAppContext()
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.testing.services.MockAttachmentStorage getAttachments()
|
||||
@org.jetbrains.annotations.NotNull public java.time.Clock getClock()
|
||||
@org.jetbrains.annotations.NotNull public net.corda.core.node.services.ContractUpgradeService getContractUpgradeService()
|
||||
@ -4180,6 +4260,7 @@ public static final class net.corda.testing.node.MockServicesKt$createMockCordaS
|
||||
@org.jetbrains.annotations.NotNull public net.corda.core.crypto.TransactionSignature createSignature(net.corda.core.transactions.FilteredTransaction, java.security.PublicKey)
|
||||
@org.jetbrains.annotations.NotNull public net.corda.core.crypto.TransactionSignature createSignature(net.corda.core.transactions.SignedTransaction)
|
||||
@org.jetbrains.annotations.NotNull public net.corda.core.crypto.TransactionSignature createSignature(net.corda.core.transactions.SignedTransaction, java.security.PublicKey)
|
||||
@org.jetbrains.annotations.NotNull public net.corda.core.cordapp.CordappContext getAppContext()
|
||||
@org.jetbrains.annotations.NotNull public net.corda.core.node.services.AttachmentStorage getAttachments()
|
||||
@org.jetbrains.annotations.NotNull public java.time.Clock getClock()
|
||||
@org.jetbrains.annotations.NotNull public net.corda.core.node.services.ContractUpgradeService getContractUpgradeService()
|
||||
@ -4284,18 +4365,22 @@ public final class net.corda.client.rpc.CordaRPCClient extends java.lang.Object
|
||||
##
|
||||
public static final class net.corda.client.rpc.CordaRPCClient$Companion extends java.lang.Object
|
||||
##
|
||||
public final class net.corda.client.rpc.CordaRPCClientConfiguration extends java.lang.Object
|
||||
public <init>(java.time.Duration)
|
||||
@org.jetbrains.annotations.NotNull public final java.time.Duration component1()
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.client.rpc.CordaRPCClientConfiguration copy(java.time.Duration)
|
||||
public boolean equals(Object)
|
||||
@org.jetbrains.annotations.NotNull public final java.time.Duration getConnectionMaxRetryInterval()
|
||||
public int hashCode()
|
||||
public String toString()
|
||||
public interface net.corda.client.rpc.CordaRPCClientConfiguration
|
||||
public abstract int getCacheConcurrencyLevel()
|
||||
@org.jetbrains.annotations.NotNull public abstract java.time.Duration getConnectionMaxRetryInterval()
|
||||
@org.jetbrains.annotations.NotNull public abstract java.time.Duration getConnectionRetryInterval()
|
||||
public abstract double getConnectionRetryIntervalMultiplier()
|
||||
@org.jetbrains.annotations.NotNull public abstract java.time.Duration getDeduplicationCacheExpiry()
|
||||
public abstract int getMaxFileSize()
|
||||
public abstract int getMaxReconnectAttempts()
|
||||
public abstract int getMinimumServerProtocolVersion()
|
||||
public abstract int getObservationExecutorPoolSize()
|
||||
@org.jetbrains.annotations.NotNull public abstract java.time.Duration getReapInterval()
|
||||
public abstract boolean getTrackRpcCallSites()
|
||||
public static final net.corda.client.rpc.CordaRPCClientConfiguration$Companion Companion
|
||||
@org.jetbrains.annotations.NotNull public static final net.corda.client.rpc.CordaRPCClientConfiguration DEFAULT
|
||||
##
|
||||
public static final class net.corda.client.rpc.CordaRPCClientConfiguration$Companion extends java.lang.Object
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.client.rpc.CordaRPCClientConfiguration default()
|
||||
##
|
||||
@net.corda.core.DoNotImplement public final class net.corda.client.rpc.CordaRPCConnection extends java.lang.Object implements net.corda.client.rpc.RPCConnection
|
||||
public <init>(net.corda.client.rpc.RPCConnection)
|
||||
|
@ -58,9 +58,9 @@ class NodeMonitorModel {
|
||||
fun register(nodeHostAndPort: NetworkHostAndPort, username: String, password: String) {
|
||||
val client = CordaRPCClient(
|
||||
nodeHostAndPort,
|
||||
CordaRPCClientConfiguration.DEFAULT.copy(
|
||||
connectionMaxRetryInterval = 10.seconds
|
||||
)
|
||||
object : CordaRPCClientConfiguration {
|
||||
override val connectionMaxRetryInterval = 10.seconds
|
||||
}
|
||||
)
|
||||
val connection = client.start(username, password)
|
||||
val proxy = connection.proxy
|
||||
|
@ -18,28 +18,28 @@ import net.corda.finance.flows.CashPaymentFlow
|
||||
import net.corda.finance.schemas.CashSchemaV1
|
||||
import net.corda.node.internal.Node
|
||||
import net.corda.node.internal.StartedNode
|
||||
import net.corda.node.services.Permissions.Companion.invokeRpc
|
||||
import net.corda.node.services.Permissions.Companion.startFlow
|
||||
import net.corda.node.services.Permissions.Companion.all
|
||||
import net.corda.testing.core.*
|
||||
import net.corda.testing.node.User
|
||||
import net.corda.testing.node.internal.NodeBasedTest
|
||||
import org.apache.activemq.artemis.api.core.ActiveMQNotConnectedException
|
||||
import org.apache.activemq.artemis.api.core.ActiveMQSecurityException
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.assertj.core.api.Assertions.assertThatExceptionOfType
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import rx.subjects.PublishSubject
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.ScheduledExecutorService
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class CordaRPCClientTest : NodeBasedTest(listOf("net.corda.finance.contracts", CashSchemaV1::class.packageName)) {
|
||||
private val rpcUser = User("user1", "test", permissions = setOf(
|
||||
startFlow<CashIssueFlow>(),
|
||||
startFlow<CashPaymentFlow>(),
|
||||
invokeRpc("vaultQueryBy"),
|
||||
invokeRpc(CordaRPCOps::stateMachinesFeed),
|
||||
invokeRpc("vaultQueryByCriteria"))
|
||||
private val rpcUser = User("user1", "test", permissions = setOf(all())
|
||||
)
|
||||
private lateinit var node: StartedNode<Node>
|
||||
private lateinit var identity: Party
|
||||
@ -53,7 +53,9 @@ class CordaRPCClientTest : NodeBasedTest(listOf("net.corda.finance.contracts", C
|
||||
@Before
|
||||
fun setUp() {
|
||||
node = startNode(ALICE_NAME, rpcUsers = listOf(rpcUser))
|
||||
client = CordaRPCClient(node.internals.configuration.rpcOptions.address!!)
|
||||
client = CordaRPCClient(node.internals.configuration.rpcOptions.address!!, object : CordaRPCClientConfiguration {
|
||||
override val maxReconnectAttempts = 5
|
||||
})
|
||||
identity = node.info.identityFromX500Name(ALICE_NAME)
|
||||
}
|
||||
|
||||
@ -81,6 +83,61 @@ class CordaRPCClientTest : NodeBasedTest(listOf("net.corda.finance.contracts", C
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `shutdown command stops the node`() {
|
||||
|
||||
val nodeIsShut: PublishSubject<Unit> = PublishSubject.create()
|
||||
val latch = CountDownLatch(1)
|
||||
var successful = false
|
||||
val maxCount = 20
|
||||
var count = 0
|
||||
CloseableExecutor(Executors.newSingleThreadScheduledExecutor()).use { scheduler ->
|
||||
|
||||
val task = scheduler.scheduleAtFixedRate({
|
||||
try {
|
||||
println("Checking whether node is still running...")
|
||||
client.start(rpcUser.username, rpcUser.password).use {
|
||||
println("... node is still running.")
|
||||
if (count == maxCount) {
|
||||
nodeIsShut.onError(AssertionError("Node does not get shutdown by RPC"))
|
||||
}
|
||||
count++
|
||||
}
|
||||
} catch (e: ActiveMQNotConnectedException) {
|
||||
println("... node is not running.")
|
||||
nodeIsShut.onCompleted()
|
||||
} catch (e: ActiveMQSecurityException) {
|
||||
// nothing here - this happens if trying to connect before the node is started
|
||||
} catch (e: Throwable) {
|
||||
nodeIsShut.onError(e)
|
||||
}
|
||||
}, 1, 1, TimeUnit.SECONDS)
|
||||
|
||||
nodeIsShut.doOnError { error ->
|
||||
error.printStackTrace()
|
||||
successful = false
|
||||
task.cancel(true)
|
||||
latch.countDown()
|
||||
}.doOnCompleted {
|
||||
successful = (node.internals.started == null)
|
||||
task.cancel(true)
|
||||
latch.countDown()
|
||||
}.subscribe()
|
||||
|
||||
client.start(rpcUser.username, rpcUser.password).use { rpc -> rpc.proxy.shutdown() }
|
||||
|
||||
latch.await()
|
||||
assertThat(successful).isTrue()
|
||||
}
|
||||
}
|
||||
|
||||
private class CloseableExecutor(private val delegate: ScheduledExecutorService) : AutoCloseable, ScheduledExecutorService by delegate {
|
||||
|
||||
override fun close() {
|
||||
delegate.shutdown()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `close-send deadlock and premature shutdown on empty observable`() {
|
||||
println("Starting client")
|
||||
@ -141,7 +198,7 @@ class CordaRPCClientTest : NodeBasedTest(listOf("net.corda.finance.contracts", C
|
||||
|
||||
val updates = proxy.stateMachinesFeed().updates
|
||||
|
||||
node.services.startFlow(CashIssueFlow(2000.DOLLARS, OpaqueBytes.of(0),identity), InvocationContext.shell()).flatMap { it.resultFuture }.getOrThrow()
|
||||
node.services.startFlow(CashIssueFlow(2000.DOLLARS, OpaqueBytes.of(0), identity), InvocationContext.shell()).flatMap { it.resultFuture }.getOrThrow()
|
||||
proxy.startFlow(::CashIssueFlow, 123.DOLLARS, OpaqueBytes.of(0), identity).returnValue.getOrThrow()
|
||||
proxy.startFlowDynamic(CashIssueFlow::class.java, 1000.DOLLARS, OpaqueBytes.of(0), identity).returnValue.getOrThrow()
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
package net.corda.client.rpc
|
||||
|
||||
import net.corda.client.rpc.internal.RPCClient
|
||||
import net.corda.client.rpc.internal.RPCClientConfiguration
|
||||
import net.corda.client.rpc.internal.CordaRPCClientConfigurationImpl
|
||||
import net.corda.core.context.Trace
|
||||
import net.corda.core.crypto.random63BitValue
|
||||
import net.corda.core.internal.concurrent.fork
|
||||
@ -105,7 +105,7 @@ class RPCStabilityTests {
|
||||
Try.on {
|
||||
startRpcClient<RPCOps>(
|
||||
server.get().broker.hostAndPort!!,
|
||||
configuration = RPCClientConfiguration.default.copy(minimumServerProtocolVersion = 1)
|
||||
configuration = CordaRPCClientConfigurationImpl.default.copy(minimumServerProtocolVersion = 1)
|
||||
).get()
|
||||
}
|
||||
}
|
||||
@ -240,7 +240,7 @@ class RPCStabilityTests {
|
||||
val serverPort = startRpcServer<ReconnectOps>(ops = ops).getOrThrow().broker.hostAndPort!!
|
||||
serverFollower.unfollow()
|
||||
// Set retry interval to 1s to reduce test duration
|
||||
val clientConfiguration = RPCClientConfiguration.default.copy(connectionRetryInterval = 1.seconds)
|
||||
val clientConfiguration = CordaRPCClientConfigurationImpl.default.copy(connectionRetryInterval = 1.seconds)
|
||||
val clientFollower = shutdownManager.follower()
|
||||
val client = startRpcClient<ReconnectOps>(serverPort, configuration = clientConfiguration).getOrThrow()
|
||||
clientFollower.unfollow()
|
||||
@ -266,7 +266,7 @@ class RPCStabilityTests {
|
||||
val serverPort = startRpcServer<ReconnectOps>(ops = ops).getOrThrow().broker.hostAndPort!!
|
||||
serverFollower.unfollow()
|
||||
// Set retry interval to 1s to reduce test duration
|
||||
val clientConfiguration = RPCClientConfiguration.default.copy(connectionRetryInterval = 1.seconds, maxReconnectAttempts = 5)
|
||||
val clientConfiguration = CordaRPCClientConfigurationImpl.default.copy(connectionRetryInterval = 1.seconds, maxReconnectAttempts = 5)
|
||||
val clientFollower = shutdownManager.follower()
|
||||
val client = startRpcClient<ReconnectOps>(serverPort, configuration = clientConfiguration).getOrThrow()
|
||||
clientFollower.unfollow()
|
||||
@ -298,7 +298,7 @@ class RPCStabilityTests {
|
||||
val serverPort = startRpcServer<NoOps>(ops = ops).getOrThrow().broker.hostAndPort!!
|
||||
serverFollower.unfollow()
|
||||
|
||||
val clientConfiguration = RPCClientConfiguration.default.copy(connectionRetryInterval = 500.millis, maxReconnectAttempts = 1)
|
||||
val clientConfiguration = CordaRPCClientConfigurationImpl.default.copy(connectionRetryInterval = 500.millis, maxReconnectAttempts = 1)
|
||||
val clientFollower = shutdownManager.follower()
|
||||
val client = startRpcClient<NoOps>(serverPort, configuration = clientConfiguration).getOrThrow()
|
||||
clientFollower.unfollow()
|
||||
|
@ -2,7 +2,7 @@ package net.corda.client.rpc
|
||||
|
||||
import net.corda.client.rpc.internal.KryoClientSerializationScheme
|
||||
import net.corda.client.rpc.internal.RPCClient
|
||||
import net.corda.client.rpc.internal.RPCClientConfiguration
|
||||
import net.corda.client.rpc.internal.CordaRPCClientConfigurationImpl
|
||||
import net.corda.core.context.Actor
|
||||
import net.corda.core.context.Trace
|
||||
import net.corda.core.messaging.CordaRPCOps
|
||||
@ -23,23 +23,46 @@ class CordaRPCConnection internal constructor(connection: RPCConnection<CordaRPC
|
||||
|
||||
/**
|
||||
* Can be used to configure the RPC client connection.
|
||||
*
|
||||
* @property connectionMaxRetryInterval How much time to wait between connection retries if the server goes down. This
|
||||
* time will be reached via exponential backoff.
|
||||
*/
|
||||
data class CordaRPCClientConfiguration(val connectionMaxRetryInterval: Duration) {
|
||||
internal fun toRpcClientConfiguration(): RPCClientConfiguration {
|
||||
return RPCClientConfiguration.default.copy(
|
||||
connectionMaxRetryInterval = connectionMaxRetryInterval
|
||||
)
|
||||
}
|
||||
interface CordaRPCClientConfiguration {
|
||||
|
||||
/** The minimum protocol version required from the server */
|
||||
val minimumServerProtocolVersion: Int get() = default().minimumServerProtocolVersion
|
||||
/**
|
||||
* If set to true the client will track RPC call sites. If an error occurs subsequently during the RPC or in a
|
||||
* returned Observable stream the stack trace of the originating RPC will be shown as well. Note that
|
||||
* constructing call stacks is a moderately expensive operation.
|
||||
*/
|
||||
val trackRpcCallSites: Boolean get() = default().trackRpcCallSites
|
||||
/**
|
||||
* The interval of unused observable reaping. Leaked Observables (unused ones) are detected using weak references
|
||||
* and are cleaned up in batches in this interval. If set too large it will waste server side resources for this
|
||||
* duration. If set too low it wastes client side cycles.
|
||||
*/
|
||||
val reapInterval: Duration get() = default().reapInterval
|
||||
/** The number of threads to use for observations (for executing [Observable.onNext]) */
|
||||
val observationExecutorPoolSize: Int get() = default().observationExecutorPoolSize
|
||||
/**
|
||||
* Determines the concurrency level of the Observable Cache. This is exposed because it implicitly determines
|
||||
* the limit on the number of leaked observables reaped because of garbage collection per reaping.
|
||||
* See the implementation of [com.google.common.cache.LocalCache] for details.
|
||||
*/
|
||||
val cacheConcurrencyLevel: Int get() = default().cacheConcurrencyLevel
|
||||
/** The retry interval of artemis connections in milliseconds */
|
||||
val connectionRetryInterval: Duration get() = default().connectionRetryInterval
|
||||
/** The retry interval multiplier for exponential backoff */
|
||||
val connectionRetryIntervalMultiplier: Double get() = default().connectionRetryIntervalMultiplier
|
||||
/** Maximum retry interval */
|
||||
val connectionMaxRetryInterval: Duration get() = default().connectionMaxRetryInterval
|
||||
/** Maximum reconnect attempts on failover */
|
||||
val maxReconnectAttempts: Int get() = default().maxReconnectAttempts
|
||||
/** Maximum file size */
|
||||
val maxFileSize: Int get() = default().maxFileSize
|
||||
/** The cache expiry of a deduplication watermark per client. */
|
||||
val deduplicationCacheExpiry: Duration get() = default().deduplicationCacheExpiry
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Returns the default configuration we recommend you use.
|
||||
*/
|
||||
@JvmField
|
||||
val DEFAULT = CordaRPCClientConfiguration(connectionMaxRetryInterval = RPCClientConfiguration.default.connectionMaxRetryInterval)
|
||||
fun default(): CordaRPCClientConfiguration = CordaRPCClientConfigurationImpl.default
|
||||
}
|
||||
}
|
||||
|
||||
@ -72,17 +95,17 @@ data class CordaRPCClientConfiguration(val connectionMaxRetryInterval: Duration)
|
||||
*/
|
||||
class CordaRPCClient private constructor(
|
||||
hostAndPort: NetworkHostAndPort,
|
||||
configuration: CordaRPCClientConfiguration = CordaRPCClientConfiguration.DEFAULT,
|
||||
configuration: CordaRPCClientConfiguration = CordaRPCClientConfiguration.default(),
|
||||
sslConfiguration: SSLConfiguration? = null,
|
||||
classLoader: ClassLoader? = null
|
||||
) {
|
||||
@JvmOverloads
|
||||
constructor(hostAndPort: NetworkHostAndPort, configuration: CordaRPCClientConfiguration = CordaRPCClientConfiguration.DEFAULT) : this(hostAndPort, configuration, null)
|
||||
constructor(hostAndPort: NetworkHostAndPort, configuration: CordaRPCClientConfiguration = CordaRPCClientConfiguration.default()) : this(hostAndPort, configuration, null)
|
||||
|
||||
companion object {
|
||||
internal fun createWithSsl(
|
||||
hostAndPort: NetworkHostAndPort,
|
||||
configuration: CordaRPCClientConfiguration = CordaRPCClientConfiguration.DEFAULT,
|
||||
configuration: CordaRPCClientConfiguration = CordaRPCClientConfiguration.default(),
|
||||
sslConfiguration: SSLConfiguration? = null
|
||||
): CordaRPCClient {
|
||||
return CordaRPCClient(hostAndPort, configuration, sslConfiguration)
|
||||
@ -90,7 +113,7 @@ class CordaRPCClient private constructor(
|
||||
|
||||
internal fun createWithSslAndClassLoader(
|
||||
hostAndPort: NetworkHostAndPort,
|
||||
configuration: CordaRPCClientConfiguration = CordaRPCClientConfiguration.DEFAULT,
|
||||
configuration: CordaRPCClientConfiguration = CordaRPCClientConfiguration.default(),
|
||||
sslConfiguration: SSLConfiguration? = null,
|
||||
classLoader: ClassLoader? = null
|
||||
): CordaRPCClient {
|
||||
@ -112,7 +135,7 @@ class CordaRPCClient private constructor(
|
||||
|
||||
private val rpcClient = RPCClient<CordaRPCOps>(
|
||||
tcpTransport(ConnectionDirection.Outbound(), hostAndPort, config = sslConfiguration),
|
||||
configuration.toRpcClientConfiguration(),
|
||||
configuration,
|
||||
if (classLoader != null) KRYO_RPC_CLIENT_CONTEXT.withClassLoader(classLoader) else KRYO_RPC_CLIENT_CONTEXT
|
||||
)
|
||||
|
||||
|
@ -2,19 +2,32 @@ package net.corda.client.rpc.internal
|
||||
|
||||
import net.corda.client.rpc.CordaRPCClient
|
||||
import net.corda.client.rpc.CordaRPCClientConfiguration
|
||||
import net.corda.core.messaging.CordaRPCOps
|
||||
import net.corda.core.messaging.pendingFlowsCount
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
import net.corda.nodeapi.internal.config.SSLConfiguration
|
||||
import rx.Observable
|
||||
|
||||
/** Utility which exposes the internal Corda RPC constructor to other internal Corda components */
|
||||
fun createCordaRPCClientWithSsl(
|
||||
hostAndPort: NetworkHostAndPort,
|
||||
configuration: CordaRPCClientConfiguration = CordaRPCClientConfiguration.DEFAULT,
|
||||
configuration: CordaRPCClientConfiguration = CordaRPCClientConfiguration.default(),
|
||||
sslConfiguration: SSLConfiguration? = null
|
||||
) = CordaRPCClient.createWithSsl(hostAndPort, configuration, sslConfiguration)
|
||||
|
||||
fun createCordaRPCClientWithSslAndClassLoader(
|
||||
hostAndPort: NetworkHostAndPort,
|
||||
configuration: CordaRPCClientConfiguration = CordaRPCClientConfiguration.DEFAULT,
|
||||
configuration: CordaRPCClientConfiguration = CordaRPCClientConfiguration.default(),
|
||||
sslConfiguration: SSLConfiguration? = null,
|
||||
classLoader: ClassLoader? = null
|
||||
) = CordaRPCClient.createWithSslAndClassLoader(hostAndPort, configuration, sslConfiguration, classLoader)
|
||||
) = CordaRPCClient.createWithSslAndClassLoader(hostAndPort, configuration, sslConfiguration, classLoader)
|
||||
|
||||
fun CordaRPCOps.drainAndShutdown(): Observable<Unit> {
|
||||
|
||||
setFlowsDrainingModeEnabled(true)
|
||||
return pendingFlowsCount().updates
|
||||
.doOnError { error ->
|
||||
throw error
|
||||
}
|
||||
.doOnCompleted { shutdown() }.map { }
|
||||
}
|
@ -1,6 +1,5 @@
|
||||
package net.corda.client.rpc.internal
|
||||
|
||||
import net.corda.client.rpc.CordaRPCClient
|
||||
import net.corda.client.rpc.CordaRPCClientConfiguration
|
||||
import net.corda.client.rpc.RPCConnection
|
||||
import net.corda.client.rpc.RPCException
|
||||
@ -27,40 +26,22 @@ import java.time.Duration
|
||||
/**
|
||||
* This configuration may be used to tweak the internals of the RPC client.
|
||||
*/
|
||||
data class RPCClientConfiguration(
|
||||
/** The minimum protocol version required from the server */
|
||||
val minimumServerProtocolVersion: Int,
|
||||
/**
|
||||
* If set to true the client will track RPC call sites. If an error occurs subsequently during the RPC or in a
|
||||
* returned Observable stream the stack trace of the originating RPC will be shown as well. Note that
|
||||
* constructing call stacks is a moderately expensive operation.
|
||||
*/
|
||||
val trackRpcCallSites: Boolean,
|
||||
/**
|
||||
* The interval of unused observable reaping. Leaked Observables (unused ones) are detected using weak references
|
||||
* and are cleaned up in batches in this interval. If set too large it will waste server side resources for this
|
||||
* duration. If set too low it wastes client side cycles.
|
||||
*/
|
||||
val reapInterval: Duration,
|
||||
/** The number of threads to use for observations (for executing [Observable.onNext]) */
|
||||
val observationExecutorPoolSize: Int,
|
||||
/** The retry interval of artemis connections in milliseconds */
|
||||
val connectionRetryInterval: Duration,
|
||||
/** The retry interval multiplier for exponential backoff */
|
||||
val connectionRetryIntervalMultiplier: Double,
|
||||
/** Maximum retry interval */
|
||||
val connectionMaxRetryInterval: Duration,
|
||||
/** Maximum reconnect attempts on failover */
|
||||
val maxReconnectAttempts: Int,
|
||||
/** Maximum file size */
|
||||
val maxFileSize: Int,
|
||||
/** The cache expiry of a deduplication watermark per client. */
|
||||
val deduplicationCacheExpiry: Duration
|
||||
) {
|
||||
data class CordaRPCClientConfigurationImpl(
|
||||
override val minimumServerProtocolVersion: Int,
|
||||
override val trackRpcCallSites: Boolean,
|
||||
override val reapInterval: Duration,
|
||||
override val observationExecutorPoolSize: Int,
|
||||
override val connectionRetryInterval: Duration,
|
||||
override val connectionRetryIntervalMultiplier: Double,
|
||||
override val connectionMaxRetryInterval: Duration,
|
||||
override val maxReconnectAttempts: Int,
|
||||
override val maxFileSize: Int,
|
||||
override val deduplicationCacheExpiry: Duration
|
||||
) : CordaRPCClientConfiguration {
|
||||
companion object {
|
||||
val unlimitedReconnectAttempts = -1
|
||||
private const val unlimitedReconnectAttempts = -1
|
||||
@JvmStatic
|
||||
val default = RPCClientConfiguration(
|
||||
val default = CordaRPCClientConfigurationImpl(
|
||||
minimumServerProtocolVersion = 0,
|
||||
trackRpcCallSites = false,
|
||||
reapInterval = 1.seconds,
|
||||
@ -78,13 +59,13 @@ data class RPCClientConfiguration(
|
||||
|
||||
class RPCClient<I : RPCOps>(
|
||||
val transport: TransportConfiguration,
|
||||
val rpcConfiguration: RPCClientConfiguration = RPCClientConfiguration.default,
|
||||
val rpcConfiguration: CordaRPCClientConfiguration = CordaRPCClientConfigurationImpl.default,
|
||||
val serializationContext: SerializationContext = SerializationDefaults.RPC_CLIENT_CONTEXT
|
||||
) {
|
||||
constructor(
|
||||
hostAndPort: NetworkHostAndPort,
|
||||
sslConfiguration: SSLConfiguration? = null,
|
||||
configuration: RPCClientConfiguration = RPCClientConfiguration.default,
|
||||
configuration: CordaRPCClientConfiguration = CordaRPCClientConfigurationImpl.default,
|
||||
serializationContext: SerializationContext = SerializationDefaults.RPC_CLIENT_CONTEXT
|
||||
) : this(tcpTransport(ConnectionDirection.Outbound(), hostAndPort, sslConfiguration), configuration, serializationContext)
|
||||
|
||||
|
@ -11,6 +11,7 @@ import com.github.benmanes.caffeine.cache.RemovalCause
|
||||
import com.github.benmanes.caffeine.cache.RemovalListener
|
||||
import com.google.common.util.concurrent.SettableFuture
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder
|
||||
import net.corda.client.rpc.CordaRPCClientConfiguration
|
||||
import net.corda.client.rpc.RPCException
|
||||
import net.corda.client.rpc.RPCSinceVersion
|
||||
import net.corda.core.context.Actor
|
||||
@ -70,7 +71,7 @@ import kotlin.reflect.jvm.javaMethod
|
||||
* The cleanup happens in batches using a dedicated reaper, scheduled on [reaperExecutor].
|
||||
*/
|
||||
class RPCClientProxyHandler(
|
||||
private val rpcConfiguration: RPCClientConfiguration,
|
||||
private val rpcConfiguration: CordaRPCClientConfiguration,
|
||||
private val rpcUsername: String,
|
||||
private val rpcPassword: String,
|
||||
private val serverLocator: ServerLocator,
|
||||
@ -414,8 +415,8 @@ class RPCClientProxyHandler(
|
||||
sendingEnabled = false
|
||||
}
|
||||
|
||||
log.warn("RPC server unavailable.")
|
||||
log.warn("Terminating observables and in flight RPCs.")
|
||||
log.warn("RPC server unavailable. RPC calls are being buffered.")
|
||||
log.warn("Terminating observables.")
|
||||
val m = observableContext.observableMap.asMap()
|
||||
m.keys.forEach { k ->
|
||||
observationExecutorPool.run(k) {
|
||||
|
@ -1,6 +1,6 @@
|
||||
package net.corda.client.rpc
|
||||
|
||||
import net.corda.client.rpc.internal.RPCClientConfiguration
|
||||
import net.corda.client.rpc.internal.CordaRPCClientConfigurationImpl
|
||||
import net.corda.core.internal.concurrent.flatMap
|
||||
import net.corda.core.internal.concurrent.map
|
||||
import net.corda.core.messaging.RPCOps
|
||||
@ -44,7 +44,7 @@ open class AbstractRPCTest {
|
||||
inline fun <reified I : RPCOps> RPCDriverDSL.testProxy(
|
||||
ops: I,
|
||||
rpcUser: User = rpcTestUser,
|
||||
clientConfiguration: RPCClientConfiguration = RPCClientConfiguration.default,
|
||||
clientConfiguration: CordaRPCClientConfigurationImpl = CordaRPCClientConfigurationImpl.default,
|
||||
serverConfiguration: RPCServerConfiguration = RPCServerConfiguration.default
|
||||
): TestProxy<I> {
|
||||
return when (mode) {
|
||||
|
@ -1,6 +1,6 @@
|
||||
package net.corda.client.rpc
|
||||
|
||||
import net.corda.client.rpc.internal.RPCClientConfiguration
|
||||
import net.corda.client.rpc.internal.CordaRPCClientConfigurationImpl
|
||||
import net.corda.core.crypto.random63BitValue
|
||||
import net.corda.core.internal.concurrent.fork
|
||||
import net.corda.core.internal.concurrent.transpose
|
||||
@ -90,7 +90,7 @@ class RPCConcurrencyTests : AbstractRPCTest() {
|
||||
private fun RPCDriverDSL.testProxy(): TestProxy<TestOps> {
|
||||
return testProxy<TestOps>(
|
||||
TestOpsImpl(pool),
|
||||
clientConfiguration = RPCClientConfiguration.default.copy(
|
||||
clientConfiguration = CordaRPCClientConfigurationImpl.default.copy(
|
||||
reapInterval = 100.millis
|
||||
),
|
||||
serverConfiguration = RPCServerConfiguration.default.copy(
|
||||
|
@ -1,9 +1,8 @@
|
||||
package net.corda.client.rpc
|
||||
|
||||
import com.google.common.base.Stopwatch
|
||||
import net.corda.client.rpc.internal.RPCClientConfiguration
|
||||
import net.corda.client.rpc.internal.CordaRPCClientConfigurationImpl
|
||||
import net.corda.core.messaging.RPCOps
|
||||
import net.corda.core.utilities.days
|
||||
import net.corda.core.utilities.minutes
|
||||
import net.corda.core.utilities.seconds
|
||||
import net.corda.node.services.messaging.RPCServerConfiguration
|
||||
@ -43,7 +42,7 @@ class RPCPerformanceTests : AbstractRPCTest() {
|
||||
}
|
||||
|
||||
private fun RPCDriverDSL.testProxy(
|
||||
clientConfiguration: RPCClientConfiguration,
|
||||
clientConfiguration: CordaRPCClientConfigurationImpl,
|
||||
serverConfiguration: RPCServerConfiguration
|
||||
): TestProxy<TestOps> {
|
||||
return testProxy<TestOps>(
|
||||
@ -56,7 +55,7 @@ class RPCPerformanceTests : AbstractRPCTest() {
|
||||
private fun warmup() {
|
||||
rpcDriver {
|
||||
val proxy = testProxy(
|
||||
RPCClientConfiguration.default,
|
||||
CordaRPCClientConfigurationImpl.default,
|
||||
RPCServerConfiguration.default
|
||||
)
|
||||
val executor = Executors.newFixedThreadPool(4)
|
||||
@ -86,7 +85,7 @@ class RPCPerformanceTests : AbstractRPCTest() {
|
||||
measure(inputOutputSizes, (1..5)) { inputOutputSize, _ ->
|
||||
rpcDriver {
|
||||
val proxy = testProxy(
|
||||
RPCClientConfiguration.default.copy(
|
||||
CordaRPCClientConfigurationImpl.default.copy(
|
||||
observationExecutorPoolSize = 2
|
||||
),
|
||||
RPCServerConfiguration.default.copy(
|
||||
@ -125,7 +124,7 @@ class RPCPerformanceTests : AbstractRPCTest() {
|
||||
rpcDriver {
|
||||
val metricRegistry = startReporter(shutdownManager)
|
||||
val proxy = testProxy(
|
||||
RPCClientConfiguration.default.copy(
|
||||
CordaRPCClientConfigurationImpl.default.copy(
|
||||
reapInterval = 1.seconds
|
||||
),
|
||||
RPCServerConfiguration.default.copy(
|
||||
@ -157,7 +156,7 @@ class RPCPerformanceTests : AbstractRPCTest() {
|
||||
// TODO this hangs with more parallelism
|
||||
rpcDriver {
|
||||
val proxy = testProxy(
|
||||
RPCClientConfiguration.default,
|
||||
CordaRPCClientConfigurationImpl.default,
|
||||
RPCServerConfiguration.default
|
||||
)
|
||||
val numberOfMessages = 1000
|
||||
|
@ -21,6 +21,7 @@ import net.corda.core.serialization.CordaSerializable
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.utilities.Try
|
||||
import rx.Observable
|
||||
import rx.subjects.PublishSubject
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.security.PublicKey
|
||||
@ -365,6 +366,44 @@ interface CordaRPCOps : RPCOps {
|
||||
* @see setFlowsDrainingModeEnabled
|
||||
*/
|
||||
fun isFlowsDrainingModeEnabled(): Boolean
|
||||
|
||||
/**
|
||||
* Shuts the node down. Returns immediately.
|
||||
* This does not wait for flows to be completed.
|
||||
*/
|
||||
fun shutdown()
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a [DataFeed] that keeps track on the count of pending flows.
|
||||
*/
|
||||
fun CordaRPCOps.pendingFlowsCount(): DataFeed<Int, Pair<Int, Int>> {
|
||||
|
||||
val stateMachineState = stateMachinesFeed()
|
||||
var pendingFlowsCount = stateMachineState.snapshot.size
|
||||
var completedFlowsCount = 0
|
||||
val updates = PublishSubject.create<Pair<Int, Int>>()
|
||||
stateMachineState
|
||||
.updates
|
||||
.doOnNext { update ->
|
||||
when (update) {
|
||||
is StateMachineUpdate.Added -> {
|
||||
pendingFlowsCount++
|
||||
updates.onNext(completedFlowsCount to pendingFlowsCount)
|
||||
}
|
||||
is StateMachineUpdate.Removed -> {
|
||||
completedFlowsCount++
|
||||
updates.onNext(completedFlowsCount to pendingFlowsCount)
|
||||
if (completedFlowsCount == pendingFlowsCount) {
|
||||
updates.onCompleted()
|
||||
}
|
||||
}
|
||||
}
|
||||
}.subscribe()
|
||||
if (completedFlowsCount == 0) {
|
||||
updates.onCompleted()
|
||||
}
|
||||
return DataFeed(pendingFlowsCount, updates)
|
||||
}
|
||||
|
||||
inline fun <reified T : ContractState> CordaRPCOps.vaultQueryBy(criteria: QueryCriteria = QueryCriteria.VaultQueryCriteria(),
|
||||
|
@ -116,7 +116,7 @@ class ContractUpgradeFlowTest {
|
||||
return startRpcClient<CordaRPCOps>(
|
||||
rpcAddress = startRpcServer(
|
||||
rpcUser = user,
|
||||
ops = SecureCordaRPCOps(node.services, node.smm, node.database, node.services)
|
||||
ops = SecureCordaRPCOps(node.services, node.smm, node.database, node.services, { })
|
||||
).get().broker.hostAndPort!!,
|
||||
username = user.username,
|
||||
password = user.password
|
||||
|
@ -7,6 +7,8 @@ Unreleased
|
||||
Here are brief summaries of what's changed between each snapshot release. This includes guidance on how to upgrade code
|
||||
from the previous milestone release.
|
||||
|
||||
* Node can be shut down abruptly by ``shutdown`` function in `CordaRPCOps` or gracefully (draining flows first) through ``gracefulShutdown`` command from shell.
|
||||
|
||||
* Parsing of ``NodeConfiguration`` will now fail if unknown configuration keys are found.
|
||||
|
||||
* The web server now has its own ``web-server.conf`` file, separate from ``node.conf``.
|
||||
|
@ -18,6 +18,7 @@ the `CRaSH`_ shell and supports many of the same features. These features includ
|
||||
* Issuing SQL queries to the underlying database
|
||||
* Viewing JMX metrics and monitoring exports
|
||||
* UNIX style pipes for both text and objects, an ``egrep`` command and a command for working with columnular data
|
||||
* Shutting the node down.
|
||||
|
||||
Permissions
|
||||
-----------
|
||||
@ -197,6 +198,14 @@ Some RPCs return a stream of events that will be shown on screen until you press
|
||||
You can find a list of the available RPC methods
|
||||
`here <https://docs.corda.net/api/kotlin/corda/net.corda.core.messaging/-corda-r-p-c-ops/index.html>`_.
|
||||
|
||||
Shutting down the node
|
||||
**********************
|
||||
|
||||
You can shut the node down via shell:
|
||||
|
||||
* ``gracefulShutdown`` will put node into draining mode, and shut down when there are no flows running
|
||||
* ``shutdown`` will shut the node down immediately
|
||||
|
||||
Flow commands
|
||||
*************
|
||||
|
||||
|
@ -156,9 +156,9 @@ class Node(
|
||||
val user = config.users.first()
|
||||
val address = config.nodeInterface
|
||||
val targetHost = NetworkHostAndPort(address.host, address.rpcPort)
|
||||
val config = CordaRPCClientConfiguration(
|
||||
connectionMaxRetryInterval = 10.seconds
|
||||
)
|
||||
val config = object : CordaRPCClientConfiguration {
|
||||
override val connectionMaxRetryInterval = 10.seconds
|
||||
}
|
||||
log.info("Establishing RPC connection to ${targetHost.host} on port ${targetHost.port} ...")
|
||||
CordaRPCClient(targetHost, config).use(user.username, user.password) {
|
||||
log.info("RPC connection to ${targetHost.host}:${targetHost.port} established")
|
||||
|
@ -1,6 +1,7 @@
|
||||
package net.corda.node.modes.draining
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.client.rpc.internal.drainAndShutdown
|
||||
import net.corda.core.flows.*
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.internal.concurrent.map
|
||||
@ -13,11 +14,13 @@ import net.corda.testing.core.singleIdentity
|
||||
import net.corda.testing.driver.DriverParameters
|
||||
import net.corda.testing.driver.PortAllocation
|
||||
import net.corda.testing.driver.driver
|
||||
import net.corda.testing.internal.chooseIdentity
|
||||
import net.corda.testing.node.User
|
||||
import org.assertj.core.api.AssertionsForInterfaceTypes.assertThat
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.ScheduledExecutorService
|
||||
import java.util.concurrent.TimeUnit
|
||||
@ -49,6 +52,7 @@ class P2PFlowsDrainingModeTest {
|
||||
fun `flows draining mode suspends consumption of initial session messages`() {
|
||||
|
||||
driver(DriverParameters(isDebug = true, startNodesInProcess = false, portAllocation = portAllocation)) {
|
||||
|
||||
val initiatedNode = startNode().getOrThrow()
|
||||
val initiating = startNode(rpcUsers = users).getOrThrow().rpc
|
||||
val counterParty = initiatedNode.nodeInfo.singleIdentity()
|
||||
@ -76,27 +80,54 @@ class P2PFlowsDrainingModeTest {
|
||||
}
|
||||
}
|
||||
|
||||
@StartableByRPC
|
||||
@InitiatingFlow
|
||||
class InitiateSessionFlow(private val counterParty: Party) : FlowLogic<String>() {
|
||||
@Test
|
||||
fun `clean shutdown by draining`() {
|
||||
|
||||
@Suspendable
|
||||
override fun call(): String {
|
||||
driver(DriverParameters(isDebug = true, startNodesInProcess = true, portAllocation = portAllocation)) {
|
||||
|
||||
val session = initiateFlow(counterParty)
|
||||
session.send("Hi there")
|
||||
return session.receive<String>().unwrap { it }
|
||||
val nodeA = startNode(rpcUsers = users).getOrThrow()
|
||||
val nodeB = startNode(rpcUsers = users).getOrThrow()
|
||||
var successful = false
|
||||
val latch = CountDownLatch(1)
|
||||
nodeB.rpc.setFlowsDrainingModeEnabled(true)
|
||||
IntRange(1, 10).forEach { nodeA.rpc.startFlow(::InitiateSessionFlow, nodeB.nodeInfo.chooseIdentity()) }
|
||||
|
||||
nodeA.rpc.drainAndShutdown()
|
||||
.doOnError { error ->
|
||||
error.printStackTrace()
|
||||
successful = false
|
||||
}
|
||||
.doOnCompleted { successful = true }
|
||||
.doAfterTerminate { latch.countDown() }
|
||||
.subscribe()
|
||||
nodeB.rpc.setFlowsDrainingModeEnabled(false)
|
||||
latch.await()
|
||||
|
||||
assertThat(successful).isTrue()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@InitiatedBy(InitiateSessionFlow::class)
|
||||
class InitiatedFlow(private val initiatingSession: FlowSession) : FlowLogic<Unit>() {
|
||||
@StartableByRPC
|
||||
@InitiatingFlow
|
||||
class InitiateSessionFlow(private val counterParty: Party) : FlowLogic<String>() {
|
||||
|
||||
@Suspendable
|
||||
override fun call() {
|
||||
@Suspendable
|
||||
override fun call(): String {
|
||||
|
||||
val message = initiatingSession.receive<String>().unwrap { it }
|
||||
initiatingSession.send("$message answer")
|
||||
}
|
||||
val session = initiateFlow(counterParty)
|
||||
session.send("Hi there")
|
||||
return session.receive<String>().unwrap { it }
|
||||
}
|
||||
}
|
||||
|
||||
@InitiatedBy(InitiateSessionFlow::class)
|
||||
class InitiatedFlow(private val initiatingSession: FlowSession) : FlowLogic<Unit>() {
|
||||
|
||||
@Suspendable
|
||||
override fun call() {
|
||||
|
||||
val message = initiatingSession.receive<String>().unwrap { it }
|
||||
initiatingSession.send("$message answer")
|
||||
}
|
||||
}
|
@ -86,6 +86,7 @@ import java.time.Duration
|
||||
import java.util.*
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.concurrent.ExecutorService
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.TimeUnit.SECONDS
|
||||
import kotlin.collections.set
|
||||
import kotlin.reflect.KClass
|
||||
@ -145,6 +146,8 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
||||
protected lateinit var networkMapUpdater: NetworkMapUpdater
|
||||
lateinit var securityManager: RPCSecurityManager
|
||||
|
||||
private val shutdownExecutor = Executors.newSingleThreadExecutor()
|
||||
|
||||
/** Completes once the node has successfully registered with the network map service
|
||||
* or has loaded network map data from local database */
|
||||
val nodeReadyFuture: CordaFuture<Unit> get() = _nodeReadyFuture
|
||||
@ -159,7 +162,8 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
||||
|
||||
/** The implementation of the [CordaRPCOps] interface used by this node. */
|
||||
open fun makeRPCOps(flowStarter: FlowStarter, database: CordaPersistence, smm: StateMachineManager): CordaRPCOps {
|
||||
return SecureCordaRPCOps(services, smm, database, flowStarter)
|
||||
|
||||
return SecureCordaRPCOps(services, smm, database, flowStarter, { shutdownExecutor.submit { stop() } })
|
||||
}
|
||||
|
||||
private fun initCertificate() {
|
||||
@ -712,6 +716,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
||||
toRun()
|
||||
}
|
||||
runOnStop.clear()
|
||||
shutdownExecutor.shutdown()
|
||||
_started = null
|
||||
}
|
||||
|
||||
|
@ -45,7 +45,8 @@ internal class CordaRPCOpsImpl(
|
||||
private val services: ServiceHubInternal,
|
||||
private val smm: StateMachineManager,
|
||||
private val database: CordaPersistence,
|
||||
private val flowStarter: FlowStarter
|
||||
private val flowStarter: FlowStarter,
|
||||
private val shutdownNode: () -> Unit
|
||||
) : CordaRPCOps {
|
||||
override fun networkMapSnapshot(): List<NodeInfo> {
|
||||
val (snapshot, updates) = networkMapFeed()
|
||||
@ -298,6 +299,10 @@ internal class CordaRPCOpsImpl(
|
||||
return services.nodeProperties.flowsDrainingMode.isEnabled()
|
||||
}
|
||||
|
||||
override fun shutdown() {
|
||||
shutdownNode.invoke()
|
||||
}
|
||||
|
||||
private fun stateMachineInfoFromFlowLogic(flowLogic: FlowLogic<*>): StateMachineInfo {
|
||||
return StateMachineInfo(flowLogic.runId, flowLogic.javaClass.name, flowLogic.stateMachine.context.toFlowInitiator(), flowLogic.track(), flowLogic.stateMachine.context)
|
||||
}
|
||||
|
@ -167,6 +167,8 @@ class RpcAuthorisationProxy(private val implementation: CordaRPCOps, private val
|
||||
|
||||
override fun isFlowsDrainingModeEnabled(): Boolean = guard("isFlowsDrainingModeEnabled", implementation::isFlowsDrainingModeEnabled)
|
||||
|
||||
override fun shutdown() = guard("shutdown", implementation::shutdown)
|
||||
|
||||
// TODO change to KFunction reference after Kotlin fixes https://youtrack.jetbrains.com/issue/KT-12140
|
||||
private inline fun <RESULT> guard(methodName: String, action: () -> RESULT) = guard(methodName, emptyList(), action)
|
||||
|
||||
|
@ -14,7 +14,8 @@ class SecureCordaRPCOps(services: ServiceHubInternal,
|
||||
smm: StateMachineManager,
|
||||
database: CordaPersistence,
|
||||
flowStarter: FlowStarter,
|
||||
val unsafe: CordaRPCOps = CordaRPCOpsImpl(services, smm, database, flowStarter)) : CordaRPCOps by RpcAuthorisationProxy(unsafe, ::rpcContext) {
|
||||
shutdownNode: () -> Unit,
|
||||
val unsafe: CordaRPCOps = CordaRPCOpsImpl(services, smm, database, flowStarter, shutdownNode)) : CordaRPCOps by RpcAuthorisationProxy(unsafe, ::rpcContext) {
|
||||
|
||||
/**
|
||||
* Returns the RPC protocol version, which is the same the node's Platform Version. Exists since version 1 so guaranteed
|
||||
|
@ -82,7 +82,7 @@ class CordaRPCOpsImplTest {
|
||||
fun setup() {
|
||||
mockNet = InternalMockNetwork(cordappPackages = listOf("net.corda.finance.contracts.asset"))
|
||||
aliceNode = mockNet.createNode(InternalMockNodeParameters(legalName = ALICE_NAME))
|
||||
rpc = SecureCordaRPCOps(aliceNode.services, aliceNode.smm, aliceNode.database, aliceNode.services)
|
||||
rpc = SecureCordaRPCOps(aliceNode.services, aliceNode.smm, aliceNode.database, aliceNode.services, { })
|
||||
CURRENT_RPC_CONTEXT.set(RpcAuthContext(InvocationContext.rpc(testActor()), buildSubject("TEST_USER", emptySet())))
|
||||
|
||||
mockNet.runNetwork()
|
||||
|
@ -1,5 +1,7 @@
|
||||
package net.corda.testing.node.internal
|
||||
|
||||
import net.corda.client.rpc.CordaRPCClient
|
||||
import net.corda.client.rpc.CordaRPCClientConfiguration
|
||||
import net.corda.core.CordaException
|
||||
import net.corda.core.concurrent.CordaFuture
|
||||
import net.corda.core.context.InvocationContext
|
||||
@ -14,8 +16,10 @@ import net.corda.core.utilities.seconds
|
||||
import net.corda.node.services.api.StartedNodeServices
|
||||
import net.corda.node.services.messaging.Message
|
||||
import net.corda.node.services.messaging.MessagingService
|
||||
import net.corda.testing.driver.NodeHandle
|
||||
import net.corda.testing.internal.chooseIdentity
|
||||
import net.corda.testing.node.InMemoryMessagingNetwork
|
||||
import net.corda.testing.node.User
|
||||
import net.corda.testing.node.testContext
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.net.Socket
|
||||
@ -113,4 +117,4 @@ internal interface InternalMockMessagingService : MessagingService {
|
||||
fun stop()
|
||||
}
|
||||
|
||||
|
||||
fun CordaRPCClient.start(user: User) = start(user.username, user.password)
|
@ -3,7 +3,7 @@ package net.corda.testing.node.internal
|
||||
import net.corda.client.mock.Generator
|
||||
import net.corda.client.rpc.internal.KryoClientSerializationScheme
|
||||
import net.corda.client.rpc.internal.RPCClient
|
||||
import net.corda.client.rpc.internal.RPCClientConfiguration
|
||||
import net.corda.client.rpc.internal.CordaRPCClientConfigurationImpl
|
||||
import net.corda.core.concurrent.CordaFuture
|
||||
import net.corda.core.context.AuthServiceId
|
||||
import net.corda.core.context.Trace
|
||||
@ -59,7 +59,7 @@ import net.corda.nodeapi.internal.config.User as InternalUser
|
||||
inline fun <reified I : RPCOps> RPCDriverDSL.startInVmRpcClient(
|
||||
username: String = rpcTestUser.username,
|
||||
password: String = rpcTestUser.password,
|
||||
configuration: RPCClientConfiguration = RPCClientConfiguration.default
|
||||
configuration: CordaRPCClientConfigurationImpl = CordaRPCClientConfigurationImpl.default
|
||||
) = startInVmRpcClient(I::class.java, username, password, configuration)
|
||||
|
||||
inline fun <reified I : RPCOps> RPCDriverDSL.startRandomRpcClient(
|
||||
@ -72,7 +72,7 @@ inline fun <reified I : RPCOps> RPCDriverDSL.startRpcClient(
|
||||
rpcAddress: NetworkHostAndPort,
|
||||
username: String = rpcTestUser.username,
|
||||
password: String = rpcTestUser.password,
|
||||
configuration: RPCClientConfiguration = RPCClientConfiguration.default
|
||||
configuration: CordaRPCClientConfigurationImpl = CordaRPCClientConfigurationImpl.default
|
||||
) = startRpcClient(I::class.java, rpcAddress, username, password, configuration)
|
||||
|
||||
data class RpcBrokerHandle(
|
||||
@ -253,7 +253,7 @@ data class RPCDriverDSL(
|
||||
rpcOpsClass: Class<I>,
|
||||
username: String = rpcTestUser.username,
|
||||
password: String = rpcTestUser.password,
|
||||
configuration: RPCClientConfiguration = RPCClientConfiguration.default
|
||||
configuration: CordaRPCClientConfigurationImpl = CordaRPCClientConfigurationImpl.default
|
||||
): CordaFuture<I> {
|
||||
return driverDSL.executorService.fork {
|
||||
val client = RPCClient<I>(inVmClientTransportConfiguration, configuration)
|
||||
@ -324,7 +324,7 @@ data class RPCDriverDSL(
|
||||
rpcAddress: NetworkHostAndPort,
|
||||
username: String = rpcTestUser.username,
|
||||
password: String = rpcTestUser.password,
|
||||
configuration: RPCClientConfiguration = RPCClientConfiguration.default
|
||||
configuration: CordaRPCClientConfigurationImpl = CordaRPCClientConfigurationImpl.default
|
||||
): CordaFuture<I> {
|
||||
return driverDSL.executorService.fork {
|
||||
val client = RPCClient<I>(ArtemisTcpTransport.tcpTransport(ConnectionDirection.Outbound(), rpcAddress, null), configuration)
|
||||
|
@ -1,12 +1,20 @@
|
||||
package net.corda.tools.shell;
|
||||
|
||||
import net.corda.core.messaging.*;
|
||||
import net.corda.client.jackson.*;
|
||||
import org.crsh.cli.*;
|
||||
import org.crsh.command.*;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
import net.corda.client.jackson.StringToMethodCallParser;
|
||||
import net.corda.core.messaging.CordaRPCOps;
|
||||
import org.crsh.cli.Argument;
|
||||
import org.crsh.cli.Command;
|
||||
import org.crsh.cli.Man;
|
||||
import org.crsh.cli.Usage;
|
||||
import org.crsh.command.InvocationContext;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import static java.util.Comparator.comparing;
|
||||
|
||||
// Note that this class cannot be converted to Kotlin because CRaSH does not understand InvocationContext<Map<?, ?>> which
|
||||
// is the closest you can get in Kotlin to raw types.
|
||||
|
||||
@ -29,8 +37,7 @@ public class RunShellCommand extends InteractiveShellCommand {
|
||||
emitHelp(context, parser);
|
||||
return null;
|
||||
}
|
||||
|
||||
return InteractiveShell.runRPCFromString(command, out, context, ops(), objectMapper());
|
||||
return InteractiveShell.runRPCFromString(command, out, context, ops(), objectMapper(), isSsh());
|
||||
}
|
||||
|
||||
private void emitHelp(InvocationContext<Map> context, StringToMethodCallParser<CordaRPCOps> parser) {
|
||||
@ -38,21 +45,37 @@ public class RunShellCommand extends InteractiveShellCommand {
|
||||
// Each element we emit is a map of column -> content.
|
||||
Set<Map.Entry<String, String>> entries = parser.getAvailableCommands().entrySet();
|
||||
ArrayList<Map.Entry<String, String>> entryList = new ArrayList<>(entries);
|
||||
entryList.sort(Comparator.comparing(Map.Entry::getKey));
|
||||
entryList.sort(comparing(Map.Entry::getKey));
|
||||
for (Map.Entry<String, String> entry : entryList) {
|
||||
// Skip these entries as they aren't really interesting for the user.
|
||||
if (entry.getKey().equals("startFlowDynamic")) continue;
|
||||
if (entry.getKey().equals("getProtocolVersion")) continue;
|
||||
|
||||
// Use a LinkedHashMap to ensure that the Command column comes first.
|
||||
Map<String, String> m = new LinkedHashMap<>();
|
||||
m.put("Command", entry.getKey());
|
||||
m.put("Parameter types", entry.getValue());
|
||||
try {
|
||||
context.provide(m);
|
||||
context.provide(commandAndDesc(entry.getKey(), entry.getValue()));
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
Lists.newArrayList(
|
||||
commandAndDesc("shutdown", "Shuts node down (immediately)"),
|
||||
commandAndDesc("gracefulShutdown", "Shuts node down gracefully, waiting for all flows to complete first.")
|
||||
).forEach(stringStringMap -> {
|
||||
try {
|
||||
context.provide(stringStringMap);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private Map<String, String> commandAndDesc(String command, String description) {
|
||||
// Use a LinkedHashMap to ensure that the Command column comes first.
|
||||
Map<String, String> abruptShutdown = Maps.newLinkedHashMap();
|
||||
abruptShutdown.put("Command", command);
|
||||
abruptShutdown.put("Parameter types", description);
|
||||
return abruptShutdown;
|
||||
}
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ class CordaAuthenticationPlugin(private val rpcOps: (username: String, credentia
|
||||
}
|
||||
try {
|
||||
val ops = rpcOps(username, credential)
|
||||
return CordaSSHAuthInfo(true, ops)
|
||||
return CordaSSHAuthInfo(true, ops, isSsh = true)
|
||||
} catch (e: ActiveMQSecurityException) {
|
||||
logger.warn(e.message)
|
||||
} catch (e: Exception) {
|
||||
|
@ -6,7 +6,7 @@ import net.corda.tools.shell.InteractiveShell.createYamlInputMapper
|
||||
import net.corda.tools.shell.utlities.ANSIProgressRenderer
|
||||
import org.crsh.auth.AuthInfo
|
||||
|
||||
class CordaSSHAuthInfo(val successful: Boolean, val rpcOps: CordaRPCOps, val ansiProgressRenderer: ANSIProgressRenderer? = null) : AuthInfo {
|
||||
class CordaSSHAuthInfo(val successful: Boolean, val rpcOps: CordaRPCOps, val ansiProgressRenderer: ANSIProgressRenderer? = null, val isSsh: Boolean = false) : AuthInfo {
|
||||
override fun isSuccessful(): Boolean = successful
|
||||
|
||||
val yamlInputMapper: ObjectMapper by lazy {
|
||||
|
@ -1,13 +1,16 @@
|
||||
package net.corda.tools.shell
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator
|
||||
import com.fasterxml.jackson.core.JsonParser
|
||||
import com.fasterxml.jackson.databind.*
|
||||
import com.fasterxml.jackson.databind.JsonSerializer
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import com.fasterxml.jackson.databind.SerializationFeature
|
||||
import com.fasterxml.jackson.databind.SerializerProvider
|
||||
import com.fasterxml.jackson.databind.module.SimpleModule
|
||||
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory
|
||||
import com.google.common.io.Closeables
|
||||
import net.corda.client.jackson.JacksonSupport
|
||||
import net.corda.client.jackson.StringToMethodCallParser
|
||||
import net.corda.client.rpc.CordaRPCClientConfiguration
|
||||
import net.corda.client.rpc.CordaRPCConnection
|
||||
import net.corda.client.rpc.PermissionException
|
||||
import net.corda.client.rpc.internal.createCordaRPCClientWithSslAndClassLoader
|
||||
import net.corda.core.CordaException
|
||||
@ -18,13 +21,9 @@ import net.corda.core.identity.Party
|
||||
import net.corda.core.internal.*
|
||||
import net.corda.core.internal.concurrent.doneFuture
|
||||
import net.corda.core.internal.concurrent.openFuture
|
||||
import net.corda.core.messaging.CordaRPCOps
|
||||
import net.corda.core.messaging.DataFeed
|
||||
import net.corda.core.messaging.FlowProgressHandle
|
||||
import net.corda.core.messaging.StateMachineUpdate
|
||||
import net.corda.core.messaging.*
|
||||
import net.corda.core.node.NodeInfo
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
import net.corda.nodeapi.internal.config.SSLConfiguration
|
||||
import net.corda.tools.shell.utlities.ANSIProgressRenderer
|
||||
import net.corda.tools.shell.utlities.StdoutANSIProgressRenderer
|
||||
import org.crsh.command.InvocationContext
|
||||
@ -50,12 +49,13 @@ import org.json.JSONObject
|
||||
import org.slf4j.LoggerFactory
|
||||
import rx.Observable
|
||||
import rx.Subscriber
|
||||
import java.io.*
|
||||
import java.io.FileDescriptor
|
||||
import java.io.FileInputStream
|
||||
import java.io.InputStream
|
||||
import java.io.PrintWriter
|
||||
import java.lang.reflect.InvocationTargetException
|
||||
import java.lang.reflect.UndeclaredThrowableException
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
import java.util.*
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.ExecutionException
|
||||
@ -73,69 +73,30 @@ import kotlin.concurrent.thread
|
||||
// TODO: Resurrect or reimplement the mail plugin.
|
||||
// TODO: Make it notice new shell commands added after the node started.
|
||||
|
||||
data class SSHDConfiguration(val port: Int) {
|
||||
companion object {
|
||||
internal const val INVALID_PORT_FORMAT = "Invalid port: %s"
|
||||
private const val MISSING_PORT_FORMAT = "Missing port: %s"
|
||||
|
||||
/**
|
||||
* Parses a string of the form port into a [SSHDConfiguration].
|
||||
* @throws IllegalArgumentException if the port is missing or the string is garbage.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun parse(str: String): SSHDConfiguration {
|
||||
require(!str.isNullOrBlank()) { SSHDConfiguration.MISSING_PORT_FORMAT.format(str) }
|
||||
val port = try {
|
||||
str.toInt()
|
||||
} catch (ex: NumberFormatException) {
|
||||
throw IllegalArgumentException("Port syntax is invalid, expected port")
|
||||
}
|
||||
return SSHDConfiguration(port)
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
require(port in (0..0xffff)) { INVALID_PORT_FORMAT.format(port) }
|
||||
}
|
||||
}
|
||||
|
||||
data class ShellSslOptions(override val sslKeystore: Path, override val keyStorePassword: String, override val trustStoreFile:Path, override val trustStorePassword: String) : SSLConfiguration {
|
||||
override val certificatesDirectory: Path get() = Paths.get("")
|
||||
}
|
||||
|
||||
data class ShellConfiguration(
|
||||
val commandsDirectory: Path,
|
||||
val cordappsDirectory: Path? = null,
|
||||
var user: String = "",
|
||||
var password: String = "",
|
||||
val hostAndPort: NetworkHostAndPort,
|
||||
val ssl: ShellSslOptions? = null,
|
||||
val sshdPort: Int? = null,
|
||||
val sshHostKeyDirectory: Path? = null,
|
||||
val noLocalShell: Boolean = false) {
|
||||
companion object {
|
||||
const val SSH_PORT = 2222
|
||||
const val COMMANDS_DIR = "shell-commands"
|
||||
const val CORDAPPS_DIR = "cordapps"
|
||||
const val SSHD_HOSTKEY_DIR = "ssh"
|
||||
}
|
||||
}
|
||||
|
||||
object InteractiveShell {
|
||||
private val log = LoggerFactory.getLogger(javaClass)
|
||||
private lateinit var rpcOps: (username: String, credentials: String) -> CordaRPCOps
|
||||
private lateinit var connection: CordaRPCOps
|
||||
private lateinit var ops: CordaRPCOps
|
||||
private lateinit var connection: CordaRPCConnection
|
||||
private var shell: Shell? = null
|
||||
private var classLoader: ClassLoader? = null
|
||||
private lateinit var shellConfiguration: ShellConfiguration
|
||||
private var onExit: () -> Unit = {}
|
||||
/**
|
||||
* Starts an interactive shell connected to the local terminal. This shell gives administrator access to the node
|
||||
* internals.
|
||||
*/
|
||||
fun startShell(configuration: ShellConfiguration, classLoader: ClassLoader? = null) {
|
||||
shellConfiguration = configuration
|
||||
rpcOps = { username: String, credentials: String ->
|
||||
val client = createCordaRPCClientWithSslAndClassLoader(hostAndPort = configuration.hostAndPort,
|
||||
sslConfiguration = configuration.ssl, classLoader = classLoader)
|
||||
client.start(username, credentials).proxy
|
||||
configuration = object : CordaRPCClientConfiguration {
|
||||
override val maxReconnectAttempts = 1
|
||||
},
|
||||
sslConfiguration = configuration.ssl,
|
||||
classLoader = classLoader)
|
||||
this.connection = client.start(username, credentials)
|
||||
connection.proxy
|
||||
}
|
||||
InteractiveShell.classLoader = classLoader
|
||||
val runSshDaemon = configuration.sshdPort != null
|
||||
@ -160,6 +121,7 @@ object InteractiveShell {
|
||||
}
|
||||
|
||||
fun runLocalShell(onExit: () -> Unit = {}) {
|
||||
this.onExit = onExit
|
||||
val terminal = TerminalFactory.create()
|
||||
val consoleReader = ConsoleReader("Corda", FileInputStream(FileDescriptor.`in`), System.out, terminal)
|
||||
val jlineProcessor = JLineProcessor(terminal.isAnsiSupported, shell, consoleReader, System.out)
|
||||
@ -205,18 +167,18 @@ object InteractiveShell {
|
||||
return super.getPlugins().filterNot { it is JavaLanguage } + CordaAuthenticationPlugin(rpcOps)
|
||||
}
|
||||
}
|
||||
val attributes = emptyMap<String,Any>()
|
||||
val attributes = emptyMap<String, Any>()
|
||||
val context = PluginContext(discovery, attributes, commandsFS, confFS, classLoader)
|
||||
context.refresh()
|
||||
this.config = config
|
||||
start(context)
|
||||
connection = makeRPCOps(rpcOps, localUserName, localUserPassword)
|
||||
return context.getPlugin(ShellFactory::class.java).create(null, CordaSSHAuthInfo(false, connection, StdoutANSIProgressRenderer))
|
||||
ops = makeRPCOps(rpcOps, localUserName, localUserPassword)
|
||||
return context.getPlugin(ShellFactory::class.java).create(null, CordaSSHAuthInfo(false, ops, StdoutANSIProgressRenderer))
|
||||
}
|
||||
}
|
||||
|
||||
fun nodeInfo() = try {
|
||||
connection.nodeInfo()
|
||||
ops.nodeInfo()
|
||||
} catch (e: UndeclaredThrowableException) {
|
||||
throw e.cause ?: e
|
||||
}
|
||||
@ -411,14 +373,16 @@ object InteractiveShell {
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun runRPCFromString(input: List<String>, out: RenderPrintWriter, context: InvocationContext<out Any>, cordaRPCOps: CordaRPCOps, om: ObjectMapper): Any? {
|
||||
fun runRPCFromString(input: List<String>, out: RenderPrintWriter, context: InvocationContext<out Any>, cordaRPCOps: CordaRPCOps, om: ObjectMapper, isSsh: Boolean = false): Any? {
|
||||
val cmd = input.joinToString(" ").trim { it <= ' ' }
|
||||
if (cmd.toLowerCase().startsWith("startflow")) {
|
||||
if (cmd.startsWith("startflow", ignoreCase = true)) {
|
||||
// The flow command provides better support and startFlow requires special handling anyway due to
|
||||
// the generic startFlow RPC interface which offers no type information with which to parse the
|
||||
// string form of the command.
|
||||
out.println("Please use the 'flow' command to interact with flows rather than the 'run' command.", Color.yellow)
|
||||
return null
|
||||
} else if (cmd.substringAfter(" ").trim().equals("gracefulShutdown", ignoreCase = true)) {
|
||||
return InteractiveShell.gracefulShutdown(out, cordaRPCOps, isSsh)
|
||||
}
|
||||
|
||||
var result: Any? = null
|
||||
@ -457,6 +421,68 @@ object InteractiveShell {
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
@JvmStatic
|
||||
fun gracefulShutdown(userSessionOut: RenderPrintWriter, cordaRPCOps: CordaRPCOps, isSsh: Boolean = false) {
|
||||
|
||||
fun display(statements: RenderPrintWriter.() -> Unit) {
|
||||
statements.invoke(userSessionOut)
|
||||
userSessionOut.flush()
|
||||
}
|
||||
|
||||
var isShuttingDown = false
|
||||
try {
|
||||
display {
|
||||
println("Orchestrating a clean shutdown...")
|
||||
println("...enabling draining mode")
|
||||
}
|
||||
cordaRPCOps.setFlowsDrainingModeEnabled(true)
|
||||
display {
|
||||
println("...waiting for in-flight flows to be completed")
|
||||
}
|
||||
cordaRPCOps.pendingFlowsCount().updates
|
||||
.doOnError { error ->
|
||||
log.error(error.message)
|
||||
throw error
|
||||
}
|
||||
.doOnNext { remaining ->
|
||||
display {
|
||||
println("...remaining: ${remaining.first}/${remaining.second}")
|
||||
}
|
||||
}
|
||||
.doOnCompleted {
|
||||
if (isSsh) {
|
||||
// print in the original Shell process
|
||||
System.out.println("Shutting down the node via remote SSH session (it may take a while)")
|
||||
}
|
||||
display {
|
||||
println("Shutting down the node (it may take a while)")
|
||||
}
|
||||
cordaRPCOps.shutdown()
|
||||
isShuttingDown = true
|
||||
connection.forceClose()
|
||||
display {
|
||||
println("...done, quitting standalone shell now.")
|
||||
}
|
||||
onExit.invoke()
|
||||
}.toBlocking().single()
|
||||
} catch (e: StringToMethodCallParser.UnparseableCallException) {
|
||||
display {
|
||||
println(e.message, Color.red)
|
||||
println("Please try 'man run' to learn what syntax is acceptable")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
if (!isShuttingDown) {
|
||||
display {
|
||||
println("RPC failed: ${e.rootCause}", Color.red)
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
InputStreamSerializer.invokeContext = null
|
||||
InputStreamDeserializer.closeAll()
|
||||
}
|
||||
}
|
||||
|
||||
private fun printAndFollowRPCResponse(response: Any?, out: PrintWriter): CordaFuture<Unit> {
|
||||
|
||||
val mapElement: (Any?) -> String = { element -> outputMapper.writerWithDefaultPrettyPrinter().writeValueAsString(element) }
|
||||
@ -518,6 +544,7 @@ object InteractiveShell {
|
||||
return printNextElements(response.updates, printerFun, out)
|
||||
}
|
||||
if (response is Observable<*>) {
|
||||
|
||||
return printNextElements(response, printerFun, out)
|
||||
}
|
||||
|
||||
@ -532,94 +559,4 @@ object InteractiveShell {
|
||||
return subscriber.future
|
||||
}
|
||||
|
||||
//region Extra serializers
|
||||
//
|
||||
// These serializers are used to enable the user to specify objects that aren't natural data containers in the shell,
|
||||
// and for the shell to print things out that otherwise wouldn't be usefully printable.
|
||||
|
||||
private object ObservableSerializer : JsonSerializer<Observable<*>>() {
|
||||
override fun serialize(value: Observable<*>, gen: JsonGenerator, serializers: SerializerProvider) {
|
||||
gen.writeString("(observable)")
|
||||
}
|
||||
}
|
||||
|
||||
// A file name is deserialized to an InputStream if found.
|
||||
object InputStreamDeserializer : JsonDeserializer<InputStream>() {
|
||||
// Keep track of them so we can close them later.
|
||||
private val streams = Collections.synchronizedSet(HashSet<InputStream>())
|
||||
|
||||
override fun deserialize(p: JsonParser, ctxt: DeserializationContext): InputStream {
|
||||
val stream = object : BufferedInputStream(Files.newInputStream(Paths.get(p.text))) {
|
||||
override fun close() {
|
||||
super.close()
|
||||
streams.remove(this)
|
||||
}
|
||||
}
|
||||
streams += stream
|
||||
return stream
|
||||
}
|
||||
|
||||
fun closeAll() {
|
||||
// Clone the set with toList() here so each closed stream can be removed from the set inside close().
|
||||
streams.toList().forEach { Closeables.closeQuietly(it) }
|
||||
}
|
||||
}
|
||||
|
||||
// An InputStream found in a response triggers a request to the user to provide somewhere to save it.
|
||||
private object InputStreamSerializer : JsonSerializer<InputStream>() {
|
||||
var invokeContext: InvocationContext<*>? = null
|
||||
|
||||
override fun serialize(value: InputStream, gen: JsonGenerator, serializers: SerializerProvider) {
|
||||
try {
|
||||
val toPath = invokeContext!!.readLine("Path to save stream to (enter to ignore): ", true)
|
||||
if (toPath == null || toPath.isBlank()) {
|
||||
gen.writeString("<not saved>")
|
||||
} else {
|
||||
val path = Paths.get(toPath)
|
||||
value.copyTo(path)
|
||||
gen.writeString("<saved to: ${path.toAbsolutePath()}>")
|
||||
}
|
||||
} finally {
|
||||
try {
|
||||
value.close()
|
||||
} catch (e: IOException) {
|
||||
// Ignore.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* String value deserialized to [UniqueIdentifier].
|
||||
* Any string value used as [UniqueIdentifier.externalId].
|
||||
* If string contains underscore(i.e. externalId_uuid) then split with it.
|
||||
* Index 0 as [UniqueIdentifier.externalId]
|
||||
* Index 1 as [UniqueIdentifier.id]
|
||||
* */
|
||||
object UniqueIdentifierDeserializer : JsonDeserializer<UniqueIdentifier>() {
|
||||
override fun deserialize(p: JsonParser, ctxt: DeserializationContext): UniqueIdentifier {
|
||||
//Check if externalId and UUID may be separated by underscore.
|
||||
if (p.text.contains("_")) {
|
||||
val ids = p.text.split("_")
|
||||
//Create UUID object from string.
|
||||
val uuid: UUID = UUID.fromString(ids[1])
|
||||
//Create UniqueIdentifier object using externalId and UUID.
|
||||
return UniqueIdentifier(ids[0], uuid)
|
||||
}
|
||||
//Any other string used as externalId.
|
||||
return UniqueIdentifier.fromString(p.text)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* String value deserialized to [UUID].
|
||||
* */
|
||||
object UUIDDeserializer : JsonDeserializer<UUID>() {
|
||||
override fun deserialize(p: JsonParser, ctxt: DeserializationContext): UUID {
|
||||
//Create UUID object from string.
|
||||
return UUID.fromString(p.text)
|
||||
}
|
||||
}
|
||||
|
||||
//endregion
|
||||
}
|
||||
|
@ -10,4 +10,5 @@ open class InteractiveShellCommand : BaseCommand() {
|
||||
fun ops() = ((context.session as CRaSHSession).authInfo as CordaSSHAuthInfo).rpcOps
|
||||
fun ansiProgressRenderer() = ((context.session as CRaSHSession).authInfo as CordaSSHAuthInfo).ansiProgressRenderer
|
||||
fun objectMapper() = ((context.session as CRaSHSession).authInfo as CordaSSHAuthInfo).yamlInputMapper
|
||||
fun isSsh() = ((context.session as CRaSHSession).authInfo as CordaSSHAuthInfo).isSsh
|
||||
}
|
||||
|
@ -16,6 +16,5 @@ fun makeRPCOps(getCordaRPCOps: (username: String, credential: String) -> CordaRP
|
||||
// Unpack exception.
|
||||
throw e.targetException
|
||||
}
|
||||
}
|
||||
) as CordaRPCOps
|
||||
}) as CordaRPCOps
|
||||
}
|
||||
|
@ -0,0 +1,27 @@
|
||||
package net.corda.tools.shell
|
||||
|
||||
data class SSHDConfiguration(val port: Int) {
|
||||
companion object {
|
||||
internal const val INVALID_PORT_FORMAT = "Invalid port: %s"
|
||||
private const val MISSING_PORT_FORMAT = "Missing port: %s"
|
||||
|
||||
/**
|
||||
* Parses a string of the form port into a [SSHDConfiguration].
|
||||
* @throws IllegalArgumentException if the port is missing or the string is garbage.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun parse(str: String): SSHDConfiguration {
|
||||
require(!str.isNullOrBlank()) { SSHDConfiguration.MISSING_PORT_FORMAT.format(str) }
|
||||
val port = try {
|
||||
str.toInt()
|
||||
} catch (ex: NumberFormatException) {
|
||||
throw IllegalArgumentException("Port syntax is invalid, expected port")
|
||||
}
|
||||
return SSHDConfiguration(port)
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
require(port in (0..0xffff)) { INVALID_PORT_FORMAT.format(port) }
|
||||
}
|
||||
}
|
@ -0,0 +1,103 @@
|
||||
package net.corda.tools.shell
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator
|
||||
import com.fasterxml.jackson.core.JsonParser
|
||||
import com.fasterxml.jackson.databind.DeserializationContext
|
||||
import com.fasterxml.jackson.databind.JsonDeserializer
|
||||
import com.fasterxml.jackson.databind.JsonSerializer
|
||||
import com.fasterxml.jackson.databind.SerializerProvider
|
||||
import com.google.common.io.Closeables
|
||||
import net.corda.core.contracts.UniqueIdentifier
|
||||
import net.corda.core.internal.copyTo
|
||||
import org.crsh.command.InvocationContext
|
||||
import rx.Observable
|
||||
import java.io.BufferedInputStream
|
||||
import java.io.InputStream
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Paths
|
||||
import java.util.*
|
||||
|
||||
//region Extra serializers
|
||||
//
|
||||
// These serializers are used to enable the user to specify objects that aren't natural data containers in the shell,
|
||||
// and for the shell to print things out that otherwise wouldn't be usefully printable.
|
||||
|
||||
object ObservableSerializer : JsonSerializer<Observable<*>>() {
|
||||
override fun serialize(value: Observable<*>, gen: JsonGenerator, serializers: SerializerProvider) {
|
||||
gen.writeString("(observable)")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* String value deserialized to [UniqueIdentifier].
|
||||
* Any string value used as [UniqueIdentifier.externalId].
|
||||
* If string contains underscore(i.e. externalId_uuid) then split with it.
|
||||
* Index 0 as [UniqueIdentifier.externalId]
|
||||
* Index 1 as [UniqueIdentifier.id]
|
||||
* */
|
||||
object UniqueIdentifierDeserializer : JsonDeserializer<UniqueIdentifier>() {
|
||||
override fun deserialize(p: JsonParser, ctxt: DeserializationContext): UniqueIdentifier {
|
||||
//Check if externalId and UUID may be separated by underscore.
|
||||
if (p.text.contains("_")) {
|
||||
val ids = p.text.split("_")
|
||||
//Create UUID object from string.
|
||||
val uuid: UUID = UUID.fromString(ids[1])
|
||||
//Create UniqueIdentifier object using externalId and UUID.
|
||||
return UniqueIdentifier(ids[0], uuid)
|
||||
}
|
||||
//Any other string used as externalId.
|
||||
return UniqueIdentifier.fromString(p.text)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* String value deserialized to [UUID].
|
||||
* */
|
||||
object UUIDDeserializer : JsonDeserializer<UUID>() {
|
||||
override fun deserialize(p: JsonParser, ctxt: DeserializationContext): UUID {
|
||||
//Create UUID object from string.
|
||||
return UUID.fromString(p.text)
|
||||
}
|
||||
}
|
||||
|
||||
// An InputStream found in a response triggers a request to the user to provide somewhere to save it.
|
||||
object InputStreamSerializer : JsonSerializer<InputStream>() {
|
||||
var invokeContext: InvocationContext<*>? = null
|
||||
|
||||
override fun serialize(value: InputStream, gen: JsonGenerator, serializers: SerializerProvider) {
|
||||
|
||||
value.use {
|
||||
val toPath = invokeContext!!.readLine("Path to save stream to (enter to ignore): ", true)
|
||||
if (toPath == null || toPath.isBlank()) {
|
||||
gen.writeString("<not saved>")
|
||||
} else {
|
||||
val path = Paths.get(toPath)
|
||||
it.copyTo(path)
|
||||
gen.writeString("<saved to: ${path.toAbsolutePath()}>")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A file name is deserialized to an InputStream if found.
|
||||
object InputStreamDeserializer : JsonDeserializer<InputStream>() {
|
||||
// Keep track of them so we can close them later.
|
||||
private val streams = Collections.synchronizedSet(HashSet<InputStream>())
|
||||
|
||||
override fun deserialize(p: JsonParser, ctxt: DeserializationContext): InputStream {
|
||||
val stream = object : BufferedInputStream(Files.newInputStream(Paths.get(p.text))) {
|
||||
override fun close() {
|
||||
super.close()
|
||||
streams.remove(this)
|
||||
}
|
||||
}
|
||||
streams += stream
|
||||
return stream
|
||||
}
|
||||
|
||||
fun closeAll() {
|
||||
// Clone the set with toList() here so each closed stream can be removed from the set inside close().
|
||||
streams.toList().forEach { Closeables.closeQuietly(it) }
|
||||
}
|
||||
}
|
||||
//endregion
|
@ -0,0 +1,28 @@
|
||||
package net.corda.tools.shell
|
||||
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
import net.corda.nodeapi.internal.config.SSLConfiguration
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
|
||||
data class ShellConfiguration(
|
||||
val commandsDirectory: Path,
|
||||
val cordappsDirectory: Path? = null,
|
||||
var user: String = "",
|
||||
var password: String = "",
|
||||
val hostAndPort: NetworkHostAndPort,
|
||||
val ssl: ShellSslOptions? = null,
|
||||
val sshdPort: Int? = null,
|
||||
val sshHostKeyDirectory: Path? = null,
|
||||
val noLocalShell: Boolean = false) {
|
||||
companion object {
|
||||
const val SSH_PORT = 2222
|
||||
const val COMMANDS_DIR = "shell-commands"
|
||||
const val CORDAPPS_DIR = "cordapps"
|
||||
const val SSHD_HOSTKEY_DIR = "ssh"
|
||||
}
|
||||
}
|
||||
|
||||
data class ShellSslOptions(override val sslKeystore: Path, override val keyStorePassword: String, override val trustStoreFile:Path, override val trustStorePassword: String) : SSLConfiguration {
|
||||
override val certificatesDirectory: Path get() = Paths.get("")
|
||||
}
|
@ -105,6 +105,7 @@ class StandaloneShell(private val configuration: ShellConfiguration) {
|
||||
configuration.sshdPort?.apply{ println("SSH server listening on port $this.") }
|
||||
|
||||
exit.await()
|
||||
// because we can't clean certain Crash Shell threads that block on read()
|
||||
exitProcess(0)
|
||||
}
|
||||
}
|
||||
|
@ -27,8 +27,8 @@ class CustomTypeJsonParsingTests {
|
||||
fun setup() {
|
||||
objectMapper = ObjectMapper()
|
||||
val simpleModule = SimpleModule()
|
||||
simpleModule.addDeserializer(UniqueIdentifier::class.java, InteractiveShell.UniqueIdentifierDeserializer)
|
||||
simpleModule.addDeserializer(UUID::class.java, InteractiveShell.UUIDDeserializer)
|
||||
simpleModule.addDeserializer(UniqueIdentifier::class.java, UniqueIdentifierDeserializer)
|
||||
simpleModule.addDeserializer(UUID::class.java, UUIDDeserializer)
|
||||
objectMapper.registerModule(simpleModule)
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user