mirror of
https://github.com/corda/corda.git
synced 2024-12-30 09:48:59 +00:00
* System test for IRS Demo utilizing docker, docker-compose and PhantomJS to automate full-stack testing
(cherry picked from commit a9856b9
)
This commit is contained in:
parent
133bff90a6
commit
0b76c5d3f0
@ -65,6 +65,10 @@ buildscript {
|
||||
ext.jsr305_version = constants.getProperty("jsr305Version")
|
||||
ext.shiro_version = '1.4.0'
|
||||
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:
|
||||
ext.java8_minUpdateVersion = '131'
|
||||
|
@ -1,4 +1,4 @@
|
||||
gradlePluginsVersion=3.0.4
|
||||
gradlePluginsVersion=3.0.5
|
||||
kotlinVersion=1.1.60
|
||||
platformVersion=2
|
||||
guavaVersion=21.0
|
||||
@ -6,3 +6,4 @@ bouncycastleVersion=1.57
|
||||
typesafeConfigVersion=1.3.1
|
||||
jsr305Version=3.0.2
|
||||
artifactoryPluginVersion=4.4.18
|
||||
snakeYamlVersion=1.19
|
@ -14,6 +14,7 @@ buildscript {
|
||||
jsr305_version = constants.getProperty("jsr305Version")
|
||||
kotlin_version = constants.getProperty("kotlinVersion")
|
||||
artifactory_plugin_version = constants.getProperty('artifactoryPluginVersion')
|
||||
snake_yaml_version = constants.getProperty('snakeYamlVersion')
|
||||
}
|
||||
|
||||
repositories {
|
||||
|
@ -20,7 +20,8 @@ public class CordformNode implements NodeDefinition {
|
||||
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;
|
||||
|
||||
@ -28,6 +29,20 @@ public class CordformNode implements NodeDefinition {
|
||||
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.
|
||||
* The recommended current structure is:
|
||||
@ -79,6 +94,7 @@ public class CordformNode implements NodeDefinition {
|
||||
*/
|
||||
public void p2pPort(int p2pPort) {
|
||||
p2pAddress(DEFAULT_HOST + ':' + p2pPort);
|
||||
this.p2pPort = p2pPort;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -110,6 +126,7 @@ public class CordformNode implements NodeDefinition {
|
||||
@Deprecated
|
||||
public void rpcPort(int rpcPort) {
|
||||
rpcAddress(DEFAULT_HOST + ':' + rpcPort);
|
||||
this.rpcPort = rpcPort;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -8,6 +8,17 @@ public final class RpcSettings {
|
||||
|
||||
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.
|
||||
*/
|
||||
@ -15,6 +26,14 @@ public final class RpcSettings {
|
||||
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).
|
||||
*/
|
||||
@ -22,6 +41,11 @@ public final class RpcSettings {
|
||||
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.
|
||||
*/
|
||||
@ -43,7 +67,7 @@ public final class RpcSettings {
|
||||
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()) {
|
||||
return config;
|
||||
}
|
||||
|
@ -43,7 +43,7 @@ public final class SslOptions {
|
||||
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()) {
|
||||
return config;
|
||||
}
|
||||
|
@ -40,6 +40,8 @@ dependencies {
|
||||
noderunner "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version"
|
||||
|
||||
compile project(':cordform-common')
|
||||
// Docker-compose file generation
|
||||
compile "org.yaml:snakeyaml:$snake_yaml_version"
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
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.
|
||||
@ -20,59 +14,12 @@ import java.util.jar.JarInputStream
|
||||
* See documentation for examples.
|
||||
*/
|
||||
@Suppress("unused")
|
||||
open class Cordform : DefaultTask() {
|
||||
open class Cordform : Baseform() {
|
||||
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
|
||||
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.
|
||||
*/
|
||||
@ -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.
|
||||
*/
|
||||
@ -139,80 +63,4 @@ open class Cordform : DefaultTask() {
|
||||
bootstrapNetwork()
|
||||
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.ConfigRenderOptions
|
||||
import com.typesafe.config.ConfigValueFactory
|
||||
import com.typesafe.config.ConfigObject
|
||||
import groovy.lang.Closure
|
||||
import net.corda.cordform.CordformNode
|
||||
import net.corda.cordform.RpcSettings
|
||||
import org.gradle.api.Project
|
||||
import java.io.File
|
||||
import java.nio.charset.StandardCharsets
|
||||
@ -34,6 +36,11 @@ class Node(private val project: Project) : CordformNode() {
|
||||
private set
|
||||
internal lateinit var rootDir: File
|
||||
private set
|
||||
internal lateinit var containerName: String
|
||||
private set
|
||||
|
||||
internal var rpcSettings: RpcSettings = RpcSettings()
|
||||
private set
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
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)
|
||||
}
|
||||
|
||||
@ -81,6 +88,19 @@ class Node(private val project: Project) : CordformNode() {
|
||||
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) {
|
||||
if (name == null) {
|
||||
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.
|
||||
val organizationName = name.trim().split(",").firstOrNull { it.startsWith("O=") }?.substringAfter("=")
|
||||
val dirName = organizationName ?: name
|
||||
containerName = dirName.replace("\\s+".toRegex(), "-").toLowerCase()
|
||||
this.rootDir = rootDir.toFile()
|
||||
nodeDir = File(this.rootDir, dirName)
|
||||
nodeDir = File(this.rootDir, dirName.replace("\\s", ""))
|
||||
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
|
||||
.defaults()
|
||||
.setOriginComments(false)
|
||||
.setComments(false)
|
||||
.setFormatted(true)
|
||||
.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.
|
||||
val tmpDir = File(project.buildDir, "tmp")
|
||||
Files.createDirectories(tmpDir.toPath())
|
||||
@ -178,7 +199,27 @@ class Node(private val project: Project) : CordformNode() {
|
||||
*/
|
||||
internal fun installConfig() {
|
||||
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)
|
||||
project.copy {
|
||||
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
|
||||
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.
|
||||
|
||||
## 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
|
||||
ext['artemis.version'] = "$artemis_version"
|
||||
ext['hibernate.version'] = "$hibernate_version"
|
||||
ext['selenium.version'] = "$selenium_version"
|
||||
|
||||
apply plugin: 'java'
|
||||
apply plugin: 'kotlin'
|
||||
@ -33,12 +34,29 @@ sourceSets {
|
||||
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 {
|
||||
integrationTestCompile.extendsFrom testCompile
|
||||
integrationTestRuntime.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 {
|
||||
@ -55,6 +73,9 @@ dependencies {
|
||||
testCompile "org.assertj:assertj-core:${assertj_version}"
|
||||
|
||||
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 {
|
||||
@ -66,6 +87,22 @@ task integrationTest(type: Test, dependsOn: []) {
|
||||
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 {
|
||||
module {
|
||||
downloadJavadoc = true // defaults to false
|
||||
|
@ -43,63 +43,96 @@ dependencies {
|
||||
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']) {
|
||||
|
||||
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 {
|
||||
name "O=Notary Service,L=Zurich,C=CH"
|
||||
notary = [validating : true]
|
||||
p2pPort 10002
|
||||
rpcSettings {
|
||||
address "localhost:10003"
|
||||
adminAddress "localhost:10023"
|
||||
port 10003
|
||||
adminPort 10023
|
||||
}
|
||||
cordapps = ["${project.group}:finance:$corda_release_version"]
|
||||
rpcUsers = ext.rpcUsers
|
||||
cordapps = ["${project(":finance").group}:finance:$corda_release_version"]
|
||||
rpcUsers = rpcUsersList
|
||||
useTestClock true
|
||||
}
|
||||
node {
|
||||
name "O=Bank A,L=London,C=GB"
|
||||
p2pPort 10005
|
||||
rpcSettings {
|
||||
address "localhost:10006"
|
||||
adminAddress "localhost:10026"
|
||||
port 10006
|
||||
adminPort 10026
|
||||
}
|
||||
cordapps = ["${project.group}:finance:$corda_release_version"]
|
||||
rpcUsers = ext.rpcUsers
|
||||
cordapps = ["${project(":finance").group}:finance:$corda_release_version"]
|
||||
rpcUsers = rpcUsersList
|
||||
useTestClock true
|
||||
}
|
||||
node {
|
||||
name "O=Bank B,L=New York,C=US"
|
||||
p2pPort 10008
|
||||
rpcSettings {
|
||||
address "localhost:10009"
|
||||
adminAddress "localhost:10029"
|
||||
port 10009
|
||||
adminPort 10029
|
||||
}
|
||||
cordapps = ["${project.group}:finance:$corda_release_version"]
|
||||
rpcUsers = ext.rpcUsers
|
||||
rpcUsers = rpcUsersList
|
||||
useTestClock true
|
||||
}
|
||||
node {
|
||||
name "O=Regulator,L=Moscow,C=RU"
|
||||
p2pPort 10010
|
||||
rpcPort 10011
|
||||
rpcSettings {
|
||||
port 10009
|
||||
adminPort 10029
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
|
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 {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
|
||||
classpath("org.jetbrains.kotlin:kotlin-allopen:${kotlin_version}")
|
||||
}
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
|
||||
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 {
|
||||
id 'com.craigburke.client-dependencies' version '1.4.0'
|
||||
id 'com.craigburke.client-dependencies' version '1.4.0'
|
||||
}
|
||||
|
||||
group = "${parent.group}.irs-demo"
|
||||
|
||||
clientDependencies {
|
||||
registry 'realBower', type:'bower', url:'https://registry.bower.io'
|
||||
realBower {
|
||||
@ -69,6 +80,8 @@ jar {
|
||||
dependsOn clientInstall
|
||||
}
|
||||
|
||||
def docker_dir = file("$project.buildDir/docker")
|
||||
|
||||
task deployWebapps(type: Copy, dependsOn: ['jar', 'bootRepackage']) {
|
||||
ext.webappDir = file("build/webapps")
|
||||
|
||||
@ -90,3 +103,57 @@ task demoJar(type: Jar) {
|
||||
artifacts {
|
||||
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.utilities.NetworkHostAndPort
|
||||
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.Value
|
||||
import org.springframework.boot.SpringApplication
|
||||
@ -29,10 +31,22 @@ class IrsDemoWebApplication {
|
||||
@Value("\${corda.password}")
|
||||
lateinit var cordaPassword:String
|
||||
|
||||
|
||||
@Bean
|
||||
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
|
||||
@ -44,6 +58,8 @@ class IrsDemoWebApplication {
|
||||
|
||||
// running as standalone java app
|
||||
companion object {
|
||||
private val log = LoggerFactory.getLogger(this::class.java)
|
||||
|
||||
@JvmStatic fun main(args: Array<String>) {
|
||||
SpringApplication.run(IrsDemoWebApplication::class.java, *args)
|
||||
}
|
||||
|
@ -1,36 +1,32 @@
|
||||
"use strict";
|
||||
|
||||
define(['viewmodel/FixedRate'], (fixedRateViewModel) => {
|
||||
let calculationModel = {
|
||||
define(['viewmodel/FixedRate'], function (fixedRateViewModel) {
|
||||
var calculationModel = {
|
||||
expression: "( fixedLeg.notional.quantity * (fixedLeg.fixedRate.ratioUnit.value)) - (floatingLeg.notional.quantity * (calculation.fixingSchedule.get(context.getDate('currentDate')).rate.ratioUnit.value))",
|
||||
floatingLegPaymentSchedule: {
|
||||
|
||||
},
|
||||
fixedLegPaymentSchedule: {
|
||||
|
||||
}
|
||||
floatingLegPaymentSchedule: {},
|
||||
fixedLegPaymentSchedule: {}
|
||||
};
|
||||
|
||||
let indexLookup = {
|
||||
var indexLookup = {
|
||||
"GBP": "ICE LIBOR",
|
||||
"USD": "ICE LIBOR",
|
||||
"EUR": "EURIBOR"
|
||||
};
|
||||
|
||||
let calendarLookup = {
|
||||
var calendarLookup = {
|
||||
"GBP": "London",
|
||||
"USD": "NewYork",
|
||||
"EUR": "London"
|
||||
};
|
||||
|
||||
let Deal = function(dealViewModel) {
|
||||
let now = new Date();
|
||||
let tradeId = `T${now.getUTCFullYear()}-${now.getUTCMonth()}-${now.getUTCDate()}.${now.getUTCHours()}:${now.getUTCMinutes()}:${now.getUTCSeconds()}:${now.getUTCMilliseconds()}`;
|
||||
var Deal = function Deal(dealViewModel) {
|
||||
var now = new Date();
|
||||
var tradeId = "T" + now.getUTCFullYear() + "-" + now.getUTCMonth() + "-" + now.getUTCDate() + "." + now.getUTCHours() + ":" + now.getUTCMinutes() + ":" + now.getUTCSeconds() + ":" + now.getUTCMilliseconds();
|
||||
|
||||
this.toJson = () => {
|
||||
let fixedLeg = {};
|
||||
let floatingLeg = {};
|
||||
let common = {};
|
||||
this.toJson = function () {
|
||||
var fixedLeg = {};
|
||||
var floatingLeg = {};
|
||||
var common = {};
|
||||
_.assign(fixedLeg, dealViewModel.fixedLeg);
|
||||
_.assign(floatingLeg, dealViewModel.floatingLeg);
|
||||
_.assign(common, dealViewModel.common);
|
||||
@ -65,7 +61,7 @@ define(['viewmodel/FixedRate'], (fixedRateViewModel) => {
|
||||
delete common.effectiveDate;
|
||||
delete common.terminationDate;
|
||||
|
||||
let json = {
|
||||
var json = {
|
||||
fixedLeg: fixedLeg,
|
||||
floatingLeg: floatingLeg,
|
||||
calculation: calculationModel,
|
||||
|
@ -2,23 +2,17 @@
|
||||
|
||||
function formatDateForNode(date) {
|
||||
// Produces yyyy-dd-mm. JS is missing proper date formatting libs
|
||||
let day = ("0" + (date.getDate())).slice(-2);
|
||||
let month = ("0" + (date.getMonth() + 1)).slice(-2);
|
||||
return `${date.getFullYear()}-${month}-${day}`;
|
||||
var day = ("0" + date.getDate()).slice(-2);
|
||||
var month = ("0" + (date.getMonth() + 1)).slice(-2);
|
||||
return date.getFullYear() + "-" + month + "-" + day;
|
||||
}
|
||||
|
||||
function formatDateForAngular(dateStr) {
|
||||
let parts = dateStr.split("-");
|
||||
var parts = dateStr.split("-");
|
||||
return new Date(parts[0], parts[1], parts[2]);
|
||||
}
|
||||
|
||||
define([
|
||||
'angular',
|
||||
'angularRoute',
|
||||
'jquery',
|
||||
'fcsaNumber',
|
||||
'semantic'
|
||||
], (angular, angularRoute, $, fcsaNumber, semantic) => {
|
||||
define(['angular', 'angularRoute', 'jquery', 'fcsaNumber', 'semantic'], function (angular, angularRoute, $, fcsaNumber, semantic) {
|
||||
angular.module('irsViewer', ['ngRoute', 'fcsa-number']);
|
||||
requirejs(['routes']);
|
||||
});
|
@ -1,34 +1,27 @@
|
||||
'use strict';
|
||||
|
||||
define([
|
||||
'angular',
|
||||
'maskedInput',
|
||||
'utils/semantic',
|
||||
'utils/dayCountBasisLookup',
|
||||
'services/NodeApi',
|
||||
'Deal',
|
||||
'services/HttpErrorHandler'
|
||||
], (angular, maskedInput, semantic, dayCountBasisLookup, nodeApi, Deal) => {
|
||||
define(['angular', 'maskedInput', 'utils/semantic', 'utils/dayCountBasisLookup', 'services/NodeApi', 'Deal', 'services/HttpErrorHandler'], function (angular, maskedInput, semantic, dayCountBasisLookup, nodeApi, Deal) {
|
||||
angular.module('irsViewer').controller('CreateDealController', function CreateDealController($http, $scope, $location, nodeService, httpErrorHandler) {
|
||||
semantic.init($scope, nodeService.isLoading);
|
||||
let handleHttpFail = httpErrorHandler.createErrorHandler($scope);
|
||||
var handleHttpFail = httpErrorHandler.createErrorHandler($scope);
|
||||
|
||||
$scope.dayCountBasisLookup = dayCountBasisLookup;
|
||||
$scope.deal = nodeService.newDeal();
|
||||
$scope.createDeal = () => {
|
||||
nodeService.createDeal(new Deal($scope.deal))
|
||||
.then((tradeId) => $location.path('#/deal/' + tradeId), (resp) => {
|
||||
$scope.createDeal = function () {
|
||||
nodeService.createDeal(new Deal($scope.deal)).then(function (tradeId) {
|
||||
return $location.path('#/deal/' + tradeId);
|
||||
}, function (resp) {
|
||||
$scope.formError = resp.data;
|
||||
}, handleHttpFail);
|
||||
};
|
||||
$('input.percent').mask("9.999999", {placeholder: "", autoclear: false});
|
||||
$('#swapirscolumns').click(() => {
|
||||
let first = $('#irscolumns .irscolumn:eq( 0 )');
|
||||
let last = $('#irscolumns .irscolumn:eq( 1 )');
|
||||
$('input.percent').mask("9.999999", { placeholder: "", autoclear: false });
|
||||
$('#swapirscolumns').click(function () {
|
||||
var first = $('#irscolumns .irscolumn:eq( 0 )');
|
||||
var last = $('#irscolumns .irscolumn:eq( 1 )');
|
||||
first.before(last);
|
||||
|
||||
let swapPayers = () => {
|
||||
let tmp = $scope.deal.floatingLeg.floatingRatePayer;
|
||||
var swapPayers = function swapPayers() {
|
||||
var tmp = $scope.deal.floatingLeg.floatingRatePayer;
|
||||
$scope.deal.floatingLeg.floatingRatePayer = $scope.deal.fixedLeg.fixedRatePayer;
|
||||
$scope.deal.fixedLeg.fixedRatePayer = tmp;
|
||||
};
|
||||
|
@ -1,19 +1,21 @@
|
||||
'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) {
|
||||
semantic.init($scope, nodeService.isLoading);
|
||||
let handleHttpFail = httpErrorHandler.createErrorHandler($scope);
|
||||
let decorateDeal = (deal) => {
|
||||
let paymentSchedule = deal.calculation.floatingLegPaymentSchedule;
|
||||
Object.keys(paymentSchedule).map((key, index) => {
|
||||
const sign = paymentSchedule[key].rate.positive ? 1 : -1;
|
||||
paymentSchedule[key].ratePercent = paymentSchedule[key].rate.ratioUnit ? (paymentSchedule[key].rate.ratioUnit.value * 100 * sign).toFixed(5) + "%": "";
|
||||
var handleHttpFail = httpErrorHandler.createErrorHandler($scope);
|
||||
var decorateDeal = function decorateDeal(deal) {
|
||||
var paymentSchedule = deal.calculation.floatingLegPaymentSchedule;
|
||||
Object.keys(paymentSchedule).map(function (key, index) {
|
||||
var sign = paymentSchedule[key].rate.positive ? 1 : -1;
|
||||
paymentSchedule[key].ratePercent = paymentSchedule[key].rate.ratioUnit ? (paymentSchedule[key].rate.ratioUnit.value * 100 * sign).toFixed(5) + "%" : "";
|
||||
});
|
||||
|
||||
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';
|
||||
|
||||
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) {
|
||||
semantic.addLoadingModal($scope, nodeService.isLoading);
|
||||
|
||||
let handleHttpFail = httpErrorHandler.createErrorHandler($scope);
|
||||
var handleHttpFail = httpErrorHandler.createErrorHandler($scope);
|
||||
|
||||
$scope.infoMsg = "";
|
||||
$scope.errorText = "";
|
||||
$scope.date = { "year": "...", "month": "...", "day": "..." };
|
||||
$scope.updateDate = (type) => {
|
||||
nodeService.updateDate(type).then((newDate) => {
|
||||
$scope.date = newDate
|
||||
$scope.updateDate = function (type) {
|
||||
nodeService.updateDate(type).then(function (newDate) {
|
||||
$scope.date = newDate;
|
||||
}, handleHttpFail);
|
||||
};
|
||||
/* Extract the common name from an X500 name */
|
||||
$scope.renderX500Name = (x500Name) => {
|
||||
var name = x500Name
|
||||
x500Name.split(',').forEach(function(element) {
|
||||
$scope.renderX500Name = function (x500Name) {
|
||||
var name = x500Name;
|
||||
x500Name.split(',').forEach(function (element) {
|
||||
var keyValue = element.split('=');
|
||||
if (keyValue[0].toUpperCase() == 'CN') {
|
||||
name = keyValue[1];
|
||||
@ -26,7 +26,11 @@ define(['angular', 'utils/semantic', 'services/NodeApi', 'services/HttpErrorHand
|
||||
return name;
|
||||
};
|
||||
|
||||
nodeService.getDate().then((date) => $scope.date = date, handleHttpFail);
|
||||
nodeService.getDeals().then((deals) => $scope.deals = deals, handleHttpFail);
|
||||
nodeService.getDate().then(function (date) {
|
||||
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'
|
||||
},
|
||||
shim: {
|
||||
'angular' : {'exports' : 'angular'},
|
||||
'angular': { 'exports': 'angular' },
|
||||
'angularRoute': ['angular'],
|
||||
'fcsaNumber': ['angular'],
|
||||
'semantic': ['jquery'],
|
||||
'maskedInput': ['jquery']
|
||||
},
|
||||
priority: [
|
||||
"angular"
|
||||
],
|
||||
baseUrl: 'js',
|
||||
priority: ["angular"],
|
||||
baseUrl: 'js'
|
||||
});
|
||||
|
||||
require(['angular', 'app'], (angular, app) => {
|
||||
|
||||
});
|
||||
require(['angular', 'app'], function (angular, app) {});
|
@ -1,32 +1,22 @@
|
||||
'use strict';
|
||||
|
||||
define([
|
||||
'angular',
|
||||
'controllers/Home',
|
||||
'controllers/Deal',
|
||||
'controllers/CreateDeal'
|
||||
], (angular) => {
|
||||
angular.module('irsViewer').config(($routeProvider, $locationProvider) => {
|
||||
$routeProvider
|
||||
.when('/', {
|
||||
controller: 'HomeController',
|
||||
templateUrl: 'view/home.html'
|
||||
})
|
||||
.when('/deal/:dealId', {
|
||||
controller: 'DealController',
|
||||
templateUrl: 'view/deal.html'
|
||||
})
|
||||
.when('/party/:partyId', {
|
||||
templateUrl: 'view/party.html'
|
||||
})
|
||||
.when('/create-deal', {
|
||||
controller: 'CreateDealController',
|
||||
templateUrl: 'view/create-deal.html'
|
||||
})
|
||||
.otherwise({redirectTo: '/'});
|
||||
define(['angular', 'controllers/Home', 'controllers/Deal', 'controllers/CreateDeal'], function (angular) {
|
||||
angular.module('irsViewer').config(function ($routeProvider, $locationProvider) {
|
||||
$routeProvider.when('/', {
|
||||
controller: 'HomeController',
|
||||
templateUrl: 'view/home.html'
|
||||
}).when('/deal/:dealId', {
|
||||
controller: 'DealController',
|
||||
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
|
||||
angular.bootstrap(document, ['irsViewer']);
|
||||
});
|
||||
|
@ -1,11 +1,11 @@
|
||||
'use strict';
|
||||
|
||||
define(['angular', 'lodash', 'viewmodel/Deal'], (angular, _) => {
|
||||
angular.module('irsViewer').factory('httpErrorHandler', () => {
|
||||
define(['angular', 'lodash', 'viewmodel/Deal'], function (angular, _) {
|
||||
angular.module('irsViewer').factory('httpErrorHandler', function () {
|
||||
return {
|
||||
createErrorHandler: (scope) => {
|
||||
return (resp) => {
|
||||
if(resp.status == -1) {
|
||||
createErrorHandler: function createErrorHandler(scope) {
|
||||
return function (resp) {
|
||||
if (resp.status == -1) {
|
||||
scope.httpError = "Could not connect to node web server";
|
||||
} else {
|
||||
scope.httpError = resp.data;
|
||||
|
@ -1,44 +1,48 @@
|
||||
'use strict';
|
||||
|
||||
define(['angular', 'lodash', 'viewmodel/Deal'], (angular, _, dealViewModel) => {
|
||||
angular.module('irsViewer').factory('nodeService', ($http) => {
|
||||
return new (function() {
|
||||
let date = new Date(2016, 0, 1, 0, 0, 0);
|
||||
let curLoading = {};
|
||||
let serverAddr = ''; // Leave empty to target the same host this page is served from
|
||||
define(['angular', 'lodash', 'viewmodel/Deal'], function (angular, _, dealViewModel) {
|
||||
angular.module('irsViewer').factory('nodeService', function ($http) {
|
||||
return new function () {
|
||||
var _this = this;
|
||||
|
||||
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;
|
||||
return promise.then((arg) => {
|
||||
return promise.then(function (arg) {
|
||||
curLoading[type] = false;
|
||||
return arg;
|
||||
}, (arg) => {
|
||||
}, function (arg) {
|
||||
curLoading[type] = false;
|
||||
throw arg;
|
||||
});
|
||||
};
|
||||
|
||||
let endpoint = (target) => serverAddr + target;
|
||||
var endpoint = function endpoint(target) {
|
||||
return serverAddr + target;
|
||||
};
|
||||
|
||||
let changeDateOnNode = (newDate) => {
|
||||
const dateStr = formatDateForNode(newDate);
|
||||
return load('date', $http.put(endpoint('/api/irs/demodate'), "\"" + dateStr + "\"")).then((resp) => {
|
||||
var changeDateOnNode = function changeDateOnNode(newDate) {
|
||||
var dateStr = formatDateForNode(newDate);
|
||||
return load('date', $http.put(endpoint('/api/irs/demodate'), "\"" + dateStr + "\"")).then(function (resp) {
|
||||
date = newDate;
|
||||
return this.getDateModel(date);
|
||||
return _this.getDateModel(date);
|
||||
});
|
||||
};
|
||||
|
||||
this.getDate = () => {
|
||||
return load('date', $http.get(endpoint('/api/irs/demodate'))).then((resp) => {
|
||||
const dateParts = resp.data;
|
||||
this.getDate = function () {
|
||||
return load('date', $http.get(endpoint('/api/irs/demodate'))).then(function (resp) {
|
||||
var dateParts = resp.data;
|
||||
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) => {
|
||||
let newDate = date;
|
||||
switch(type) {
|
||||
this.updateDate = function (type) {
|
||||
var newDate = date;
|
||||
switch (type) {
|
||||
case "year":
|
||||
newDate.setFullYear(date.getFullYear() + 1);
|
||||
break;
|
||||
@ -55,22 +59,22 @@ define(['angular', 'lodash', 'viewmodel/Deal'], (angular, _, dealViewModel) => {
|
||||
return changeDateOnNode(newDate);
|
||||
};
|
||||
|
||||
this.getDeals = () => {
|
||||
return load('deals', $http.get(endpoint('/api/irs/deals'))).then((resp) => {
|
||||
this.getDeals = function () {
|
||||
return load('deals', $http.get(endpoint('/api/irs/deals'))).then(function (resp) {
|
||||
return resp.data.reverse();
|
||||
});
|
||||
};
|
||||
|
||||
this.getDeal = (dealId) => {
|
||||
return load('deal' + dealId, $http.get(endpoint('/api/irs/deals/' + dealId))).then((resp) => {
|
||||
this.getDeal = function (dealId) {
|
||||
return load('deal' + dealId, $http.get(endpoint('/api/irs/deals/' + dealId))).then(function (resp) {
|
||||
// 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);
|
||||
return deal;
|
||||
});
|
||||
};
|
||||
|
||||
this.getDateModel = (date) => {
|
||||
this.getDateModel = function (date) {
|
||||
return {
|
||||
"year": date.getFullYear(),
|
||||
"month": date.getMonth() + 1, // JS uses 0 based months
|
||||
@ -78,24 +82,23 @@ define(['angular', 'lodash', 'viewmodel/Deal'], (angular, _, dealViewModel) => {
|
||||
};
|
||||
};
|
||||
|
||||
this.isLoading = () => {
|
||||
return _.reduce(Object.keys(curLoading), (last, key) => {
|
||||
return (last || curLoading[key]);
|
||||
this.isLoading = function () {
|
||||
return _.reduce(Object.keys(curLoading), function (last, key) {
|
||||
return last || curLoading[key];
|
||||
}, false);
|
||||
};
|
||||
|
||||
this.newDeal = () => {
|
||||
this.newDeal = function () {
|
||||
return dealViewModel;
|
||||
};
|
||||
|
||||
this.createDeal = (deal) => {
|
||||
return load('create-deal', $http.post(endpoint('/api/irs/deals'), deal.toJson()))
|
||||
.then((resp) => {
|
||||
this.createDeal = function (deal) {
|
||||
return load('create-deal', $http.post(endpoint('/api/irs/deals'), deal.toJson())).then(function (resp) {
|
||||
return deal.tradeId;
|
||||
}, (resp) => {
|
||||
}, function (resp) {
|
||||
throw resp;
|
||||
})
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
}();
|
||||
});
|
||||
});
|
@ -1,6 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
define([], () => {
|
||||
define([], function () {
|
||||
return {
|
||||
"30/360": {
|
||||
"day": "D30",
|
||||
@ -29,6 +29,6 @@ define([], () => {
|
||||
"ACT/ACT ICMA": {
|
||||
"day": "DActual",
|
||||
"year": "YICMA"
|
||||
},
|
||||
}
|
||||
};
|
||||
});
|
@ -1,17 +1,17 @@
|
||||
'use strict';
|
||||
|
||||
define(['jquery', 'semantic'], ($, semantic) => {
|
||||
define(['jquery', 'semantic'], function ($, semantic) {
|
||||
return {
|
||||
init: function($scope, loadingFunc) {
|
||||
init: function init($scope, loadingFunc) {
|
||||
$('.ui.accordion').accordion();
|
||||
$('.ui.dropdown').dropdown();
|
||||
$('.ui.sticky').sticky();
|
||||
|
||||
this.addLoadingModal($scope, loadingFunc);
|
||||
},
|
||||
addLoadingModal: ($scope, loadingFunc) => {
|
||||
$scope.$watch(loadingFunc, (newVal) => {
|
||||
if(newVal === true) {
|
||||
addLoadingModal: function addLoadingModal($scope, loadingFunc) {
|
||||
$scope.$watch(loadingFunc, function (newVal) {
|
||||
if (newVal === true) {
|
||||
$('#loading').modal('setting', 'closable', false).modal('show');
|
||||
} else {
|
||||
$('#loading').modal('hide');
|
||||
|
@ -1,6 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
define([], () => {
|
||||
define([], function () {
|
||||
return {
|
||||
baseCurrency: "USD",
|
||||
effectiveDate: new Date(2016, 2, 11),
|
||||
@ -31,7 +31,7 @@ define([], () => {
|
||||
},
|
||||
addressForTransfers: "",
|
||||
exposure: {},
|
||||
localBusinessDay: [ "London" , "NewYork" ],
|
||||
localBusinessDay: ["London", "NewYork"],
|
||||
dailyInterestAmount: "(CashAmount * InterestRate ) / (fixedLeg.notional.token.currencyCode.equals('GBP')) ? 365 : 360",
|
||||
hashLegalDocs: "put hash here"
|
||||
};
|
||||
|
@ -1,6 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
define(['viewmodel/FixedLeg', 'viewmodel/FloatingLeg', 'viewmodel/Common'], (fixedLeg, floatingLeg, common) => {
|
||||
define(['viewmodel/FixedLeg', 'viewmodel/FloatingLeg', 'viewmodel/Common'], function (fixedLeg, floatingLeg, common) {
|
||||
return {
|
||||
fixedLeg: fixedLeg,
|
||||
floatingLeg: floatingLeg,
|
||||
|
@ -1,6 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
define(['utils/dayCountBasisLookup'], (dayCountBasisLookup) => {
|
||||
define(['utils/dayCountBasisLookup'], function (dayCountBasisLookup) {
|
||||
return {
|
||||
fixedRatePayer: "O=Bank A,L=London,C=GB",
|
||||
notional: 2500000000,
|
||||
|
@ -1,6 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
define(['utils/dayCountBasisLookup'], (dayCountBasisLookup) => {
|
||||
define(['utils/dayCountBasisLookup'], function (dayCountBasisLookup) {
|
||||
return {
|
||||
floatingRatePayer: "O=Bank B,L=New York,C=US",
|
||||
notional: 2500000000,
|
||||
@ -20,7 +20,7 @@ define(['utils/dayCountBasisLookup'], (dayCountBasisLookup) => {
|
||||
fixingsPerPayment: "Quarterly",
|
||||
indexSource: "Rates Service Provider",
|
||||
indexTenor: {
|
||||
name: "3M"
|
||||
name: "3M"
|
||||
}
|
||||
};
|
||||
});
|
@ -34,7 +34,7 @@
|
||||
<i class="browser icon"></i>
|
||||
Recent deals
|
||||
</h3>
|
||||
<table class="ui striped table">
|
||||
<table class="ui striped table" id="deal-list">
|
||||
<thead>
|
||||
<tr class="center aligned">
|
||||
<th>Trade Id</th>
|
||||
@ -45,7 +45,7 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<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 class="single line">{{renderX500Name(deal.fixedLeg.fixedRatePayer)}}</td>
|
||||
<td class="single line">{{deal.fixedLeg.notional}}</td>
|
||||
|
Loading…
Reference in New Issue
Block a user