2016-11-22 12:00:09 -05:00
|
|
|
import org.elasticsearch.gradle.test.NodeInfo
|
|
|
|
import org.elasticsearch.gradle.test.RestIntegTestTask
|
2017-05-18 05:25:54 -04:00
|
|
|
import org.elasticsearch.gradle.Version
|
2016-11-22 12:00:09 -05:00
|
|
|
|
|
|
|
import java.nio.charset.StandardCharsets
|
2017-06-08 12:55:10 -04:00
|
|
|
import java.util.regex.Matcher
|
|
|
|
|
|
|
|
// Apply the java plugin to this project so the sources can be edited in an IDE
|
|
|
|
apply plugin: 'elasticsearch.build'
|
|
|
|
test.enabled = false
|
|
|
|
|
|
|
|
dependencies {
|
2018-01-20 00:30:17 -05:00
|
|
|
testCompile project(path: ':x-pack-elasticsearch:plugin:core', configuration: 'runtime')
|
|
|
|
testCompile project(path: ':x-pack-elasticsearch:plugin:security', configuration: 'runtime')
|
|
|
|
testCompile project(path: ':x-pack-elasticsearch:plugin:core', configuration: 'testArtifacts') // to be moved in a later commit
|
2017-06-08 12:55:10 -04:00
|
|
|
}
|
2016-11-22 12:00:09 -05:00
|
|
|
|
|
|
|
Closure waitWithAuth = { NodeInfo node, AntBuilder ant ->
|
|
|
|
File tmpFile = new File(node.cwd, 'wait.success')
|
2017-06-29 16:27:57 -04:00
|
|
|
|
2017-11-08 12:46:53 -05:00
|
|
|
// wait up to two minutes
|
|
|
|
final long stopTime = System.currentTimeMillis() + (2 * 60000L);
|
2016-11-22 12:00:09 -05:00
|
|
|
Exception lastException = null;
|
2017-06-29 16:27:57 -04:00
|
|
|
|
2016-11-22 12:00:09 -05:00
|
|
|
while (System.currentTimeMillis() < stopTime) {
|
2017-06-29 16:27:57 -04:00
|
|
|
|
|
|
|
lastException = null;
|
|
|
|
// we use custom wait logic here as the elastic user is not available immediately and ant.get will fail when a 401 is returned
|
|
|
|
HttpURLConnection httpURLConnection = null;
|
|
|
|
try {
|
|
|
|
// TODO this sucks having to hardcode number of nodes, but node.config.numNodes isn't necessarily accurate for rolling
|
|
|
|
httpURLConnection = (HttpURLConnection) new URL("http://${node.httpUri()}/_cluster/health?wait_for_nodes=2&wait_for_status=yellow").openConnection();
|
|
|
|
httpURLConnection.setRequestProperty("Authorization", "Basic " +
|
2017-07-13 20:59:50 -04:00
|
|
|
Base64.getEncoder().encodeToString("test_user:x-pack-test-password".getBytes(StandardCharsets.UTF_8)));
|
2016-11-22 12:00:09 -05:00
|
|
|
httpURLConnection.setRequestMethod("GET");
|
|
|
|
httpURLConnection.setConnectTimeout(1000);
|
|
|
|
httpURLConnection.setReadTimeout(30000); // read needs to wait for nodes!
|
|
|
|
httpURLConnection.connect();
|
|
|
|
if (httpURLConnection.getResponseCode() == 200) {
|
|
|
|
tmpFile.withWriter StandardCharsets.UTF_8.name(), {
|
|
|
|
it.write(httpURLConnection.getInputStream().getText(StandardCharsets.UTF_8.name()))
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
} catch (Exception e) {
|
|
|
|
logger.debug("failed to call cluster health", e)
|
|
|
|
lastException = e
|
|
|
|
} finally {
|
|
|
|
if (httpURLConnection != null) {
|
|
|
|
httpURLConnection.disconnect();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// did not start, so wait a bit before trying again
|
|
|
|
Thread.sleep(500L);
|
|
|
|
}
|
|
|
|
if (tmpFile.exists() == false && lastException != null) {
|
|
|
|
logger.error("final attempt of calling cluster health failed", lastException)
|
|
|
|
}
|
|
|
|
return tmpFile.exists()
|
|
|
|
}
|
|
|
|
|
2017-06-08 12:55:10 -04:00
|
|
|
Project mainProject = project
|
2017-02-07 11:39:31 -05:00
|
|
|
|
2018-01-17 14:18:44 -05:00
|
|
|
compileTestJava.options.compilerArgs << "-Xlint:-cast,-deprecation,-rawtypes,-try,-unchecked"
|
|
|
|
|
2017-06-08 12:55:10 -04:00
|
|
|
/**
|
|
|
|
* Subdirectories of this project are test rolling upgrades with various
|
|
|
|
* configuration options based on their name.
|
|
|
|
*/
|
|
|
|
subprojects {
|
2018-01-17 14:18:44 -05:00
|
|
|
// TODO remove after backport
|
|
|
|
ext.bwc_tests_enabled = false
|
2017-06-16 11:44:51 -04:00
|
|
|
Matcher m = project.name =~ /with(out)?-system-key/
|
2017-06-08 12:55:10 -04:00
|
|
|
if (false == m.matches()) {
|
|
|
|
throw new InvalidUserDataException("Invalid project name [${project.name}]")
|
|
|
|
}
|
2017-06-16 11:44:51 -04:00
|
|
|
boolean withSystemKey = m.group(1) == null
|
2017-06-08 12:55:10 -04:00
|
|
|
|
|
|
|
apply plugin: 'elasticsearch.standalone-test'
|
2017-02-22 03:56:52 -05:00
|
|
|
|
2017-06-08 12:55:10 -04:00
|
|
|
// Use resources from the rolling-upgrade project in subdirectories
|
|
|
|
sourceSets {
|
|
|
|
test {
|
|
|
|
java {
|
|
|
|
srcDirs = ["${mainProject.projectDir}/src/test/java"]
|
|
|
|
}
|
|
|
|
resources {
|
|
|
|
srcDirs = ["${mainProject.projectDir}/src/test/resources"]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
String outputDir = "generated-resources/${project.name}"
|
|
|
|
|
|
|
|
// This is a top level task which we will add dependencies to below.
|
|
|
|
// It is a single task that can be used to backcompat tests against all versions.
|
|
|
|
task bwcTest {
|
|
|
|
description = 'Runs backwards compatibility tests.'
|
|
|
|
group = 'verification'
|
|
|
|
}
|
|
|
|
|
|
|
|
String output = "generated-resources/${project.name}"
|
|
|
|
task copyTestNodeKeystore(type: Copy) {
|
2018-01-20 00:30:17 -05:00
|
|
|
from project(':x-pack-elasticsearch:plugin:core')
|
2017-05-23 21:42:16 -04:00
|
|
|
.file('src/test/resources/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.jks')
|
|
|
|
into outputDir
|
2017-06-08 12:55:10 -04:00
|
|
|
}
|
2017-05-23 21:42:16 -04:00
|
|
|
|
2017-11-21 03:45:10 -05:00
|
|
|
for (Version version : versionCollection.versionsWireCompatibleWithCurrent) {
|
2017-06-08 12:55:10 -04:00
|
|
|
String baseName = "v${version}"
|
2017-05-18 05:25:54 -04:00
|
|
|
|
2017-05-26 10:02:13 -04:00
|
|
|
Task oldClusterTest = tasks.create(name: "${baseName}#oldClusterTest", type: RestIntegTestTask) {
|
|
|
|
mustRunAfter(precommit)
|
|
|
|
}
|
2017-05-18 05:25:54 -04:00
|
|
|
|
2017-05-26 10:02:13 -04:00
|
|
|
Object extension = extensions.findByName("${baseName}#oldClusterTestCluster")
|
|
|
|
configure(extensions.findByName("${baseName}#oldClusterTestCluster")) {
|
|
|
|
dependsOn copyTestNodeKeystore
|
|
|
|
plugin ':x-pack-elasticsearch:plugin'
|
2017-07-13 20:59:50 -04:00
|
|
|
setupCommand 'setupTestUser', 'bin/x-pack/users', 'useradd', 'test_user', '-p', 'x-pack-test-password', '-r', 'superuser'
|
2017-05-26 10:02:13 -04:00
|
|
|
distribution = 'zip'
|
|
|
|
bwcVersion = version
|
|
|
|
numBwcNodes = 2
|
|
|
|
numNodes = 2
|
2017-11-08 12:46:53 -05:00
|
|
|
minimumMasterNodes = { 2 }
|
2017-05-26 10:02:13 -04:00
|
|
|
clusterName = 'rolling-upgrade'
|
2017-07-06 11:37:48 -04:00
|
|
|
waitCondition = waitWithAuth
|
2017-11-09 12:49:37 -05:00
|
|
|
setting 'xpack.monitoring.collection.interval', '-1'
|
|
|
|
setting 'xpack.monitoring.exporters._http.type', 'http'
|
|
|
|
setting 'xpack.monitoring.exporters._http.enabled', 'false'
|
|
|
|
setting 'xpack.monitoring.exporters._http.auth.username', 'test_user'
|
|
|
|
setting 'xpack.monitoring.exporters._http.auth.password', 'x-pack-test-password'
|
2017-05-26 10:02:13 -04:00
|
|
|
setting 'xpack.security.transport.ssl.enabled', 'true'
|
2017-08-23 06:21:30 -04:00
|
|
|
setting 'xpack.security.authc.token.enabled', 'true'
|
2017-05-26 10:02:13 -04:00
|
|
|
setting 'xpack.ssl.keystore.path', 'testnode.jks'
|
|
|
|
setting 'xpack.ssl.keystore.password', 'testnode'
|
2017-06-08 12:55:10 -04:00
|
|
|
dependsOn copyTestNodeKeystore
|
2017-05-26 10:02:13 -04:00
|
|
|
extraConfigFile 'testnode.jks', new File(outputDir + '/testnode.jks')
|
|
|
|
if (withSystemKey) {
|
2017-08-01 15:48:04 -04:00
|
|
|
if (version.onOrAfter('5.1.0') && version.before('6.0.0')) {
|
2017-05-26 10:02:13 -04:00
|
|
|
// The setting didn't exist until 5.1.0
|
|
|
|
setting 'xpack.security.system_key.required', 'true'
|
|
|
|
}
|
2017-08-01 15:48:04 -04:00
|
|
|
if (version.onOrAfter('6.0.0')) {
|
|
|
|
setupCommand 'create-elasticsearch-keystore', 'bin/elasticsearch-keystore', 'create'
|
|
|
|
setupCommand 'add-key-elasticsearch-keystore', 'bin/elasticsearch-keystore', 'add-file', 'xpack.watcher.encryption_key',
|
|
|
|
"${mainProject.projectDir}/src/test/resources/system_key"
|
|
|
|
} else {
|
|
|
|
extraConfigFile 'x-pack/system_key', "${mainProject.projectDir}/src/test/resources/system_key"
|
|
|
|
}
|
2017-06-29 16:58:35 -04:00
|
|
|
setting 'xpack.watcher.encrypt_sensitive_data', 'true'
|
2017-05-26 10:02:13 -04:00
|
|
|
}
|
|
|
|
}
|
2016-11-22 12:00:09 -05:00
|
|
|
|
2017-05-26 10:02:13 -04:00
|
|
|
Task oldClusterTestRunner = tasks.getByName("${baseName}#oldClusterTestRunner")
|
|
|
|
oldClusterTestRunner.configure {
|
|
|
|
systemProperty 'tests.rest.suite', 'old_cluster'
|
|
|
|
}
|
2017-02-22 03:56:52 -05:00
|
|
|
|
2017-05-26 10:02:13 -04:00
|
|
|
Task mixedClusterTest = tasks.create(name: "${baseName}#mixedClusterTest", type: RestIntegTestTask)
|
|
|
|
|
|
|
|
configure(extensions.findByName("${baseName}#mixedClusterTestCluster")) {
|
|
|
|
dependsOn oldClusterTestRunner, "${baseName}#oldClusterTestCluster#node1.stop"
|
|
|
|
plugin ':x-pack-elasticsearch:plugin'
|
2017-07-13 20:59:50 -04:00
|
|
|
setupCommand 'setupTestUser', 'bin/x-pack/users', 'useradd', 'test_user', '-p', 'x-pack-test-password', '-r', 'superuser'
|
2017-05-26 10:02:13 -04:00
|
|
|
distribution = 'zip'
|
|
|
|
clusterName = 'rolling-upgrade'
|
|
|
|
unicastTransportUri = { seedNode, node, ant -> oldClusterTest.nodes.get(0).transportUri() }
|
2017-10-09 04:59:58 -04:00
|
|
|
minimumMasterNodes = { 2 }
|
2017-05-26 17:31:50 -04:00
|
|
|
dataDir = { nodeNumber -> oldClusterTest.nodes[1].dataDir }
|
2017-05-26 10:02:13 -04:00
|
|
|
waitCondition = waitWithAuth
|
2017-11-09 12:49:37 -05:00
|
|
|
setting 'xpack.monitoring.collection.interval', '-1'
|
|
|
|
setting 'xpack.monitoring.exporters._http.type', 'http'
|
|
|
|
setting 'xpack.monitoring.exporters._http.enabled', 'false'
|
|
|
|
setting 'xpack.monitoring.exporters._http.auth.username', 'test_user'
|
|
|
|
setting 'xpack.monitoring.exporters._http.auth.password', 'x-pack-test-password'
|
2017-09-14 14:18:54 -04:00
|
|
|
setting 'xpack.security.transport.ssl.enabled', 'true'
|
2017-05-26 10:02:13 -04:00
|
|
|
setting 'xpack.ssl.keystore.path', 'testnode.jks'
|
2017-06-27 13:15:12 -04:00
|
|
|
keystoreSetting 'xpack.ssl.keystore.secure_password', 'testnode'
|
2017-06-06 12:23:18 -04:00
|
|
|
setting 'node.attr.upgraded', 'first'
|
2017-08-23 06:21:30 -04:00
|
|
|
setting 'xpack.security.authc.token.enabled', 'true'
|
2017-06-08 12:55:10 -04:00
|
|
|
dependsOn copyTestNodeKeystore
|
2017-05-26 10:02:13 -04:00
|
|
|
extraConfigFile 'testnode.jks', new File(outputDir + '/testnode.jks')
|
|
|
|
if (withSystemKey) {
|
2017-06-29 16:58:35 -04:00
|
|
|
setting 'xpack.watcher.encrypt_sensitive_data', 'true'
|
|
|
|
setupCommand 'create-elasticsearch-keystore', 'bin/elasticsearch-keystore', 'create'
|
|
|
|
setupCommand 'add-key-elasticsearch-keystore',
|
|
|
|
'bin/elasticsearch-keystore', 'add-file', 'xpack.watcher.encryption_key', "${mainProject.projectDir}/src/test/resources/system_key"
|
2017-05-26 10:02:13 -04:00
|
|
|
}
|
|
|
|
}
|
2017-02-22 03:56:52 -05:00
|
|
|
|
2017-05-26 10:02:13 -04:00
|
|
|
Task mixedClusterTestRunner = tasks.getByName("${baseName}#mixedClusterTestRunner")
|
|
|
|
mixedClusterTestRunner.configure {
|
|
|
|
systemProperty 'tests.rest.suite', 'mixed_cluster'
|
|
|
|
finalizedBy "${baseName}#oldClusterTestCluster#node0.stop"
|
|
|
|
}
|
2016-11-22 12:00:09 -05:00
|
|
|
|
2017-05-26 10:02:13 -04:00
|
|
|
Task upgradedClusterTest = tasks.create(name: "${baseName}#upgradedClusterTest", type: RestIntegTestTask)
|
|
|
|
|
|
|
|
configure(extensions.findByName("${baseName}#upgradedClusterTestCluster")) {
|
|
|
|
dependsOn(mixedClusterTestRunner, "${baseName}#oldClusterTestCluster#node0.stop")
|
|
|
|
plugin ':x-pack-elasticsearch:plugin'
|
2017-07-13 20:59:50 -04:00
|
|
|
setupCommand 'setupTestUser', 'bin/x-pack/users', 'useradd', 'test_user', '-p', 'x-pack-test-password', '-r', 'superuser'
|
2017-05-26 10:02:13 -04:00
|
|
|
distribution = 'zip'
|
|
|
|
clusterName = 'rolling-upgrade'
|
|
|
|
unicastTransportUri = { seedNode, node, ant -> mixedClusterTest.nodes.get(0).transportUri() }
|
2017-10-09 04:59:58 -04:00
|
|
|
minimumMasterNodes = { 2 }
|
2017-05-26 17:31:50 -04:00
|
|
|
dataDir = { nodeNumber -> oldClusterTest.nodes[0].dataDir }
|
2017-05-26 10:02:13 -04:00
|
|
|
waitCondition = waitWithAuth
|
2017-11-09 12:49:37 -05:00
|
|
|
setting 'xpack.monitoring.collection.interval', '-1'
|
|
|
|
setting 'xpack.monitoring.exporters._http.type', 'http'
|
|
|
|
setting 'xpack.monitoring.exporters._http.enabled', 'false'
|
|
|
|
setting 'xpack.monitoring.exporters._http.auth.username', 'test_user'
|
|
|
|
setting 'xpack.monitoring.exporters._http.auth.password', 'x-pack-test-password'
|
2017-09-14 14:18:54 -04:00
|
|
|
setting 'xpack.security.transport.ssl.enabled', 'true'
|
2017-05-26 10:02:13 -04:00
|
|
|
setting 'xpack.ssl.keystore.path', 'testnode.jks'
|
2017-06-27 13:15:12 -04:00
|
|
|
keystoreSetting 'xpack.ssl.keystore.secure_password', 'testnode'
|
2017-08-23 06:21:30 -04:00
|
|
|
setting 'xpack.security.authc.token.enabled', 'true'
|
2017-06-08 12:55:10 -04:00
|
|
|
dependsOn copyTestNodeKeystore
|
2017-05-26 10:02:13 -04:00
|
|
|
extraConfigFile 'testnode.jks', new File(outputDir + '/testnode.jks')
|
|
|
|
if (withSystemKey) {
|
2017-06-29 16:58:35 -04:00
|
|
|
setting 'xpack.watcher.encrypt_sensitive_data', 'true'
|
|
|
|
setupCommand 'create-elasticsearch-keystore', 'bin/elasticsearch-keystore', 'create'
|
|
|
|
setupCommand 'add-key-elasticsearch-keystore',
|
|
|
|
'bin/elasticsearch-keystore', 'add-file', 'xpack.watcher.encryption_key', "${mainProject.projectDir}/src/test/resources/system_key"
|
2017-05-26 10:02:13 -04:00
|
|
|
}
|
|
|
|
}
|
2017-02-22 03:56:52 -05:00
|
|
|
|
2017-05-26 10:02:13 -04:00
|
|
|
Task upgradedClusterTestRunner = tasks.getByName("${baseName}#upgradedClusterTestRunner")
|
|
|
|
upgradedClusterTestRunner.configure {
|
|
|
|
systemProperty 'tests.rest.suite', 'upgraded_cluster'
|
|
|
|
|
|
|
|
// migration tests should only run when the original/old cluster nodes where versions < 5.2.0.
|
|
|
|
// this stinks but we do the check here since our rest tests do not support conditionals
|
|
|
|
// otherwise we could check the index created version
|
|
|
|
String versionStr = project.extensions.findByName("${baseName}#oldClusterTestCluster").properties.get('bwcVersion')
|
|
|
|
String[] versionParts = versionStr.split('\\.')
|
|
|
|
if (versionParts[0].equals("5")) {
|
|
|
|
Integer minor = Integer.parseInt(versionParts[1])
|
|
|
|
if (minor >= 2) {
|
|
|
|
systemProperty 'tests.rest.blacklist', '/20_security/Verify default password migration results in upgraded cluster'
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// only need to kill the mixed cluster tests node here because we explicitly told it to not stop nodes upon completion
|
|
|
|
finalizedBy "${baseName}#mixedClusterTestCluster#stop"
|
2017-05-18 14:39:36 -04:00
|
|
|
}
|
2016-11-22 12:00:09 -05:00
|
|
|
|
2017-05-26 10:02:13 -04:00
|
|
|
Task versionBwcTest = tasks.create(name: "${baseName}#bwcTest") {
|
|
|
|
dependsOn = [upgradedClusterTest]
|
|
|
|
}
|
2017-05-18 05:25:54 -04:00
|
|
|
|
2017-06-15 07:45:20 -04:00
|
|
|
if (project.bwc_tests_enabled) {
|
|
|
|
bwcTest.dependsOn(versionBwcTest)
|
|
|
|
}
|
2017-05-26 10:02:13 -04:00
|
|
|
}
|
2016-11-22 12:00:09 -05:00
|
|
|
|
2017-06-08 12:55:10 -04:00
|
|
|
test.enabled = false // no unit tests for rolling upgrades, only the rest integration test
|
2016-11-22 12:00:09 -05:00
|
|
|
|
2017-06-08 12:55:10 -04:00
|
|
|
// basic integ tests includes testing bwc against the most recent version
|
|
|
|
task integTest {
|
2017-06-15 07:45:20 -04:00
|
|
|
if (project.bwc_tests_enabled) {
|
2017-11-24 03:13:13 -05:00
|
|
|
for (final def version : versionCollection.basicIntegrationTestVersions) {
|
|
|
|
dependsOn "v${version}#bwcTest"
|
2017-11-21 03:45:10 -05:00
|
|
|
}
|
2017-06-15 07:45:20 -04:00
|
|
|
}
|
2017-06-08 12:55:10 -04:00
|
|
|
}
|
2017-07-06 11:37:48 -04:00
|
|
|
check.dependsOn(integTest)
|
2016-11-22 12:00:09 -05:00
|
|
|
|
2017-06-08 12:55:10 -04:00
|
|
|
dependencies {
|
2018-01-20 00:30:17 -05:00
|
|
|
testCompile project(path: ':x-pack-elasticsearch:plugin:core', configuration: 'runtime')
|
|
|
|
testCompile project(path: ':x-pack-elasticsearch:plugin:core', configuration: 'testArtifacts')
|
|
|
|
testCompile project(path: ':x-pack-elasticsearch:plugin:watcher')
|
2017-06-08 12:55:10 -04:00
|
|
|
}
|
2016-12-01 11:15:15 -05:00
|
|
|
|
2018-01-17 14:18:44 -05:00
|
|
|
compileTestJava.options.compilerArgs << "-Xlint:-cast,-deprecation,-rawtypes,-try,-unchecked"
|
|
|
|
|
2017-06-08 12:55:10 -04:00
|
|
|
// copy x-pack plugin info so it is on the classpath and security manager has the right permissions
|
|
|
|
task copyXPackRestSpec(type: Copy) {
|
2017-01-09 16:06:50 -05:00
|
|
|
dependsOn(project.configurations.restSpec, 'processTestResources')
|
2018-01-20 00:30:17 -05:00
|
|
|
from project(':x-pack-elasticsearch:plugin:core').sourceSets.test.resources
|
2017-01-09 16:06:50 -05:00
|
|
|
include 'rest-api-spec/api/**'
|
|
|
|
into project.sourceSets.test.output.resourcesDir
|
2017-06-08 12:55:10 -04:00
|
|
|
}
|
2017-01-09 16:06:50 -05:00
|
|
|
|
2017-06-08 12:55:10 -04:00
|
|
|
task copyXPackPluginProps(type: Copy) {
|
2017-05-23 21:42:16 -04:00
|
|
|
dependsOn(copyXPackRestSpec)
|
2018-01-20 00:30:17 -05:00
|
|
|
from project(':x-pack-elasticsearch:plugin:core').file('src/main/plugin-metadata')
|
|
|
|
from project(':x-pack-elasticsearch:plugin:core').tasks.pluginProperties
|
2016-12-01 11:15:15 -05:00
|
|
|
into outputDir
|
2017-06-08 12:55:10 -04:00
|
|
|
}
|
|
|
|
project.sourceSets.test.output.dir(outputDir, builtBy: copyXPackPluginProps)
|
2016-12-01 11:15:15 -05:00
|
|
|
|
2017-06-08 12:55:10 -04:00
|
|
|
repositories {
|
2016-11-22 12:00:09 -05:00
|
|
|
maven {
|
2017-06-08 12:55:10 -04:00
|
|
|
url "https://artifacts.elastic.co/maven"
|
2016-11-22 12:00:09 -05:00
|
|
|
}
|
2017-06-08 12:55:10 -04:00
|
|
|
}
|
2016-11-22 12:00:09 -05:00
|
|
|
}
|