From 0cad65ffda22745c5c5b29ced2059c1705e5b79a Mon Sep 17 00:00:00 2001 From: Kevin McGoldrick Date: Mon, 2 Nov 2020 19:15:35 -0800 Subject: [PATCH] Release 3.0.0 * More test cases & code coverage for issue Co-authored-by: vhegde1 * Update/Compress existing serializedScriptStep Blob * added test coverage for com.intuit.tank.dao package to increase from 46% to 55% Co-authored-by: Sreekakula, Manikanta * Improving test coverage in data-model module Co-authored-by: djagaluru * Improving test coverage for data-model project module Co-authored-by: djagaluru * increase test coverage for api module Co-authored-by: atayal * JDK11 support Co-authored-by: Niti * AWS SDK V2 Co-authored-by: dratler Co-authored-by: Juan Parra <41169130+Ujoa@users.noreply.github.com> Co-authored-by: vaishakhvh <72062381+vaishakhvh@users.noreply.github.com> Co-authored-by: vhegde1 Co-authored-by: felixgao Co-authored-by: Manikanta Co-authored-by: Sreekakula, Manikanta Co-authored-by: darshan-sj Co-authored-by: djagaluru Co-authored-by: interactwithankush Co-authored-by: atayal Co-authored-by: Niti --- .travis.yml | 5 +- agent/agent_common/pom.xml | 7 +- .../tank/http/xml/GenericXMLHandler.java | 6 +- .../tank/http/soap/SOAPRequestTest.java | 2 + agent/agent_standalone/pom.xml | 2 +- agent/agent_standalone_pkg/pom.xml | 3 +- agent/agent_startup/pom.xml | 2 +- .../com/intuit/tank/agent/AgentStartup.java | 4 +- agent/agent_startup_pkg/pom.xml | 2 +- agent/apiharness/pom.xml | 7 +- .../com/intuit/tank/harness/APIMonitor.java | 19 +- .../intuit/tank/harness/APITestHarness.java | 40 +- .../harness/logging/ThreadLocalLogEvent.java | 3 +- .../apiharness/src/main/resources/log4j2.xml | 3 +- .../java/com/intuit/tank/common/TPSTest.java | 17 +- agent/apiharness_pkg/pom.xml | 7 +- agent/http_client_3/pom.xml | 2 +- agent/http_client_4/pom.xml | 2 +- agent/http_client_5/pom.xml | 2 +- agent/pom.xml | 2 +- all-in-one.sh | 43 +- api/pom.xml | 10 +- .../com/intuit/tank/harness/AmazonUtil.java | 222 +++----- .../com/intuit/tank/harness/StopBehavior.java | 5 +- .../intuit/tank/storage/S3FileStorage.java | 167 +++--- .../tank/vm/api/enumerated/VMRegion.java | 2 +- .../intuit/tank/vm/api/enumerated/VMSize.java | 60 --- .../tank/vm/common/PasswordEncoder.java | 2 +- .../tank/vm/settings/CloudCredentials.java | 41 +- api/src/main/resources/settings.xml | 49 +- .../intuit/tank/harness/AmazonUtilTest.java | 90 ++++ .../com/intuit/tank/http/AuthSchemeTest.java | 22 + .../intuit/tank/logging/LogEventTypeTest.java | 19 + .../intuit/tank/logging/SourceTypeTest.java | 18 + .../tank/script/RequestDataTypeTest.java | 19 + .../intuit/tank/script/TimerActionTest.java | 19 + .../vm/agent/messages/AgentDataCpTest.java | 18 +- .../vm/api/enumerated/VMRegionCpTest.java | 26 +- .../tank/vm/api/enumerated/VMSizeCpTest.java | 95 ---- .../vm/common/util/MethodTimerCpTest.java | 23 + .../tank/vm/common/util/ReportUtilCpTest.java | 76 +-- .../vm/settings/CloudCredentialsCpTest.java | 19 +- .../InstanceDescriptionDefaultsCpTest.java | 5 - .../tank/vm/settings/InstanceTagTest.java | 30 ++ .../vm/settings/ModificationTypeTest.java | 19 + .../settings/ModifiedEntityMessageTest.java | 9 + .../tank/vm/settings/PropertiesFileTest.java | 9 + .../tank/vm/settings/ReportingConfigTest.java | 9 + api/src/test/resources/sampleProp.properties | 2 + api/src/test/resources/settings.xml | 83 ++- assets/cleanup_prior_to_3.0.0.sql | 37 ++ assets/settings-all-in-one.xml | 490 ++++++++++++++++++ assets/tankIcon.png | Bin 0 -> 80076 bytes aws-config/tank_agent.yml | 177 +++++++ aws-config/tank_controller.yml | 289 +++++++++++ aws-config/tank_instanceProfiles.yml | 84 +++ buildspec.yml | 18 +- data_access/pom.xml | 2 +- .../java/com/intuit/tank/dao/GroupDao.java | 3 - .../intuit/tank/dao/JobNotificationDao.java | 6 +- .../java/com/intuit/tank/dao/JobQueueDao.java | 6 - .../java/com/intuit/tank/dao/ProjectDao.java | 2 +- .../java/com/intuit/tank/dao/ScriptDao.java | 34 +- .../java/com/intuit/tank/dao/UserDao.java | 9 +- .../java/com/intuit/tank/dao/BaseDaoTest.java | 50 ++ .../java/com/intuit/tank/dao/DaoTestUtil.java | 80 ++- .../com/intuit/tank/dao/DataFileDaoTest.java | 57 ++ .../intuit/tank/dao/FilterGroupDaoTest.java | 47 ++ .../com/intuit/tank/dao/GroupDaoTest.java | 77 +++ .../tank/dao/JobNotificationDaoTest.java | 56 ++ .../com/intuit/tank/dao/JobQueueDaoTest.java | 56 ++ .../com/intuit/tank/dao/JobRegionDaoTest.java | 69 +++ .../com/intuit/tank/dao/PeriodicDataTest.java | 95 ++++ .../com/intuit/tank/dao/ScriptDaoTest.java | 41 +- .../java/com/intuit/tank/dao/UserDaoTest.java | 72 +++ data_model/pom.xml | 2 +- .../com/intuit/tank/project/DataFile.java | 71 +++ .../java/com/intuit/tank/project/Group.java | 47 ++ .../intuit/tank/project/JobConfiguration.java | 49 ++ .../com/intuit/tank/project/JobRegion.java | 56 ++ .../com/intuit/tank/project/PeriodicData.java | 99 ++++ .../java/com/intuit/tank/project/Script.java | 34 +- .../tank/project/SerializedScriptStep.java | 11 +- .../java/com/intuit/tank/project/User.java | 77 +++ .../intuit/tank/project/ProjectDTOTest.java | 70 +++ .../tank/project/RevisionedEntityTest.java | 9 +- .../com/intuit/tank/project/ScriptTest.java | 19 +- .../tank/project/SummaryDataBuilderTest.java | 62 +++ .../TankDefaultRevisionEntityTest.java | 55 ++ .../tank/util/CreateDateComparatorTest.java | 12 + .../intuit/tank/util/DataFileUtilTest.java | 53 ++ doc/doc_xslt/pom.xml | 2 +- doc/jdocbook_style/pom.xml | 2 +- doc/pom.xml | 2 +- doc/tank_installation_guide/pom.xml | 2 +- doc/tank_user_guide/pom.xml | 2 +- harness_data/pom.xml | 2 +- mail/pom.xml | 2 +- pom.xml | 121 ++--- proxy-parent/WebConversation/pom.xml | 2 +- .../com/intuit/tank/conversation/Header.java | 13 +- .../com/intuit/tank/conversation/Request.java | 7 +- .../intuit/tank/conversation/Response.java | 7 +- proxy-parent/owasp-proxy/pom.xml | 2 +- .../server/BufferingHttpRequestHandler.java | 3 +- .../org/owasp/proxy/util/AsciiString.java | 22 +- .../java/org/owasp/proxy/util/Base64.java | 3 +- .../org/owasp/proxy/util/TextFormatter.java | 2 +- proxy-parent/pom.xml | 5 +- proxy-parent/proxy-extension/pom.xml | 2 +- proxy-parent/proxy_pkg/pom.xml | 13 +- readme.md | 6 +- reporting/api/pom.xml | 2 +- reporting/db/pom.xml | 10 +- .../databases/AmazonDynamoDatabaseDocApi.java | 320 ++++++------ .../databases/CloudWatchDataSource.java | 109 ++-- .../persistence/databases/S3Datasource.java | 59 ++- .../reporting/db/DatabaseResultsReporter.java | 1 - .../databases/AmazonDynamoDatabaseTest.java | 23 +- reporting/local/pom.xml | 2 +- reporting/pom.xml | 2 +- reporting/rest/pom.xml | 2 +- rest/api/agent/pom.xml | 2 +- rest/api/automation/pom.xml | 2 +- .../model/v1/automation/CreateJobRequest.java | 22 +- .../v1/automation/AutomationService.java | 18 +- rest/api/cloud/pom.xml | 2 +- rest/api/common/pom.xml | 2 +- rest/api/datafile/pom.xml | 2 +- rest/api/filter/pom.xml | 2 +- rest/api/job/pom.xml | 2 +- rest/api/pom.xml | 2 +- rest/api/project/pom.xml | 2 +- rest/api/reporting/pom.xml | 2 +- rest/api/script/pom.xml | 2 +- rest/api/user/pom.xml | 2 +- rest/client/agent/pom.xml | 2 +- rest/client/automation/pom.xml | 2 +- rest/client/cloud/pom.xml | 2 +- rest/client/common/pom.xml | 2 +- rest/client/datafile/pom.xml | 2 +- rest/client/filter/pom.xml | 2 +- rest/client/job/pom.xml | 2 +- rest/client/pom.xml | 2 +- rest/client/project/pom.xml | 2 +- rest/client/reporting/pom.xml | 2 +- rest/client/script/pom.xml | 2 +- rest/client/user/pom.xml | 2 +- rest/pom.xml | 2 +- rest/service/agent/pom.xml | 2 +- rest/service/automation/pom.xml | 2 +- .../v1/automation/AutomationServiceV1.java | 65 +-- rest/service/cloud/pom.xml | 2 +- .../service/impl/v1/cloud/JobController.java | 10 +- rest/service/common/pom.xml | 2 +- rest/service/datafile/pom.xml | 2 +- rest/service/filter/pom.xml | 2 +- rest/service/job/pom.xml | 2 +- rest/service/pom.xml | 2 +- rest/service/project/pom.xml | 2 +- rest/service/reporting/pom.xml | 2 +- rest/service/script/pom.xml | 2 +- .../impl/v1/script/ScriptServiceV1.java | 104 ++-- rest/service/user/pom.xml | 2 +- script_processor/pom.xml | 2 +- .../tank/script/util/ScriptServiceUtil.java | 32 ++ search/document_util/pom.xml | 2 +- search/lucene_indexer/pom.xml | 2 +- search/pom.xml | 2 +- search/script_search/pom.xml | 2 +- tank_common/pom.xml | 2 +- tank_vmManager/pom.xml | 25 +- .../intuit/tank/vmManager/VMTrackerImpl.java | 2 - .../environment/IEnvironmentInstance.java | 2 +- .../vmManager/environment/JobRequest.java | 1 - .../amazon/AmazonDataConverter.java | 38 +- .../environment/amazon/AmazonInstance.java | 360 ++++++------- .../environment/amazon/AmazonS3.java | 122 ++--- .../amazon/CloudwatchInstance.java | 166 +++--- .../amazon/AmazonDataConverterTest.java | 13 +- test_support/pom.xml | 17 +- tools/.pom.xml.un~ | Bin 1524 -> 0 bytes tools/agent_debugger/pom.xml | 2 +- .../tank/tools/debugger/ActionProducer.java | 7 +- .../tank/tools/debugger/SelectDialog.java | 13 +- .../main/java/org/fife/io/UnicodeWriter.java | 37 +- .../org/fife/ui/rtextarea/IconRowHeader.java | 1 - tools/agent_debugger_pkg/pom.xml | 19 +- tools/jenkins_plugin/pom.xml | 2 +- tools/pom.xml | 2 +- tools/script_engine/pom.xml | 2 +- tools/script_filter/pom.xml | 11 +- .../tank/tools/script/ScriptFilterRunner.java | 3 +- .../tank/tools/script/SelectDialog.java | 48 ++ tools/script_filter_pkg/pom.xml | 7 +- web/pom.xml | 2 +- web/web_support/pom.xml | 14 +- .../com/intuit/tank/admin/UserLoader.java | 1 - .../intuit/tank/auth/TankAuthenticator.java | 14 +- .../com/intuit/tank/job/ActJobNodeBean.java | 7 +- .../intuit/tank/project/ProjectLoader.java | 1 - .../com/intuit/tank/script/LogicTestData.java | 1 - .../com/intuit/tank/script/ScriptBean.java | 1 - .../intuit/tank/script/TankXmlUploadBean.java | 80 +-- .../tank/service/InitializeEnvironment.java | 7 +- .../tank/project/JobDetailFormatterTest.java | 4 +- web/web_ui/enunciate.xml | 2 + web/web_ui/pom.xml | 6 +- 208 files changed, 4238 insertions(+), 1822 deletions(-) delete mode 100644 api/src/main/java/com/intuit/tank/vm/api/enumerated/VMSize.java create mode 100644 api/src/test/java/com/intuit/tank/harness/AmazonUtilTest.java create mode 100644 api/src/test/java/com/intuit/tank/http/AuthSchemeTest.java create mode 100644 api/src/test/java/com/intuit/tank/logging/LogEventTypeTest.java create mode 100644 api/src/test/java/com/intuit/tank/logging/SourceTypeTest.java create mode 100644 api/src/test/java/com/intuit/tank/script/RequestDataTypeTest.java create mode 100644 api/src/test/java/com/intuit/tank/script/TimerActionTest.java delete mode 100644 api/src/test/java/com/intuit/tank/vm/api/enumerated/VMSizeCpTest.java create mode 100644 api/src/test/java/com/intuit/tank/vm/settings/InstanceTagTest.java create mode 100644 api/src/test/java/com/intuit/tank/vm/settings/ModificationTypeTest.java create mode 100644 api/src/test/java/com/intuit/tank/vm/settings/ModifiedEntityMessageTest.java create mode 100644 api/src/test/java/com/intuit/tank/vm/settings/PropertiesFileTest.java create mode 100644 api/src/test/java/com/intuit/tank/vm/settings/ReportingConfigTest.java create mode 100644 api/src/test/resources/sampleProp.properties create mode 100644 assets/cleanup_prior_to_3.0.0.sql create mode 100644 assets/settings-all-in-one.xml create mode 100644 assets/tankIcon.png create mode 100644 aws-config/tank_agent.yml create mode 100644 aws-config/tank_controller.yml create mode 100644 aws-config/tank_instanceProfiles.yml create mode 100644 data_access/src/test/java/com/intuit/tank/dao/BaseDaoTest.java create mode 100644 data_access/src/test/java/com/intuit/tank/dao/DataFileDaoTest.java create mode 100644 data_access/src/test/java/com/intuit/tank/dao/FilterGroupDaoTest.java create mode 100644 data_access/src/test/java/com/intuit/tank/dao/GroupDaoTest.java create mode 100644 data_access/src/test/java/com/intuit/tank/dao/JobNotificationDaoTest.java create mode 100644 data_access/src/test/java/com/intuit/tank/dao/JobQueueDaoTest.java create mode 100644 data_access/src/test/java/com/intuit/tank/dao/JobRegionDaoTest.java create mode 100644 data_access/src/test/java/com/intuit/tank/dao/PeriodicDataTest.java create mode 100644 data_access/src/test/java/com/intuit/tank/dao/UserDaoTest.java create mode 100644 data_model/src/test/java/com/intuit/tank/project/ProjectDTOTest.java create mode 100644 data_model/src/test/java/com/intuit/tank/project/TankDefaultRevisionEntityTest.java create mode 100644 data_model/src/test/java/com/intuit/tank/util/DataFileUtilTest.java delete mode 100644 tools/.pom.xml.un~ diff --git a/.travis.yml b/.travis.yml index 882a7a7bf..756b049d0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,8 @@ language: java jdk: - - openjdk8 + - openjdk11 + +env: + - SKIP_METHODTIMER_TEST=true script: mvn clean install -P default,coverage diff --git a/agent/agent_common/pom.xml b/agent/agent_common/pom.xml index 2cdf1e141..127ef4a7f 100644 --- a/agent/agent_common/pom.xml +++ b/agent/agent_common/pom.xml @@ -5,7 +5,7 @@ com.intuit.tank agent-parent - 2.3.4 + 3.0.0 agent-common @@ -36,6 +36,11 @@ org.jdom jdom + + + jakarta.xml.ws + jakarta.xml.ws-api + diff --git a/agent/agent_common/src/main/java/com/intuit/tank/http/xml/GenericXMLHandler.java b/agent/agent_common/src/main/java/com/intuit/tank/http/xml/GenericXMLHandler.java index 554e95867..0310c6e32 100644 --- a/agent/agent_common/src/main/java/com/intuit/tank/http/xml/GenericXMLHandler.java +++ b/agent/agent_common/src/main/java/com/intuit/tank/http/xml/GenericXMLHandler.java @@ -33,6 +33,7 @@ import org.jdom2.JDOMException; import org.jdom2.Namespace; import org.jdom2.input.SAXBuilder; +import org.jdom2.input.sax.XMLReaders; import org.jdom2.output.XMLOutputter; import org.jdom2.xpath.XPath; import org.xml.sax.InputSource; @@ -72,7 +73,7 @@ public GenericXMLHandler(File xmlFile) { this.xmlFile = xmlFile; this.xmlDocument = new org.jdom2.Document(); SAXBuilder builder = new SAXBuilder(); - builder.setValidation(false); + builder.setXMLReaderFactory(XMLReaders.NONVALIDATING); this.xmlDocument = builder.build(this.xmlFile); this.namespaces = new HashMap(); } catch (Exception ex) { @@ -94,7 +95,7 @@ public GenericXMLHandler(String xmlFile) { this.xmlFile = null; this.xmlDocument = new org.jdom2.Document(); SAXBuilder builder = new SAXBuilder(); - builder.setValidation(false); + builder.setXMLReaderFactory(XMLReaders.NONVALIDATING); // LOG.debug("XML string to load: "+xmlFile); xmlFile = xmlFile.substring(xmlFile.indexOf("<")); this.xmlDocument = builder.build(new StringReader(xmlFile)); @@ -156,6 +157,7 @@ private Element SetElementText(String xPathExpression, int currentNode) throws J if (xPathExists(currentPath)) { if (currentPath.equals(xPathExpression)) { return (org.jdom2.Element) XPath.selectSingleNode(this.xmlDocument, xPathExpression); + } else { return SetElementText(xPathExpression, currentNode + 1); diff --git a/agent/agent_common/src/test/java/com/intuit/tank/http/soap/SOAPRequestTest.java b/agent/agent_common/src/test/java/com/intuit/tank/http/soap/SOAPRequestTest.java index 0638540a7..198aa7ed3 100644 --- a/agent/agent_common/src/test/java/com/intuit/tank/http/soap/SOAPRequestTest.java +++ b/agent/agent_common/src/test/java/com/intuit/tank/http/soap/SOAPRequestTest.java @@ -2,6 +2,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; /** @@ -129,6 +130,7 @@ public void testGetKey_1() * @generatedBy CodePro at 12/16/14 4:29 PM */ @Test + @Disabled public void testSetKey_1() throws Exception { SOAPRequest fixture = new SOAPRequest(null, null); diff --git a/agent/agent_standalone/pom.xml b/agent/agent_standalone/pom.xml index 93a5cd7ab..cdacc09f6 100755 --- a/agent/agent_standalone/pom.xml +++ b/agent/agent_standalone/pom.xml @@ -5,7 +5,7 @@ com.intuit.tank agent-parent - 2.3.4 + 3.0.0 agent-standalone diff --git a/agent/agent_standalone_pkg/pom.xml b/agent/agent_standalone_pkg/pom.xml index 5fe5ffc32..93b217148 100755 --- a/agent/agent_standalone_pkg/pom.xml +++ b/agent/agent_standalone_pkg/pom.xml @@ -5,7 +5,7 @@ com.intuit.tank agent-parent - 2.3.4 + 3.0.0 agent-standalone-pkg @@ -40,6 +40,7 @@ agent-standalone src/main/assembly/assembly.xml + src/main/assembly/zip-assembly.xml diff --git a/agent/agent_startup/pom.xml b/agent/agent_startup/pom.xml index aaf661531..e47fb6386 100755 --- a/agent/agent_startup/pom.xml +++ b/agent/agent_startup/pom.xml @@ -5,7 +5,7 @@ com.intuit.tank agent-parent - 2.3.4 + 3.0.0 agent-startup diff --git a/agent/agent_startup/src/main/java/com/intuit/tank/agent/AgentStartup.java b/agent/agent_startup/src/main/java/com/intuit/tank/agent/AgentStartup.java index 07737c472..5adafd412 100644 --- a/agent/agent_startup/src/main/java/com/intuit/tank/agent/AgentStartup.java +++ b/agent/agent_startup/src/main/java/com/intuit/tank/agent/AgentStartup.java @@ -24,7 +24,6 @@ import java.util.zip.ZipException; import java.util.zip.ZipInputStream; -import com.amazonaws.util.StringUtils; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.apache.logging.log4j.LogManager; @@ -32,6 +31,7 @@ import com.intuit.tank.harness.AmazonUtil; import com.intuit.tank.vm.common.TankConstants; +import software.amazon.awssdk.utils.StringUtils; public class AgentStartup implements Runnable { private static Logger logger = LogManager.getLogger(AgentStartup.class); @@ -110,7 +110,7 @@ public static void main(String[] args) { controllerBaseUrl = values[1]; } } - if (StringUtils.isNullOrEmpty(controllerBaseUrl)) { + if (StringUtils.isEmpty(controllerBaseUrl)) { controllerBaseUrl = AmazonUtil.getControllerBaseUrl(); } AgentStartup agentStartup = new AgentStartup(controllerBaseUrl); diff --git a/agent/agent_startup_pkg/pom.xml b/agent/agent_startup_pkg/pom.xml index 9d5995e6c..a27c144ad 100755 --- a/agent/agent_startup_pkg/pom.xml +++ b/agent/agent_startup_pkg/pom.xml @@ -5,7 +5,7 @@ com.intuit.tank agent-parent - 2.3.4 + 3.0.0 agent-startup-pkg diff --git a/agent/apiharness/pom.xml b/agent/apiharness/pom.xml index 93889de75..fdbf2701c 100644 --- a/agent/apiharness/pom.xml +++ b/agent/apiharness/pom.xml @@ -5,7 +5,7 @@ com.intuit.tank agent-parent - 2.3.4 + 3.0.0 agent @@ -81,6 +81,11 @@ ${project.version} + + com.sun.xml.bind + jaxb-impl + runtime + org.simpleframework simple-http diff --git a/agent/apiharness/src/main/java/com/intuit/tank/harness/APIMonitor.java b/agent/apiharness/src/main/java/com/intuit/tank/harness/APIMonitor.java index 7c32710be..5ca31cb91 100644 --- a/agent/apiharness/src/main/java/com/intuit/tank/harness/APIMonitor.java +++ b/agent/apiharness/src/main/java/com/intuit/tank/harness/APIMonitor.java @@ -105,17 +105,14 @@ private CloudVmStatus createStatus(WatsAgentStatusResponse agentStatus) { * @return */ private JobStatus calculateJobStatus(WatsAgentStatusResponse agentStatus, JobStatus currentStatus) { - if (APITestHarness.getInstance().getCmd() == WatsAgentCommand.pause) { - return JobStatus.Paused; - } else if (APITestHarness.getInstance().getCmd() == WatsAgentCommand.stop) { - return JobStatus.Stopped; - } else if (APITestHarness.getInstance().getCmd() == WatsAgentCommand.pause_ramp) { - return JobStatus.RampPaused; - } else if ((currentStatus == JobStatus.Unknown || currentStatus == JobStatus.Starting) - && agentStatus.getCurrentNumberUsers() > 0) { - return JobStatus.Running; - } - return currentStatus; + WatsAgentCommand cmd = APITestHarness.getInstance().getCmd(); + return cmd == WatsAgentCommand.pause ? JobStatus.Paused + : cmd == WatsAgentCommand.stop ? JobStatus.Stopped + : cmd == WatsAgentCommand.pause_ramp ? JobStatus.RampPaused + : currentStatus == JobStatus.Unknown + || currentStatus == JobStatus.Starting + && agentStatus.getCurrentNumberUsers() > 0 ? JobStatus.Running + : currentStatus; } public static void setDoMonitor(boolean monitor) { diff --git a/agent/apiharness/src/main/java/com/intuit/tank/harness/APITestHarness.java b/agent/apiharness/src/main/java/com/intuit/tank/harness/APITestHarness.java index 01b3b4520..9bb625e3c 100644 --- a/agent/apiharness/src/main/java/com/intuit/tank/harness/APITestHarness.java +++ b/agent/apiharness/src/main/java/com/intuit/tank/harness/APITestHarness.java @@ -30,7 +30,6 @@ import java.util.Vector; import java.util.concurrent.CountDownLatch; -import com.amazonaws.regions.Regions; import com.google.common.collect.ImmutableMap; import com.intuit.tank.http.TankHttpClient; import org.apache.commons.io.FileUtils; @@ -43,6 +42,7 @@ import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.core.config.LoggerConfig; +import org.apache.logging.log4j.message.ObjectMessage; import com.intuit.tank.AgentServiceClient; import com.intuit.tank.api.model.v1.cloud.CloudVmStatus; @@ -67,7 +67,7 @@ import com.intuit.tank.vm.api.enumerated.WatsAgentCommand; import com.intuit.tank.vm.common.TankConstants; import com.intuit.tank.vm.settings.TankConfig; -import org.apache.logging.log4j.message.ObjectMessage; +import software.amazon.awssdk.regions.internal.util.EC2MetadataUtils; public class APITestHarness { private static Logger LOG = LogManager.getLogger(APITestHarness.class); @@ -141,12 +141,13 @@ public static void main(String[] args) { } HostInfo hostInfo = new HostInfo(); - ThreadContext.put("jobId", getInstance().getAgentRunData().getJobId()); - ThreadContext.put("projectName", getInstance().getAgentRunData().getProjectName()); - ThreadContext.put("instanceId", getInstance().getAgentRunData().getInstanceId()); + ThreadContext.put("jobId", AmazonUtil.getJobId()); + ThreadContext.put("projectName", AmazonUtil.getProjectName()); + ThreadContext.put("instanceId", AmazonUtil.getInstanceId()); ThreadContext.put("publicIp", hostInfo.getPublicIp()); - ThreadContext.put("region", AmazonUtil.getVMRegion().getRegion()); + ThreadContext.put("location", AmazonUtil.getZone()); ThreadContext.put("httpHost", AmazonUtil.getControllerBaseUrl()); + ThreadContext.put("loggingProfile", AmazonUtil.getLoggingProfile().getDisplayName()); getInstance().initializeFromArgs(args); } @@ -302,23 +303,12 @@ private void startHttp(String baseUrl) { if (capacity < 0) { capacity = AmazonUtil.getCapacity(); } - VMRegion region = VMRegion.STANDALONE; - if (AmazonUtil.isInAmazon()) { - region = AmazonUtil.getVMRegion(); - } agentRunData.setJobId(AmazonUtil.getJobId()); agentRunData.setStopBehavior(AmazonUtil.getStopBehavior()); - LogUtil.getLogEvent().setJobId(agentRunData.getJobId()); - ThreadContext.put("jobId", agentRunData.getJobId()); - ThreadContext.put("projectName", agentRunData.getProjectName()); - ThreadContext.put("instanceId", agentRunData.getInstanceId()); - ThreadContext.put("publicIp", hostInfo.getPublicIp()); - ThreadContext.put("region", Regions.getCurrentRegion().getName()); - ThreadContext.put("httpHost", baseUrl); - LOG.info(new ObjectMessage(ImmutableMap.of("Message", "Active Profile" + agentRunData.getActiveProfile().getDisplayName()))); + AgentData data = new AgentData(agentRunData.getJobId(), instanceId, instanceUrl, capacity, - region, AmazonUtil.getZone()); + AmazonUtil.getVMRegion(), AmazonUtil.getZone()); try { AgentTestStartData startData = null; int count = 0; @@ -614,12 +604,8 @@ public CloudVmStatus getInitialStatus() { VMRegion region = VMRegion.STANDALONE; String secGroups = "unknown"; if (AmazonUtil.isInAmazon()) { - try { - region = AmazonUtil.getVMRegion(); - secGroups = AmazonUtil.getMetaData(CloudMetaDataType.security_groups); - } catch (IOException e) { - LOG.warn(new ObjectMessage(ImmutableMap.of("Message", "Error gettting region. using Custom..."))); - } + region = AmazonUtil.getVMRegion(); + secGroups = EC2MetadataUtils.getSecurityGroups().get(0); } status = new CloudVmStatus(instanceId, agentRunData.getJobId(), secGroups, JobStatus.Unknown, VMImageType.AGENT, region, VMStatus.running, @@ -713,9 +699,9 @@ public void checkAgentThreads() { threadGroup.enumerate(threads); int activeThreads = (int) Arrays.stream(threads).filter(Objects::nonNull).filter( t -> t.getState() == Thread.State.TIMED_WAITING || t.getState() == Thread.State.WAITING).count(); - LOG.info(new ObjectMessage(ImmutableMap.of("Message", "Have " + activeThreads + " of " + activeCount + LOG.info(LogUtil.getLogMessage("Have " + activeThreads + " of " + activeCount + " active Threads in thread group " - + threadGroup.getName()))); + + threadGroup.getName())); } if (hasMetSimulationTime()) { // && doneSignal.getCount() != 0) { LOG.info(LogUtil.getLogMessage("Max simulation time has been met and there are " diff --git a/agent/apiharness/src/main/java/com/intuit/tank/harness/logging/ThreadLocalLogEvent.java b/agent/apiharness/src/main/java/com/intuit/tank/harness/logging/ThreadLocalLogEvent.java index 2ba676734..3312e4927 100644 --- a/agent/apiharness/src/main/java/com/intuit/tank/harness/logging/ThreadLocalLogEvent.java +++ b/agent/apiharness/src/main/java/com/intuit/tank/harness/logging/ThreadLocalLogEvent.java @@ -41,8 +41,9 @@ public LogEvent initialValue() { ThreadContext.put("projectName", APITestHarness.getInstance().getAgentRunData().getProjectName()); ThreadContext.put("instanceId", APITestHarness.getInstance().getAgentRunData().getInstanceId()); ThreadContext.put("publicIp", hostInfo.getPublicIp()); - ThreadContext.put("region", AmazonUtil.getVMRegion().getRegion()); + ThreadContext.put("location", AmazonUtil.getZone()); ThreadContext.put("httpHost", AmazonUtil.getControllerBaseUrl()); + ThreadContext.put("loggingProfile", APITestHarness.getInstance().getAgentRunData().getActiveProfile().getDisplayName()); return logEvent; diff --git a/agent/apiharness/src/main/resources/log4j2.xml b/agent/apiharness/src/main/resources/log4j2.xml index ad798746d..ce2ac7b9c 100644 --- a/agent/apiharness/src/main/resources/log4j2.xml +++ b/agent/apiharness/src/main/resources/log4j2.xml @@ -14,8 +14,9 @@ - + + diff --git a/agent/apiharness/src/test/java/com/intuit/tank/common/TPSTest.java b/agent/apiharness/src/test/java/com/intuit/tank/common/TPSTest.java index e0f7395d0..1de5d83b6 100644 --- a/agent/apiharness/src/test/java/com/intuit/tank/common/TPSTest.java +++ b/agent/apiharness/src/test/java/com/intuit/tank/common/TPSTest.java @@ -22,10 +22,6 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import com.amazonaws.ClientConfiguration; -import com.amazonaws.auth.AWSCredentials; -import com.amazonaws.auth.BasicAWSCredentials; -import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClient; import com.intuit.tank.persistence.databases.AmazonDynamoDatabaseDocApi; import com.intuit.tank.persistence.databases.DatabaseKeys; import com.intuit.tank.reporting.api.TPSInfo; @@ -37,19 +33,22 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; +import software.amazon.awssdk.auth.credentials.AwsCredentials; +import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; +import software.amazon.awssdk.services.dynamodb.DynamoDbClient; public class TPSTest { private static final Logger LOG = LogManager.getLogger(TPSTest.class); - private AmazonDynamoDBClient dbclient; + private DynamoDbClient dynamoDbClient; @BeforeAll @Tag(TestGroups.EXPERIMENTAL) public void init() { - ClientConfiguration clientConfig = new ClientConfiguration(); - AWSCredentials credentials = new BasicAWSCredentials(System.getProperty("AWS_KEY_ID"), + AwsCredentials credentials = AwsBasicCredentials.create(System.getProperty("AWS_KEY_ID"), System.getProperty("AWS_KEY")); - dbclient = new AmazonDynamoDBClient(credentials, clientConfig); + dynamoDbClient = DynamoDbClient.builder().credentialsProvider(StaticCredentialsProvider.create(credentials)).build(); } @Test @@ -57,7 +56,7 @@ public void init() { private void sendTps() { TPSInfoContainer tpsInfo = createTPsInfo(); try { - IDatabase db = new AmazonDynamoDatabaseDocApi(dbclient); + IDatabase db = new AmazonDynamoDatabaseDocApi(dynamoDbClient); List items = new ArrayList(); for (TPSInfo info : tpsInfo.getTpsInfos()) { com.intuit.tank.reporting.databases.Item item = createItem(info); diff --git a/agent/apiharness_pkg/pom.xml b/agent/apiharness_pkg/pom.xml index 0a09f24aa..3f4bcf909 100644 --- a/agent/apiharness_pkg/pom.xml +++ b/agent/apiharness_pkg/pom.xml @@ -6,18 +6,15 @@ com.intuit.tank agent-parent - 2.3.4 + 3.0.0 - apiharness pom Apiharness Packaging - - ${project.groupId} agent @@ -25,8 +22,6 @@ - - diff --git a/agent/http_client_3/pom.xml b/agent/http_client_3/pom.xml index 298650e43..a170d641b 100644 --- a/agent/http_client_3/pom.xml +++ b/agent/http_client_3/pom.xml @@ -5,7 +5,7 @@ com.intuit.tank agent-parent - 2.3.4 + 3.0.0 http_client_commons_3 diff --git a/agent/http_client_4/pom.xml b/agent/http_client_4/pom.xml index 0d43e5630..5d8e7680a 100644 --- a/agent/http_client_4/pom.xml +++ b/agent/http_client_4/pom.xml @@ -5,7 +5,7 @@ com.intuit.tank agent-parent - 2.3.4 + 3.0.0 http_client_commons_4 diff --git a/agent/http_client_5/pom.xml b/agent/http_client_5/pom.xml index fffa8b84e..1c7babf38 100644 --- a/agent/http_client_5/pom.xml +++ b/agent/http_client_5/pom.xml @@ -5,7 +5,7 @@ com.intuit.tank agent-parent - 2.3.4 + 3.0.0 http_client_commons_5 diff --git a/agent/pom.xml b/agent/pom.xml index e39d6069e..6f4d1f956 100644 --- a/agent/pom.xml +++ b/agent/pom.xml @@ -6,7 +6,7 @@ com.intuit.tank tank-parent - 2.3.4 + 3.0.0 agent-parent diff --git a/all-in-one.sh b/all-in-one.sh index 02089b194..ea9cbb4f6 100644 --- a/all-in-one.sh +++ b/all-in-one.sh @@ -7,36 +7,49 @@ if [ -z "$1" ] fi mkdir -p $INSTALL_DIR 2>/dev/null echo "Installing all in one in $INSTALL_DIR" -echo "downloading and extracting tomcat 6..." -wget -O /tmp/apache-tomcat.tgz http://archive.apache.org/dist/tomcat/tomcat-6/v6.0.41/bin/apache-tomcat-6.0.41.tar.gz 2>/dev/null +echo "downloading and extracting tomcat 9..." +wget -O /tmp/apache-tomcat.tgz http://archive.apache.org/dist/tomcat/tomcat-9/v9.0.39/bin/apache-tomcat-9.0.39.tar.gz 2>/dev/null tar -zxf /tmp/apache-tomcat.tgz -C $INSTALL_DIR 2>/dev/null rm -f /tmp/apache-tomcat.tgz 2>/dev/null -ln -snf $INSTALL_DIR/apache-tomcat-6.0.41 $INSTALL_DIR/tomcat6 2>/dev/null -mkdir $INSTALL_DIR/tomcat6/db 2>/dev/null -mkdir $INSTALL_DIR/tomcat6/jars 2>/dev/null +ln -snf $INSTALL_DIR/apache-tomcat-9.0.39 $INSTALL_DIR/tomcat 2>/dev/null +mkdir $INSTALL_DIR/tomcat/db 2>/dev/null +mkdir $INSTALL_DIR/tomcat/jars 2>/dev/null echo "downloading and extracting agent-standalone..." -wget -O /tmp/agent-standalone-pkg.zip http://tank-public.s3-website-us-east-1.amazonaws.com/agent-standalone-pkg.zip 2>/dev/null +wget -O /tmp/agent-standalone-pkg.zip https://github.com/intuit/Tank/releases/download/3.0.0/agent-standalone-pkg.zip 2>/dev/null unzip -q -d $INSTALL_DIR /tmp/agent-standalone-pkg 2>/dev/null rm -f /tmp/agent-standalone-pkg 2>/dev/null echo "downloading and extracting support libraries..." -wget -O $INSTALL_DIR/tomcat6/lib/weld-tomcat-support-1.0.1-Final.jar http://central.maven.org/maven2/org/jboss/weld/servlet/weld-tomcat-support/1.0.1-Final/weld-tomcat-support-1.0.1-Final.jar 2>/dev/null -wget -O /$INSTALL_DIR/tomcat6/lib/h2-1.4.187.jar http://repo2.maven.org/maven2/com/h2database/h2/1.4.187/h2-1.4.187.jar 2>/dev/null -wget -O /$INSTALL_DIR/tomcat6/conf/server.xml http://tank-public.s3-website-us-east-1.amazonaws.com/server-all-in-one.xml 2>/dev/null -wget -O /$INSTALL_DIR/tomcat6/settings.xml http://tank-public.s3-website-us-east-1.amazonaws.com/settings-all-in-one.xml 2>/dev/null -wget -O $INSTALL_DIR/tomcat6/conf/context.xml http://tank-public.s3-website-us-east-1.amazonaws.com/context.xml 2>/dev/null +wget -O /$INSTALL_DIR/tomcat/lib/h2-1.4.200.jar https://repo1.maven.org/maven2/com/h2database/h2/1.4.200/h2-1.4.200.jar 2>/dev/null +wget -O /$INSTALL_DIR/tomcat/settings.xml https://github.com/intuit/Tank/blob/master/assets/settings-all-in-one.xml 2>/dev/null echo "downloading and installing tank war file..." -rm -fr $INSTALL_DIR/tomcat6/webapps/docs $INSTALL_DIR/tomcat6/webapps/examples $INSTALL_DIR/tomcat6/webapps/ROOT 2>/dev/null -wget -O $INSTALL_DIR/tomcat6/webapps/ROOT.war http://tank-public.s3-website-us-east-1.amazonaws.com/tank.war 2>/dev/null +rm -fr $INSTALL_DIR/tomcat/webapps/docs $INSTALL_DIR/tomcat/webapps/examples $INSTALL_DIR/tomcat/webapps/ROOT 2>/dev/null +wget -O $INSTALL_DIR/tomcat/webapps/ROOT.war https://github.com/intuit/Tank/releases/download/3.0.0/tank.war 2>/dev/null + +echo "Creating context file at $INSTALL_DIR/start.sh ..." +cat << EOF > $INSTALL_DIR/tomcat/conf/context.xml +#!/bin/bash + + + + + +EOF +chmod 644 $INSTALL_DIR/tomcat/conf/context.xml 2>/dev/null echo "Creating start script at $INSTALL_DIR/start.sh ..." cat << EOF > $INSTALL_DIR/start.sh #!/bin/bash echo "Starting Tomcat..." export JAVA_OPTS="-Xms256m -Xmx1024m -XX:PermSize=64m -XX:MaxPermSize=128m -Djava.awt.headless=true" -cd $INSTALL_DIR/tomcat6/ +cd $INSTALL_DIR/tomcat/ bin/startup.sh &> /dev/null echo "Tomcat started." cd $INSTALL_DIR/agent-standalone/ @@ -50,7 +63,7 @@ echo "Creating stop script at $INSTALL_DIR/stop.sh..." cat << EOF > $INSTALL_DIR/stop.sh #!/bin/bash echo "Stopping Tomcat..." -$INSTALL_DIR/tomcat6/bin/shutdown.sh &> /dev/null +$INSTALL_DIR/tomcat/bin/shutdown.sh &> /dev/null echo "Stopping Agent..." kill $(ps aux | grep '[a]gent-standalone' | awk '{print $2}') &> /dev/null kill $(ps aux | grep '[a]piharness' | awk '{print $2}') &> /dev/null diff --git a/api/pom.xml b/api/pom.xml index 2cd764b95..b8d48d2be 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -6,7 +6,7 @@ com.intuit.tank tank-parent - 2.3.4 + 3.0.0 api @@ -61,8 +61,12 @@ - com.amazonaws - aws-java-sdk-s3 + software.amazon.awssdk + s3 + + + software.amazon.awssdk + apache-client diff --git a/api/src/main/java/com/intuit/tank/harness/AmazonUtil.java b/api/src/main/java/com/intuit/tank/harness/AmazonUtil.java index 320bf1f48..620ed184d 100644 --- a/api/src/main/java/com/intuit/tank/harness/AmazonUtil.java +++ b/api/src/main/java/com/intuit/tank/harness/AmazonUtil.java @@ -19,25 +19,24 @@ import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.URL; -import java.util.HashMap; +import java.nio.charset.StandardCharsets; +import java.util.Collections; import java.util.Map; +import java.util.stream.Collectors; import javax.annotation.Nonnull; +import com.google.common.base.Splitter; import com.google.common.collect.ImmutableMap; -import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import com.intuit.tank.logging.LoggingProfile; import com.intuit.tank.vm.api.enumerated.VMRegion; -import com.intuit.tank.vm.api.enumerated.VMSize; import com.intuit.tank.vm.common.TankConstants; import org.apache.logging.log4j.message.ObjectMessage; -import static java.nio.charset.StandardCharsets.UTF_8; - /** * * AmazonUtil @@ -55,7 +54,6 @@ public class AmazonUtil { public static VMRegion getVMRegion() { try { String zone = getMetaData(CloudMetaDataType.zone); - LOG.info(new ObjectMessage(ImmutableMap.of("Message", "Running in zone " + zone))); return VMRegion.getRegionFromZone(zone); } catch (IOException ioe) { LOG.warn(new ObjectMessage(ImmutableMap.of("Message","Error getting region. using CUSTOM..."))); @@ -84,27 +82,20 @@ public static boolean isInAmazon() { */ public static String getZone() { try { - String zone = getMetaData(CloudMetaDataType.zone); - LOG.info(new ObjectMessage(ImmutableMap.of("Message","Running in zone " + zone))); - return zone; - } catch (Exception e) { + return getMetaData(CloudMetaDataType.zone); + } catch (IOException e) { LOG.info(new ObjectMessage(ImmutableMap.of("Message","cannot determine zone"))); } return "unknown"; } public static String getPublicHostName() throws IOException { - String ret = null; try { - ret = getMetaData(CloudMetaDataType.public_hostname); - } catch (Exception e) { - LOG.debug(new ObjectMessage(ImmutableMap.of("Message","Failed getting public host: " + e))); - } - if (StringUtils.isBlank(ret)) { - //LOG.info("getting local_ipv4..."); - ret = getMetaData(CloudMetaDataType.local_ipv4); + return getMetaData(CloudMetaDataType.public_hostname); + } catch (IOException e) { + // returns private IP because private hostname is unresolvable + return getMetaData(CloudMetaDataType.local_ipv4); } - return ret; } /** @@ -114,35 +105,11 @@ public static String getPublicHostName() throws IOException { * @throws IOException */ public static String getPublicIp() throws IOException { - return getMetaData(CloudMetaDataType.public_ipv4); - } - - /** - * - * @return - */ - public static String getAWSKeyFromUserData() { - String ret = null; try { - ret = getUserDataAsMap().get(TankConstants.KEY_AWS_SECRET_KEY); + return getMetaData(CloudMetaDataType.public_ipv4); } catch (IOException e) { - LOG.warn(new ObjectMessage(ImmutableMap.of("Message", "Error getting key: " + e.toString()))); + return getMetaData(CloudMetaDataType.local_ipv4); } - return ret; - } - - /** - * - * @return - */ - public static String getAWSKeyIdFromUserData() { - String ret = null; - try { - ret = getUserDataAsMap().get(TankConstants.KEY_AWS_SECRET_KEY_ID); - } catch (IOException e) { - LOG.warn(new ObjectMessage(ImmutableMap.of("Message", "Error getting key ID: " + e.toString()))); - } - return ret; } /** @@ -168,10 +135,8 @@ public static String getInstanceId() { * if there is an error communicating with the amazon cloud. */ @Nonnull - public static VMSize getInstanceType() throws IOException { - String metaData = getMetaData(CloudMetaDataType.instance_type); - VMSize ret = VMSize.fromRepresentation(metaData); - return ret != null ? ret : VMSize.HighCPUExtraLarge; + public static String getInstanceType() throws IOException { + return getMetaData(CloudMetaDataType.instance_type); } /** @@ -183,8 +148,7 @@ public static VMSize getInstanceType() throws IOException { */ @Nonnull public static String getMetaData(CloudMetaDataType metaData) throws IOException { - InputStream inputStream = getInputStream(BASE + META_DATA + "/" + metaData.getKey()); - return convertStreamToString(inputStream); + return getResponseString(BASE + META_DATA + "/" + metaData.getKey()); } /** @@ -194,168 +158,98 @@ public static String getMetaData(CloudMetaDataType metaData) throws IOException * @throws IOException */ public static String getUserDataAsString() throws IOException { - String result = null; - InputStream inputStream = getInputStream(BASE + USER_DATA); - result = convertStreamToString(inputStream); - return result; + return getResponseString(BASE + USER_DATA); } /** * gets the job id form user data - * + * * @return */ public static String getJobId() { - String ret = null; - try { - ret = getUserDataAsMap().get(TankConstants.KEY_JOB_ID); - } catch (IOException e) { - LOG.warn(new ObjectMessage(ImmutableMap.of("Message", "Error getting job ID: " + e.toString()))); - } - return ret != null ? ret : "unknown"; + String result = getUserDataAsMap().get(TankConstants.KEY_JOB_ID); + return StringUtils.isNotEmpty(result) + ? result + : "unknown"; } /** * gets the project name for user data - * + * * @return */ public static String getProjectName() { - String ret = null; - try { - ret = getUserDataAsMap().get(TankConstants.KEY_PROJECT_NAME); - } catch (IOException e) { - LOG.warn(new ObjectMessage(ImmutableMap.of("Message", "Error getting Project Name: " + e.toString()))); - } - return ret != null ? ret : "unknown"; + String result = getUserDataAsMap().get(TankConstants.KEY_PROJECT_NAME); + return StringUtils.isNotEmpty(result) + ? result + : "unknown"; } /** * gets logging profile form user data - * + * * @return LoggingProfile */ public static LoggingProfile getLoggingProfile() { - LoggingProfile ret = LoggingProfile.STANDARD; - try { - String lp = getUserDataAsMap().get(TankConstants.KEY_LOGGING_PROFILE); - if (lp != null) { - ret = LoggingProfile.valueOf(lp); - } - } catch (Exception e) { - LOG.warn(new ObjectMessage(ImmutableMap.of("Message", "Error getting LoggingProfile: " + e.toString()))); - } - return ret; + String result = getUserDataAsMap().get(TankConstants.KEY_LOGGING_PROFILE); + return StringUtils.isNotEmpty(result) + ? LoggingProfile.valueOf(result) + : LoggingProfile.STANDARD; } public static int getCapacity() { - int ret = 4000; - try { - String lp = getUserDataAsMap().get(TankConstants.KEY_NUM_USERS_PER_AGENT); - if (lp != null) { - ret = Integer.valueOf(lp); - } - } catch (Exception e) { - LOG.warn(new ObjectMessage(ImmutableMap.of("Message", "Error getting capacity: " + e.toString()))); - } - return ret; + String result = getUserDataAsMap().get(TankConstants.KEY_NUM_USERS_PER_AGENT); + return StringUtils.isNotEmpty(result) + ? Integer.parseInt(result) + : 4000; } /** * gets stop behavior form user data. - * + * * @return Stopbehavior */ public static StopBehavior getStopBehavior() { - StopBehavior ret = StopBehavior.END_OF_SCRIPT_GROUP; - try { - String sb = getUserDataAsMap().get(TankConstants.KEY_STOP_BEHAVIOR); - if (sb != null) { - ret = StopBehavior.valueOf(sb); - } - } catch (Exception e) { - LOG.warn(new ObjectMessage(ImmutableMap.of("Message", "Error getting StopBehavior: " + e.toString()))); - } - return ret; + String result = getUserDataAsMap().get(TankConstants.KEY_STOP_BEHAVIOR); + return StringUtils.isNotEmpty(result) + ? StopBehavior.valueOf(result) + : StopBehavior.END_OF_SCRIPT_GROUP; } /** * gets if we are using EIP from user data - * + * * @return */ public static boolean usingEip() { - boolean ret = false; - try { - ret = getUserDataAsMap().get(TankConstants.KEY_USING_BIND_EIP) != null; - } catch (IOException e) { - LOG.warn(new ObjectMessage(ImmutableMap.of("Message", "Error getting is using EIP: " + e.toString()))); - } - return ret; + return StringUtils.isNotEmpty(getUserDataAsMap().get(TankConstants.KEY_USING_BIND_EIP)); } /** * gewts controller base form user data - * + * * @return */ public static String getControllerBaseUrl() { - String ret = null; - try { - ret = getUserDataAsMap().get(TankConstants.KEY_CONTROLLER_URL); - } catch (IOException e) { - LOG.warn(new ObjectMessage(ImmutableMap.of("Message", "Error getting controller url: " + e.toString()))); - } - return ret != null ? ret : "http://localhost:8080/"; + String result = getUserDataAsMap().get(TankConstants.KEY_CONTROLLER_URL); + return StringUtils.isNotEmpty(result) + ? result + : "http://localhost:8080/"; } /** * Gets the user data associated with this instance. * * @return the user data as a Map - * @throws IOException */ - public static Map getUserDataAsMap() throws IOException { - Map result = null; - InputStream inputStream = getInputStream(BASE + USER_DATA); - result = convertStreamToMap(inputStream); - return result; - } - - private static Map convertStreamToMap(InputStream is) throws IOException { - Map result = new HashMap(); - if (is != null) { - try { - BufferedReader r = new BufferedReader(new InputStreamReader(is)); - String s = r.readLine(); - while (s != null) { - String[] pair = s.split("=", 2); - if (pair.length == 2) { - result.put(pair[0], pair[1]); - } - s = r.readLine(); - } - } finally { - try { - is.close(); - } catch (IOException e) {} - } - } - return result; - } - - private static String convertStreamToString(InputStream is) throws IOException { - if (is != null) { - try { - return IOUtils.toString(is, UTF_8); - } finally { - try { - is.close(); - } catch (IOException e) {} + public static Map getUserDataAsMap() { + try { + String userData = getResponseString(BASE + USER_DATA); + if (StringUtils.isNotEmpty(userData)) { + return Splitter.on(System.getProperty("line.separator")).withKeyValueSeparator("=").split(userData); } - } else { - return ""; - } + } catch (IllegalArgumentException | IOException e) { } + return Collections.emptyMap(); } /** @@ -363,11 +257,13 @@ private static String convertStreamToString(InputStream is) throws IOException { * @return * @throws IOException */ - private static InputStream getInputStream(String url) throws IOException { + private static String getResponseString(String url) throws IOException { HttpURLConnection con = (HttpURLConnection) new URL(url).openConnection(); con.setRequestMethod("GET"); con.setConnectTimeout(3000); - return con.getInputStream(); + InputStream is = con.getInputStream(); + return new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8)) + .lines() + .collect(Collectors.joining(System.getProperty("line.separator"))); } - } diff --git a/api/src/main/java/com/intuit/tank/harness/StopBehavior.java b/api/src/main/java/com/intuit/tank/harness/StopBehavior.java index 04083ac14..3920adbe1 100644 --- a/api/src/main/java/com/intuit/tank/harness/StopBehavior.java +++ b/api/src/main/java/com/intuit/tank/harness/StopBehavior.java @@ -43,13 +43,12 @@ public String getDescription() { } public static StopBehavior fromString(String stopBehavior) { - StopBehavior ret = StopBehavior.END_OF_SCRIPT_GROUP; try { - ret = valueOf(stopBehavior); + return valueOf(stopBehavior); } catch (Exception e) { // bad name return default + return StopBehavior.END_OF_SCRIPT_GROUP; } - return ret; } } diff --git a/api/src/main/java/com/intuit/tank/storage/S3FileStorage.java b/api/src/main/java/com/intuit/tank/storage/S3FileStorage.java index 2ec5290ab..72203bc2f 100644 --- a/api/src/main/java/com/intuit/tank/storage/S3FileStorage.java +++ b/api/src/main/java/com/intuit/tank/storage/S3FileStorage.java @@ -1,37 +1,41 @@ package com.intuit.tank.storage; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.io.Serializable; import java.util.ArrayList; import java.util.List; -import java.util.Map.Entry; import java.util.zip.GZIPInputStream; -import com.amazonaws.auth.AWSStaticCredentialsProvider; -import com.amazonaws.services.s3.AmazonS3; -import com.amazonaws.services.s3.AmazonS3ClientBuilder; -import com.amazonaws.util.IOUtils; import org.apache.commons.io.FilenameUtils; import org.apache.commons.lang3.StringUtils; +import org.apache.http.client.utils.URIBuilder; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import com.amazonaws.ClientConfiguration; -import com.amazonaws.auth.BasicAWSCredentials; -import com.amazonaws.services.s3.model.AccessControlList; -import com.amazonaws.services.s3.model.AmazonS3Exception; -import com.amazonaws.services.s3.model.Bucket; -import com.amazonaws.services.s3.model.ListObjectsRequest; -import com.amazonaws.services.s3.model.ObjectListing; -import com.amazonaws.services.s3.model.ObjectMetadata; -import com.amazonaws.services.s3.model.S3Object; -import com.amazonaws.services.s3.model.S3ObjectInputStream; -import com.amazonaws.services.s3.model.S3ObjectSummary; import com.intuit.tank.vm.settings.CloudCredentials; import com.intuit.tank.vm.settings.CloudProvider; import com.intuit.tank.vm.settings.TankConfig; +import org.apache.tomcat.util.http.fileupload.IOUtils; +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; +import software.amazon.awssdk.auth.credentials.AwsCredentials; +import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; +import software.amazon.awssdk.core.sync.RequestBody; +import software.amazon.awssdk.http.apache.ApacheHttpClient; +import software.amazon.awssdk.http.apache.ProxyConfiguration; +import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.awssdk.services.s3.S3ClientBuilder; +import software.amazon.awssdk.services.s3.model.BucketAlreadyExistsException; +import software.amazon.awssdk.services.s3.model.CreateBucketRequest; +import software.amazon.awssdk.services.s3.model.DeleteObjectRequest; +import software.amazon.awssdk.services.s3.model.GetObjectRequest; +import software.amazon.awssdk.services.s3.model.HeadObjectRequest; +import software.amazon.awssdk.services.s3.model.ListObjectsRequest; +import software.amazon.awssdk.services.s3.model.ListObjectsResponse; +import software.amazon.awssdk.services.s3.model.NoSuchKeyException; +import software.amazon.awssdk.services.s3.model.PutObjectRequest; +import software.amazon.awssdk.services.s3.model.S3Exception; +import software.amazon.awssdk.services.s3.model.S3Object; +import software.amazon.awssdk.services.s3.model.ServerSideEncryption; /** * FileStorage that writes to the file system. @@ -49,7 +53,7 @@ public class S3FileStorage implements FileStorage, Serializable { private boolean compress = true; private boolean encrypt = true; - private AmazonS3 s3Client; + private S3Client s3Client; /** * @param bucketName @@ -61,32 +65,28 @@ public S3FileStorage(String bucketName, boolean compress) { this.compress = compress; try { TankConfig tankConfig = new TankConfig(); - encrypt = tankConfig.isS3EncryptionEnabled(); + this.encrypt = tankConfig.isS3EncryptionEnabled(); CloudCredentials creds = tankConfig.getVmManagerConfig().getCloudCredentials(CloudProvider.amazon); - ClientConfiguration config = new ClientConfiguration(); - if (StringUtils.isNotBlank(System.getProperty("http.proxyHost"))) { + S3ClientBuilder s3ClientBuilder = S3Client.builder(); + if (creds != null && StringUtils.isNotBlank(System.getProperty("http.proxyHost"))) { try { - config.setProxyHost(System.getProperty("http.proxyHost")); + URIBuilder uriBuilder = new URIBuilder().setHost(System.getProperty("http.proxyHost")); if (StringUtils.isNotBlank(System.getProperty("http.proxyPort"))) { - config.setProxyPort(Integer.parseInt(System.getProperty("http.proxyPort"))); + uriBuilder.setPort(Integer.parseInt(System.getProperty("http.proxyPort"))); } + ApacheHttpClient.Builder httpClientBuilder = ApacheHttpClient.builder() + .proxyConfiguration( + ProxyConfiguration.builder().endpoint(uriBuilder.build()).build()); + s3ClientBuilder.httpClientBuilder(httpClientBuilder); } catch (NumberFormatException e) { LOG.error("invalid proxy setup."); } - } - assert creds != null; - if (StringUtils.isNotBlank(creds.getKeyId()) && StringUtils.isNotBlank(creds.getKey())) { - BasicAWSCredentials credentials = new BasicAWSCredentials(creds.getKeyId(), creds.getKey()); - this.s3Client = AmazonS3ClientBuilder.standard() - .withCredentials(new AWSStaticCredentialsProvider(credentials)) - .withClientConfiguration(config) - .build(); - } else { - this.s3Client = AmazonS3ClientBuilder.standard() - .withClientConfiguration(config) - .build(); + if (creds != null && StringUtils.isNotBlank(creds.getKeyId()) && StringUtils.isNotBlank(creds.getKey())) { + AwsCredentials credentials = AwsBasicCredentials.create(creds.getKeyId(), creds.getKey()); + s3ClientBuilder.credentialsProvider(StaticCredentialsProvider.create(credentials)); } + s3Client = s3ClientBuilder.build(); createBucket(bucketName); } catch (Exception ex) { LOG.error(ex.getMessage(), ex); @@ -107,74 +107,63 @@ private void parseBucketName(String name) { @Override public void storeFileData(FileData fileData, InputStream in) { - String path = FilenameUtils.separatorsToUnix(FilenameUtils.normalize(extraPath + fileData.getPath() + "/" + fileData.getFileName())); - path = StringUtils.stripStart(path, "/"); + String key = FilenameUtils.separatorsToUnix( + FilenameUtils.normalize(extraPath + fileData.getPath() + "/" + fileData.getFileName())); + key = StringUtils.stripStart(key, "/"); try { - ObjectMetadata metaData = new ObjectMetadata(); + PutObjectRequest.Builder request = PutObjectRequest.builder().bucket(bucketName).key(key); if (encrypt) { - metaData.setSSEAlgorithm(ObjectMetadata.AES_256_SERVER_SIDE_ENCRYPTION); + request.serverSideEncryption(ServerSideEncryption.AES256); } if (fileData.getAttributes() != null) { - for (Entry entry : fileData.getAttributes().entrySet()) { - metaData.addUserMetadata(entry.getKey(), entry.getValue()); - } + request.metadata(fileData.getAttributes()); } if (compress) { in = new GZIPInputStream(in); } - s3Client.putObject(bucketName, path, in, metaData); + s3Client.putObject(request.build(), RequestBody.fromInputStream(in, in.available())); } catch (Exception e) { LOG.error("Error storing file: " + e, e); throw new RuntimeException(e); } finally { - IOUtils.closeQuietly(in, null); + IOUtils.closeQuietly(in); } } @Override public InputStream readFileData(FileData fileData) { - String path = FilenameUtils.separatorsToUnix(FilenameUtils.normalize(extraPath + fileData.getPath() + "/" + fileData.getFileName())); - path = StringUtils.stripStart(path, "/"); - InputStream ret = null; - try { - S3Object object = s3Client.getObject(bucketName, path); - if (object != null) { - ret = object.getObjectContent(); - if (compress) { - ret = new GZIPInputStream(ret); - } - } - } catch (Exception e) { - LOG.error("Error getting File: " + e, e); - throw new RuntimeException(e); - } - return ret; + String key = FilenameUtils.separatorsToUnix( + FilenameUtils.normalize(extraPath + fileData.getPath() + "/" + fileData.getFileName())); + key = StringUtils.stripStart(key, "/"); + return s3Client.getObject(GetObjectRequest.builder().bucket(bucketName).key(key).build()); } private void createBucket(String bucketName) { - AccessControlList configuration = null; try { - configuration = s3Client.getBucketAcl(bucketName); - } catch (Exception e) { - LOG.info("Bucket " + bucketName + " does not exist."); - } - if (configuration == null) { - Bucket bucket = s3Client.createBucket(bucketName); - LOG.info("Created bucket " + bucket.getName() + " at " + bucket.getCreationDate()); + s3Client.createBucket(CreateBucketRequest.builder().bucket(bucketName).build()); + LOG.info("Created bucket " + bucketName + " at " + "now"); + } catch (BucketAlreadyExistsException baee) {//Good + } catch (S3Exception e) { + LOG.error("Error creating bucket: " + e, e); } } @Override public boolean exists(FileData fileData) { boolean ret = true; - String path = FilenameUtils.separatorsToUnix(FilenameUtils.normalize(extraPath + fileData.getPath() + "/" + fileData.getFileName())); - path = StringUtils.stripStart(path, "/"); + String key = FilenameUtils.separatorsToUnix( + FilenameUtils.normalize(extraPath + fileData.getPath() + "/" + fileData.getFileName())); + key = StringUtils.stripStart(key, "/"); try { - s3Client.getObjectMetadata(bucketName, path); - } catch (AmazonS3Exception e) { - ret = false; + s3Client.headObject(HeadObjectRequest.builder().bucket(bucketName).key(key).build()); + } catch (NoSuchKeyException e) { + return false; + } catch (S3Exception e) { + LOG.error("Error Checking existence of S3 object: " + e, e); + return false; + } - return ret; + return true; } @Override @@ -186,20 +175,14 @@ public List listFileData(String path) { prefix = prefix + "/"; } prefix = StringUtils.removeStart(prefix, "/"); - ListObjectsRequest listObjectsRequest = new ListObjectsRequest().withBucketName(bucketName).withPrefix(prefix); - listObjectsRequest.setDelimiter("/"); - ObjectListing objectListing; - do { - objectListing = s3Client.listObjects(listObjectsRequest); - for (S3ObjectSummary objectSummary : objectListing.getObjectSummaries()) { - String fileName = FilenameUtils.getName(FilenameUtils.normalize(objectSummary.getKey())); - if (StringUtils.isNotBlank(fileName)) { - ret.add(new FileData(path, fileName)); - } + ListObjectsResponse response = s3Client.listObjects(ListObjectsRequest.builder().bucket(bucketName).prefix(prefix).delimiter("/").build()); + for (S3Object object : response.contents()) { + String fileName = FilenameUtils.getName(FilenameUtils.normalize(object.key())); + if (StringUtils.isNotBlank(fileName)) { + ret.add(new FileData(path, fileName)); } - listObjectsRequest.setMarker(objectListing.getNextMarker()); - } while (objectListing.isTruncated()); - } catch (AmazonS3Exception e) { + } + } catch (S3Exception e) { LOG.error("Error Listing Files: " + e, e); throw new RuntimeException(e); } @@ -208,11 +191,11 @@ public List listFileData(String path) { @Override public boolean delete(FileData fileData) { - String path = FilenameUtils.separatorsToUnix(FilenameUtils.normalize(fileData.getPath() + "/" + fileData.getFileName())); - path = StringUtils.stripStart(path, "/"); + String key = FilenameUtils.separatorsToUnix(FilenameUtils.normalize(fileData.getPath() + "/" + fileData.getFileName())); + key = StringUtils.stripStart(key, "/"); try { - s3Client.deleteObject(bucketName, path); - } catch (AmazonS3Exception e) { + s3Client.deleteObject(DeleteObjectRequest.builder().bucket(bucketName).key(key).build()); + } catch (S3Exception e) { return false; } return true; diff --git a/api/src/main/java/com/intuit/tank/vm/api/enumerated/VMRegion.java b/api/src/main/java/com/intuit/tank/vm/api/enumerated/VMRegion.java index 40dac7da3..007b3fd5c 100644 --- a/api/src/main/java/com/intuit/tank/vm/api/enumerated/VMRegion.java +++ b/api/src/main/java/com/intuit/tank/vm/api/enumerated/VMRegion.java @@ -64,7 +64,7 @@ public String getName() { * @return */ public static VMRegion getRegionFromZone(String zone) { - return Arrays.stream(VMRegion.values()).filter(vmr -> zone.toLowerCase().startsWith(vmr.region.toLowerCase())).findFirst().orElse(VMRegion.US_EAST); + return Arrays.stream(VMRegion.values()).filter(vmr -> zone.toLowerCase().startsWith(vmr.region.toLowerCase())).findFirst().orElse(VMRegion.STANDALONE); } /** diff --git a/api/src/main/java/com/intuit/tank/vm/api/enumerated/VMSize.java b/api/src/main/java/com/intuit/tank/vm/api/enumerated/VMSize.java deleted file mode 100644 index f3b3a5b9e..000000000 --- a/api/src/main/java/com/intuit/tank/vm/api/enumerated/VMSize.java +++ /dev/null @@ -1,60 +0,0 @@ -package com.intuit.tank.vm.api.enumerated; - -/* - * #%L - * Intuit Tank Api - * %% - * Copyright (C) 2011 - 2015 Intuit Inc. - * %% - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * #L% - */ - -/* - * Large : 4 ECUs, 2 Cores, 7.5 GB - * ExtraLarge : 8 ECUs, 4 Cores, 15 GB - * HighMemoryExtraLarge : 6.5 ECUs, 2 Cores, 17.1 GB - * HighMemoryDoubleExtraLarge : 13 ECUs, 4 Cores, 34.2 GB - * HighMemoryQuadrupleExtraLarge : 26 ECUs, 8 Cores, 68.4 GB - * HighCPUExtraLarge : 20 ECUs, 8 Cores, 7 GB - */ - -import java.util.Arrays; - -public enum VMSize { - Micro("t1.micro"), // $0.020 / hour - Small("m1.small"), // $0.080 / hour - Medium("m1.medium"), // $0.160 / hour - Large("m1.large"), // $0.32 / hour - ExtraLarge("m1.xlarge"), // $0.640 / hour - HighMemoryExtraLarge("m2.xlarge"), // $0.450 / hour - HighMemoryDoubleExtraLarge("m2.2xlarge"), // $0.900 / hour - HighMemoryQuadrupleExtraLarge("m2.4xlarge"), // $1.800 / hour - HighCPUMedium("c1.medium"), // $0.165 / hour - HighCPUExtraLarge("c1.xlarge"), // $0.660 / hour - HighIOExtraLarge("hi1.4xlarge")// $3.100 / hour - ; - - private String representation; - - /** - * @param representation - */ - private VMSize(String representation) { - this.representation = representation; - } - - /** - * @return the representation - */ - public String getRepresentation() { - return representation; - } - - public static VMSize fromRepresentation(String representation) { - return Arrays.stream(VMSize.values()).filter(s -> s.representation.equalsIgnoreCase(representation)).findFirst().orElse(null); - } -} diff --git a/api/src/main/java/com/intuit/tank/vm/common/PasswordEncoder.java b/api/src/main/java/com/intuit/tank/vm/common/PasswordEncoder.java index 7df07113f..14011316d 100644 --- a/api/src/main/java/com/intuit/tank/vm/common/PasswordEncoder.java +++ b/api/src/main/java/com/intuit/tank/vm/common/PasswordEncoder.java @@ -16,7 +16,7 @@ * #L% */ -import org.apache.commons.codec.binary.Base64; +import org.apache.tomcat.util.codec.binary.Base64; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; diff --git a/api/src/main/java/com/intuit/tank/vm/settings/CloudCredentials.java b/api/src/main/java/com/intuit/tank/vm/settings/CloudCredentials.java index dcdab0c60..0ad3ee735 100644 --- a/api/src/main/java/com/intuit/tank/vm/settings/CloudCredentials.java +++ b/api/src/main/java/com/intuit/tank/vm/settings/CloudCredentials.java @@ -50,21 +50,16 @@ public CloudProvider getType() { * @return the keyId */ public String getKeyId() { - // first try userData - String ret = AmazonUtil.getAWSKeyIdFromUserData(); + // try to get from property + String key = config.getString("secret-key-id-property", "AWS_SECRET_KEY_ID"); + String ret = System.getProperty(key); if (StringUtils.isBlank(ret)) { - // try to get from property - String key = config.getString("secret-key-id-property", "AWS_SECRET_KEY_ID"); - ret = System.getProperty(key); - if (StringUtils.isBlank(ret)) { - ret = System.getenv(key); - } - if (StringUtils.isBlank(ret)) { - // finally get straight from config - ret = config.getString("secret-key-id"); - } + ret = System.getenv(key); + } + if (StringUtils.isBlank(ret)) { + // finally get straight from config + ret = config.getString("secret-key-id"); } - return ret; } @@ -72,19 +67,15 @@ public String getKeyId() { * @return the key */ public String getKey() { - // first try userData - String ret = AmazonUtil.getAWSKeyFromUserData(); + // try to get from property + String key = config.getString("secret-key-property", "AWS_SECRET_KEY"); + String ret = System.getProperty(key); + if (StringUtils.isBlank(ret)) { + ret = System.getenv(key); + } if (StringUtils.isBlank(ret)) { - // try to get from property - String key = config.getString("secret-key-property", "AWS_SECRET_KEY"); - ret = System.getProperty(key); - if (StringUtils.isBlank(ret)) { - ret = System.getenv(key); - } - if (StringUtils.isBlank(ret)) { - // finally get straight from config - ret = config.getString("secret-key"); - } + // finally get straight from config + ret = config.getString("secret-key"); } return ret; } diff --git a/api/src/main/resources/settings.xml b/api/src/main/resources/settings.xml index 33baad811..87adc28c1 100644 --- a/api/src/main/resources/settings.xml +++ b/api/src/main/resources/settings.xml @@ -3,23 +3,23 @@ - datafiles + or relative path or an s3 bucket by prefixing a bucket with s3: e.g. s3:tank-data-files + datafiles --> - timing + timing --> - jars + with s3: e.g. s3:tank-agent-supports + jars --> - tmpfiles + file system. + tmpfiles --> tank @@ -36,7 +36,7 @@ - true + false @@ -172,9 +172,6 @@ - - AWS_SECRET_KEY_ID - AWS_SECRET_KEY @@ -188,8 +185,10 @@ - + + + [AMI-ID] [KEYPAIR] @@ -199,8 +198,9 @@ - + + [SSM-AMI-ID] [AMI-ID] [KEYPAIR] @@ -212,16 +212,11 @@ - - - - - + + + + + @@ -242,13 +237,7 @@ - - 50 - 10 - - com.intuit.tank.persistence.databases.AmazonDynamoDatabaseDocApi - - + com.intuit.tank.persistence.databases.CloudWatchDataSource diff --git a/api/src/test/java/com/intuit/tank/harness/AmazonUtilTest.java b/api/src/test/java/com/intuit/tank/harness/AmazonUtilTest.java new file mode 100644 index 000000000..56217bd41 --- /dev/null +++ b/api/src/test/java/com/intuit/tank/harness/AmazonUtilTest.java @@ -0,0 +1,90 @@ +package com.intuit.tank.harness; + +import com.intuit.tank.logging.LoggingProfile; +import com.intuit.tank.vm.api.enumerated.VMRegion; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import javax.inject.Inject; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Purpose: Test AmazonUtil class utility methods + * @author : atayal + **/ +public class AmazonUtilTest { + + @Inject + private AmazonUtil amazonUtil; + + @Test + void testGetVMRegion() { + VMRegion vmRegion = AmazonUtil.getVMRegion(); + Assertions.assertNotNull(vmRegion); + assertEquals("Standalone Agent", vmRegion.getDescription()); + assertEquals("standalone-agent", vmRegion.getRegion()); + } + + @Test + void testIsInAmazon() { + boolean inAmazon = AmazonUtil.isInAmazon(); + assertFalse(inAmazon); + } + + @Test + void testGetZone() { + String zone = AmazonUtil.getZone(); + Assertions.assertNotNull(zone); + assertEquals("unknown", zone); + } + + @Test + void testGetInstanceId() { + String instanceId = AmazonUtil.getInstanceId(); + Assertions.assertNotNull(instanceId); + assertEquals("", instanceId); + } + + @Test + void testGetLoggingProfile() { + LoggingProfile loggingProfile = AmazonUtil.getLoggingProfile(); + Assertions.assertNotNull(loggingProfile); + assertEquals("Standard", loggingProfile.getDisplayName()); + assertEquals("Logs common fields.", loggingProfile.getDescription()); + } + + @Test + void testGetCapacity() { + int capacity = AmazonUtil.getCapacity(); + assertEquals(4000, capacity); + } + + @Test + void testGetStopBehavior() { + StopBehavior stopBehavior = AmazonUtil.getStopBehavior(); + Assertions.assertNotNull(stopBehavior); + assertEquals("Script Group", stopBehavior.getDisplay()); + assertEquals("Stop after current script group completes.", stopBehavior.getDescription()); + } + + @Test + void testUsingEip() { + boolean usingEip = AmazonUtil.usingEip(); + assertFalse(usingEip); + } + + @Test + void testGetControllerBaseUrl() { + String url = AmazonUtil.getControllerBaseUrl(); + Assertions.assertNotNull(url); + assertEquals("http://localhost:8080/", url); + } + + @Test + void testGetUserDataAsMap() { + Object o = AmazonUtil.getUserDataAsMap(); + Assertions.assertNotNull(o); + } + +} diff --git a/api/src/test/java/com/intuit/tank/http/AuthSchemeTest.java b/api/src/test/java/com/intuit/tank/http/AuthSchemeTest.java new file mode 100644 index 000000000..d1ed5f718 --- /dev/null +++ b/api/src/test/java/com/intuit/tank/http/AuthSchemeTest.java @@ -0,0 +1,22 @@ +package com.intuit.tank.http; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * Purpose: Test AuthScheme + * + * @author : atayal + **/ +public class AuthSchemeTest { + + @Test + void testGetScheme() { + AuthScheme scheme = AuthScheme.getScheme("BASIC"); + Assertions.assertNotNull(scheme); + assert scheme == AuthScheme.Basic; + assertEquals("BASIC", scheme.getRepresentation()); + } +} diff --git a/api/src/test/java/com/intuit/tank/logging/LogEventTypeTest.java b/api/src/test/java/com/intuit/tank/logging/LogEventTypeTest.java new file mode 100644 index 000000000..bfa9fd85b --- /dev/null +++ b/api/src/test/java/com/intuit/tank/logging/LogEventTypeTest.java @@ -0,0 +1,19 @@ +package com.intuit.tank.logging; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * Purpose: Test LogEventType Enum + * + * @author : atayal + **/ +public class LogEventTypeTest { + + @Test + void testLogEventTypeEnum() { + LogEventType type = LogEventType.valueOf("System"); + Assertions.assertNotNull(type); + assert LogEventType.System == type; + } +} diff --git a/api/src/test/java/com/intuit/tank/logging/SourceTypeTest.java b/api/src/test/java/com/intuit/tank/logging/SourceTypeTest.java new file mode 100644 index 000000000..8c05f2876 --- /dev/null +++ b/api/src/test/java/com/intuit/tank/logging/SourceTypeTest.java @@ -0,0 +1,18 @@ +package com.intuit.tank.logging; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * Purpose: Test Enum SourceType + * + * @author : atayal + **/ +public class SourceTypeTest { + @Test + void testSourceTypeEnum() { + SourceType type = SourceType.valueOf("controller"); + Assertions.assertNotNull(type); + assert SourceType.controller == type; + } +} diff --git a/api/src/test/java/com/intuit/tank/script/RequestDataTypeTest.java b/api/src/test/java/com/intuit/tank/script/RequestDataTypeTest.java new file mode 100644 index 000000000..d0aa14fce --- /dev/null +++ b/api/src/test/java/com/intuit/tank/script/RequestDataTypeTest.java @@ -0,0 +1,19 @@ +package com.intuit.tank.script; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * Purpose: Test RequestDataType Enum + * + * @author : atayal + **/ +public class RequestDataTypeTest { + + @Test + void testRequestDataTypeEnum() { + RequestDataType type = RequestDataType.valueOf("responseContent"); + Assertions.assertNotNull(type); + assert RequestDataType.responseContent == type; + } +} diff --git a/api/src/test/java/com/intuit/tank/script/TimerActionTest.java b/api/src/test/java/com/intuit/tank/script/TimerActionTest.java new file mode 100644 index 000000000..e4273074d --- /dev/null +++ b/api/src/test/java/com/intuit/tank/script/TimerActionTest.java @@ -0,0 +1,19 @@ +package com.intuit.tank.script; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * Purpose: Test RequestDataType Enum + * + * @author : atayal + **/ +public class TimerActionTest { + + @Test + void testTimerActionEnum() { + TimerAction type = TimerAction.valueOf("START"); + Assertions.assertNotNull(type); + assert TimerAction.START == type; + } +} diff --git a/api/src/test/java/com/intuit/tank/vm/agent/messages/AgentDataCpTest.java b/api/src/test/java/com/intuit/tank/vm/agent/messages/AgentDataCpTest.java index 984cd8e01..ee1b65b32 100644 --- a/api/src/test/java/com/intuit/tank/vm/agent/messages/AgentDataCpTest.java +++ b/api/src/test/java/com/intuit/tank/vm/agent/messages/AgentDataCpTest.java @@ -40,12 +40,12 @@ public void testAgentData_1() assertNotNull(result); assertEquals(0, result.getUsers()); - assertEquals(null, result.getZone()); - assertEquals(null, result.getInstanceId()); - assertEquals(null, result.getRegion()); - assertEquals(null, result.getInstanceUrl()); + assertNull(result.getZone()); + assertNull(result.getInstanceId()); + assertNull(result.getRegion()); + assertNull(result.getInstanceUrl()); assertEquals(0, result.getCapacity()); - assertEquals(null, result.getJobId()); + assertNull(result.getJobId()); } /** @@ -92,7 +92,7 @@ public void testEquals_1() boolean result = fixture.equals(obj); - assertEquals(false, result); + assertFalse(result); } /** @@ -111,7 +111,7 @@ public void testEquals_2() boolean result = fixture.equals(obj); - assertEquals(false, result); + assertFalse(result); } /** @@ -130,7 +130,7 @@ public void testEquals_3() boolean result = fixture.equals(obj); - assertEquals(true, result); + assertTrue(result); } /** @@ -149,7 +149,7 @@ public void testEquals_4() boolean result = fixture.equals(obj); - assertEquals(true, result); + assertTrue(result); } /** diff --git a/api/src/test/java/com/intuit/tank/vm/api/enumerated/VMRegionCpTest.java b/api/src/test/java/com/intuit/tank/vm/api/enumerated/VMRegionCpTest.java index f4792febd..8d3f5f79a 100644 --- a/api/src/test/java/com/intuit/tank/vm/api/enumerated/VMRegionCpTest.java +++ b/api/src/test/java/com/intuit/tank/vm/api/enumerated/VMRegionCpTest.java @@ -15,8 +15,6 @@ import org.junit.jupiter.api.*; -import com.intuit.tank.vm.api.enumerated.VMRegion; - import static org.junit.jupiter.api.Assertions.*; /** @@ -75,10 +73,10 @@ public void testGetRegionFromZone_1() VMRegion result = VMRegion.getRegionFromZone(zone); assertNotNull(result); - assertEquals("US East (Northern Virginia)", result.getDescription()); - assertEquals("US East (Northern Virginia)", result.toString()); - assertEquals("ec2.us-east-1.amazonaws.com", result.getEndpoint()); - assertEquals("US_EAST", result.name()); + assertEquals("Standalone Agent", result.getDescription()); + assertEquals("Standalone Agent", result.toString()); + assertEquals("", result.getEndpoint()); + assertEquals("STANDALONE", result.name()); } /** @@ -96,10 +94,10 @@ public void testGetRegionFromZone_2() VMRegion result = VMRegion.getRegionFromZone(zone); assertNotNull(result); - assertEquals("US East (Northern Virginia)", result.getDescription()); - assertEquals("US East (Northern Virginia)", result.toString()); - assertEquals("ec2.us-east-1.amazonaws.com", result.getEndpoint()); - assertEquals("US_EAST", result.name()); + assertEquals("Standalone Agent", result.getDescription()); + assertEquals("Standalone Agent", result.toString()); + assertEquals("", result.getEndpoint()); + assertEquals("STANDALONE", result.name()); } /** @@ -117,10 +115,10 @@ public void testGetRegionFromZone_3() VMRegion result = VMRegion.getRegionFromZone(zone); assertNotNull(result); - assertEquals("US East (Northern Virginia)", result.getDescription()); - assertEquals("US East (Northern Virginia)", result.toString()); - assertEquals("ec2.us-east-1.amazonaws.com", result.getEndpoint()); - assertEquals("US_EAST", result.name()); + assertEquals("Standalone Agent", result.getDescription()); + assertEquals("Standalone Agent", result.toString()); + assertEquals("", result.getEndpoint()); + assertEquals("STANDALONE", result.name()); } /** diff --git a/api/src/test/java/com/intuit/tank/vm/api/enumerated/VMSizeCpTest.java b/api/src/test/java/com/intuit/tank/vm/api/enumerated/VMSizeCpTest.java deleted file mode 100644 index 8f16e70ce..000000000 --- a/api/src/test/java/com/intuit/tank/vm/api/enumerated/VMSizeCpTest.java +++ /dev/null @@ -1,95 +0,0 @@ -package com.intuit.tank.vm.api.enumerated; - -/* - * #%L - * Intuit Tank Api - * %% - * Copyright (C) 2011 - 2015 Intuit Inc. - * %% - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * #L% - */ - -import org.junit.jupiter.api.*; - -import com.intuit.tank.vm.api.enumerated.VMSize; - -import static org.junit.jupiter.api.Assertions.*; - -/** - * The class VMSizeCpTest contains tests for the class {@link VMSize}. - * - * @generatedBy CodePro at 9/3/14 3:44 PM - */ -public class VMSizeCpTest { - /** - * Run the VMSize fromRepresentation(String) method test. - * - * @throws Exception - * - * @generatedBy CodePro at 9/3/14 3:44 PM - */ - @Test - public void testFromRepresentation_1() - throws Exception { - String representation = ""; - - VMSize result = VMSize.fromRepresentation(representation); - - assertEquals(null, result); - } - - /** - * Run the VMSize fromRepresentation(String) method test. - * - * @throws Exception - * - * @generatedBy CodePro at 9/3/14 3:44 PM - */ - @Test - public void testFromRepresentation_2() - throws Exception { - String representation = ""; - - VMSize result = VMSize.fromRepresentation(representation); - - assertEquals(null, result); - } - - /** - * Run the VMSize fromRepresentation(String) method test. - * - * @throws Exception - * - * @generatedBy CodePro at 9/3/14 3:44 PM - */ - @Test - public void testFromRepresentation_3() - throws Exception { - String representation = ""; - - VMSize result = VMSize.fromRepresentation(representation); - - assertEquals(null, result); - } - - /** - * Run the String getRepresentation() method test. - * - * @throws Exception - * - * @generatedBy CodePro at 9/3/14 3:44 PM - */ - @Test - public void testGetRepresentation_1() - throws Exception { - VMSize fixture = VMSize.ExtraLarge; - - String result = fixture.getRepresentation(); - - assertEquals("m1.xlarge", result); - } -} \ No newline at end of file diff --git a/api/src/test/java/com/intuit/tank/vm/common/util/MethodTimerCpTest.java b/api/src/test/java/com/intuit/tank/vm/common/util/MethodTimerCpTest.java index 3a1975fb5..1e2ed2606 100644 --- a/api/src/test/java/com/intuit/tank/vm/common/util/MethodTimerCpTest.java +++ b/api/src/test/java/com/intuit/tank/vm/common/util/MethodTimerCpTest.java @@ -18,6 +18,10 @@ import org.junit.jupiter.api.*; import com.intuit.tank.vm.common.util.MethodTimer; +import org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable; + +import java.util.Arrays; +import java.util.List; import static org.junit.jupiter.api.Assertions.*; @@ -35,6 +39,7 @@ public class MethodTimerCpTest { * @generatedBy CodePro at 9/3/14 3:41 PM */ @Test + @DisabledIfEnvironmentVariable(named = "SKIP_METHODTIMER_TEST", matches = "true") public void testMethodTimer_1() throws Exception { Logger log = LogManager.getLogger(LogManager.ROOT_LOGGER_NAME); @@ -56,6 +61,7 @@ public void testMethodTimer_1() * @generatedBy CodePro at 9/3/14 3:41 PM */ @Test + @DisabledIfEnvironmentVariable(named = "SKIP_METHODTIMER_TEST", matches = "true") public void testMethodTimer_2() throws Exception { Logger log = LogManager.getLogger(LogManager.ROOT_LOGGER_NAME); @@ -77,6 +83,7 @@ public void testMethodTimer_2() * @generatedBy CodePro at 9/3/14 3:41 PM */ @Test + @DisabledIfEnvironmentVariable(named = "SKIP_METHODTIMER_TEST", matches = "true") public void testEnd_1() throws Exception { MethodTimer fixture = new MethodTimer(LogManager.getLogger(LogManager.ROOT_LOGGER_NAME), Object.class, ""); @@ -97,6 +104,7 @@ public void testEnd_1() * @generatedBy CodePro at 9/3/14 3:41 PM */ @Test + @DisabledIfEnvironmentVariable(named = "SKIP_METHODTIMER_TEST", matches = "true") public void testEndAndLog_1() throws Exception { MethodTimer fixture = new MethodTimer(LogManager.getLogger(LogManager.ROOT_LOGGER_NAME), Object.class, ""); @@ -117,6 +125,7 @@ public void testEndAndLog_1() * @generatedBy CodePro at 9/3/14 3:41 PM */ @Test + @DisabledIfEnvironmentVariable(named = "SKIP_METHODTIMER_TEST", matches = "true") public void testGetMarkTimeMessage_1() throws Exception { MethodTimer fixture = new MethodTimer(LogManager.getLogger(LogManager.ROOT_LOGGER_NAME), Object.class, ""); @@ -136,6 +145,7 @@ public void testGetMarkTimeMessage_1() * @generatedBy CodePro at 9/3/14 3:41 PM */ @Test + @DisabledIfEnvironmentVariable(named = "SKIP_METHODTIMER_TEST", matches = "true") public void testGetMarkTimeMessage_2() throws Exception { MethodTimer fixture = new MethodTimer(LogManager.getLogger(LogManager.ROOT_LOGGER_NAME), Object.class, ""); @@ -155,6 +165,7 @@ public void testGetMarkTimeMessage_2() * @generatedBy CodePro at 9/3/14 3:41 PM */ @Test + @DisabledIfEnvironmentVariable(named = "SKIP_METHODTIMER_TEST", matches = "true") public void testGetNaturalTimeMessage_1() throws Exception { MethodTimer fixture = new MethodTimer(LogManager.getLogger(LogManager.ROOT_LOGGER_NAME), Object.class, ""); @@ -173,6 +184,7 @@ public void testGetNaturalTimeMessage_1() * @generatedBy CodePro at 9/3/14 3:41 PM */ @Test + @DisabledIfEnvironmentVariable(named = "SKIP_METHODTIMER_TEST", matches = "true") public void testGetNaturalTimeMessage_2() throws Exception { MethodTimer fixture = new MethodTimer(LogManager.getLogger(LogManager.ROOT_LOGGER_NAME), Object.class, ""); @@ -191,6 +203,7 @@ public void testGetNaturalTimeMessage_2() * @generatedBy CodePro at 9/3/14 3:41 PM */ @Test + @DisabledIfEnvironmentVariable(named = "SKIP_METHODTIMER_TEST", matches = "true") public void testGetTimeMessage_1() throws Exception { MethodTimer fixture = new MethodTimer(LogManager.getLogger(LogManager.ROOT_LOGGER_NAME), Object.class, ""); @@ -209,6 +222,7 @@ public void testGetTimeMessage_1() * @generatedBy CodePro at 9/3/14 3:41 PM */ @Test + @DisabledIfEnvironmentVariable(named = "SKIP_METHODTIMER_TEST", matches = "true") public void testGetTimeMessage_2() throws Exception { MethodTimer fixture = new MethodTimer(LogManager.getLogger(LogManager.ROOT_LOGGER_NAME), Object.class, ""); @@ -227,6 +241,7 @@ public void testGetTimeMessage_2() * @generatedBy CodePro at 9/3/14 3:41 PM */ @Test + @DisabledIfEnvironmentVariable(named = "SKIP_METHODTIMER_TEST", matches = "true") public void testLogMark_1() throws Exception { MethodTimer fixture = new MethodTimer((Logger) null, Object.class, ""); @@ -248,6 +263,7 @@ public void testLogMark_1() * @generatedBy CodePro at 9/3/14 3:41 PM */ @Test + @DisabledIfEnvironmentVariable(named = "SKIP_METHODTIMER_TEST", matches = "true") public void testLogMark_2() throws Exception { MethodTimer fixture = new MethodTimer(LogManager.getLogger(LogManager.ROOT_LOGGER_NAME), Object.class, ""); @@ -269,6 +285,7 @@ public void testLogMark_2() * @generatedBy CodePro at 9/3/14 3:41 PM */ @Test + @DisabledIfEnvironmentVariable(named = "SKIP_METHODTIMER_TEST", matches = "true") public void testLogTime_1() throws Exception { MethodTimer fixture = new MethodTimer((Logger) null, Object.class, ""); @@ -289,6 +306,7 @@ public void testLogTime_1() * @generatedBy CodePro at 9/3/14 3:41 PM */ @Test + @DisabledIfEnvironmentVariable(named = "SKIP_METHODTIMER_TEST", matches = "true") public void testLogTime_2() throws Exception { MethodTimer fixture = new MethodTimer(LogManager.getLogger(LogManager.ROOT_LOGGER_NAME), Object.class, ""); @@ -309,6 +327,7 @@ public void testLogTime_2() * @generatedBy CodePro at 9/3/14 3:41 PM */ @Test + @DisabledIfEnvironmentVariable(named = "SKIP_METHODTIMER_TEST", matches = "true") public void testMark_1() throws Exception { MethodTimer fixture = new MethodTimer(LogManager.getLogger(LogManager.ROOT_LOGGER_NAME), Object.class, ""); @@ -329,6 +348,7 @@ public void testMark_1() * @generatedBy CodePro at 9/3/14 3:41 PM */ @Test + @DisabledIfEnvironmentVariable(named = "SKIP_METHODTIMER_TEST", matches = "true") public void testMark_2() throws Exception { MethodTimer fixture = new MethodTimer(LogManager.getLogger(LogManager.ROOT_LOGGER_NAME), Object.class, ""); @@ -349,6 +369,7 @@ public void testMark_2() * @generatedBy CodePro at 9/3/14 3:41 PM */ @Test + @DisabledIfEnvironmentVariable(named = "SKIP_METHODTIMER_TEST", matches = "true") public void testMarkAndLog_1() throws Exception { MethodTimer fixture = new MethodTimer(LogManager.getLogger(LogManager.ROOT_LOGGER_NAME), Object.class, ""); @@ -369,6 +390,7 @@ public void testMarkAndLog_1() * @generatedBy CodePro at 9/3/14 3:41 PM */ @Test + @DisabledIfEnvironmentVariable(named = "SKIP_METHODTIMER_TEST", matches = "true") public void testMarkAndLog_2() throws Exception { MethodTimer fixture = new MethodTimer(LogManager.getLogger(LogManager.ROOT_LOGGER_NAME), Object.class, ""); @@ -390,6 +412,7 @@ public void testMarkAndLog_2() * @generatedBy CodePro at 9/3/14 3:41 PM */ @Test + @DisabledIfEnvironmentVariable(named = "SKIP_METHODTIMER_TEST", matches = "true") public void testStart_1() throws Exception { MethodTimer fixture = new MethodTimer(LogManager.getLogger(LogManager.ROOT_LOGGER_NAME), Object.class, ""); diff --git a/api/src/test/java/com/intuit/tank/vm/common/util/ReportUtilCpTest.java b/api/src/test/java/com/intuit/tank/vm/common/util/ReportUtilCpTest.java index ff26139eb..7e2f13a51 100644 --- a/api/src/test/java/com/intuit/tank/vm/common/util/ReportUtilCpTest.java +++ b/api/src/test/java/com/intuit/tank/vm/common/util/ReportUtilCpTest.java @@ -77,25 +77,25 @@ public void testGetSummaryData_1() assertEquals(23, result.length); assertEquals("", result[0]); assertEquals("0", result[1]); - assertEquals("�", result[2]); - assertEquals("�", result[3]); - assertEquals("�", result[4]); - assertEquals("�", result[5]); - assertEquals("�", result[6]); - assertEquals("�", result[7]); - assertEquals("�", result[8]); - assertEquals("�", result[9]); - assertEquals("�", result[10]); - assertEquals("�", result[11]); - assertEquals("�", result[12]); - assertEquals("�", result[13]); - assertEquals("�", result[14]); - assertEquals("�", result[15]); - assertEquals("�", result[16]); - assertEquals("�", result[17]); - assertEquals("�", result[18]); - assertEquals("�", result[19]); - assertEquals("�", result[20]); + assertEquals("NaN", result[2]); + assertEquals("NaN", result[3]); + assertEquals("NaN", result[4]); + assertEquals("NaN", result[5]); + assertEquals("NaN", result[6]); + assertEquals("NaN", result[7]); + assertEquals("NaN", result[8]); + assertEquals("NaN", result[9]); + assertEquals("NaN", result[10]); + assertEquals("NaN", result[11]); + assertEquals("NaN", result[12]); + assertEquals("NaN", result[13]); + assertEquals("NaN", result[14]); + assertEquals("NaN", result[15]); + assertEquals("NaN", result[16]); + assertEquals("NaN", result[17]); + assertEquals("NaN", result[18]); + assertEquals("NaN", result[19]); + assertEquals("NaN", result[20]); assertEquals(null, result[21]); assertEquals(null, result[22]); } @@ -119,25 +119,25 @@ public void testGetSummaryData_2() assertEquals(23, result.length); assertEquals("", result[0]); assertEquals("0", result[1]); - assertEquals("�", result[2]); - assertEquals("�", result[3]); - assertEquals("�", result[4]); - assertEquals("�", result[5]); - assertEquals("�", result[6]); - assertEquals("�", result[7]); - assertEquals("�", result[8]); - assertEquals("�", result[9]); - assertEquals("�", result[10]); - assertEquals("�", result[11]); - assertEquals("�", result[12]); - assertEquals("�", result[13]); - assertEquals("�", result[14]); - assertEquals("�", result[15]); - assertEquals("�", result[16]); - assertEquals("�", result[17]); - assertEquals("�", result[18]); - assertEquals("�", result[19]); - assertEquals("�", result[20]); + assertEquals("NaN", result[2]); + assertEquals("NaN", result[3]); + assertEquals("NaN", result[4]); + assertEquals("NaN", result[5]); + assertEquals("NaN", result[6]); + assertEquals("NaN", result[7]); + assertEquals("NaN", result[8]); + assertEquals("NaN", result[9]); + assertEquals("NaN", result[10]); + assertEquals("NaN", result[11]); + assertEquals("NaN", result[12]); + assertEquals("NaN", result[13]); + assertEquals("NaN", result[14]); + assertEquals("NaN", result[15]); + assertEquals("NaN", result[16]); + assertEquals("NaN", result[17]); + assertEquals("NaN", result[18]); + assertEquals("NaN", result[19]); + assertEquals("NaN", result[20]); assertEquals(null, result[21]); assertEquals(null, result[22]); } diff --git a/api/src/test/java/com/intuit/tank/vm/settings/CloudCredentialsCpTest.java b/api/src/test/java/com/intuit/tank/vm/settings/CloudCredentialsCpTest.java index 9b293fa18..5e634a873 100644 --- a/api/src/test/java/com/intuit/tank/vm/settings/CloudCredentialsCpTest.java +++ b/api/src/test/java/com/intuit/tank/vm/settings/CloudCredentialsCpTest.java @@ -16,9 +16,6 @@ import org.apache.commons.configuration.HierarchicalConfiguration; import org.junit.jupiter.api.*; -import com.intuit.tank.vm.settings.CloudCredentials; -import com.intuit.tank.vm.settings.CloudProvider; - import static org.junit.jupiter.api.Assertions.*; /** @@ -42,10 +39,10 @@ public void testCloudCredentials_1() CloudCredentials result = new CloudCredentials(config); assertNotNull(result); - assertEquals(null, result.getKey()); - assertEquals(null, result.getProxyHost()); - assertEquals(null, result.getProxyPort()); - assertEquals(null, result.getKeyId()); + assertNull(result.getKey()); + assertNull(result.getProxyHost()); + assertNull(result.getProxyPort()); + assertNull(result.getKeyId()); } /** @@ -62,7 +59,7 @@ public void testGetKey_1() String result = fixture.getKey(); - assertEquals(null, result); + assertNull(result); } /** @@ -79,7 +76,7 @@ public void testGetKeyId_1() String result = fixture.getKeyId(); - assertEquals(null, result); + assertNull(result); } /** @@ -96,7 +93,7 @@ public void testGetProxyHost_1() String result = fixture.getProxyHost(); - assertEquals(null, result); + assertNull(result); } /** @@ -113,7 +110,7 @@ public void testGetProxyPort_1() String result = fixture.getProxyPort(); - assertEquals(null, result); + assertNull(result); } /** diff --git a/api/src/test/java/com/intuit/tank/vm/settings/InstanceDescriptionDefaultsCpTest.java b/api/src/test/java/com/intuit/tank/vm/settings/InstanceDescriptionDefaultsCpTest.java index cf41b9965..8013450a6 100644 --- a/api/src/test/java/com/intuit/tank/vm/settings/InstanceDescriptionDefaultsCpTest.java +++ b/api/src/test/java/com/intuit/tank/vm/settings/InstanceDescriptionDefaultsCpTest.java @@ -18,11 +18,6 @@ import static org.junit.jupiter.api.Assertions.*; -import java.util.List; - -import com.intuit.tank.vm.api.enumerated.VMSize; -import com.intuit.tank.vm.settings.InstanceDescriptionDefaults; - /** * The class InstanceDescriptionDefaultsCpTest contains tests for the class * {@link InstanceDescriptionDefaults}. diff --git a/api/src/test/java/com/intuit/tank/vm/settings/InstanceTagTest.java b/api/src/test/java/com/intuit/tank/vm/settings/InstanceTagTest.java new file mode 100644 index 000000000..e013c7198 --- /dev/null +++ b/api/src/test/java/com/intuit/tank/vm/settings/InstanceTagTest.java @@ -0,0 +1,30 @@ +package com.intuit.tank.vm.settings; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * Purpose: test InstanceTag class + * + * @author : atayal + **/ +public class InstanceTagTest { + @Test + void testInstanceTag() { + InstanceTag tag = new InstanceTag("tag1", "val1"); + InstanceTag tag2 = new InstanceTag("tag2", "val2"); + Assertions.assertEquals("tag1", tag.getName()); + Assertions.assertEquals("val1", tag.getValue()); + Map tags = new LinkedHashMap<>(); + tags.put(tag, 1); + tags.put(tag2, 2); + assert tag != tag2; + Assertions.assertNotEquals(tag, tag2); + Assertions.assertNotEquals(tag, ""); + Assertions.assertEquals("tag1 = val1", tag.toString()); + + } +} diff --git a/api/src/test/java/com/intuit/tank/vm/settings/ModificationTypeTest.java b/api/src/test/java/com/intuit/tank/vm/settings/ModificationTypeTest.java new file mode 100644 index 000000000..ba94399b9 --- /dev/null +++ b/api/src/test/java/com/intuit/tank/vm/settings/ModificationTypeTest.java @@ -0,0 +1,19 @@ +package com.intuit.tank.vm.settings; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * Purpose: Test ModificationType Enum + * + * @author : atayal + **/ +public class ModificationTypeTest { + + @Test + void testModificationTypeEnum() { + ModificationType type = ModificationType.valueOf("ADD"); + Assertions.assertNotNull(type); + assert ModificationType.ADD == type; + } +} diff --git a/api/src/test/java/com/intuit/tank/vm/settings/ModifiedEntityMessageTest.java b/api/src/test/java/com/intuit/tank/vm/settings/ModifiedEntityMessageTest.java new file mode 100644 index 000000000..23ce72b71 --- /dev/null +++ b/api/src/test/java/com/intuit/tank/vm/settings/ModifiedEntityMessageTest.java @@ -0,0 +1,9 @@ +package com.intuit.tank.vm.settings; + +/** + * Purpose: + * + * @author : atayal + **/ +public class ModifiedEntityMessageTest { +} diff --git a/api/src/test/java/com/intuit/tank/vm/settings/PropertiesFileTest.java b/api/src/test/java/com/intuit/tank/vm/settings/PropertiesFileTest.java new file mode 100644 index 000000000..33a345090 --- /dev/null +++ b/api/src/test/java/com/intuit/tank/vm/settings/PropertiesFileTest.java @@ -0,0 +1,9 @@ +package com.intuit.tank.vm.settings; + +/** + * Purpose: + * + * @author : atayal + **/ +public class PropertiesFileTest { +} diff --git a/api/src/test/java/com/intuit/tank/vm/settings/ReportingConfigTest.java b/api/src/test/java/com/intuit/tank/vm/settings/ReportingConfigTest.java new file mode 100644 index 000000000..d7169c3fb --- /dev/null +++ b/api/src/test/java/com/intuit/tank/vm/settings/ReportingConfigTest.java @@ -0,0 +1,9 @@ +package com.intuit.tank.vm.settings; + +/** + * Purpose: + * + * @author : atayal + **/ +public class ReportingConfigTest { +} diff --git a/api/src/test/resources/sampleProp.properties b/api/src/test/resources/sampleProp.properties new file mode 100644 index 000000000..86fbde046 --- /dev/null +++ b/api/src/test/resources/sampleProp.properties @@ -0,0 +1,2 @@ +a=1 +b=2 \ No newline at end of file diff --git a/api/src/test/resources/settings.xml b/api/src/test/resources/settings.xml index 33baad811..fdcba513c 100644 --- a/api/src/test/resources/settings.xml +++ b/api/src/test/resources/settings.xml @@ -3,23 +3,23 @@ - datafiles + or relative path or an s3 bucket by prefixing a bucket with s3: e.g. s3:tank-data-files + datafiles --> - - timing + timing --> - - jars + - - tmpfiles + tank @@ -27,16 +27,16 @@ http://localhost:8080/tank - false false - - true + false @@ -67,9 +67,9 @@ - - @@ -98,11 +98,11 @@ 30000 - 5000 - 180000 @@ -157,7 +157,7 @@ - security_group @@ -172,24 +172,23 @@ - - AWS_SECRET_KEY_ID - AWS_SECRET_KEY - tank_admin - + + + [AMI-ID] [KEYPAIR] @@ -199,8 +198,9 @@ - + + [SSM-AMI-ID] [AMI-ID] [KEYPAIR] @@ -212,25 +212,20 @@ - - - - - + + + + + - 5m - 10m @@ -239,16 +234,10 @@ 30s - - - 50 - 10 - - com.intuit.tank.persistence.databases.AmazonDynamoDatabaseDocApi - - + com.intuit.tank.persistence.databases.CloudWatchDataSource diff --git a/assets/cleanup_prior_to_3.0.0.sql b/assets/cleanup_prior_to_3.0.0.sql new file mode 100644 index 000000000..6c1b5825b --- /dev/null +++ b/assets/cleanup_prior_to_3.0.0.sql @@ -0,0 +1,37 @@ +/* +Script to help cleanup the abandoned rows in the searlized_script_step table +Recommed running one line at a time, and validating the output +*/ + +SELECT count(*) FROM tank.serialized_script_step; +SELECT count(*) FROM tank.script; +SELECT `AUTO_INCREMENT` FROM INFORMATION_SCHEMA.TABLES +WHERE TABLE_SCHEMA = 'tank' AND TABLE_NAME = 'serialized_script_step'; + +CREATE TABLE IF NOT EXISTS tank.serialized_script_step_temp +( + `id` int(11) NOT NULL AUTO_INCREMENT, + `created` datetime NOT NULL, + `modified` datetime NOT NULL, + `serialized_data` longblob NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT= DEFAULT CHARSET=utf8 +SELECT steps.* +FROM tank.serialized_script_step AS steps +JOIN tank.script AS script +ON script.serial_step_id=steps.id; + +SELECT count(*) FROM tank.serialized_script_step_temp; + +ALTER TABLE tank.script DROP FOREIGN KEY FKC9E5D0CBD63BDD22; + +RENAME TABLE tank.serialized_script_step TO tank.serialized_script_step_old; +RENAME TABLE tank.serialized_script_step_temp TO tank.serialized_script_step; + +ALTER TABLE tank.script ADD CONSTRAINT `FKC9E5D0CBD63BDD22` FOREIGN KEY (`serial_step_id`) REFERENCES `serialized_script_step` (`id`); + + +/* +Validate scripts in Tank before performing the DROP +*/ +DROP TABLE tank.serialized_script_step_old; diff --git a/assets/settings-all-in-one.xml b/assets/settings-all-in-one.xml new file mode 100644 index 000000000..15d8bd18f --- /dev/null +++ b/assets/settings-all-in-one.xml @@ -0,0 +1,490 @@ + + + + +datafiles + +timing + +tmpfiles + +jars + +tank-demo-all-in-one + +http://localhost:8080 + +true + +false + + + All Products + My Product + + + + + + + + + localhost + 25 + do_not_reply@mydomain.com + + + + + + + com.intuit.tank.reporting.rest.RestResultsReporter + com.intuit.tank.reporting.rest.RestResultsReader + + + + + + + + + + + + /tmp + + + 8090 + + + 5000 + + + 360000 + + + 15000 + + 30000 + + + 5000 + + + 180000 + + + 7200000 + + + false + + + false + + + false + + + 30 + + + + .*text.* + .*json.* + .*xml.* + + + + +
test_flag
+
+ + +
+ + US_EAST + + + false + + + + + + + + security_group + myKey + false + + + + + + AWS_SECRET_KEY_ID + AWS_SECRET_KEY + + + + + + + + + + [AMI-ID] + [KEYPAIR] + + + + + + [AMI-ID] + [KEYPAIR] + + + + + + + + + + + + + + + + + 5m + + 10m + + 2 + + 30s + + + + com.intuit.tank.persistence.databases.CloudWatchDataSource + + + + + + + admin + user + script-manager + project-manager + job-manager + guest + + + + + + + user + project-manager + + + + + project-manager + + + + + project-manager + + + + + + user + script-manager + + + + + script-manager + + + + + script-manager + + + + + + user + script-manager + + + + + script-manager + + + + + script-manager + + + + + + user + script-manager + project-manager + + + + + script-manager + project-manager + + + + + + job-manager + project-manager + + + + + + + admin + admin + admin@example.com + admin + + + + + + + + + + + + +
diff --git a/assets/tankIcon.png b/assets/tankIcon.png new file mode 100644 index 0000000000000000000000000000000000000000..10bd9989058280004abba9f7439cfbc5ecab5fc0 GIT binary patch literal 80076 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%0D30=MZ0iLW&4A25<%GrJ|=%0)i{bM7Djh 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

x3&_g*9xg-G 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' + Description: Subnet IDs + SecurityGroupIds: + Type: 'List' + Description: SecurityGroup IDs + +Resources: + AgentInstance: + Type: AWS::EC2::Instance + Metadata: + AWS::CloudFormation::Init: + configSets: + default: + - 'cloudwatch-install' + - 'agent-install' + cloudwatch-install: + packages: {} + sources: {} + files: + /opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.d/tank-cloudwatch-config.json: + content: | + {"logs":{"force_flush_interval":5,"logs_collected":{ + "files":{"collect_list":[{"file_path":"/opt/tank_agent/logs/agent.log","log_group_name":"/opt/tank_agent/logs/agent.log","log_stream_name":"{instance_id}"}]}}}, + "metrics":{"append_dimensions":{"InstanceId":"${aws:InstanceId}"},"metrics_collected":{ + "netstat":{"measurement":["tcp_established","tcp_syn_sent","tcp_close"],"append_dimensions":{"Service":"TankAgent"},"metrics_collection_interval":60}, + "disk":{"measurement":["used_percent"],"resources":["/"],"append_dimensions":{"Service":"TankAgent"},"metrics_collection_interval":60}, + "mem":{"measurement":["mem_used_percent"],"append_dimensions":{"Service":"TankAgent"},"metrics_collection_interval":60}}}} + mode: '000644' + owner: 'cwagent' + group: 'cwagent' + commands: {} + services: + sysvinit: + amazon-cloudwatch-agent: + enabled: 'true' + ensureRunning: 'true' + files: + - "/opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.d/tank-cloudwatch-config.json" + users: {} + groups: {} + agent-install: + packages: + yum: + unzip: [] + java-11-amazon-corretto-headless: [] + sources: {} + files: + /etc/init.d/tank_agent: + content: | + #!/bin/bash + # tank_agent + # chkconfig: 2345 99 99 + # description: agent for Tank project + ### BEGIN INIT INFO + # Provides: $tank_agent + ### END INIT INFO + # Source function library. + . /etc/init.d/functions + + RETVAL=0 + umask 077 + start() { + echo -n $"Starting Tank Agent: " + pushd /opt/tank_agent + ulimit -n 20000; + ulimit -a >> /tmp/ulimit_out; + daemon /opt/tank_agent/run.sh& + echo + return $RETVAL + } + stop() { + echo -n $"Shutdown agent startup: " + kill -9 `ps -ef | grep agent-startup-all | grep -v grep | awk '{print $2}'` + return $RETVAL + } + restart() { + stop + start + } + case "$1" in + start) + start + ;; + stop) + stop + ;; + restart|reload) + restart + ;; + *) + echo $"Usage: $0 {start|stop|restart}" + exit 1 + esac + exit $? + mode: '000755' + owner: 'root' + group: 'root' + commands: + 010_update_DNS_in_security: + command: 'sed -i.bak "s/networkaddress.cache.negative.ttl=10/networkaddress.cache.negative.ttl=0/g" /etc/alternatives/jre/conf/security/java.security' + 020_download_startup_pkg: + command: !Sub 'aws s3 cp ${TankZip} /tmp/agent-startup-pkg.zip' + 021_unzip_startup_pkg: + command: 'unzip /tmp/agent-startup-pkg.zip -d /opt' + 022_mkdir_logs: + command: 'mkdir /opt/tank_agent/logs' + 030_increase_Ephemeral Ports: + command: 'echo "net.ipv4.ip_local_port_range=1024 65000" >> /etc/sysctl.conf' + 031_set_file_open_limit: + command: 'sed -i "s/#DefaultLimitNOFILE=/DefaultLimitNOFILE=81960/" /etc/systemd/system.conf' + 032_set_file_open_limit: + command: 'sed -i "s/#DefaultLimitNOFILE=/DefaultLimitNOFILE=81960/" /etc/systemd/user.conf' + services: + sysvinit: + tank_agent: + enabled: 'true' + ensureRunning: 'true' + users: {} + groups: {} + Properties: + ImageId: !Ref AmiId + InstanceType: !Ref InstanceType + SecurityGroupIds: !Ref SecurityGroupIds + KeyName: !Ref KeyName + SubnetId: !Ref SubnetIds + Tags: + - Key: 'Name' + Value: 'Tank Agent AMI' + UserData: + Fn::Base64: !Sub | + #!/bin/bash -v + # Ensure our PATH is set correctly (on Amazon Linux cfn-signal is in /opt/aws/bin) + . ~/.bash_profile + yum -y update + cfn-init -v \ + --region ${AWS::Region} \ + --stack ${AWS::StackName} \ + --resource AgentInstance + cfn-signal -e $? \ + --region ${AWS::Region} \ + --stack ${AWS::StackName} \ + '${UIInstanceWaitHandle}' + UIInstanceWaitHandle: + Type: AWS::CloudFormation::WaitConditionHandle + Properties: {} + UIInstanceWaitCondition: + Type: AWS::CloudFormation::WaitCondition + DependsOn: AgentInstance + Properties: + Handle: !Ref UIInstanceWaitHandle + Timeout: '1500' + +Outputs: + AgentInstanceId: + Description: InstanceId of the newly created agent instance + Value: !Ref AgentInstance + AZ: + Description: Availability Zone of the newly created EC2 instance + Value: !GetAtt [ 'AgentInstance', 'AvailabilityZone' ] diff --git a/aws-config/tank_controller.yml b/aws-config/tank_controller.yml new file mode 100644 index 000000000..6357b191d --- /dev/null +++ b/aws-config/tank_controller.yml @@ -0,0 +1,289 @@ +AWSTemplateFormatVersion: 2010-09-09 +Description: Controller Instance - Creates a Tank controller installing java tomcat and configuring the instances appropriately. +Parameters: + AmiId: + Type: AWS::SSM::Parameter::Value + Default: /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2 + Description: The ID of the latest Amazon Linux 2 baseline AMI + KeyName: + Type: AWS::EC2::KeyPair::KeyName + Description: Name of an existing EC2 KeyPair to enable SSH access to the instance. + SubnetId: + Type: AWS::EC2::Subnet::Id + Description: Private subnet for the internal instance ip + TankWar: + Type: String + Description: File path to Tank tank.war Filename in S3 (ie. s3://bucket/keyname) + TankSettings: + Type: String + Description: File path to Tank settings.xml Filename in S3 (ie. s3://bucket/keyname) + RdsPassword: + Type: String + NoEcho: true + Description: Something secret (more than 8 characters) + InstanceType: + Description: EC2 instance type + Type: String + Default: t3a.medium + AllowedValues: [ 't3a.medium', 't3.medium', 't3a.large', 't3.large', 't3a.xlarge', 't3.xlarge', 'm5.large', 'm5.xlarge', 'c5.xlarge', 'c5.2xlarge' ] + ConstraintDescription: Must be one of t3.medium t3.large t3.xlarge m5.large m5.xlarge c5.xlarge or c5.2xlarge + +Mappings: + InstanceMap: + t3a.medium: + JvmHeap: '2g' + t3.medium: + JvmHeap: '2g' + t3a.large: + JvmHeap: '4g' + t3.large: + JvmHeap: '4g' + t3a.xlarge: + JvmHeap: '10g' + t3.xlarge: + JvmHeap: '10g' + c5.large: + JvmHeap: '1g' + c5.xlarge: + JvmHeap: '5g' + c5.2xlarge: + JvmHeap: '8g' + m5.large: + JvmHeap: '4g' + m5.xlarge: + JvmHeap: '10g' + +Resources: + RDSInstance: + Type: AWS::RDS::DBInstance + Properties: + DBInstanceIdentifier: tank + AutoMinorVersionUpgrade: true + DBInstanceClass: db.t3.large + Port: 3306 + AllocatedStorage: 100 + BackupRetentionPeriod: 30 + DBName: tank + Engine: mysql + EngineVersion: 8.0.21 + MasterUsername: admin + MasterUserPassword: !Ref RdsPassword + DBSecurityGroups: + - !Ref RDSSecurityGroup + + RDSSecurityGroup: + Type: AWS::RDS::DBSecurityGroup + Properties: + GroupDescription: Ingress for Amazon EC2 security group + DBSecurityGroupIngress: + - EC2SecurityGroupName: !Ref SecurityGroup + + SecurityGroup: + Type: AWS::EC2::SecurityGroup + Properties: + GroupDescription: Allow ssh and http tomcat to client host + SecurityGroupIngress: + - IpProtocol: tcp + FromPort: 22 + ToPort: 22 + CidrIp: 0.0.0.0/0 + - IpProtocol: tcp + FromPort: 8080 + ToPort: 8080 + CidrIp: 0.0.0.0/0 + + ControllerInstance: + Type: AWS::EC2::Instance + Metadata: + AWS::CloudFormation::Init: + configSets: + default: ['pre-install', 'tomcat-install' ] + pre-install: + packages: + yum: + amazon-cloudwatch-agent: [] + rpm: + xray-daemon: 'https://s3.dualstack.us-east-2.amazonaws.com/aws-xray-assets.us-east-2/xray-daemon/aws-xray-daemon-3.x.rpm' + groups: {} + users: {} + sources: {} + files: + /opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.d/tank-cloudwatch-config.json: + content: | + {"logs":{"force_flush_interval":5,"logs_collected":{ + "files":{"collect_list":[ + {"file_path":"/opt/tomcat/logs/tank.log","log_group_name":"/opt/tomcat/logs/tank.log","log_stream_name":"{instance_id}"}, + {"file_path":"/var/log/messages","log_group_name":"/var/log/messages","log_stream_name":"{instance_id}"}]}}}, + "metrics":{"append_dimensions":{"InstanceId":"${aws:InstanceId}","InstanceType":"${aws:InstanceType}"},"metrics_collected":{ + "disk":{"measurement":["used_percent"],"resources":["/"],"append_dimensions":{"Service":"Tank"},"metrics_collection_interval":60}, + "mem":{"measurement":["mem_used_percent"],"append_dimensions":{"Service":"Tank"},"metrics_collection_interval":60}}}} + mode: '000644' + owner: 'cwagent' + group: 'cwagent' + commands: + 000_install_epel: + command: 'amazon-linux-extras install epel' #For tomcat-native + services: + amazon-cloudwatch-agent: + enabled: 'true' + ensureRunning: 'true' + files: + - "/opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.d/tank-cloudwatch-config.json" + tomcat-install: + packages: + yum: + tomcat-native: [] + java-11-amazon-corretto-headless: [] + sources: + /opt: 'http://archive.apache.org/dist/tomcat/tomcat-9/v9.0.39/bin/apache-tomcat-9.0.39.tar.gz' + files: + /etc/init.d/tomcat: + content: | + #!/bin/bash + # + # tomcat Start tomcat + # + # + # chkconfig: 345 88 12 + # description: start stop tomcat + ### BEGIN INIT INFO + # Provides: $tomcat + ### END INIT INFO + # Source function library. + . /etc/init.d/functions + . /etc/init.d/java_opts + + ulimit -n 50000 + export CATALINA_HOME=/opt/tomcat + RETVAL=0 + umask 077 + start() { + cd $CATALINA_HOME + echo -n $"Starting tomcat: " + daemon $CATALINA_HOME/bin/startup.sh + echo + return $RETVAL + } + stop() { + echo -n $"Shutting down tomcat: " + daemon $CATALINA_HOME/bin/shutdown.sh + echo + return $RETVAL + } + restart() { + stop + start + } + case "$1" in + start) + start + ;; + stop) + stop + ;; + restart|reload) + restart + ;; + *) + echo $"Usage: $0 {start|stop|restart}" + exit 1 + esac + exit $? + mode: '000755' + owner: 'root' + group: 'root' + /etc/init.d/java_opts: + content: !Sub + - | + export CATALINA_OPTS="-Xms${heap} -Xmx${heap} -XX:+UseG1GC -Dcom.amazonaws.sdk.enableDefaultMetrics=cloudwatchRegion=${AWS::Region} -Xlog:gc=debug:file=/opt/tomcat/logs/gc.log:time,uptime,level,tags:filecount=3,filesize=200m + - { + heap: !FindInMap [ InstanceMap, !Ref InstanceType, JvmHeap ] + } + mode: '000755' + owner: 'root' + group: 'root' + /tmp/context.xml: + content: !Sub | + + + + + + mode: '000644' + owner: 'root' + group: 'root' + commands: + 010_tomcat_symboliclink: + command: 'ln -snf /opt/apache-tomcat-9.0.39 /opt/tomcat' + 020_clear_out_builtin: + command: 'rm -rf /opt/tomcat/webapps/*' + 030_download_settings: + command: !Sub 'aws s3 cp ${TankSettings} /opt/tomcat/settings.xml' + #031_set_publicDNSName: + # command: !Sub 'sed -i "s/localhost:8080/${ControllerInstance.PublicDnsName}:8080/" /opt/tomcat/settings.xml' + 040_download_tank: + command: !Sub 'aws s3 cp ${TankWar} /opt/tomcat/webapps/ROOT.war' + 050_copy_context: + command: 'cp /tmp/context.xml /opt/tomcat/conf/context.xml' + services: + sysvinit: + tomcat: + enabled : true + ensureRunning: true + users: {} + groups: {} + Properties: + ImageId: !Ref AmiId + InstanceType: !Ref InstanceType + IamInstanceProfile: !ImportValue TankControllerProfile + SecurityGroupIds: + - !GetAtt SecurityGroup.GroupId + KeyName: !Ref KeyName + SubnetId: !Ref SubnetId + Tags: + - + Key: Name + Value: Tank + UserData: + Fn::Base64: !Sub | + #!/bin/bash -v + # Ensure our PATH is set correctly (on Amazon Linux cfn-signal is in /opt/aws/bin) + . ~/.bash_profile + yum update -y + /opt/aws/bin/cfn-init -v \ + --region ${AWS::Region} \ + --stack ${AWS::StackName} \ + --resource ControllerInstance + /opt/aws/bin/cfn-signal -e $? \ + --region ${AWS::Region} \ + --stack ${AWS::StackName} \ + '${UIInstanceWaitHandle}' + UIInstanceWaitHandle: + Type: AWS::CloudFormation::WaitConditionHandle + Properties: {} + UIInstanceWaitCondition: + Type: AWS::CloudFormation::WaitCondition + DependsOn: ControllerInstance + Properties: + Handle: !Ref UIInstanceWaitHandle + Timeout: 1800 + +Outputs: + InstanceId: + Description: InstanceId of the newly created controller instance + Value: !Ref ControllerInstance + PublicDnsName: + Description: PublicDnsName endpoint of the newly created ControllerInstance + Value: !GetAtt [ 'ControllerInstance', 'PublicDnsName' ] diff --git a/aws-config/tank_instanceProfiles.yml b/aws-config/tank_instanceProfiles.yml new file mode 100644 index 000000000..675aae485 --- /dev/null +++ b/aws-config/tank_instanceProfiles.yml @@ -0,0 +1,84 @@ +AWSTemplateFormatVersion: 2010-09-09 +Description: Create Controller and Agent IAM Instance Profiles for Intuit/Tank installation +Resources: + ControllerProfile: + Type: AWS::IAM::InstanceProfile + Properties: + Path: / + Roles: + - !Ref ControllerRole + ControllerRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: 2012-10-17 + Statement: + - + Effect: Allow + Action: sts:AssumeRole + Principal: + Service: + - ec2.amazonaws.com + Path: / + ManagedPolicyArns: + - arn:aws:iam::aws:policy/AmazonEC2FullAccess + - arn:aws:iam::aws:policy/AmazonS3FullAccess + - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess + - arn:aws:iam::aws:policy/CloudWatchAgentServerPolicy + Policies: + - + PolicyName: tank-controller-role + PolicyDocument: + Version: 2012-10-17 + Statement: + - + Effect: Allow + Action: + - iam:PassRole + - iam:ListInstanceProfiles + - iam:AddRoleToInstanceProfile + - appconfig:GetConfiguration + Resource: "*" + AgentProfile: + Type: AWS::IAM::InstanceProfile + Properties: + Path: / + Roles: + - !Ref AgentRole + AgentRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: 2012-10-17 + Statement: + - + Effect: Allow + Action: sts:AssumeRole + Principal: + Service: ec2.amazonaws.com + Path: / + ManagedPolicyArns: + - arn:aws:iam::aws:policy/AmazonS3FullAccess + - arn:aws:iam::aws:policy/CloudWatchAgentServerPolicy + +Outputs: + TankControllerProfile: + Description: Tank Controller Profile + Value: !Ref ControllerProfile + Export: + Name: TankControllerProfile + TankControllerRole: + Description: Tank Controller Role + Value: !Ref ControllerRole + Export: + Name: TankControllerRole + TankAgentProfile: + Description: Tank Agent Profile + Value: !Ref AgentProfile + Export: + Name: TankAgentProfile + TankAgentRole: + Description: Tank Agent Role + Value: !Ref AgentRole + Export: + Name: TankAgentRole diff --git a/buildspec.yml b/buildspec.yml index 7a89ab0e3..65a314259 100644 --- a/buildspec.yml +++ b/buildspec.yml @@ -3,15 +3,29 @@ version: 0.2 env: variables: MAVEN_OPTS: "-Xms1g -Xmx2g" + SKIP_METHODTIMER_TEST: true phases: + install: + runtime-versions: + java: corretto11 build: commands: - - mvn clean install -P release + - java -version + - mvn clean install surefire-report:report -P release + +reports: + SurefireReports: # CodeBuild will create a report group called "SurefireReports". + files: #Store all of the files + - '**/*' + base-directory: 'web/web_support/target/surefire-reports' # Location of the reports artifacts: - type: NONE files: - web/web_ui/target/tank.war - agent/agent_startup_pkg/target/agent-startup-pkg.zip discard-paths: yes + +cache: + paths: + - '/root/.m2/**/*' diff --git a/data_access/pom.xml b/data_access/pom.xml index e66f2e1c5..c37f68048 100644 --- a/data_access/pom.xml +++ b/data_access/pom.xml @@ -6,7 +6,7 @@ com.intuit.tank tank-parent - 2.3.4 + 3.0.0 diff --git a/data_access/src/main/java/com/intuit/tank/dao/GroupDao.java b/data_access/src/main/java/com/intuit/tank/dao/GroupDao.java index 610f6c94c..a0a6aac35 100644 --- a/data_access/src/main/java/com/intuit/tank/dao/GroupDao.java +++ b/data_access/src/main/java/com/intuit/tank/dao/GroupDao.java @@ -26,9 +26,6 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import java.util.Collections; -import java.util.List; - /** * GroupDao * diff --git a/data_access/src/main/java/com/intuit/tank/dao/JobNotificationDao.java b/data_access/src/main/java/com/intuit/tank/dao/JobNotificationDao.java index 6bd7c69eb..fcbc4ce45 100644 --- a/data_access/src/main/java/com/intuit/tank/dao/JobNotificationDao.java +++ b/data_access/src/main/java/com/intuit/tank/dao/JobNotificationDao.java @@ -61,8 +61,10 @@ public JobNotification findRevision(int id, int revisionNumber) { begin(); AuditReader reader = AuditReaderFactory.get(getEntityManager()); result = reader.find(JobNotification.class, id, revisionNumber); - Hibernate.initialize(result.getLifecycleEvents()); - result.getLifecycleEvents().contains(JobLifecycleEvent.QUEUE_ADD); + if(result != null) { + Hibernate.initialize(result.getLifecycleEvents()); + result.getLifecycleEvents().contains(JobLifecycleEvent.QUEUE_ADD); + } commit(); } catch (NoResultException e) { rollback(); diff --git a/data_access/src/main/java/com/intuit/tank/dao/JobQueueDao.java b/data_access/src/main/java/com/intuit/tank/dao/JobQueueDao.java index 40cd21754..c5cac31a8 100644 --- a/data_access/src/main/java/com/intuit/tank/dao/JobQueueDao.java +++ b/data_access/src/main/java/com/intuit/tank/dao/JobQueueDao.java @@ -25,16 +25,10 @@ import javax.persistence.Query; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; -import javax.persistence.criteria.Fetch; -import javax.persistence.criteria.JoinType; import javax.persistence.criteria.Root; -import com.intuit.tank.project.JobInstance; -import com.intuit.tank.project.Project; -import com.intuit.tank.project.Workload; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.hibernate.LockOptions; import com.intuit.tank.project.JobQueue; diff --git a/data_access/src/main/java/com/intuit/tank/dao/ProjectDao.java b/data_access/src/main/java/com/intuit/tank/dao/ProjectDao.java index 659c2982b..b31215990 100644 --- a/data_access/src/main/java/com/intuit/tank/dao/ProjectDao.java +++ b/data_access/src/main/java/com/intuit/tank/dao/ProjectDao.java @@ -116,7 +116,7 @@ public synchronized Project saveOrUpdateProject(Project project) { } @Override - public Project saveOrUpdate(Project entity) throws HibernateException { + public Project saveOrUpdate(@Nonnull Project entity) throws HibernateException { entity.setModified(new Date()); return super.saveOrUpdate(entity); } diff --git a/data_access/src/main/java/com/intuit/tank/dao/ScriptDao.java b/data_access/src/main/java/com/intuit/tank/dao/ScriptDao.java index d9293e6f6..1bf6d705a 100644 --- a/data_access/src/main/java/com/intuit/tank/dao/ScriptDao.java +++ b/data_access/src/main/java/com/intuit/tank/dao/ScriptDao.java @@ -19,7 +19,9 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; +import java.util.Date; import java.util.List; +import java.util.zip.GZIPOutputStream; import javax.annotation.Nonnull; import javax.persistence.EntityManager; @@ -107,7 +109,6 @@ public void delete(Integer id) throws HibernateException { } LOG.debug("deleting entity " + entity.toString()); em.remove(entity); - commit(); } commit(); } catch (Exception e) { @@ -154,7 +155,7 @@ public Script loadScriptSteps(@Nonnull Script script) { if (script.getScriptSteps() == null || script.getScriptSteps().isEmpty()) { SerializedScriptStep serializedScriptStep = new SerializedScriptStepDao().findById(script .getSerializedScriptStepId()); - script.setSerializedSteps(serializedScriptStep); + script.deserializeSteps(serializedScriptStep); } return script; } @@ -174,17 +175,18 @@ public Script saveOrUpdate(Script script) { EntityManager em = getEntityManager(); try { begin(); - SerializedScriptStep serializedScriptStep = serialize(script.getScriptSteps()); + SerializedScriptStep serializedScriptStep = serialize(script); serializedScriptStep.setSerialzedData( Hibernate.getLobCreator(getHibernateSession()).createBlob(serializedScriptStep.getBytes())); - SerializedScriptStep serializedSteps = new SerializedScriptStepDao().saveOrUpdate(serializedScriptStep); + SerializedScriptStep savedSerializedStep = new SerializedScriptStepDao().saveOrUpdate(serializedScriptStep); script.setSerializedScriptStepId(serializedScriptStep.getId()); if (script.getId() == 0) { em.persist(script); } else { + script.setModified(new Date()); script = em.merge(script); } - LOG.debug("Saved Script Steps with id " + serializedSteps.getId() + " for script " + script.getId()); + LOG.debug("Saved Script Steps with id " + savedSerializedStep.getId() + " for script " + script.getId()); commit(); } catch (Exception e) { rollback(); @@ -198,16 +200,24 @@ public Script saveOrUpdate(Script script) { return script; } - public SerializedScriptStep serialize(List steps) { - try ( ByteArrayOutputStream bos = new ByteArrayOutputStream(); - ObjectOutputStream s = new ObjectOutputStream(bos) ) { - // if (steps.size() > 0) { - s.writeObject(steps); - return new SerializedScriptStep(bos.toByteArray()); - // } + private SerializedScriptStep serialize(Script script) { + SerializedScriptStep serializedScriptStep = script.getId() > 0 ? + new SerializedScriptStepDao().findById(script.getSerializedScriptStepId()) : + null; + try (ByteArrayOutputStream bos = new ByteArrayOutputStream(); + GZIPOutputStream gz = new GZIPOutputStream(bos); + ObjectOutputStream s = new ObjectOutputStream(gz) ) { + s.writeObject(script.getScriptSteps()); + s.close(); //Necessary to get the last few bites written + if ( serializedScriptStep != null) { + serializedScriptStep.setBytes(bos.toByteArray()); + } else { + serializedScriptStep = new SerializedScriptStep(bos.toByteArray()); + } } catch (IOException e) { throw new AnnotationException(e.toString()); } + return serializedScriptStep; } // private String getUniqueProjects(List steps) { diff --git a/data_access/src/main/java/com/intuit/tank/dao/UserDao.java b/data_access/src/main/java/com/intuit/tank/dao/UserDao.java index 2fb7f9cac..852f8dfb7 100644 --- a/data_access/src/main/java/com/intuit/tank/dao/UserDao.java +++ b/data_access/src/main/java/com/intuit/tank/dao/UserDao.java @@ -59,13 +59,12 @@ public UserDao() { @Nullable public User authenticate(@Nonnull String userName, @Nonnull String password) { User user = findByUserName(userName); - User result = null; if (user != null) { if (PasswordEncoder.validatePassword(password, user.getPassword())) { - result = user; + return user; } } - return result; + return null; } /** @@ -91,12 +90,12 @@ public User findByUserName(@Nonnull String userName) { commit(); } catch (NoResultException nre) { rollback(); - LOG.info("no entity matching username: " + userName, nre); + LOG.info("No entity matching username: " + userName); } catch (Exception e) { rollback(); String printQuery = (query != null) ? query.toString() : "Failed to connect to database: "; - LOG.info("no entity matching query: " + printQuery, e); + LOG.info("No entity matching query: " + printQuery, e); } finally { cleanup(); } diff --git a/data_access/src/test/java/com/intuit/tank/dao/BaseDaoTest.java b/data_access/src/test/java/com/intuit/tank/dao/BaseDaoTest.java new file mode 100644 index 000000000..13d93a059 --- /dev/null +++ b/data_access/src/test/java/com/intuit/tank/dao/BaseDaoTest.java @@ -0,0 +1,50 @@ +package com.intuit.tank.dao; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.config.Configuration; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +import com.intuit.tank.project.User; +import com.intuit.tank.test.TestGroups; + +public class BaseDaoTest { + + private BaseDao dao; + + @BeforeEach + public void configure() { + LoggerContext ctx = (LoggerContext) LogManager.getContext(false); + Configuration config = ctx.getConfiguration(); + config.getLoggerConfig(LogManager.ROOT_LOGGER_NAME).setLevel(Level.INFO); + ctx.updateLoggers(); + dao = new UserDao(); + } + + @Test + @Tag(TestGroups.FUNCTIONAL) + public void testBaseDao() throws Exception { + User entity = DaoTestUtil.createUserData("BaseTestUser1", "TestUser1_Password", "BaseTestUser1@intuit.com", "TestGroup1"); + List entities = new ArrayList(); + entities.add(entity); + dao.persistCollection(entities); + + List users = dao.findAll(); + for(User user: users) { + if(user.getEmail().equals(entity.getEmail())) { + assertEquals(entity.getName(), user.getName()); + break; + } + } + dao.delete(entity); + } +} diff --git a/data_access/src/test/java/com/intuit/tank/dao/DaoTestUtil.java b/data_access/src/test/java/com/intuit/tank/dao/DaoTestUtil.java index 4f0ca04f6..dcac76e86 100644 --- a/data_access/src/test/java/com/intuit/tank/dao/DaoTestUtil.java +++ b/data_access/src/test/java/com/intuit/tank/dao/DaoTestUtil.java @@ -17,6 +17,8 @@ */ import java.util.ArrayList; +import java.util.Date; +import java.util.HashSet; import java.util.List; import java.util.Random; import java.util.Set; @@ -26,12 +28,19 @@ import javax.validation.ConstraintViolation; import javax.validation.ConstraintViolationException; +import com.intuit.tank.project.DataFile; +import com.intuit.tank.project.Group; +import com.intuit.tank.project.JobRegion; +import com.intuit.tank.project.PeriodicData; import com.intuit.tank.project.Project; import com.intuit.tank.project.Script; import com.intuit.tank.project.ScriptGroup; import com.intuit.tank.project.ScriptGroupStep; import com.intuit.tank.project.ScriptStep; +import com.intuit.tank.project.User; import com.intuit.tank.project.Workload; +import com.intuit.tank.vm.api.enumerated.VMRegion; +import com.intuit.tank.vm.common.PasswordEncoder; import static org.junit.jupiter.api.Assertions.*; @@ -61,7 +70,7 @@ public static Project createProject() { .productName("Test Product Name") .name("Test Project Name " + generateStringOfLength(15)).build(); } - + /** * Generate a new Workload object for testing. * @@ -180,5 +189,74 @@ public static void checkConstraintViolation(@Nonnull ConstraintViolationExceptio } fail("Constraint violation did not contain a violation on property " + property); } + + /** + * Generate a new PeriodicData object for testing. + * + * @return the PeriodicData + */ + public static PeriodicData createPeriodicData(int jobId, Date timestamp) { + return PeriodicData.builder() + .min(1) + .max(5) + .jobId(jobId) + .pageId("Test Page Id "+generateStringOfLength(5)) + .timestamp(timestamp) + .build(); + } + + /** + * Generate a new User object for testing. + * + * @return the User + */ + public static User createUserData(String userName, String password,String email, String group) { + Set groups = new HashSet(); + groups.add(createGroupData(group)); + return User.builder() + .name(userName) + .password(PasswordEncoder.encodePassword(password)) + .email(email) + .groups(groups) + .generateApiToken() + .build(); + } + + /** + * Generate a new Group object for testing. + * + * @return the Group + */ + public static Group createGroupData(String groupName) { + return Group.builder() + .name(groupName) + .build(); + } + + /** + * Generate a new DataFile object for testing. + * + * @return the DataFile + */ + public static DataFile createDataFileData(String fileName) { + return DataFile.builder() + .fileName(fileName) + .path("/test_path") + .comments("test_comments") + .creator("test_creator") + .build(); + } + + /** + * Generate a new JobRegion object for testing. + * + * @return the DataFile + */ + public static JobRegion createJobRegionData(String users) { + return JobRegion.builder() + .users(users) + .region(VMRegion.US_WEST_2) + .build(); + } } diff --git a/data_access/src/test/java/com/intuit/tank/dao/DataFileDaoTest.java b/data_access/src/test/java/com/intuit/tank/dao/DataFileDaoTest.java new file mode 100644 index 000000000..f1da5e04a --- /dev/null +++ b/data_access/src/test/java/com/intuit/tank/dao/DataFileDaoTest.java @@ -0,0 +1,57 @@ +package com.intuit.tank.dao; + +/* + * #%L + * Data Access + * %% + * Copyright (C) 2011 - 2015 Intuit Inc. + * %% + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * #L% + */ + +import java.io.ByteArrayInputStream; +import java.io.InputStream; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.config.Configuration; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +import com.intuit.tank.project.DataFile; +import com.intuit.tank.test.TestGroups; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class DataFileDaoTest { + private DataFileDao dao; + + @BeforeEach + public void configure() { + LoggerContext ctx = (LoggerContext) LogManager.getContext(false); + Configuration config = ctx.getConfiguration(); + config.getLoggerConfig(LogManager.ROOT_LOGGER_NAME).setLevel(Level.INFO); + ctx.updateLoggers(); + dao = new DataFileDao(); + } + + @Test + @Tag(TestGroups.FUNCTIONAL) + public void testStoreDataFile() throws Exception { + DataFile dataFile = DaoTestUtil.createDataFileData("TestFile"); + + String initialString = "text"; + InputStream targetStream = new ByteArrayInputStream(initialString.getBytes()); + + DataFile result = dao.storeDataFile(dataFile, targetStream); + assertEquals(dataFile.getFileName(), result.getFileName()); + + + } +} diff --git a/data_access/src/test/java/com/intuit/tank/dao/FilterGroupDaoTest.java b/data_access/src/test/java/com/intuit/tank/dao/FilterGroupDaoTest.java new file mode 100644 index 000000000..b98ecabae --- /dev/null +++ b/data_access/src/test/java/com/intuit/tank/dao/FilterGroupDaoTest.java @@ -0,0 +1,47 @@ +/** + * Copyright 2011 Intuit Inc. All Rights Reserved + */ +package com.intuit.tank.dao; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.List; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.config.Configuration; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +import com.intuit.tank.project.ScriptFilterGroup; +import com.intuit.tank.test.TestGroups; + +/** + * JobQueueDaoTest + * + * @author msreekakula + * + */ +public class FilterGroupDaoTest { + + private FilterGroupDao dao; + + @BeforeEach + public void setUp() { + LoggerContext ctx = (LoggerContext) LogManager.getContext(false); + Configuration config = ctx.getConfiguration(); + config.getLoggerConfig(LogManager.ROOT_LOGGER_NAME).setLevel(Level.INFO); + ctx.updateLoggers(); // This causes all Loggers to refetch information from their LoggerConfig. + dao = new FilterGroupDao(); + } + + @Test + @Tag(TestGroups.FUNCTIONAL) + public void testFindFilterGroups() throws Exception { + List configurations = dao.getFilterGroupsForProduct("Test"); + assertEquals(0, configurations.size()); + } + +} diff --git a/data_access/src/test/java/com/intuit/tank/dao/GroupDaoTest.java b/data_access/src/test/java/com/intuit/tank/dao/GroupDaoTest.java new file mode 100644 index 000000000..8598689f5 --- /dev/null +++ b/data_access/src/test/java/com/intuit/tank/dao/GroupDaoTest.java @@ -0,0 +1,77 @@ +package com.intuit.tank.dao; + +/* + * #%L + * Data Access + * %% + * Copyright (C) 2011 - 2015 Intuit Inc. + * %% + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * #L% + */ + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.config.Configuration; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +import com.intuit.tank.project.Group; +import com.intuit.tank.test.TestGroups; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class GroupDaoTest { + private GroupDao dao; + + @BeforeEach + public void configure() { + LoggerContext ctx = (LoggerContext) LogManager.getContext(false); + Configuration config = ctx.getConfiguration(); + config.getLoggerConfig(LogManager.ROOT_LOGGER_NAME).setLevel(Level.INFO); + ctx.updateLoggers(); + dao = new GroupDao(); + } + + @Test + @Tag(TestGroups.FUNCTIONAL) + public void testFindByName() throws Exception { + Group first = DaoTestUtil.createGroupData("testGroup"); + first = dao.saveOrUpdate(first); + + //Try to fetch the group which was created + Group result = dao.findByName("testGroup"); + assertEquals(first.getId(), result.getId()); + + //Try to fetch a group which was not created, it will return null value + result = dao.findByName("testGroup1"); + assertEquals(null,result); + + dao.delete(first); + } + + @Test + @Tag(TestGroups.FUNCTIONAL) + public void testGetOrCreateGroup() throws Exception { + Group first = DaoTestUtil.createGroupData("testGroup"); + first = dao.saveOrUpdate(first); + + //Try to create a group which was created earlier, it will not create a new group + // and will match with the group id + Group result = dao.getOrCreateGroup("testGroup"); + assertEquals(first.getId(), result.getId()); + + //Try to create a group with a new name, it will create a new group + Group result2 = dao.getOrCreateGroup("testGroup2"); + assertEquals("testGroup2", result2.getName()); + + dao.delete(first); + dao.delete(result2); + + } +} diff --git a/data_access/src/test/java/com/intuit/tank/dao/JobNotificationDaoTest.java b/data_access/src/test/java/com/intuit/tank/dao/JobNotificationDaoTest.java new file mode 100644 index 000000000..63484b45d --- /dev/null +++ b/data_access/src/test/java/com/intuit/tank/dao/JobNotificationDaoTest.java @@ -0,0 +1,56 @@ +/** + * Copyright 2011 Intuit Inc. All Rights Reserved + */ +package com.intuit.tank.dao; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.Arrays; +import java.util.Date; +import java.util.List; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.config.Configuration; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +import com.intuit.tank.project.JobNotification; +import com.intuit.tank.project.JobQueue; +import com.intuit.tank.test.TestGroups; + +/** + * JobQueueDaoTest + * + * @author msreekakula + * + */ +public class JobNotificationDaoTest { + + private JobNotificationDao dao; + + @BeforeEach + public void setUp() { + LoggerContext ctx = (LoggerContext) LogManager.getContext(false); + Configuration config = ctx.getConfiguration(); + config.getLoggerConfig(LogManager.ROOT_LOGGER_NAME).setLevel(Level.INFO); + ctx.updateLoggers(); // This causes all Loggers to refetch information from their LoggerConfig. + dao = new JobNotificationDao(); + } + + @Test + @Tag(TestGroups.FUNCTIONAL) + public void testFindRevisions() throws Exception { + JobNotification jobnotification = new JobNotification(); + jobnotification.setId(1); + jobnotification.setBody("Test"); + JobNotification result = dao.saveOrUpdate(jobnotification); + assertEquals(jobnotification.getBody(), result.getBody()); + JobNotification revisionJob = dao.findRevision(0,1); + assertEquals(null, revisionJob); + + } + +} diff --git a/data_access/src/test/java/com/intuit/tank/dao/JobQueueDaoTest.java b/data_access/src/test/java/com/intuit/tank/dao/JobQueueDaoTest.java new file mode 100644 index 000000000..0e0494ca7 --- /dev/null +++ b/data_access/src/test/java/com/intuit/tank/dao/JobQueueDaoTest.java @@ -0,0 +1,56 @@ +/** + * Copyright 2011 Intuit Inc. All Rights Reserved + */ +package com.intuit.tank.dao; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.Arrays; +import java.util.Date; +import java.util.List; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.config.Configuration; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +import com.intuit.tank.project.JobQueue; +import com.intuit.tank.test.TestGroups; + +/** + * JobQueueDaoTest + * + * @author msreekakula + * + */ +public class JobQueueDaoTest { + + private JobQueueDao dao; + + @BeforeEach + public void setUp() { + LoggerContext ctx = (LoggerContext) LogManager.getContext(false); + Configuration config = ctx.getConfiguration(); + config.getLoggerConfig(LogManager.ROOT_LOGGER_NAME).setLevel(Level.INFO); + ctx.updateLoggers(); // This causes all Loggers to refetch information from their LoggerConfig. + dao = new JobQueueDao(); + } + + @Test + @Tag(TestGroups.FUNCTIONAL) + public void testFindJobQueues() throws Exception { + JobQueue queue = dao.findOrCreateForProjectId(1); + assertEquals(1, queue.getId()); + JobQueue queue1 = dao.findForJobId(2); + assertEquals(null, queue1); + List queue2 = dao.getForProjectIds(Arrays.asList(1,2,3)); + assertEquals(1, queue2.size()); + List recentQueues = dao.findRecent(new Date()); + assertEquals(0, recentQueues.size()); + + } + +} diff --git a/data_access/src/test/java/com/intuit/tank/dao/JobRegionDaoTest.java b/data_access/src/test/java/com/intuit/tank/dao/JobRegionDaoTest.java new file mode 100644 index 000000000..e4f8aa289 --- /dev/null +++ b/data_access/src/test/java/com/intuit/tank/dao/JobRegionDaoTest.java @@ -0,0 +1,69 @@ +package com.intuit.tank.dao; + +/* + * #%L + * Data Access + * %% + * Copyright (C) 2011 - 2015 Intuit Inc. + * %% + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Set; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.config.Configuration; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +import com.intuit.tank.project.JobRegion; +import com.intuit.tank.test.TestGroups; + +public class JobRegionDaoTest { + + private JobRegionDao dao; + + @BeforeEach + public void configure() { + LoggerContext ctx = (LoggerContext) LogManager.getContext(false); + Configuration config = ctx.getConfiguration(); + config.getLoggerConfig(LogManager.ROOT_LOGGER_NAME).setLevel(Level.INFO); + ctx.updateLoggers(); + dao = new JobRegionDao(); + } + + @Test + @Tag(TestGroups.FUNCTIONAL) + public void testCleanRegions() throws Exception { + JobRegion jobRegion3 = DaoTestUtil.createJobRegionData("TestUser_1"); + jobRegion3.setCreated(new Date()); + + JobRegion jobRegion2 = DaoTestUtil.createJobRegionData("TestUser_1"); + jobRegion2 = dao.saveOrUpdate(jobRegion2); + + JobRegion jobRegion1 = DaoTestUtil.createJobRegionData("TestUser_1"); + jobRegion1.setCreated(new Date()); + jobRegion1 = dao.saveOrUpdate(jobRegion1); + + + List jobRegions = new ArrayList(2); + jobRegions.add(jobRegion1); + jobRegions.add(jobRegion3); + + Set regions = dao.cleanRegions(jobRegions); + assertEquals(1, regions.size()); + + } +} diff --git a/data_access/src/test/java/com/intuit/tank/dao/PeriodicDataTest.java b/data_access/src/test/java/com/intuit/tank/dao/PeriodicDataTest.java new file mode 100644 index 000000000..d46a19e29 --- /dev/null +++ b/data_access/src/test/java/com/intuit/tank/dao/PeriodicDataTest.java @@ -0,0 +1,95 @@ +package com.intuit.tank.dao; + +/* + * #%L + * Data Access + * %% + * Copyright (C) 2011 - 2015 Intuit Inc. + * %% + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.Calendar; +import java.util.Date; +import java.util.List; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.config.Configuration; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +import com.intuit.tank.project.PeriodicData; +import com.intuit.tank.test.TestGroups; + +public class PeriodicDataTest { + private PeriodicDataDao dao; + + @BeforeEach + public void configure() { + LoggerContext ctx = (LoggerContext) LogManager.getContext(false); + Configuration config = ctx.getConfiguration(); + config.getLoggerConfig(LogManager.ROOT_LOGGER_NAME).setLevel(Level.INFO); + ctx.updateLoggers(); + dao = new PeriodicDataDao(); + } + + @Test + @Tag(TestGroups.FUNCTIONAL) + public void testFindByJobId() throws Exception { + PeriodicData first = DaoTestUtil.createPeriodicData(1, new Date()); + first = dao.saveOrUpdate(first); + + PeriodicData second = DaoTestUtil.createPeriodicData(2, new Date()); + second = dao.saveOrUpdate(second); + + PeriodicData third = DaoTestUtil.createPeriodicData(3, new Date()); + third = dao.saveOrUpdate(third); + + List list = dao.findByJobId(2); + assertEquals(second.getJobId(), list.get(0).getJobId()); + } + + @Test + @Tag(TestGroups.FUNCTIONAL) + public void testFindByJobIdForDateRange() throws Exception { + Date tenDaysBeforeDate = getCustomDate(-10); + PeriodicData first = DaoTestUtil.createPeriodicData(1, tenDaysBeforeDate); + first = dao.saveOrUpdate(first); + + Date sevenDaysBeforeDate = getCustomDate(-7); + PeriodicData second = DaoTestUtil.createPeriodicData(2, sevenDaysBeforeDate); + second = dao.saveOrUpdate(second); + + Date fiveDaysBeforeDate = getCustomDate(-5); + PeriodicData third = DaoTestUtil.createPeriodicData(2, fiveDaysBeforeDate); + third = dao.saveOrUpdate(third); + + + Date fourDaysBeforeDate = getCustomDate(-4); + List list = dao.findByJobId(2, sevenDaysBeforeDate, fourDaysBeforeDate); + assertEquals(2, list.size()); + + //Even though PeriodicData exists with jobId, the date range is outside, so empty list is returned + list = dao.findByJobId(2, new Date(), new Date()); + assertEquals(0, list.size()); + + //If both dates are null, then PeriodicData matching the jobIds are returned + list = dao.findByJobId(2, null,null); + assertEquals(2, list.size()); + } + + private Date getCustomDate(int days) { + Calendar calendar = Calendar.getInstance(); + calendar.add(Calendar.DATE, days); + return calendar.getTime(); + } +} diff --git a/data_access/src/test/java/com/intuit/tank/dao/ScriptDaoTest.java b/data_access/src/test/java/com/intuit/tank/dao/ScriptDaoTest.java index 745e5e584..617fac65e 100644 --- a/data_access/src/test/java/com/intuit/tank/dao/ScriptDaoTest.java +++ b/data_access/src/test/java/com/intuit/tank/dao/ScriptDaoTest.java @@ -19,6 +19,7 @@ import java.util.ArrayList; import java.util.Calendar; import java.util.List; +import java.util.stream.IntStream; import java.util.stream.Stream; import javax.validation.ConstraintViolationException; @@ -29,12 +30,11 @@ import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.config.Configuration; -import com.intuit.tank.dao.ScriptDao; import com.intuit.tank.project.Script; import com.intuit.tank.project.ScriptStep; import com.intuit.tank.view.filter.ViewFilterType; +import org.hibernate.PropertyValueException; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -82,7 +82,6 @@ public void configure() { @Test @Tag(TestGroups.FUNCTIONAL) - @Disabled public void testChildOrder() throws Exception { Script entity = DaoTestUtil.createScript(); entity.addStep(DaoTestUtil.createScriptStep()); @@ -97,17 +96,17 @@ public void testChildOrder() throws Exception { children.add(0, removed); persisted = dao.saveOrUpdate(persisted); persisted = dao.findById(id); - assertFalse(persisted.getScriptSteps().get(0).equals(originalOrder.get(0))); - assertTrue(persisted.getScriptSteps().get(0).equals(originalOrder.get(2))); - assertTrue(persisted.getScriptSteps().get(1).equals(originalOrder.get(0))); - assertTrue(persisted.getScriptSteps().get(2).equals(originalOrder.get(1))); + assertNotEquals(originalOrder.get(0), persisted.getScriptSteps().get(0)); + assertEquals(originalOrder.get(2), persisted.getScriptSteps().get(0)); + assertEquals(originalOrder.get(0), persisted.getScriptSteps().get(1)); + assertEquals(originalOrder.get(1), persisted.getScriptSteps().get(2)); originalOrder = new ArrayList(persisted.getScriptSteps()); persisted.getScriptSteps().remove(2); persisted.getScriptSteps().remove(0); persisted = dao.saveOrUpdate(persisted); persisted = dao.findById(id); - assertTrue(persisted.getScriptSteps().get(0).equals(originalOrder.get(1))); + assertEquals(originalOrder.get(1), persisted.getScriptSteps().get(0)); assertEquals(1, persisted.getScriptSteps().size()); } finally { @@ -160,10 +159,27 @@ public void testFilter() throws Exception { assertEquals(fourth.getId(), list.get(3).getId()); } + @Test + @Tag(TestGroups.FUNCTIONAL) + public void testCompressScripSteps() { + + Script script = DaoTestUtil.createScript(); + IntStream.range(0, 500).forEach(i -> script.addStep(DaoTestUtil.createScriptStep())); + int id = dao.saveOrUpdate(script).getId(); + + Script scriptOut = dao.findById(id); + assertNotNull(scriptOut); + + dao.loadScriptSteps(scriptOut); + List StepsOut = scriptOut.getSteps(); + assertEquals(500, StepsOut.size()); + + dao.delete(id); + } + @ParameterizedTest @Tag(TestGroups.FUNCTIONAL) @MethodSource("validations") - @Disabled public void testValidation(Script entity, String property, String messageContains) throws Exception { try { dao.saveOrUpdate(entity); @@ -171,12 +187,17 @@ public void testValidation(Script entity, String property, String messageContain } catch (ConstraintViolationException e) { // expected validation DaoTestUtil.checkConstraintViolation(e, property, messageContains); + } catch (RuntimeException e) { + if (e.getCause().getCause() instanceof PropertyValueException) { + assertTrue(e.getCause().getCause().getMessage().startsWith("not-null property references a null or transient value")); + return; + } + assertTrue(e.getCause().getCause().getCause().getMessage().startsWith("Value too long for column ")); } } @Test @Tag(TestGroups.FUNCTIONAL) - @Disabled public void testBasicCreateUpdateDelete() throws Exception { List