mirror of
https://github.com/corda/corda.git
synced 2025-02-24 02:41:22 +00:00
Docker system (end-to-end) test (#2437)
* System test for IRS Demo utilizing docker, docker-compose and PhantomJS to automate full-stack testing
This commit is contained in:
parent
a08d333d5b
commit
a9856b9ce6
build.gradleconstants.properties
gradle-plugins
build.gradle
cordform-common/src/main/java/net/corda/cordform
cordformation
samples/irs-demo
README.mdbuild.gradle
cordapp
src/system-test/kotlin
web
@ -65,6 +65,10 @@ buildscript {
|
|||||||
ext.jsr305_version = constants.getProperty("jsr305Version")
|
ext.jsr305_version = constants.getProperty("jsr305Version")
|
||||||
ext.shiro_version = '1.4.0'
|
ext.shiro_version = '1.4.0'
|
||||||
ext.artifactory_plugin_version = constants.getProperty('artifactoryPluginVersion')
|
ext.artifactory_plugin_version = constants.getProperty('artifactoryPluginVersion')
|
||||||
|
ext.snake_yaml_version = constants.getProperty('snakeYamlVersion')
|
||||||
|
ext.docker_compose_rule_version = '0.33.0'
|
||||||
|
ext.selenium_version = '3.8.1'
|
||||||
|
ext.ghostdriver_version = '2.1.0'
|
||||||
|
|
||||||
// Update 121 is required for ObjectInputFilter and at time of writing 131 was latest:
|
// Update 121 is required for ObjectInputFilter and at time of writing 131 was latest:
|
||||||
ext.java8_minUpdateVersion = '131'
|
ext.java8_minUpdateVersion = '131'
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
gradlePluginsVersion=3.0.4
|
gradlePluginsVersion=3.0.5
|
||||||
kotlinVersion=1.1.60
|
kotlinVersion=1.1.60
|
||||||
platformVersion=2
|
platformVersion=2
|
||||||
guavaVersion=21.0
|
guavaVersion=21.0
|
||||||
bouncycastleVersion=1.57
|
bouncycastleVersion=1.57
|
||||||
typesafeConfigVersion=1.3.1
|
typesafeConfigVersion=1.3.1
|
||||||
jsr305Version=3.0.2
|
jsr305Version=3.0.2
|
||||||
artifactoryPluginVersion=4.4.18
|
artifactoryPluginVersion=4.4.18
|
||||||
|
snakeYamlVersion=1.19
|
@ -14,6 +14,7 @@ buildscript {
|
|||||||
jsr305_version = constants.getProperty("jsr305Version")
|
jsr305_version = constants.getProperty("jsr305Version")
|
||||||
kotlin_version = constants.getProperty("kotlinVersion")
|
kotlin_version = constants.getProperty("kotlinVersion")
|
||||||
artifactory_plugin_version = constants.getProperty('artifactoryPluginVersion')
|
artifactory_plugin_version = constants.getProperty('artifactoryPluginVersion')
|
||||||
|
snake_yaml_version = constants.getProperty('snakeYamlVersion')
|
||||||
}
|
}
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
|
@ -20,7 +20,8 @@ public class CordformNode implements NodeDefinition {
|
|||||||
protected static final String DEFAULT_HOST = "localhost";
|
protected static final String DEFAULT_HOST = "localhost";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Name of the node.
|
* Name of the node. Node will be placed in directory based on this name - all lowercase with whitespaces removed.
|
||||||
|
* Actual node name inside node.conf will be as set here.
|
||||||
*/
|
*/
|
||||||
private String name;
|
private String name;
|
||||||
|
|
||||||
@ -28,6 +29,20 @@ public class CordformNode implements NodeDefinition {
|
|||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* p2p Port.
|
||||||
|
*/
|
||||||
|
private int p2pPort = 10002;
|
||||||
|
|
||||||
|
public int getP2pPort() { return p2pPort; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RPC Port.
|
||||||
|
*/
|
||||||
|
private int rpcPort = 10003;
|
||||||
|
|
||||||
|
public int getRpcPort() { return rpcPort; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the RPC users for this node. This configuration block allows arbitrary configuration.
|
* Set the RPC users for this node. This configuration block allows arbitrary configuration.
|
||||||
* The recommended current structure is:
|
* The recommended current structure is:
|
||||||
@ -79,6 +94,7 @@ public class CordformNode implements NodeDefinition {
|
|||||||
*/
|
*/
|
||||||
public void p2pPort(int p2pPort) {
|
public void p2pPort(int p2pPort) {
|
||||||
p2pAddress(DEFAULT_HOST + ':' + p2pPort);
|
p2pAddress(DEFAULT_HOST + ':' + p2pPort);
|
||||||
|
this.p2pPort = p2pPort;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -110,6 +126,7 @@ public class CordformNode implements NodeDefinition {
|
|||||||
@Deprecated
|
@Deprecated
|
||||||
public void rpcPort(int rpcPort) {
|
public void rpcPort(int rpcPort) {
|
||||||
rpcAddress(DEFAULT_HOST + ':' + rpcPort);
|
rpcAddress(DEFAULT_HOST + ':' + rpcPort);
|
||||||
|
this.rpcPort = rpcPort;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -8,6 +8,17 @@ public final class RpcSettings {
|
|||||||
|
|
||||||
private Config config = ConfigFactory.empty();
|
private Config config = ConfigFactory.empty();
|
||||||
|
|
||||||
|
private int port = 10003;
|
||||||
|
private int adminPort = 10005;
|
||||||
|
|
||||||
|
public int getPort() {
|
||||||
|
return port;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getAdminPort() {
|
||||||
|
return adminPort;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* RPC address for the node.
|
* RPC address for the node.
|
||||||
*/
|
*/
|
||||||
@ -15,6 +26,14 @@ public final class RpcSettings {
|
|||||||
setValue("address", value);
|
setValue("address", value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RPC Port for the node
|
||||||
|
*/
|
||||||
|
public final void port(final int value) {
|
||||||
|
this.port = value;
|
||||||
|
setValue("address", "localhost:"+port);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* RPC admin address for the node (necessary if [useSsl] is false or unset).
|
* RPC admin address for the node (necessary if [useSsl] is false or unset).
|
||||||
*/
|
*/
|
||||||
@ -22,6 +41,11 @@ public final class RpcSettings {
|
|||||||
setValue("adminAddress", value);
|
setValue("adminAddress", value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public final void adminPort(final int value) {
|
||||||
|
this.adminPort = value;
|
||||||
|
setValue("adminAddress", "localhost:"+adminPort);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Specifies whether the node RPC layer will require SSL from clients.
|
* Specifies whether the node RPC layer will require SSL from clients.
|
||||||
*/
|
*/
|
||||||
@ -43,7 +67,7 @@ public final class RpcSettings {
|
|||||||
config = options.addTo("ssl", config);
|
config = options.addTo("ssl", config);
|
||||||
}
|
}
|
||||||
|
|
||||||
final Config addTo(final String key, final Config config) {
|
public final Config addTo(final String key, final Config config) {
|
||||||
if (this.config.isEmpty()) {
|
if (this.config.isEmpty()) {
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
@ -43,7 +43,7 @@ public final class SslOptions {
|
|||||||
setValue("trustStoreFile", value);
|
setValue("trustStoreFile", value);
|
||||||
}
|
}
|
||||||
|
|
||||||
final Config addTo(final String key, final Config config) {
|
public final Config addTo(final String key, final Config config) {
|
||||||
if (this.config.isEmpty()) {
|
if (this.config.isEmpty()) {
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
@ -40,6 +40,8 @@ dependencies {
|
|||||||
noderunner "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version"
|
noderunner "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version"
|
||||||
|
|
||||||
compile project(':cordform-common')
|
compile project(':cordform-common')
|
||||||
|
// Docker-compose file generation
|
||||||
|
compile "org.yaml:snakeyaml:$snake_yaml_version"
|
||||||
}
|
}
|
||||||
|
|
||||||
task createNodeRunner(type: Jar, dependsOn: [classes]) {
|
task createNodeRunner(type: Jar, dependsOn: [classes]) {
|
||||||
|
@ -0,0 +1,174 @@
|
|||||||
|
package net.corda.plugins
|
||||||
|
|
||||||
|
import groovy.lang.Closure
|
||||||
|
import net.corda.cordform.CordformDefinition
|
||||||
|
import org.apache.tools.ant.filters.FixCrLfFilter
|
||||||
|
import org.gradle.api.DefaultTask
|
||||||
|
import org.gradle.api.plugins.JavaPluginConvention
|
||||||
|
import org.gradle.api.tasks.SourceSet.MAIN_SOURCE_SET_NAME
|
||||||
|
import org.gradle.api.tasks.TaskAction
|
||||||
|
import java.io.File
|
||||||
|
import java.lang.reflect.InvocationTargetException
|
||||||
|
import java.net.URLClassLoader
|
||||||
|
import java.nio.file.Path
|
||||||
|
import java.nio.file.Paths
|
||||||
|
import java.util.jar.JarInputStream
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates nodes based on the configuration of this task in the gradle configuration DSL.
|
||||||
|
*
|
||||||
|
* See documentation for examples.
|
||||||
|
*/
|
||||||
|
@Suppress("unused")
|
||||||
|
open class Baseform : DefaultTask() {
|
||||||
|
private companion object {
|
||||||
|
val nodeJarName = "corda.jar"
|
||||||
|
private val defaultDirectory: Path = Paths.get("build", "nodes")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optionally the name of a CordformDefinition subclass to which all configuration will be delegated.
|
||||||
|
*/
|
||||||
|
@Suppress("MemberVisibilityCanPrivate")
|
||||||
|
var definitionClass: String? = null
|
||||||
|
var directory = defaultDirectory
|
||||||
|
protected val nodes = mutableListOf<Node>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the directory to install nodes into.
|
||||||
|
*
|
||||||
|
* @param directory The directory the nodes will be installed into.
|
||||||
|
*/
|
||||||
|
fun directory(directory: String) {
|
||||||
|
this.directory = Paths.get(directory)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a node configuration.
|
||||||
|
*
|
||||||
|
* @param configureClosure A node configuration that will be deployed.
|
||||||
|
*/
|
||||||
|
@Suppress("MemberVisibilityCanPrivate")
|
||||||
|
fun node(configureClosure: Closure<in Node>) {
|
||||||
|
nodes += project.configure(Node(project), configureClosure) as Node
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a node configuration
|
||||||
|
*
|
||||||
|
* @param configureFunc A node configuration that will be deployed
|
||||||
|
*/
|
||||||
|
@Suppress("MemberVisibilityCanPrivate")
|
||||||
|
fun node(configureFunc: Node.() -> Any?): Node {
|
||||||
|
val node = Node(project).apply { configureFunc() }
|
||||||
|
nodes += node
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a node by name.
|
||||||
|
*
|
||||||
|
* @param name The name of the node as specified in the node configuration DSL.
|
||||||
|
* @return A node instance.
|
||||||
|
*/
|
||||||
|
private fun getNodeByName(name: String): Node? = nodes.firstOrNull { it.name == name }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The definitionClass needn't be compiled until just before our build method, so we load it manually via sourceSets.main.runtimeClasspath.
|
||||||
|
*/
|
||||||
|
private fun loadCordformDefinition(): CordformDefinition {
|
||||||
|
val plugin = project.convention.getPlugin(JavaPluginConvention::class.java)
|
||||||
|
val classpath = plugin.sourceSets.getByName(MAIN_SOURCE_SET_NAME).runtimeClasspath
|
||||||
|
val urls = classpath.files.map { it.toURI().toURL() }.toTypedArray()
|
||||||
|
return URLClassLoader(urls, CordformDefinition::class.java.classLoader)
|
||||||
|
.loadClass(definitionClass)
|
||||||
|
.asSubclass(CordformDefinition::class.java)
|
||||||
|
.newInstance()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The NetworkBootstrapper needn't be compiled until just before our build method, so we load it manually via sourceSets.main.runtimeClasspath.
|
||||||
|
*/
|
||||||
|
private fun loadNetworkBootstrapperClass(): Class<*> {
|
||||||
|
val plugin = project.convention.getPlugin(JavaPluginConvention::class.java)
|
||||||
|
val classpath = plugin.sourceSets.getByName(MAIN_SOURCE_SET_NAME).runtimeClasspath
|
||||||
|
val urls = classpath.files.map { it.toURI().toURL() }.toTypedArray()
|
||||||
|
return URLClassLoader(urls, javaClass.classLoader).loadClass("net.corda.nodeapi.internal.network.NetworkBootstrapper")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Installs the corda fat JAR to the root directory, for the network bootstrapper to use.
|
||||||
|
*/
|
||||||
|
protected fun installCordaJar() {
|
||||||
|
val cordaJar = Cordformation.verifyAndGetRuntimeJar(project, "corda")
|
||||||
|
project.copy {
|
||||||
|
it.apply {
|
||||||
|
from(cordaJar)
|
||||||
|
into(directory)
|
||||||
|
rename(cordaJar.name, nodeJarName)
|
||||||
|
fileMode = Cordformation.executableFileMode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun initializeConfiguration() {
|
||||||
|
if (definitionClass != null) {
|
||||||
|
val cd = loadCordformDefinition()
|
||||||
|
// If the user has specified their own directory (even if it's the same default path) then let them know
|
||||||
|
// it's not used and should just rely on the one in CordformDefinition
|
||||||
|
require(directory === defaultDirectory) {
|
||||||
|
"'directory' cannot be used when 'definitionClass' is specified. Use CordformDefinition.nodesDirectory instead."
|
||||||
|
}
|
||||||
|
directory = cd.nodesDirectory
|
||||||
|
val cordapps = cd.getMatchingCordapps()
|
||||||
|
cd.nodeConfigurers.forEach {
|
||||||
|
val node = node { }
|
||||||
|
it.accept(node)
|
||||||
|
node.additionalCordapps.addAll(cordapps)
|
||||||
|
node.rootDir(directory)
|
||||||
|
}
|
||||||
|
cd.setup { nodeName -> project.projectDir.toPath().resolve(getNodeByName(nodeName)!!.nodeDir.toPath()) }
|
||||||
|
} else {
|
||||||
|
nodes.forEach {
|
||||||
|
it.rootDir(directory)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun bootstrapNetwork() {
|
||||||
|
val networkBootstrapperClass = loadNetworkBootstrapperClass()
|
||||||
|
val networkBootstrapper = networkBootstrapperClass.newInstance()
|
||||||
|
val bootstrapMethod = networkBootstrapperClass.getMethod("bootstrap", Path::class.java).apply { isAccessible = true }
|
||||||
|
// Call NetworkBootstrapper.bootstrap
|
||||||
|
try {
|
||||||
|
val rootDir = project.projectDir.toPath().resolve(directory).toAbsolutePath().normalize()
|
||||||
|
bootstrapMethod.invoke(networkBootstrapper, rootDir)
|
||||||
|
} catch (e: InvocationTargetException) {
|
||||||
|
throw e.cause!!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun CordformDefinition.getMatchingCordapps(): List<File> {
|
||||||
|
val cordappJars = project.configuration("cordapp").files
|
||||||
|
return cordappPackages.map { `package` ->
|
||||||
|
val cordappsWithPackage = cordappJars.filter { it.containsPackage(`package`) }
|
||||||
|
when (cordappsWithPackage.size) {
|
||||||
|
0 -> throw IllegalArgumentException("There are no cordapp dependencies containing the package $`package`")
|
||||||
|
1 -> cordappsWithPackage[0]
|
||||||
|
else -> throw IllegalArgumentException("More than one cordapp dependency contains the package $`package`: $cordappsWithPackage")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun File.containsPackage(`package`: String): Boolean {
|
||||||
|
JarInputStream(inputStream()).use {
|
||||||
|
while (true) {
|
||||||
|
val name = it.nextJarEntry?.name ?: break
|
||||||
|
if (name.endsWith(".class") && name.replace('/', '.').startsWith(`package`)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,18 +1,12 @@
|
|||||||
package net.corda.plugins
|
package net.corda.plugins
|
||||||
|
|
||||||
import groovy.lang.Closure
|
|
||||||
import net.corda.cordform.CordformDefinition
|
|
||||||
import org.apache.tools.ant.filters.FixCrLfFilter
|
import org.apache.tools.ant.filters.FixCrLfFilter
|
||||||
import org.gradle.api.DefaultTask
|
import org.gradle.api.DefaultTask
|
||||||
import org.gradle.api.plugins.JavaPluginConvention
|
import org.gradle.api.plugins.JavaPluginConvention
|
||||||
import org.gradle.api.tasks.SourceSet.MAIN_SOURCE_SET_NAME
|
import org.gradle.api.tasks.SourceSet.MAIN_SOURCE_SET_NAME
|
||||||
import org.gradle.api.tasks.TaskAction
|
import org.gradle.api.tasks.TaskAction
|
||||||
import java.io.File
|
|
||||||
import java.lang.reflect.InvocationTargetException
|
|
||||||
import java.net.URLClassLoader
|
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import java.nio.file.Paths
|
import java.nio.file.Paths
|
||||||
import java.util.jar.JarInputStream
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates nodes based on the configuration of this task in the gradle configuration DSL.
|
* Creates nodes based on the configuration of this task in the gradle configuration DSL.
|
||||||
@ -20,59 +14,12 @@ import java.util.jar.JarInputStream
|
|||||||
* See documentation for examples.
|
* See documentation for examples.
|
||||||
*/
|
*/
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
open class Cordform : DefaultTask() {
|
open class Cordform : Baseform() {
|
||||||
private companion object {
|
private companion object {
|
||||||
val nodeJarName = "corda.jar"
|
val nodeJarName = "corda.jar"
|
||||||
private val defaultDirectory: Path = Paths.get("build", "nodes")
|
private val defaultDirectory: Path = Paths.get("build", "nodes")
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Optionally the name of a CordformDefinition subclass to which all configuration will be delegated.
|
|
||||||
*/
|
|
||||||
@Suppress("MemberVisibilityCanPrivate")
|
|
||||||
var definitionClass: String? = null
|
|
||||||
private var directory = defaultDirectory
|
|
||||||
private val nodes = mutableListOf<Node>()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the directory to install nodes into.
|
|
||||||
*
|
|
||||||
* @param directory The directory the nodes will be installed into.
|
|
||||||
*/
|
|
||||||
fun directory(directory: String) {
|
|
||||||
this.directory = Paths.get(directory)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a node configuration.
|
|
||||||
*
|
|
||||||
* @param configureClosure A node configuration that will be deployed.
|
|
||||||
*/
|
|
||||||
@Suppress("MemberVisibilityCanPrivate")
|
|
||||||
fun node(configureClosure: Closure<in Node>) {
|
|
||||||
nodes += project.configure(Node(project), configureClosure) as Node
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a node configuration
|
|
||||||
*
|
|
||||||
* @param configureFunc A node configuration that will be deployed
|
|
||||||
*/
|
|
||||||
@Suppress("MemberVisibilityCanPrivate")
|
|
||||||
fun node(configureFunc: Node.() -> Any?): Node {
|
|
||||||
val node = Node(project).apply { configureFunc() }
|
|
||||||
nodes += node
|
|
||||||
return node
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a node by name.
|
|
||||||
*
|
|
||||||
* @param name The name of the node as specified in the node configuration DSL.
|
|
||||||
* @return A node instance.
|
|
||||||
*/
|
|
||||||
private fun getNodeByName(name: String): Node? = nodes.firstOrNull { it.name == name }
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Installs the run script into the nodes directory.
|
* Installs the run script into the nodes directory.
|
||||||
*/
|
*/
|
||||||
@ -103,29 +50,6 @@ open class Cordform : DefaultTask() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* The definitionClass needn't be compiled until just before our build method, so we load it manually via sourceSets.main.runtimeClasspath.
|
|
||||||
*/
|
|
||||||
private fun loadCordformDefinition(): CordformDefinition {
|
|
||||||
val plugin = project.convention.getPlugin(JavaPluginConvention::class.java)
|
|
||||||
val classpath = plugin.sourceSets.getByName(MAIN_SOURCE_SET_NAME).runtimeClasspath
|
|
||||||
val urls = classpath.files.map { it.toURI().toURL() }.toTypedArray()
|
|
||||||
return URLClassLoader(urls, CordformDefinition::class.java.classLoader)
|
|
||||||
.loadClass(definitionClass)
|
|
||||||
.asSubclass(CordformDefinition::class.java)
|
|
||||||
.newInstance()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The NetworkBootstrapper needn't be compiled until just before our build method, so we load it manually via sourceSets.main.runtimeClasspath.
|
|
||||||
*/
|
|
||||||
private fun loadNetworkBootstrapperClass(): Class<*> {
|
|
||||||
val plugin = project.convention.getPlugin(JavaPluginConvention::class.java)
|
|
||||||
val classpath = plugin.sourceSets.getByName(MAIN_SOURCE_SET_NAME).runtimeClasspath
|
|
||||||
val urls = classpath.files.map { it.toURI().toURL() }.toTypedArray()
|
|
||||||
return URLClassLoader(urls, javaClass.classLoader).loadClass("net.corda.nodeapi.internal.network.NetworkBootstrapper")
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This task action will create and install the nodes based on the node configurations added.
|
* This task action will create and install the nodes based on the node configurations added.
|
||||||
*/
|
*/
|
||||||
@ -139,80 +63,4 @@ open class Cordform : DefaultTask() {
|
|||||||
bootstrapNetwork()
|
bootstrapNetwork()
|
||||||
nodes.forEach(Node::build)
|
nodes.forEach(Node::build)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Installs the corda fat JAR to the root directory, for the network bootstrapper to use.
|
|
||||||
*/
|
|
||||||
private fun installCordaJar() {
|
|
||||||
val cordaJar = Cordformation.verifyAndGetRuntimeJar(project, "corda")
|
|
||||||
project.copy {
|
|
||||||
it.apply {
|
|
||||||
from(cordaJar)
|
|
||||||
into(directory)
|
|
||||||
rename(cordaJar.name, nodeJarName)
|
|
||||||
fileMode = Cordformation.executableFileMode
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun initializeConfiguration() {
|
|
||||||
if (definitionClass != null) {
|
|
||||||
val cd = loadCordformDefinition()
|
|
||||||
// If the user has specified their own directory (even if it's the same default path) then let them know
|
|
||||||
// it's not used and should just rely on the one in CordformDefinition
|
|
||||||
require(directory === defaultDirectory) {
|
|
||||||
"'directory' cannot be used when 'definitionClass' is specified. Use CordformDefinition.nodesDirectory instead."
|
|
||||||
}
|
|
||||||
directory = cd.nodesDirectory
|
|
||||||
val cordapps = cd.getMatchingCordapps()
|
|
||||||
cd.nodeConfigurers.forEach {
|
|
||||||
val node = node { }
|
|
||||||
it.accept(node)
|
|
||||||
node.additionalCordapps.addAll(cordapps)
|
|
||||||
node.rootDir(directory)
|
|
||||||
}
|
|
||||||
cd.setup { nodeName -> project.projectDir.toPath().resolve(getNodeByName(nodeName)!!.nodeDir.toPath()) }
|
|
||||||
} else {
|
|
||||||
nodes.forEach {
|
|
||||||
it.rootDir(directory)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun bootstrapNetwork() {
|
|
||||||
val networkBootstrapperClass = loadNetworkBootstrapperClass()
|
|
||||||
val networkBootstrapper = networkBootstrapperClass.newInstance()
|
|
||||||
val bootstrapMethod = networkBootstrapperClass.getMethod("bootstrap", Path::class.java).apply { isAccessible = true }
|
|
||||||
// Call NetworkBootstrapper.bootstrap
|
|
||||||
try {
|
|
||||||
val rootDir = project.projectDir.toPath().resolve(directory).toAbsolutePath().normalize()
|
|
||||||
bootstrapMethod.invoke(networkBootstrapper, rootDir)
|
|
||||||
} catch (e: InvocationTargetException) {
|
|
||||||
throw e.cause!!
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun CordformDefinition.getMatchingCordapps(): List<File> {
|
|
||||||
val cordappJars = project.configuration("cordapp").files
|
|
||||||
return cordappPackages.map { `package` ->
|
|
||||||
val cordappsWithPackage = cordappJars.filter { it.containsPackage(`package`) }
|
|
||||||
when (cordappsWithPackage.size) {
|
|
||||||
0 -> throw IllegalArgumentException("There are no cordapp dependencies containing the package $`package`")
|
|
||||||
1 -> cordappsWithPackage[0]
|
|
||||||
else -> throw IllegalArgumentException("More than one cordapp dependency contains the package $`package`: $cordappsWithPackage")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun File.containsPackage(`package`: String): Boolean {
|
|
||||||
JarInputStream(inputStream()).use {
|
|
||||||
while (true) {
|
|
||||||
val name = it.nextJarEntry?.name ?: break
|
|
||||||
if (name.endsWith(".class") && name.replace('/', '.').startsWith(`package`)) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,66 @@
|
|||||||
|
package net.corda.plugins
|
||||||
|
|
||||||
|
import org.apache.tools.ant.filters.FixCrLfFilter
|
||||||
|
import org.gradle.api.DefaultTask
|
||||||
|
import org.gradle.api.plugins.JavaPluginConvention
|
||||||
|
import org.gradle.api.tasks.SourceSet.MAIN_SOURCE_SET_NAME
|
||||||
|
import org.gradle.api.tasks.TaskAction
|
||||||
|
import org.yaml.snakeyaml.DumperOptions
|
||||||
|
import java.nio.file.Path
|
||||||
|
import java.nio.file.Paths
|
||||||
|
import org.yaml.snakeyaml.Yaml
|
||||||
|
import java.nio.charset.StandardCharsets
|
||||||
|
import java.nio.file.Files
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates docker-compose file and image definitions based on the configuration of this task in the gradle configuration DSL.
|
||||||
|
*
|
||||||
|
* See documentation for examples.
|
||||||
|
*/
|
||||||
|
@Suppress("unused")
|
||||||
|
open class Dockerform : Baseform() {
|
||||||
|
private companion object {
|
||||||
|
val nodeJarName = "corda.jar"
|
||||||
|
private val defaultDirectory: Path = Paths.get("build", "docker")
|
||||||
|
|
||||||
|
private val dockerComposeFileVersion = "3"
|
||||||
|
|
||||||
|
private val yamlOptions = DumperOptions().apply {
|
||||||
|
indent = 2
|
||||||
|
defaultFlowStyle = DumperOptions.FlowStyle.BLOCK
|
||||||
|
}
|
||||||
|
private val yaml = Yaml(yamlOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val directoryPath = project.projectDir.toPath().resolve(directory)
|
||||||
|
|
||||||
|
val dockerComposePath = directoryPath.resolve("docker-compose.yml")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This task action will create and install the nodes based on the node configurations added.
|
||||||
|
*/
|
||||||
|
@TaskAction
|
||||||
|
fun build() {
|
||||||
|
project.logger.info("Running Cordform task")
|
||||||
|
initializeConfiguration()
|
||||||
|
nodes.forEach(Node::installDockerConfig)
|
||||||
|
installCordaJar()
|
||||||
|
bootstrapNetwork()
|
||||||
|
nodes.forEach(Node::buildDocker)
|
||||||
|
|
||||||
|
|
||||||
|
// Transform nodes path the absolute ones
|
||||||
|
val services = nodes.map { it.containerName to mapOf(
|
||||||
|
"build" to directoryPath.resolve(it.nodeDir.name).toAbsolutePath().toString(),
|
||||||
|
"ports" to listOf(it.rpcPort)) }.toMap()
|
||||||
|
|
||||||
|
|
||||||
|
val dockerComposeObject = mapOf(
|
||||||
|
"version" to dockerComposeFileVersion,
|
||||||
|
"services" to services)
|
||||||
|
|
||||||
|
val dockerComposeContent = yaml.dump(dockerComposeObject)
|
||||||
|
|
||||||
|
Files.write(dockerComposePath, dockerComposeContent.toByteArray(StandardCharsets.UTF_8))
|
||||||
|
}
|
||||||
|
}
|
@ -3,8 +3,10 @@ package net.corda.plugins
|
|||||||
import com.typesafe.config.ConfigFactory
|
import com.typesafe.config.ConfigFactory
|
||||||
import com.typesafe.config.ConfigRenderOptions
|
import com.typesafe.config.ConfigRenderOptions
|
||||||
import com.typesafe.config.ConfigValueFactory
|
import com.typesafe.config.ConfigValueFactory
|
||||||
|
import com.typesafe.config.ConfigObject
|
||||||
import groovy.lang.Closure
|
import groovy.lang.Closure
|
||||||
import net.corda.cordform.CordformNode
|
import net.corda.cordform.CordformNode
|
||||||
|
import net.corda.cordform.RpcSettings
|
||||||
import org.gradle.api.Project
|
import org.gradle.api.Project
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.nio.charset.StandardCharsets
|
import java.nio.charset.StandardCharsets
|
||||||
@ -34,6 +36,11 @@ class Node(private val project: Project) : CordformNode() {
|
|||||||
private set
|
private set
|
||||||
internal lateinit var rootDir: File
|
internal lateinit var rootDir: File
|
||||||
private set
|
private set
|
||||||
|
internal lateinit var containerName: String
|
||||||
|
private set
|
||||||
|
|
||||||
|
internal var rpcSettings: RpcSettings = RpcSettings()
|
||||||
|
private set
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets whether this node will use HTTPS communication.
|
* Sets whether this node will use HTTPS communication.
|
||||||
@ -59,7 +66,7 @@ class Node(private val project: Project) : CordformNode() {
|
|||||||
* Specifies RPC settings for the node.
|
* Specifies RPC settings for the node.
|
||||||
*/
|
*/
|
||||||
fun rpcSettings(configureClosure: Closure<in RpcSettings>) {
|
fun rpcSettings(configureClosure: Closure<in RpcSettings>) {
|
||||||
val rpcSettings = project.configure(RpcSettings(project), configureClosure) as RpcSettings
|
rpcSettings = project.configure(RpcSettings(), configureClosure) as RpcSettings
|
||||||
config = rpcSettings.addTo("rpcSettings", config)
|
config = rpcSettings.addTo("rpcSettings", config)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,6 +88,19 @@ class Node(private val project: Project) : CordformNode() {
|
|||||||
installCordapps()
|
installCordapps()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal fun buildDocker() {
|
||||||
|
project.copy {
|
||||||
|
it.apply {
|
||||||
|
from(Cordformation.getPluginFile(project, "net/corda/plugins/Dockerfile"))
|
||||||
|
from(Cordformation.getPluginFile(project, "net/corda/plugins/run-corda.sh"))
|
||||||
|
into("$nodeDir/")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
installAgentJar()
|
||||||
|
installBuiltCordapp()
|
||||||
|
installCordapps()
|
||||||
|
}
|
||||||
|
|
||||||
internal fun rootDir(rootDir: Path) {
|
internal fun rootDir(rootDir: Path) {
|
||||||
if (name == null) {
|
if (name == null) {
|
||||||
project.logger.error("Node has a null name - cannot create node")
|
project.logger.error("Node has a null name - cannot create node")
|
||||||
@ -90,8 +110,9 @@ class Node(private val project: Project) : CordformNode() {
|
|||||||
// with loading our custom X509EdDSAEngine.
|
// with loading our custom X509EdDSAEngine.
|
||||||
val organizationName = name.trim().split(",").firstOrNull { it.startsWith("O=") }?.substringAfter("=")
|
val organizationName = name.trim().split(",").firstOrNull { it.startsWith("O=") }?.substringAfter("=")
|
||||||
val dirName = organizationName ?: name
|
val dirName = organizationName ?: name
|
||||||
|
containerName = dirName.replace("\\s+".toRegex(), "-").toLowerCase()
|
||||||
this.rootDir = rootDir.toFile()
|
this.rootDir = rootDir.toFile()
|
||||||
nodeDir = File(this.rootDir, dirName)
|
nodeDir = File(this.rootDir, dirName.replace("\\s", ""))
|
||||||
Files.createDirectories(nodeDir.toPath())
|
Files.createDirectories(nodeDir.toPath())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -156,14 +177,14 @@ class Node(private val project: Project) : CordformNode() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createTempConfigFile(): File {
|
private fun createTempConfigFile(configObject: ConfigObject): File {
|
||||||
val options = ConfigRenderOptions
|
val options = ConfigRenderOptions
|
||||||
.defaults()
|
.defaults()
|
||||||
.setOriginComments(false)
|
.setOriginComments(false)
|
||||||
.setComments(false)
|
.setComments(false)
|
||||||
.setFormatted(true)
|
.setFormatted(true)
|
||||||
.setJson(false)
|
.setJson(false)
|
||||||
val configFileText = config.root().render(options).split("\n").toList()
|
val configFileText = configObject.render(options).split("\n").toList()
|
||||||
// Need to write a temporary file first to use the project.copy, which resolves directories correctly.
|
// Need to write a temporary file first to use the project.copy, which resolves directories correctly.
|
||||||
val tmpDir = File(project.buildDir, "tmp")
|
val tmpDir = File(project.buildDir, "tmp")
|
||||||
Files.createDirectories(tmpDir.toPath())
|
Files.createDirectories(tmpDir.toPath())
|
||||||
@ -178,7 +199,27 @@ class Node(private val project: Project) : CordformNode() {
|
|||||||
*/
|
*/
|
||||||
internal fun installConfig() {
|
internal fun installConfig() {
|
||||||
configureProperties()
|
configureProperties()
|
||||||
val tmpConfFile = createTempConfigFile()
|
val tmpConfFile = createTempConfigFile(config.root())
|
||||||
|
appendOptionalConfig(tmpConfFile)
|
||||||
|
project.copy {
|
||||||
|
it.apply {
|
||||||
|
from(tmpConfFile)
|
||||||
|
into(rootDir)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Installs the Dockerized configuration file to the root directory and detokenises it.
|
||||||
|
*/
|
||||||
|
internal fun installDockerConfig() {
|
||||||
|
configureProperties()
|
||||||
|
val dockerConf = config
|
||||||
|
.withValue("p2pAddress", ConfigValueFactory.fromAnyRef("$containerName:$p2pPort"))
|
||||||
|
.withValue("rpcSettings.address", ConfigValueFactory.fromAnyRef("$containerName:${rpcSettings.port}"))
|
||||||
|
.withValue("rpcSettings.adminAddress", ConfigValueFactory.fromAnyRef("$containerName:${rpcSettings.adminPort}"))
|
||||||
|
.withValue("detectPublicIp", ConfigValueFactory.fromAnyRef(false))
|
||||||
|
val tmpConfFile = createTempConfigFile(dockerConf.root())
|
||||||
appendOptionalConfig(tmpConfFile)
|
appendOptionalConfig(tmpConfFile)
|
||||||
project.copy {
|
project.copy {
|
||||||
it.apply {
|
it.apply {
|
||||||
|
@ -1,59 +0,0 @@
|
|||||||
package net.corda.plugins
|
|
||||||
|
|
||||||
import com.typesafe.config.Config
|
|
||||||
import com.typesafe.config.ConfigFactory
|
|
||||||
import com.typesafe.config.ConfigValueFactory
|
|
||||||
import groovy.lang.Closure
|
|
||||||
import org.gradle.api.Project
|
|
||||||
|
|
||||||
class RpcSettings(private val project: Project) {
|
|
||||||
private var config: Config = ConfigFactory.empty()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* RPC address for the node.
|
|
||||||
*/
|
|
||||||
fun address(value: String) {
|
|
||||||
config += "address" to value
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* RPC admin address for the node (necessary if [useSsl] is false or unset).
|
|
||||||
*/
|
|
||||||
fun adminAddress(value: String) {
|
|
||||||
config += "adminAddress" to value
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Specifies whether the node RPC layer will require SSL from clients.
|
|
||||||
*/
|
|
||||||
fun useSsl(value: Boolean) {
|
|
||||||
config += "useSsl" to value
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Specifies whether the RPC broker is separate from the node.
|
|
||||||
*/
|
|
||||||
fun standAloneBroker(value: Boolean) {
|
|
||||||
config += "standAloneBroker" to value
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Specifies SSL certificates options for the RPC layer.
|
|
||||||
*/
|
|
||||||
fun ssl(configureClosure: Closure<in SslOptions>) {
|
|
||||||
val sslOptions = project.configure(SslOptions(), configureClosure) as SslOptions
|
|
||||||
config = sslOptions.addTo("ssl", config)
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun addTo(key: String, config: Config): Config {
|
|
||||||
if (this.config.isEmpty) {
|
|
||||||
return config
|
|
||||||
}
|
|
||||||
return config + (key to this.config.root())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal operator fun Config.plus(entry: Pair<String, Any>): Config {
|
|
||||||
|
|
||||||
return withValue(entry.first, ConfigValueFactory.fromAnyRef(entry.second))
|
|
||||||
}
|
|
@ -1,50 +0,0 @@
|
|||||||
package net.corda.plugins
|
|
||||||
|
|
||||||
import com.typesafe.config.Config
|
|
||||||
import com.typesafe.config.ConfigFactory
|
|
||||||
|
|
||||||
class SslOptions {
|
|
||||||
private var config: Config = ConfigFactory.empty()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Password for the keystore.
|
|
||||||
*/
|
|
||||||
fun keyStorePassword(value: String) {
|
|
||||||
config += "keyStorePassword" to value
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Password for the truststore.
|
|
||||||
*/
|
|
||||||
fun trustStorePassword(value: String) {
|
|
||||||
config += "trustStorePassword" to value
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Directory under which key stores are to be placed.
|
|
||||||
*/
|
|
||||||
fun certificatesDirectory(value: String) {
|
|
||||||
config += "certificatesDirectory" to value
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Absolute path to SSL keystore. Default: "[certificatesDirectory]/sslkeystore.jks"
|
|
||||||
*/
|
|
||||||
fun sslKeystore(value: String) {
|
|
||||||
config += "sslKeystore" to value
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Absolute path to SSL truststore. Default: "[certificatesDirectory]/truststore.jks"
|
|
||||||
*/
|
|
||||||
fun trustStoreFile(value: String) {
|
|
||||||
config += "trustStoreFile" to value
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun addTo(key: String, config: Config): Config {
|
|
||||||
if (this.config.isEmpty) {
|
|
||||||
return config
|
|
||||||
}
|
|
||||||
return config + (key to this.config.root())
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,44 @@
|
|||||||
|
# Base image from (http://phusion.github.io/baseimage-docker)
|
||||||
|
FROM openjdk:8u151-jre-alpine
|
||||||
|
|
||||||
|
ENV CORDA_VERSION=${BUILDTIME_CORDA_VERSION}
|
||||||
|
ENV JAVA_OPTIONS=${BUILDTIME_JAVA_OPTIONS}
|
||||||
|
|
||||||
|
# Set image labels
|
||||||
|
LABEL net.corda.version = ${CORDA_VERSION} \
|
||||||
|
maintainer = "<devops@r3.com>" \
|
||||||
|
vendor = "R3"
|
||||||
|
|
||||||
|
RUN apk upgrade --update && \
|
||||||
|
apk add --update --no-cache bash iputils && \
|
||||||
|
rm -rf /var/cache/apk/* && \
|
||||||
|
# Add user to run the app && \
|
||||||
|
addgroup corda && \
|
||||||
|
adduser -G corda -D -s /bin/bash corda && \
|
||||||
|
# Create /opt/corda directory && \
|
||||||
|
mkdir -p /opt/corda/plugins && \
|
||||||
|
mkdir -p /opt/corda/logs
|
||||||
|
|
||||||
|
# Copy corda files
|
||||||
|
ADD --chown=corda:corda corda.jar /opt/corda/corda.jar
|
||||||
|
ADD --chown=corda:corda node.conf /opt/corda/node.conf
|
||||||
|
ADD --chown=corda:corda network-parameters /opt/corda/
|
||||||
|
ADD --chown=corda:corda cordapps/ /opt/corda/cordapps
|
||||||
|
ADD --chown=corda:corda additional-node-infos/ /opt/corda/additional-node-infos
|
||||||
|
ADD --chown=corda:corda certificates/ /opt/corda/certificates
|
||||||
|
ADD --chown=corda:corda drivers/ /opt/corda/drivers
|
||||||
|
ADD --chown=corda:corda persistence* /opt/corda/
|
||||||
|
|
||||||
|
COPY run-corda.sh /run-corda.sh
|
||||||
|
|
||||||
|
RUN chmod +x /run-corda.sh && \
|
||||||
|
sync && \
|
||||||
|
chown -R corda:corda /opt/corda
|
||||||
|
|
||||||
|
# Working directory for Corda
|
||||||
|
WORKDIR /opt/corda
|
||||||
|
ENV HOME=/opt/corda
|
||||||
|
USER corda
|
||||||
|
|
||||||
|
# Start it
|
||||||
|
CMD ["/run-corda.sh"]
|
@ -0,0 +1,10 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# If variable not present use default values
|
||||||
|
: ${CORDA_HOME:=/opt/corda}
|
||||||
|
: ${JAVA_OPTIONS:=-Xmx512m}
|
||||||
|
|
||||||
|
export CORDA_HOME JAVA_OPTIONS
|
||||||
|
|
||||||
|
cd ${CORDA_HOME}
|
||||||
|
java $JAVA_OPTIONS -jar ${CORDA_HOME}/corda.jar 2>&1
|
@ -35,3 +35,26 @@ view it.
|
|||||||
*Note:* The IRS web UI currently has a bug when changing the clock time where it may show no numbers or apply fixings
|
*Note:* The IRS web UI currently has a bug when changing the clock time where it may show no numbers or apply fixings
|
||||||
inconsistently. The issues will be addressed in a future milestone release. Meanwhile, you can take a look at a simpler
|
inconsistently. The issues will be addressed in a future milestone release. Meanwhile, you can take a look at a simpler
|
||||||
oracle example here: https://github.com/corda/oracle-example.
|
oracle example here: https://github.com/corda/oracle-example.
|
||||||
|
|
||||||
|
## Running the system test
|
||||||
|
|
||||||
|
The system test utilize docker. Amount of RAM required to run the IRS system test is around 2.5GB, it is important
|
||||||
|
to allocated appropriate system resources (On MacOS/Windows this may require explicit changes to docker configuration)
|
||||||
|
|
||||||
|
### Gradle
|
||||||
|
|
||||||
|
The system test is designed to exercise the entire stack, including Corda nodes and the web frontend. It uses [Docker](https://www.docker.com), [docker-compose](https://docs.docker.com/compose/), and
|
||||||
|
[PhantomJS](http://phantomjs.org/). Docker and docker-compose need to be installed and configured to be inside the system path
|
||||||
|
(default installation). PhantomJs binary have to be put in a known location and have execution permission enabled
|
||||||
|
(``chmod a+x phantomjs`` on Unix) and the full path to the binary exposed as system property named ``phantomjs.binary.path`` or
|
||||||
|
a system variable named ``PHANTOMJS_BINARY_PATH``.
|
||||||
|
Having this done, the system test can be run by running the Gradle task ``:samples:irs-demo:systemTest``.
|
||||||
|
|
||||||
|
### Other
|
||||||
|
|
||||||
|
In order to run the the test by other means that the Gradle task - two more system properties are expected -
|
||||||
|
``CORDAPP_DOCKER_COMPOSE`` and ``WEB_DOCKER_COMPOSE`` which should specify full path docker-compose file for IRS cordapp
|
||||||
|
and web frontend respectively. Those can be obtained by running ``:samples:irs-demo:cordapp:prepareDockerNodes`` and
|
||||||
|
``web:generateDockerCompose`` Gradle tasks. ``systemTest`` task simply executes those two and set proper system properties up.
|
||||||
|
|
||||||
|
|
@ -15,6 +15,7 @@ buildscript {
|
|||||||
// See https://github.com/spring-gradle-plugins/dependency-management-plugin/blob/master/README.md#changing-the-value-of-a-version-property
|
// See https://github.com/spring-gradle-plugins/dependency-management-plugin/blob/master/README.md#changing-the-value-of-a-version-property
|
||||||
ext['artemis.version'] = "$artemis_version"
|
ext['artemis.version'] = "$artemis_version"
|
||||||
ext['hibernate.version'] = "$hibernate_version"
|
ext['hibernate.version'] = "$hibernate_version"
|
||||||
|
ext['selenium.version'] = "$selenium_version"
|
||||||
|
|
||||||
apply plugin: 'java'
|
apply plugin: 'java'
|
||||||
apply plugin: 'kotlin'
|
apply plugin: 'kotlin'
|
||||||
@ -33,12 +34,29 @@ sourceSets {
|
|||||||
srcDir file('src/integration-test/kotlin')
|
srcDir file('src/integration-test/kotlin')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
systemTest {
|
||||||
|
kotlin {
|
||||||
|
compileClasspath += main.output + test.output
|
||||||
|
runtimeClasspath += main.output + test.output
|
||||||
|
srcDir file('src/system-test/kotlin')
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
configurations {
|
configurations {
|
||||||
integrationTestCompile.extendsFrom testCompile
|
integrationTestCompile.extendsFrom testCompile
|
||||||
integrationTestRuntime.extendsFrom testRuntime
|
integrationTestRuntime.extendsFrom testRuntime
|
||||||
demoArtifacts.extendsFrom testRuntime
|
demoArtifacts.extendsFrom testRuntime
|
||||||
|
systemTestCompile.extendsFrom testCompile
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
maven {
|
||||||
|
url 'https://dl.bintray.com/palantir/releases' // docker-compose-rule is published on bintray
|
||||||
|
}
|
||||||
|
repositories {
|
||||||
|
maven { url 'https://jitpack.io' }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
@ -55,6 +73,9 @@ dependencies {
|
|||||||
testCompile "org.assertj:assertj-core:${assertj_version}"
|
testCompile "org.assertj:assertj-core:${assertj_version}"
|
||||||
|
|
||||||
integrationTestCompile project(path: ":samples:irs-demo:web", configuration: "demoArtifacts")
|
integrationTestCompile project(path: ":samples:irs-demo:web", configuration: "demoArtifacts")
|
||||||
|
testCompile "com.palantir.docker.compose:docker-compose-rule-junit4:$docker_compose_rule_version"
|
||||||
|
testCompile "org.seleniumhq.selenium:selenium-java:$selenium_version"
|
||||||
|
testCompile "com.github.detro:ghostdriver:$ghostdriver_version"
|
||||||
}
|
}
|
||||||
|
|
||||||
bootRepackage {
|
bootRepackage {
|
||||||
@ -66,6 +87,22 @@ task integrationTest(type: Test, dependsOn: []) {
|
|||||||
classpath = sourceSets.integrationTest.runtimeClasspath
|
classpath = sourceSets.integrationTest.runtimeClasspath
|
||||||
}
|
}
|
||||||
|
|
||||||
|
evaluationDependsOn("cordapp")
|
||||||
|
evaluationDependsOn("web")
|
||||||
|
|
||||||
|
task systemTest(type: Test, dependsOn: ["cordapp:prepareDockerNodes", "web:generateDockerCompose"]) {
|
||||||
|
testClassesDirs = sourceSets.systemTest.output.classesDirs
|
||||||
|
classpath = sourceSets.systemTest.runtimeClasspath
|
||||||
|
|
||||||
|
systemProperty "CORDAPP_DOCKER_COMPOSE", tasks.getByPath("cordapp:prepareDockerNodes").dockerComposePath.toString()
|
||||||
|
systemProperty "WEB_DOCKER_COMPOSE", tasks.getByPath("web:generateDockerCompose").dockerComposePath.toString()
|
||||||
|
|
||||||
|
def phantomJsPath = System.getProperty("phantomjs.binary.path") ?: System.getenv("PHANTOMJS_BINARY_PATH")
|
||||||
|
if (phantomJsPath != null) {
|
||||||
|
systemProperty "phantomjs.binary.path", phantomJsPath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
idea {
|
idea {
|
||||||
module {
|
module {
|
||||||
downloadJavadoc = true // defaults to false
|
downloadJavadoc = true // defaults to false
|
||||||
|
@ -43,63 +43,96 @@ dependencies {
|
|||||||
testCompile "org.assertj:assertj-core:${assertj_version}"
|
testCompile "org.assertj:assertj-core:${assertj_version}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def rpcUsersList = [
|
||||||
|
['username' : "user",
|
||||||
|
'password' : "password",
|
||||||
|
'permissions' : [
|
||||||
|
"StartFlow.net.corda.irs.flows.AutoOfferFlow\$Requester",
|
||||||
|
"StartFlow.net.corda.irs.flows.UpdateBusinessDayFlow\$Broadcast",
|
||||||
|
"StartFlow.net.corda.irs.api.NodeInterestRates\$UploadFixesFlow",
|
||||||
|
"InvokeRpc.vaultQueryBy",
|
||||||
|
"InvokeRpc.networkMapSnapshot",
|
||||||
|
"InvokeRpc.currentNodeTime",
|
||||||
|
"InvokeRpc.wellKnownPartyFromX500Name"
|
||||||
|
]]
|
||||||
|
]
|
||||||
|
|
||||||
task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
|
task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
|
||||||
|
|
||||||
ext.rpcUsers = [
|
|
||||||
['username' : "user",
|
|
||||||
'password' : "password",
|
|
||||||
'permissions' : [
|
|
||||||
"StartFlow.net.corda.irs.flows.AutoOfferFlow\$Requester",
|
|
||||||
"StartFlow.net.corda.irs.flows.UpdateBusinessDayFlow\$Broadcast",
|
|
||||||
"StartFlow.net.corda.irs.api.NodeInterestRates\$UploadFixesFlow",
|
|
||||||
"InvokeRpc.vaultQueryBy",
|
|
||||||
"InvokeRpc.networkMapSnapshot",
|
|
||||||
"InvokeRpc.currentNodeTime",
|
|
||||||
"InvokeRpc.wellKnownPartyFromX500Name"
|
|
||||||
]]
|
|
||||||
]
|
|
||||||
|
|
||||||
directory "./build/nodes"
|
|
||||||
node {
|
node {
|
||||||
name "O=Notary Service,L=Zurich,C=CH"
|
name "O=Notary Service,L=Zurich,C=CH"
|
||||||
notary = [validating : true]
|
notary = [validating : true]
|
||||||
p2pPort 10002
|
p2pPort 10002
|
||||||
rpcSettings {
|
rpcSettings {
|
||||||
address "localhost:10003"
|
port 10003
|
||||||
adminAddress "localhost:10023"
|
adminPort 10023
|
||||||
}
|
}
|
||||||
cordapps = ["${project.group}:finance:$corda_release_version"]
|
cordapps = ["${project(":finance").group}:finance:$corda_release_version"]
|
||||||
rpcUsers = ext.rpcUsers
|
rpcUsers = rpcUsersList
|
||||||
useTestClock true
|
useTestClock true
|
||||||
}
|
}
|
||||||
node {
|
node {
|
||||||
name "O=Bank A,L=London,C=GB"
|
name "O=Bank A,L=London,C=GB"
|
||||||
p2pPort 10005
|
p2pPort 10005
|
||||||
rpcSettings {
|
rpcSettings {
|
||||||
address "localhost:10006"
|
port 10006
|
||||||
adminAddress "localhost:10026"
|
adminPort 10026
|
||||||
}
|
}
|
||||||
cordapps = ["${project.group}:finance:$corda_release_version"]
|
cordapps = ["${project(":finance").group}:finance:$corda_release_version"]
|
||||||
rpcUsers = ext.rpcUsers
|
rpcUsers = rpcUsersList
|
||||||
useTestClock true
|
useTestClock true
|
||||||
}
|
}
|
||||||
node {
|
node {
|
||||||
name "O=Bank B,L=New York,C=US"
|
name "O=Bank B,L=New York,C=US"
|
||||||
p2pPort 10008
|
p2pPort 10008
|
||||||
rpcSettings {
|
rpcSettings {
|
||||||
address "localhost:10009"
|
port 10009
|
||||||
adminAddress "localhost:10029"
|
adminPort 10029
|
||||||
}
|
}
|
||||||
cordapps = ["${project.group}:finance:$corda_release_version"]
|
cordapps = ["${project.group}:finance:$corda_release_version"]
|
||||||
rpcUsers = ext.rpcUsers
|
rpcUsers = rpcUsersList
|
||||||
useTestClock true
|
useTestClock true
|
||||||
}
|
}
|
||||||
node {
|
node {
|
||||||
name "O=Regulator,L=Moscow,C=RU"
|
name "O=Regulator,L=Moscow,C=RU"
|
||||||
p2pPort 10010
|
p2pPort 10010
|
||||||
rpcPort 10011
|
rpcSettings {
|
||||||
|
port 10009
|
||||||
|
adminPort 10029
|
||||||
|
}
|
||||||
cordapps = ["${project.group}:finance:$corda_release_version"]
|
cordapps = ["${project.group}:finance:$corda_release_version"]
|
||||||
rpcUsers = ext.rpcUsers
|
cordapps = ["${project(":finance").group}:finance:$corda_release_version"]
|
||||||
|
rpcUsers = rpcUsersList
|
||||||
|
useTestClock true
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
task prepareDockerNodes(type: net.corda.plugins.Dockerform, dependsOn: ['jar']) {
|
||||||
|
|
||||||
|
node {
|
||||||
|
name "O=Notary Service,L=Zurich,C=CH"
|
||||||
|
notary = [validating : true]
|
||||||
|
cordapps = ["${project(":finance").group}:finance:$corda_release_version"]
|
||||||
|
rpcUsers = rpcUsersList
|
||||||
|
useTestClock true
|
||||||
|
}
|
||||||
|
node {
|
||||||
|
name "O=Bank A,L=London,C=GB"
|
||||||
|
cordapps = ["${project(":finance").group}:finance:$corda_release_version"]
|
||||||
|
rpcUsers = rpcUsersList
|
||||||
|
useTestClock true
|
||||||
|
}
|
||||||
|
node {
|
||||||
|
name "O=Bank B,L=New York,C=US"
|
||||||
|
cordapps = ["${project(":finance").group}:finance:$corda_release_version"]
|
||||||
|
rpcUsers = rpcUsersList
|
||||||
|
useTestClock true
|
||||||
|
}
|
||||||
|
node {
|
||||||
|
name "O=Regulator,L=Moscow,C=RU"
|
||||||
|
cordapps = ["${project.group}:finance:$corda_release_version"]
|
||||||
|
rpcUsers = rpcUsersList
|
||||||
useTestClock true
|
useTestClock true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
80
samples/irs-demo/src/system-test/kotlin/IRSDemoDockerTest.kt
Normal file
80
samples/irs-demo/src/system-test/kotlin/IRSDemoDockerTest.kt
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
package net.corda.irs
|
||||||
|
|
||||||
|
import com.palantir.docker.compose.DockerComposeRule
|
||||||
|
import com.palantir.docker.compose.configuration.DockerComposeFiles
|
||||||
|
import com.palantir.docker.compose.connection.waiting.HealthChecks
|
||||||
|
import org.junit.ClassRule
|
||||||
|
import org.junit.Test
|
||||||
|
import org.openqa.selenium.By
|
||||||
|
import org.openqa.selenium.OutputType
|
||||||
|
import org.openqa.selenium.WebElement
|
||||||
|
import org.openqa.selenium.phantomjs.PhantomJSDriver
|
||||||
|
import org.openqa.selenium.support.ui.WebDriverWait
|
||||||
|
import java.nio.file.Files
|
||||||
|
import java.nio.file.Paths
|
||||||
|
import java.nio.file.StandardCopyOption
|
||||||
|
import kotlin.test.assertNotNull
|
||||||
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
|
class IRSDemoDockerTest {
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
private fun ensureSystemVariable(variable: String) {
|
||||||
|
if (System.getProperty(variable) == null) {
|
||||||
|
throw IllegalStateException("System variable $variable not set. Please refer to README file for proper setup instructions.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
ensureSystemVariable("CORDAPP_DOCKER_COMPOSE")
|
||||||
|
ensureSystemVariable("WEB_DOCKER_COMPOSE")
|
||||||
|
ensureSystemVariable("phantomjs.binary.path")
|
||||||
|
}
|
||||||
|
|
||||||
|
@ClassRule
|
||||||
|
@JvmField
|
||||||
|
var docker = DockerComposeRule.builder()
|
||||||
|
.files(DockerComposeFiles.from(
|
||||||
|
System.getProperty("CORDAPP_DOCKER_COMPOSE"),
|
||||||
|
System.getProperty("WEB_DOCKER_COMPOSE")))
|
||||||
|
.waitingForService("web-a", HealthChecks.toRespondOverHttp(8080, { port -> port.inFormat("http://\$HOST:\$EXTERNAL_PORT") }))
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `runs IRS demo selenium phantomjs`() {
|
||||||
|
|
||||||
|
|
||||||
|
val driver = PhantomJSDriver()
|
||||||
|
|
||||||
|
val webAPort = docker.containers()
|
||||||
|
.container("web-a")
|
||||||
|
.port(8080)
|
||||||
|
|
||||||
|
|
||||||
|
driver.get("http://${webAPort.ip}:${webAPort.externalPort}");
|
||||||
|
|
||||||
|
//no deals on fresh interface
|
||||||
|
val dealRows = driver.findElementsByCssSelector("table#deal-list tbody tr")
|
||||||
|
assertTrue(dealRows.isEmpty())
|
||||||
|
|
||||||
|
// Click Angular link and wait for form to appear
|
||||||
|
val findElementByLinkText = driver.findElementByLinkText("Create Deal")
|
||||||
|
findElementByLinkText.click()
|
||||||
|
|
||||||
|
val driverWait = WebDriverWait(driver, 120)
|
||||||
|
|
||||||
|
val form = driverWait.until<WebElement>({
|
||||||
|
it?.findElement(By.cssSelector("form"))
|
||||||
|
})
|
||||||
|
|
||||||
|
form.submit()
|
||||||
|
|
||||||
|
//Wait for deals to appear in a rows table
|
||||||
|
val dealsList = driverWait.until<WebElement>({
|
||||||
|
it?.findElement(By.cssSelector("table#deal-list tbody tr"))
|
||||||
|
})
|
||||||
|
|
||||||
|
assertNotNull(dealsList)
|
||||||
|
}
|
||||||
|
}
|
@ -1,17 +1,28 @@
|
|||||||
|
import java.nio.charset.StandardCharsets
|
||||||
|
import java.nio.file.Files
|
||||||
|
|
||||||
|
|
||||||
buildscript {
|
buildscript {
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
|
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
|
||||||
classpath("org.jetbrains.kotlin:kotlin-allopen:${kotlin_version}")
|
classpath("org.jetbrains.kotlin:kotlin-allopen:${kotlin_version}")
|
||||||
}
|
classpath 'com.bmuschko:gradle-docker-plugin:3.2.1'
|
||||||
|
classpath "org.yaml:snakeyaml:1.19"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
import org.yaml.snakeyaml.DumperOptions
|
||||||
|
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id 'com.craigburke.client-dependencies' version '1.4.0'
|
id 'com.craigburke.client-dependencies' version '1.4.0'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
group = "${parent.group}.irs-demo"
|
||||||
|
|
||||||
clientDependencies {
|
clientDependencies {
|
||||||
registry 'realBower', type:'bower', url:'https://registry.bower.io'
|
registry 'realBower', type:'bower', url:'https://registry.bower.io'
|
||||||
realBower {
|
realBower {
|
||||||
@ -69,6 +80,8 @@ jar {
|
|||||||
dependsOn clientInstall
|
dependsOn clientInstall
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def docker_dir = file("$project.buildDir/docker")
|
||||||
|
|
||||||
task deployWebapps(type: Copy, dependsOn: ['jar', 'bootRepackage']) {
|
task deployWebapps(type: Copy, dependsOn: ['jar', 'bootRepackage']) {
|
||||||
ext.webappDir = file("build/webapps")
|
ext.webappDir = file("build/webapps")
|
||||||
|
|
||||||
@ -89,4 +102,58 @@ task demoJar(type: Jar) {
|
|||||||
|
|
||||||
artifacts {
|
artifacts {
|
||||||
demoArtifacts demoJar
|
demoArtifacts demoJar
|
||||||
|
}
|
||||||
|
|
||||||
|
task createDockerfile(type: com.bmuschko.gradle.docker.tasks.image.Dockerfile, dependsOn: [bootRepackage]) {
|
||||||
|
destFile = file("$docker_dir/Dockerfile")
|
||||||
|
|
||||||
|
from 'azul/zulu-openjdk-alpine:8u152'
|
||||||
|
copyFile jar.archiveName, "/opt/irs/web/"
|
||||||
|
workingDir "/opt/irs/web/"
|
||||||
|
defaultCommand "sh", "-c", "java -Dcorda.host=\$CORDA_HOST -jar ${jar.archiveName}"
|
||||||
|
}
|
||||||
|
|
||||||
|
task prepareDockerDir(type: Copy, dependsOn: [bootRepackage, createDockerfile]) {
|
||||||
|
from jar
|
||||||
|
into docker_dir
|
||||||
|
}
|
||||||
|
|
||||||
|
task generateDockerCompose(dependsOn: [prepareDockerDir]) {
|
||||||
|
|
||||||
|
def outFile = new File(project.buildDir, "docker-compose.yml")
|
||||||
|
|
||||||
|
ext['dockerComposePath'] = outFile
|
||||||
|
|
||||||
|
doLast {
|
||||||
|
def dockerComposeObject = [
|
||||||
|
"version": "3",
|
||||||
|
"services": [
|
||||||
|
"web-a": [
|
||||||
|
"build": "$docker_dir".toString(),
|
||||||
|
"environment": [
|
||||||
|
"CORDA_HOST": "bank-a:10003"
|
||||||
|
],
|
||||||
|
"ports": ["8080"]
|
||||||
|
],
|
||||||
|
"web-b": [
|
||||||
|
"build": "$docker_dir".toString(),
|
||||||
|
"environment": [
|
||||||
|
"CORDA_HOST": "bank-b:10003"
|
||||||
|
],
|
||||||
|
"ports": ["8080"]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def options = new org.yaml.snakeyaml.DumperOptions()
|
||||||
|
options.indent = 2
|
||||||
|
options.defaultFlowStyle = DumperOptions.FlowStyle.BLOCK
|
||||||
|
|
||||||
|
def dockerComposeContent = new org.yaml.snakeyaml.Yaml(options).dump(dockerComposeObject)
|
||||||
|
|
||||||
|
Files.write(outFile.toPath(), dockerComposeContent.getBytes(StandardCharsets.UTF_8))
|
||||||
|
}
|
||||||
|
|
||||||
|
outputs.file(outFile)
|
||||||
}
|
}
|
@ -6,6 +6,8 @@ import net.corda.client.rpc.CordaRPCClient
|
|||||||
import net.corda.core.messaging.CordaRPCOps
|
import net.corda.core.messaging.CordaRPCOps
|
||||||
import net.corda.core.utilities.NetworkHostAndPort
|
import net.corda.core.utilities.NetworkHostAndPort
|
||||||
import net.corda.finance.plugin.registerFinanceJSONMappers
|
import net.corda.finance.plugin.registerFinanceJSONMappers
|
||||||
|
import org.apache.activemq.artemis.api.core.ActiveMQNotConnectedException
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
import org.springframework.beans.factory.annotation.Autowired
|
import org.springframework.beans.factory.annotation.Autowired
|
||||||
import org.springframework.beans.factory.annotation.Value
|
import org.springframework.beans.factory.annotation.Value
|
||||||
import org.springframework.boot.SpringApplication
|
import org.springframework.boot.SpringApplication
|
||||||
@ -29,10 +31,22 @@ class IrsDemoWebApplication {
|
|||||||
@Value("\${corda.password}")
|
@Value("\${corda.password}")
|
||||||
lateinit var cordaPassword:String
|
lateinit var cordaPassword:String
|
||||||
|
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
fun rpcClient(): CordaRPCOps {
|
fun rpcClient(): CordaRPCOps {
|
||||||
return CordaRPCClient(NetworkHostAndPort.parse(cordaHost)).start(cordaUser, cordaPassword).proxy
|
log.info("Connecting to Corda on $cordaHost using username $cordaUser and password $cordaPassword")
|
||||||
|
// TODO remove this when CordaRPC gets proper connection retry, please
|
||||||
|
var maxRetries = 100;
|
||||||
|
do {
|
||||||
|
try {
|
||||||
|
return CordaRPCClient(NetworkHostAndPort.parse(cordaHost)).start(cordaUser, cordaPassword).proxy
|
||||||
|
} catch (ex: ActiveMQNotConnectedException) {
|
||||||
|
if (maxRetries-- > 0) {
|
||||||
|
Thread.sleep(1000)
|
||||||
|
} else {
|
||||||
|
throw ex
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@ -44,6 +58,8 @@ class IrsDemoWebApplication {
|
|||||||
|
|
||||||
// running as standalone java app
|
// running as standalone java app
|
||||||
companion object {
|
companion object {
|
||||||
|
private val log = LoggerFactory.getLogger(this::class.java)
|
||||||
|
|
||||||
@JvmStatic fun main(args: Array<String>) {
|
@JvmStatic fun main(args: Array<String>) {
|
||||||
SpringApplication.run(IrsDemoWebApplication::class.java, *args)
|
SpringApplication.run(IrsDemoWebApplication::class.java, *args)
|
||||||
}
|
}
|
||||||
|
@ -1,2 +1,2 @@
|
|||||||
corda.host=localhost:10003
|
corda.host=localhost:10003
|
||||||
server.port=10004
|
server.port=10004
|
||||||
|
@ -1,36 +1,32 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
define(['viewmodel/FixedRate'], (fixedRateViewModel) => {
|
define(['viewmodel/FixedRate'], function (fixedRateViewModel) {
|
||||||
let calculationModel = {
|
var calculationModel = {
|
||||||
expression: "( fixedLeg.notional.quantity * (fixedLeg.fixedRate.ratioUnit.value)) - (floatingLeg.notional.quantity * (calculation.fixingSchedule.get(context.getDate('currentDate')).rate.ratioUnit.value))",
|
expression: "( fixedLeg.notional.quantity * (fixedLeg.fixedRate.ratioUnit.value)) - (floatingLeg.notional.quantity * (calculation.fixingSchedule.get(context.getDate('currentDate')).rate.ratioUnit.value))",
|
||||||
floatingLegPaymentSchedule: {
|
floatingLegPaymentSchedule: {},
|
||||||
|
fixedLegPaymentSchedule: {}
|
||||||
},
|
|
||||||
fixedLegPaymentSchedule: {
|
|
||||||
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let indexLookup = {
|
var indexLookup = {
|
||||||
"GBP": "ICE LIBOR",
|
"GBP": "ICE LIBOR",
|
||||||
"USD": "ICE LIBOR",
|
"USD": "ICE LIBOR",
|
||||||
"EUR": "EURIBOR"
|
"EUR": "EURIBOR"
|
||||||
};
|
};
|
||||||
|
|
||||||
let calendarLookup = {
|
var calendarLookup = {
|
||||||
"GBP": "London",
|
"GBP": "London",
|
||||||
"USD": "NewYork",
|
"USD": "NewYork",
|
||||||
"EUR": "London"
|
"EUR": "London"
|
||||||
};
|
};
|
||||||
|
|
||||||
let Deal = function(dealViewModel) {
|
var Deal = function Deal(dealViewModel) {
|
||||||
let now = new Date();
|
var now = new Date();
|
||||||
let tradeId = `T${now.getUTCFullYear()}-${now.getUTCMonth()}-${now.getUTCDate()}.${now.getUTCHours()}:${now.getUTCMinutes()}:${now.getUTCSeconds()}:${now.getUTCMilliseconds()}`;
|
var tradeId = "T" + now.getUTCFullYear() + "-" + now.getUTCMonth() + "-" + now.getUTCDate() + "." + now.getUTCHours() + ":" + now.getUTCMinutes() + ":" + now.getUTCSeconds() + ":" + now.getUTCMilliseconds();
|
||||||
|
|
||||||
this.toJson = () => {
|
this.toJson = function () {
|
||||||
let fixedLeg = {};
|
var fixedLeg = {};
|
||||||
let floatingLeg = {};
|
var floatingLeg = {};
|
||||||
let common = {};
|
var common = {};
|
||||||
_.assign(fixedLeg, dealViewModel.fixedLeg);
|
_.assign(fixedLeg, dealViewModel.fixedLeg);
|
||||||
_.assign(floatingLeg, dealViewModel.floatingLeg);
|
_.assign(floatingLeg, dealViewModel.floatingLeg);
|
||||||
_.assign(common, dealViewModel.common);
|
_.assign(common, dealViewModel.common);
|
||||||
@ -65,7 +61,7 @@ define(['viewmodel/FixedRate'], (fixedRateViewModel) => {
|
|||||||
delete common.effectiveDate;
|
delete common.effectiveDate;
|
||||||
delete common.terminationDate;
|
delete common.terminationDate;
|
||||||
|
|
||||||
let json = {
|
var json = {
|
||||||
fixedLeg: fixedLeg,
|
fixedLeg: fixedLeg,
|
||||||
floatingLeg: floatingLeg,
|
floatingLeg: floatingLeg,
|
||||||
calculation: calculationModel,
|
calculation: calculationModel,
|
||||||
@ -77,4 +73,4 @@ define(['viewmodel/FixedRate'], (fixedRateViewModel) => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
return Deal;
|
return Deal;
|
||||||
});
|
});
|
@ -2,23 +2,17 @@
|
|||||||
|
|
||||||
function formatDateForNode(date) {
|
function formatDateForNode(date) {
|
||||||
// Produces yyyy-dd-mm. JS is missing proper date formatting libs
|
// Produces yyyy-dd-mm. JS is missing proper date formatting libs
|
||||||
let day = ("0" + (date.getDate())).slice(-2);
|
var day = ("0" + date.getDate()).slice(-2);
|
||||||
let month = ("0" + (date.getMonth() + 1)).slice(-2);
|
var month = ("0" + (date.getMonth() + 1)).slice(-2);
|
||||||
return `${date.getFullYear()}-${month}-${day}`;
|
return date.getFullYear() + "-" + month + "-" + day;
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatDateForAngular(dateStr) {
|
function formatDateForAngular(dateStr) {
|
||||||
let parts = dateStr.split("-");
|
var parts = dateStr.split("-");
|
||||||
return new Date(parts[0], parts[1], parts[2]);
|
return new Date(parts[0], parts[1], parts[2]);
|
||||||
}
|
}
|
||||||
|
|
||||||
define([
|
define(['angular', 'angularRoute', 'jquery', 'fcsaNumber', 'semantic'], function (angular, angularRoute, $, fcsaNumber, semantic) {
|
||||||
'angular',
|
|
||||||
'angularRoute',
|
|
||||||
'jquery',
|
|
||||||
'fcsaNumber',
|
|
||||||
'semantic'
|
|
||||||
], (angular, angularRoute, $, fcsaNumber, semantic) => {
|
|
||||||
angular.module('irsViewer', ['ngRoute', 'fcsa-number']);
|
angular.module('irsViewer', ['ngRoute', 'fcsa-number']);
|
||||||
requirejs(['routes']);
|
requirejs(['routes']);
|
||||||
});
|
});
|
@ -1,34 +1,27 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
define([
|
define(['angular', 'maskedInput', 'utils/semantic', 'utils/dayCountBasisLookup', 'services/NodeApi', 'Deal', 'services/HttpErrorHandler'], function (angular, maskedInput, semantic, dayCountBasisLookup, nodeApi, Deal) {
|
||||||
'angular',
|
|
||||||
'maskedInput',
|
|
||||||
'utils/semantic',
|
|
||||||
'utils/dayCountBasisLookup',
|
|
||||||
'services/NodeApi',
|
|
||||||
'Deal',
|
|
||||||
'services/HttpErrorHandler'
|
|
||||||
], (angular, maskedInput, semantic, dayCountBasisLookup, nodeApi, Deal) => {
|
|
||||||
angular.module('irsViewer').controller('CreateDealController', function CreateDealController($http, $scope, $location, nodeService, httpErrorHandler) {
|
angular.module('irsViewer').controller('CreateDealController', function CreateDealController($http, $scope, $location, nodeService, httpErrorHandler) {
|
||||||
semantic.init($scope, nodeService.isLoading);
|
semantic.init($scope, nodeService.isLoading);
|
||||||
let handleHttpFail = httpErrorHandler.createErrorHandler($scope);
|
var handleHttpFail = httpErrorHandler.createErrorHandler($scope);
|
||||||
|
|
||||||
$scope.dayCountBasisLookup = dayCountBasisLookup;
|
$scope.dayCountBasisLookup = dayCountBasisLookup;
|
||||||
$scope.deal = nodeService.newDeal();
|
$scope.deal = nodeService.newDeal();
|
||||||
$scope.createDeal = () => {
|
$scope.createDeal = function () {
|
||||||
nodeService.createDeal(new Deal($scope.deal))
|
nodeService.createDeal(new Deal($scope.deal)).then(function (tradeId) {
|
||||||
.then((tradeId) => $location.path('#/deal/' + tradeId), (resp) => {
|
return $location.path('#/deal/' + tradeId);
|
||||||
|
}, function (resp) {
|
||||||
$scope.formError = resp.data;
|
$scope.formError = resp.data;
|
||||||
}, handleHttpFail);
|
}, handleHttpFail);
|
||||||
};
|
};
|
||||||
$('input.percent').mask("9.999999", {placeholder: "", autoclear: false});
|
$('input.percent').mask("9.999999", { placeholder: "", autoclear: false });
|
||||||
$('#swapirscolumns').click(() => {
|
$('#swapirscolumns').click(function () {
|
||||||
let first = $('#irscolumns .irscolumn:eq( 0 )');
|
var first = $('#irscolumns .irscolumn:eq( 0 )');
|
||||||
let last = $('#irscolumns .irscolumn:eq( 1 )');
|
var last = $('#irscolumns .irscolumn:eq( 1 )');
|
||||||
first.before(last);
|
first.before(last);
|
||||||
|
|
||||||
let swapPayers = () => {
|
var swapPayers = function swapPayers() {
|
||||||
let tmp = $scope.deal.floatingLeg.floatingRatePayer;
|
var tmp = $scope.deal.floatingLeg.floatingRatePayer;
|
||||||
$scope.deal.floatingLeg.floatingRatePayer = $scope.deal.fixedLeg.fixedRatePayer;
|
$scope.deal.floatingLeg.floatingRatePayer = $scope.deal.fixedLeg.fixedRatePayer;
|
||||||
$scope.deal.fixedLeg.fixedRatePayer = tmp;
|
$scope.deal.fixedLeg.fixedRatePayer = tmp;
|
||||||
};
|
};
|
||||||
|
@ -1,19 +1,21 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
define(['angular', 'utils/semantic', 'services/NodeApi', 'services/HttpErrorHandler'], (angular, semantic) => {
|
define(['angular', 'utils/semantic', 'services/NodeApi', 'services/HttpErrorHandler'], function (angular, semantic) {
|
||||||
angular.module('irsViewer').controller('DealController', function DealController($http, $scope, $routeParams, nodeService, httpErrorHandler) {
|
angular.module('irsViewer').controller('DealController', function DealController($http, $scope, $routeParams, nodeService, httpErrorHandler) {
|
||||||
semantic.init($scope, nodeService.isLoading);
|
semantic.init($scope, nodeService.isLoading);
|
||||||
let handleHttpFail = httpErrorHandler.createErrorHandler($scope);
|
var handleHttpFail = httpErrorHandler.createErrorHandler($scope);
|
||||||
let decorateDeal = (deal) => {
|
var decorateDeal = function decorateDeal(deal) {
|
||||||
let paymentSchedule = deal.calculation.floatingLegPaymentSchedule;
|
var paymentSchedule = deal.calculation.floatingLegPaymentSchedule;
|
||||||
Object.keys(paymentSchedule).map((key, index) => {
|
Object.keys(paymentSchedule).map(function (key, index) {
|
||||||
const sign = paymentSchedule[key].rate.positive ? 1 : -1;
|
var sign = paymentSchedule[key].rate.positive ? 1 : -1;
|
||||||
paymentSchedule[key].ratePercent = paymentSchedule[key].rate.ratioUnit ? (paymentSchedule[key].rate.ratioUnit.value * 100 * sign).toFixed(5) + "%": "";
|
paymentSchedule[key].ratePercent = paymentSchedule[key].rate.ratioUnit ? (paymentSchedule[key].rate.ratioUnit.value * 100 * sign).toFixed(5) + "%" : "";
|
||||||
});
|
});
|
||||||
|
|
||||||
return deal;
|
return deal;
|
||||||
};
|
};
|
||||||
|
|
||||||
nodeService.getDeal($routeParams.dealId).then((deal) => $scope.deal = decorateDeal(deal), handleHttpFail);
|
nodeService.getDeal($routeParams.dealId).then(function (deal) {
|
||||||
|
return $scope.deal = decorateDeal(deal);
|
||||||
|
}, handleHttpFail);
|
||||||
});
|
});
|
||||||
});
|
});
|
@ -1,23 +1,23 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
define(['angular', 'utils/semantic', 'services/NodeApi', 'services/HttpErrorHandler'], (angular, semantic) => {
|
define(['angular', 'utils/semantic', 'services/NodeApi', 'services/HttpErrorHandler'], function (angular, semantic) {
|
||||||
angular.module('irsViewer').controller('HomeController', function HomeController($http, $scope, nodeService, httpErrorHandler) {
|
angular.module('irsViewer').controller('HomeController', function HomeController($http, $scope, nodeService, httpErrorHandler) {
|
||||||
semantic.addLoadingModal($scope, nodeService.isLoading);
|
semantic.addLoadingModal($scope, nodeService.isLoading);
|
||||||
|
|
||||||
let handleHttpFail = httpErrorHandler.createErrorHandler($scope);
|
var handleHttpFail = httpErrorHandler.createErrorHandler($scope);
|
||||||
|
|
||||||
$scope.infoMsg = "";
|
$scope.infoMsg = "";
|
||||||
$scope.errorText = "";
|
$scope.errorText = "";
|
||||||
$scope.date = { "year": "...", "month": "...", "day": "..." };
|
$scope.date = { "year": "...", "month": "...", "day": "..." };
|
||||||
$scope.updateDate = (type) => {
|
$scope.updateDate = function (type) {
|
||||||
nodeService.updateDate(type).then((newDate) => {
|
nodeService.updateDate(type).then(function (newDate) {
|
||||||
$scope.date = newDate
|
$scope.date = newDate;
|
||||||
}, handleHttpFail);
|
}, handleHttpFail);
|
||||||
};
|
};
|
||||||
/* Extract the common name from an X500 name */
|
/* Extract the common name from an X500 name */
|
||||||
$scope.renderX500Name = (x500Name) => {
|
$scope.renderX500Name = function (x500Name) {
|
||||||
var name = x500Name
|
var name = x500Name;
|
||||||
x500Name.split(',').forEach(function(element) {
|
x500Name.split(',').forEach(function (element) {
|
||||||
var keyValue = element.split('=');
|
var keyValue = element.split('=');
|
||||||
if (keyValue[0].toUpperCase() == 'CN') {
|
if (keyValue[0].toUpperCase() == 'CN') {
|
||||||
name = keyValue[1];
|
name = keyValue[1];
|
||||||
@ -26,7 +26,11 @@ define(['angular', 'utils/semantic', 'services/NodeApi', 'services/HttpErrorHand
|
|||||||
return name;
|
return name;
|
||||||
};
|
};
|
||||||
|
|
||||||
nodeService.getDate().then((date) => $scope.date = date, handleHttpFail);
|
nodeService.getDate().then(function (date) {
|
||||||
nodeService.getDeals().then((deals) => $scope.deals = deals, handleHttpFail);
|
return $scope.date = date;
|
||||||
|
}, handleHttpFail);
|
||||||
|
nodeService.getDeals().then(function (deals) {
|
||||||
|
return $scope.deals = deals;
|
||||||
|
}, handleHttpFail);
|
||||||
});
|
});
|
||||||
});
|
});
|
@ -11,18 +11,14 @@ require.config({
|
|||||||
maskedInput: 'bower_components/jquery.maskedinput/jquery.maskedinput'
|
maskedInput: 'bower_components/jquery.maskedinput/jquery.maskedinput'
|
||||||
},
|
},
|
||||||
shim: {
|
shim: {
|
||||||
'angular' : {'exports' : 'angular'},
|
'angular': { 'exports': 'angular' },
|
||||||
'angularRoute': ['angular'],
|
'angularRoute': ['angular'],
|
||||||
'fcsaNumber': ['angular'],
|
'fcsaNumber': ['angular'],
|
||||||
'semantic': ['jquery'],
|
'semantic': ['jquery'],
|
||||||
'maskedInput': ['jquery']
|
'maskedInput': ['jquery']
|
||||||
},
|
},
|
||||||
priority: [
|
priority: ["angular"],
|
||||||
"angular"
|
baseUrl: 'js'
|
||||||
],
|
|
||||||
baseUrl: 'js',
|
|
||||||
});
|
});
|
||||||
|
|
||||||
require(['angular', 'app'], (angular, app) => {
|
require(['angular', 'app'], function (angular, app) {});
|
||||||
|
|
||||||
});
|
|
@ -1,32 +1,22 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
define([
|
define(['angular', 'controllers/Home', 'controllers/Deal', 'controllers/CreateDeal'], function (angular) {
|
||||||
'angular',
|
angular.module('irsViewer').config(function ($routeProvider, $locationProvider) {
|
||||||
'controllers/Home',
|
$routeProvider.when('/', {
|
||||||
'controllers/Deal',
|
controller: 'HomeController',
|
||||||
'controllers/CreateDeal'
|
templateUrl: 'view/home.html'
|
||||||
], (angular) => {
|
}).when('/deal/:dealId', {
|
||||||
angular.module('irsViewer').config(($routeProvider, $locationProvider) => {
|
controller: 'DealController',
|
||||||
$routeProvider
|
templateUrl: 'view/deal.html'
|
||||||
.when('/', {
|
}).when('/party/:partyId', {
|
||||||
controller: 'HomeController',
|
templateUrl: 'view/party.html'
|
||||||
templateUrl: 'view/home.html'
|
}).when('/create-deal', {
|
||||||
})
|
controller: 'CreateDealController',
|
||||||
.when('/deal/:dealId', {
|
templateUrl: 'view/create-deal.html'
|
||||||
controller: 'DealController',
|
}).otherwise({ redirectTo: '/' });
|
||||||
templateUrl: 'view/deal.html'
|
|
||||||
})
|
|
||||||
.when('/party/:partyId', {
|
|
||||||
templateUrl: 'view/party.html'
|
|
||||||
})
|
|
||||||
.when('/create-deal', {
|
|
||||||
controller: 'CreateDealController',
|
|
||||||
templateUrl: 'view/create-deal.html'
|
|
||||||
})
|
|
||||||
.otherwise({redirectTo: '/'});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
angular.element().ready(function() {
|
angular.element().ready(function () {
|
||||||
// bootstrap the app manually
|
// bootstrap the app manually
|
||||||
angular.bootstrap(document, ['irsViewer']);
|
angular.bootstrap(document, ['irsViewer']);
|
||||||
});
|
});
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
define(['angular', 'lodash', 'viewmodel/Deal'], (angular, _) => {
|
define(['angular', 'lodash', 'viewmodel/Deal'], function (angular, _) {
|
||||||
angular.module('irsViewer').factory('httpErrorHandler', () => {
|
angular.module('irsViewer').factory('httpErrorHandler', function () {
|
||||||
return {
|
return {
|
||||||
createErrorHandler: (scope) => {
|
createErrorHandler: function createErrorHandler(scope) {
|
||||||
return (resp) => {
|
return function (resp) {
|
||||||
if(resp.status == -1) {
|
if (resp.status == -1) {
|
||||||
scope.httpError = "Could not connect to node web server";
|
scope.httpError = "Could not connect to node web server";
|
||||||
} else {
|
} else {
|
||||||
scope.httpError = resp.data;
|
scope.httpError = resp.data;
|
||||||
|
@ -1,44 +1,48 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
define(['angular', 'lodash', 'viewmodel/Deal'], (angular, _, dealViewModel) => {
|
define(['angular', 'lodash', 'viewmodel/Deal'], function (angular, _, dealViewModel) {
|
||||||
angular.module('irsViewer').factory('nodeService', ($http) => {
|
angular.module('irsViewer').factory('nodeService', function ($http) {
|
||||||
return new (function() {
|
return new function () {
|
||||||
let date = new Date(2016, 0, 1, 0, 0, 0);
|
var _this = this;
|
||||||
let curLoading = {};
|
|
||||||
let serverAddr = ''; // Leave empty to target the same host this page is served from
|
|
||||||
|
|
||||||
let load = (type, promise) => {
|
var date = new Date(2016, 0, 1, 0, 0, 0);
|
||||||
|
var curLoading = {};
|
||||||
|
var serverAddr = ''; // Leave empty to target the same host this page is served from
|
||||||
|
|
||||||
|
var load = function load(type, promise) {
|
||||||
curLoading[type] = true;
|
curLoading[type] = true;
|
||||||
return promise.then((arg) => {
|
return promise.then(function (arg) {
|
||||||
curLoading[type] = false;
|
curLoading[type] = false;
|
||||||
return arg;
|
return arg;
|
||||||
}, (arg) => {
|
}, function (arg) {
|
||||||
curLoading[type] = false;
|
curLoading[type] = false;
|
||||||
throw arg;
|
throw arg;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
let endpoint = (target) => serverAddr + target;
|
var endpoint = function endpoint(target) {
|
||||||
|
return serverAddr + target;
|
||||||
|
};
|
||||||
|
|
||||||
let changeDateOnNode = (newDate) => {
|
var changeDateOnNode = function changeDateOnNode(newDate) {
|
||||||
const dateStr = formatDateForNode(newDate);
|
var dateStr = formatDateForNode(newDate);
|
||||||
return load('date', $http.put(endpoint('/api/irs/demodate'), "\"" + dateStr + "\"")).then((resp) => {
|
return load('date', $http.put(endpoint('/api/irs/demodate'), "\"" + dateStr + "\"")).then(function (resp) {
|
||||||
date = newDate;
|
date = newDate;
|
||||||
return this.getDateModel(date);
|
return _this.getDateModel(date);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
this.getDate = () => {
|
this.getDate = function () {
|
||||||
return load('date', $http.get(endpoint('/api/irs/demodate'))).then((resp) => {
|
return load('date', $http.get(endpoint('/api/irs/demodate'))).then(function (resp) {
|
||||||
const dateParts = resp.data;
|
var dateParts = resp.data;
|
||||||
date = new Date(dateParts[0], dateParts[1] - 1, dateParts[2]); // JS uses 0 based months
|
date = new Date(dateParts[0], dateParts[1] - 1, dateParts[2]); // JS uses 0 based months
|
||||||
return this.getDateModel(date);
|
return _this.getDateModel(date);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
this.updateDate = (type) => {
|
this.updateDate = function (type) {
|
||||||
let newDate = date;
|
var newDate = date;
|
||||||
switch(type) {
|
switch (type) {
|
||||||
case "year":
|
case "year":
|
||||||
newDate.setFullYear(date.getFullYear() + 1);
|
newDate.setFullYear(date.getFullYear() + 1);
|
||||||
break;
|
break;
|
||||||
@ -55,22 +59,22 @@ define(['angular', 'lodash', 'viewmodel/Deal'], (angular, _, dealViewModel) => {
|
|||||||
return changeDateOnNode(newDate);
|
return changeDateOnNode(newDate);
|
||||||
};
|
};
|
||||||
|
|
||||||
this.getDeals = () => {
|
this.getDeals = function () {
|
||||||
return load('deals', $http.get(endpoint('/api/irs/deals'))).then((resp) => {
|
return load('deals', $http.get(endpoint('/api/irs/deals'))).then(function (resp) {
|
||||||
return resp.data.reverse();
|
return resp.data.reverse();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
this.getDeal = (dealId) => {
|
this.getDeal = function (dealId) {
|
||||||
return load('deal' + dealId, $http.get(endpoint('/api/irs/deals/' + dealId))).then((resp) => {
|
return load('deal' + dealId, $http.get(endpoint('/api/irs/deals/' + dealId))).then(function (resp) {
|
||||||
// Do some data modification to simplify the model
|
// Do some data modification to simplify the model
|
||||||
let deal = resp.data;
|
var deal = resp.data;
|
||||||
deal.fixedLeg.fixedRate.value = (deal.fixedLeg.fixedRate.ratioUnit.value * 100).toString().slice(0, 6);
|
deal.fixedLeg.fixedRate.value = (deal.fixedLeg.fixedRate.ratioUnit.value * 100).toString().slice(0, 6);
|
||||||
return deal;
|
return deal;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
this.getDateModel = (date) => {
|
this.getDateModel = function (date) {
|
||||||
return {
|
return {
|
||||||
"year": date.getFullYear(),
|
"year": date.getFullYear(),
|
||||||
"month": date.getMonth() + 1, // JS uses 0 based months
|
"month": date.getMonth() + 1, // JS uses 0 based months
|
||||||
@ -78,24 +82,23 @@ define(['angular', 'lodash', 'viewmodel/Deal'], (angular, _, dealViewModel) => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
this.isLoading = () => {
|
this.isLoading = function () {
|
||||||
return _.reduce(Object.keys(curLoading), (last, key) => {
|
return _.reduce(Object.keys(curLoading), function (last, key) {
|
||||||
return (last || curLoading[key]);
|
return last || curLoading[key];
|
||||||
}, false);
|
}, false);
|
||||||
};
|
};
|
||||||
|
|
||||||
this.newDeal = () => {
|
this.newDeal = function () {
|
||||||
return dealViewModel;
|
return dealViewModel;
|
||||||
};
|
};
|
||||||
|
|
||||||
this.createDeal = (deal) => {
|
this.createDeal = function (deal) {
|
||||||
return load('create-deal', $http.post(endpoint('/api/irs/deals'), deal.toJson()))
|
return load('create-deal', $http.post(endpoint('/api/irs/deals'), deal.toJson())).then(function (resp) {
|
||||||
.then((resp) => {
|
|
||||||
return deal.tradeId;
|
return deal.tradeId;
|
||||||
}, (resp) => {
|
}, function (resp) {
|
||||||
throw resp;
|
throw resp;
|
||||||
})
|
});
|
||||||
}
|
};
|
||||||
});
|
}();
|
||||||
});
|
});
|
||||||
});
|
});
|
@ -1,6 +1,6 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
define([], () => {
|
define([], function () {
|
||||||
return {
|
return {
|
||||||
"30/360": {
|
"30/360": {
|
||||||
"day": "D30",
|
"day": "D30",
|
||||||
@ -29,6 +29,6 @@ define([], () => {
|
|||||||
"ACT/ACT ICMA": {
|
"ACT/ACT ICMA": {
|
||||||
"day": "DActual",
|
"day": "DActual",
|
||||||
"year": "YICMA"
|
"year": "YICMA"
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
@ -1,17 +1,17 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
define(['jquery', 'semantic'], ($, semantic) => {
|
define(['jquery', 'semantic'], function ($, semantic) {
|
||||||
return {
|
return {
|
||||||
init: function($scope, loadingFunc) {
|
init: function init($scope, loadingFunc) {
|
||||||
$('.ui.accordion').accordion();
|
$('.ui.accordion').accordion();
|
||||||
$('.ui.dropdown').dropdown();
|
$('.ui.dropdown').dropdown();
|
||||||
$('.ui.sticky').sticky();
|
$('.ui.sticky').sticky();
|
||||||
|
|
||||||
this.addLoadingModal($scope, loadingFunc);
|
this.addLoadingModal($scope, loadingFunc);
|
||||||
},
|
},
|
||||||
addLoadingModal: ($scope, loadingFunc) => {
|
addLoadingModal: function addLoadingModal($scope, loadingFunc) {
|
||||||
$scope.$watch(loadingFunc, (newVal) => {
|
$scope.$watch(loadingFunc, function (newVal) {
|
||||||
if(newVal === true) {
|
if (newVal === true) {
|
||||||
$('#loading').modal('setting', 'closable', false).modal('show');
|
$('#loading').modal('setting', 'closable', false).modal('show');
|
||||||
} else {
|
} else {
|
||||||
$('#loading').modal('hide');
|
$('#loading').modal('hide');
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
define([], () => {
|
define([], function () {
|
||||||
return {
|
return {
|
||||||
baseCurrency: "USD",
|
baseCurrency: "USD",
|
||||||
effectiveDate: new Date(2016, 2, 11),
|
effectiveDate: new Date(2016, 2, 11),
|
||||||
@ -31,7 +31,7 @@ define([], () => {
|
|||||||
},
|
},
|
||||||
addressForTransfers: "",
|
addressForTransfers: "",
|
||||||
exposure: {},
|
exposure: {},
|
||||||
localBusinessDay: [ "London" , "NewYork" ],
|
localBusinessDay: ["London", "NewYork"],
|
||||||
dailyInterestAmount: "(CashAmount * InterestRate ) / (fixedLeg.notional.token.currencyCode.equals('GBP')) ? 365 : 360",
|
dailyInterestAmount: "(CashAmount * InterestRate ) / (fixedLeg.notional.token.currencyCode.equals('GBP')) ? 365 : 360",
|
||||||
hashLegalDocs: "put hash here"
|
hashLegalDocs: "put hash here"
|
||||||
};
|
};
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
define(['viewmodel/FixedLeg', 'viewmodel/FloatingLeg', 'viewmodel/Common'], (fixedLeg, floatingLeg, common) => {
|
define(['viewmodel/FixedLeg', 'viewmodel/FloatingLeg', 'viewmodel/Common'], function (fixedLeg, floatingLeg, common) {
|
||||||
return {
|
return {
|
||||||
fixedLeg: fixedLeg,
|
fixedLeg: fixedLeg,
|
||||||
floatingLeg: floatingLeg,
|
floatingLeg: floatingLeg,
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
define(['utils/dayCountBasisLookup'], (dayCountBasisLookup) => {
|
define(['utils/dayCountBasisLookup'], function (dayCountBasisLookup) {
|
||||||
return {
|
return {
|
||||||
fixedRatePayer: "O=Bank A,L=London,C=GB",
|
fixedRatePayer: "O=Bank A,L=London,C=GB",
|
||||||
notional: 2500000000,
|
notional: 2500000000,
|
||||||
@ -15,4 +15,4 @@ define(['utils/dayCountBasisLookup'], (dayCountBasisLookup) => {
|
|||||||
paymentDelay: "0",
|
paymentDelay: "0",
|
||||||
interestPeriodAdjustment: "Adjusted"
|
interestPeriodAdjustment: "Adjusted"
|
||||||
};
|
};
|
||||||
});
|
});
|
@ -1,6 +1,6 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
define(['utils/dayCountBasisLookup'], (dayCountBasisLookup) => {
|
define(['utils/dayCountBasisLookup'], function (dayCountBasisLookup) {
|
||||||
return {
|
return {
|
||||||
floatingRatePayer: "O=Bank B,L=New York,C=US",
|
floatingRatePayer: "O=Bank B,L=New York,C=US",
|
||||||
notional: 2500000000,
|
notional: 2500000000,
|
||||||
@ -20,7 +20,7 @@ define(['utils/dayCountBasisLookup'], (dayCountBasisLookup) => {
|
|||||||
fixingsPerPayment: "Quarterly",
|
fixingsPerPayment: "Quarterly",
|
||||||
indexSource: "Rates Service Provider",
|
indexSource: "Rates Service Provider",
|
||||||
indexTenor: {
|
indexTenor: {
|
||||||
name: "3M"
|
name: "3M"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
@ -34,7 +34,7 @@
|
|||||||
<i class="browser icon"></i>
|
<i class="browser icon"></i>
|
||||||
Recent deals
|
Recent deals
|
||||||
</h3>
|
</h3>
|
||||||
<table class="ui striped table">
|
<table class="ui striped table" id="deal-list">
|
||||||
<thead>
|
<thead>
|
||||||
<tr class="center aligned">
|
<tr class="center aligned">
|
||||||
<th>Trade Id</th>
|
<th>Trade Id</th>
|
||||||
@ -45,7 +45,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr class="center aligned" ng-repeat="deal in deals">
|
<tr class="center aligned" ng-repeat="deal in deals" id="deal-{{deal.ref}}">
|
||||||
<td><a href="#/deal/{{deal.ref}}">{{deal.ref}}</a></td>
|
<td><a href="#/deal/{{deal.ref}}">{{deal.ref}}</a></td>
|
||||||
<td class="single line">{{renderX500Name(deal.fixedLeg.fixedRatePayer)}}</td>
|
<td class="single line">{{renderX500Name(deal.fixedLeg.fixedRatePayer)}}</td>
|
||||||
<td class="single line">{{deal.fixedLeg.notional}}</td>
|
<td class="single line">{{deal.fixedLeg.notional}}</td>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user