From 335553236bd872f980424ef35bd7c0fdfd1fdd62 Mon Sep 17 00:00:00 2001 From: Stephen Wood Date: Tue, 27 Sep 2016 16:01:48 -0700 Subject: [PATCH 1/3] Bump python version. (#397) * Bump python version. * Upgrade Gradle to 3.1 and Spring Platform to 2.0.8.RELEASE (#398) * Upgrade Gradle to 3.1 and Spring Platform to 2.0.8.RELEASE --- genie-client/src/main/python/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/genie-client/src/main/python/setup.py b/genie-client/src/main/python/setup.py index 7e3cfd5c734..87df3025da8 100644 --- a/genie-client/src/main/python/setup.py +++ b/genie-client/src/main/python/setup.py @@ -26,7 +26,7 @@ setup( name='nflx-genie-client', - version='3.0.43', + version='3.0.44', author='Netflix Inc.', author_email='genieoss@googlegroups.com', keywords='genie hadoop cloud netflix client bigdata presto', From f9e9bf0f5227e6b3872ad8f71ff58ec516ab5c1b Mon Sep 17 00:00:00 2001 From: John Lee Date: Tue, 27 Sep 2016 17:04:15 -0700 Subject: [PATCH 2/3] hive parameters use parameter file in Python client (#399) --- .../src/main/python/pygenie/jobs/hive.py | 30 +++++++--- .../src/main/python/pygenie/jobs/pig.py | 4 +- genie-client/src/main/python/setup.py | 2 +- .../python/tests/job_tests/test_hivejob.py | 60 +++++++++++-------- .../python/tests/job_tests/test_pigjob.py | 6 +- 5 files changed, 63 insertions(+), 39 deletions(-) diff --git a/genie-client/src/main/python/pygenie/jobs/hive.py b/genie-client/src/main/python/pygenie/jobs/hive.py index 7aae5c9fa15..e5cffc42ed6 100644 --- a/genie-client/src/main/python/pygenie/jobs/hive.py +++ b/genie-client/src/main/python/pygenie/jobs/hive.py @@ -63,12 +63,14 @@ def cmd_args(self): elif self._script is not None: self._add_dependency({'name': filename, 'data': self._script}) - params_str = ' '.join([ - "-d '{name}={value}'" \ - .format(name=k, - value=unicode(v).replace("'", "''")) \ - for k, v in self._parameters.items() - ]) + # put parameters into a parameter file and specify parameter file on command line + # this is to get around weird quoting issues in parameter values, etc + param_str = self._parameter_file + if param_str: + self._add_dependency({ + 'name': '_hive_parameters.txt', + 'data': param_str + }) props_str = ' '.join([ '--hiveconf {name}={value}'.format(name=k, value=v) \ @@ -83,9 +85,23 @@ def cmd_args(self): .format(prop_file=prop_file_str, props=props_str, filename=filename, - params=params_str) \ + params='-i _hive_parameters.txt' if param_str else '') \ .strip() + @property + def _parameter_file(self): + """Takes specified parameters and creates a string for the parameter file.""" + + param_file = "" + + for name, value in self._parameters.items(): + param_file = '{p}SET hivevar:{name}={value};\n' \ + .format(p=param_file, + name=name, + value=unicode(value)) + + return param_file.strip() + def headers(self): """ Sets hive.cli.print.header so that if the hive query is outputing diff --git a/genie-client/src/main/python/pygenie/jobs/pig.py b/genie-client/src/main/python/pygenie/jobs/pig.py index d82cceb5b07..664d1a2429f 100644 --- a/genie-client/src/main/python/pygenie/jobs/pig.py +++ b/genie-client/src/main/python/pygenie/jobs/pig.py @@ -70,7 +70,7 @@ def cmd_args(self): for p in self._parameter_files ]) - # put parameters into a parameter file and specify paramter file on command line + # put parameters into a parameter file and specify parameter file on command line # this is to get around weird quoting issues in parameter values, etc param_str = self._parameter_file if param_str: @@ -108,7 +108,7 @@ def _parameter_file(self): name=name, value=unicode(value).replace('"', '\\"')) - return param_file + return param_file.strip() @unicodify @arg_list diff --git a/genie-client/src/main/python/setup.py b/genie-client/src/main/python/setup.py index 87df3025da8..a2710292458 100644 --- a/genie-client/src/main/python/setup.py +++ b/genie-client/src/main/python/setup.py @@ -26,7 +26,7 @@ setup( name='nflx-genie-client', - version='3.0.44', + version='3.0.45', author='Netflix Inc.', author_email='genieoss@googlegroups.com', keywords='genie hadoop cloud netflix client bigdata presto', diff --git a/genie-client/src/main/python/tests/job_tests/test_hivejob.py b/genie-client/src/main/python/tests/job_tests/test_hivejob.py index 060efbad4ac..14742b720ba 100644 --- a/genie-client/src/main/python/tests/job_tests/test_hivejob.py +++ b/genie-client/src/main/python/tests/job_tests/test_hivejob.py @@ -59,12 +59,12 @@ def test_cmd_args_constructed_script_code(self): .property('prop2', 'p2') assert_equals( - job.cmd_args, u" ".join([ u"--hiveconf hconf1=h1 --hiveconf prop1=p1 --hiveconf prop2=p2", - u"-d 'foo=fizz' -d 'bar=buzz'", + u"-i _hive_parameters.txt", u"-f script.hive" - ]) + ]), + job.cmd_args ) @patch('pygenie.jobs.hive.is_file') @@ -84,32 +84,35 @@ def test_cmd_args_constructed_script_file(self, is_file): job.cmd_args, u" ".join([ u"--hiveconf p2=v2 --hiveconf p1=v1", - u"-d 'hello=hi' -d 'goodbye=bye'", + u"-i _hive_parameters.txt", u"-f test.hql" ]) ) - def test_cmd_args_constructed_quotes(self): - """Test HiveJob constructed cmd args with quotes.""" + +@patch.dict('os.environ', {'GENIE_BYPASS_HOME_CONFIG': '1'}) +class TestingHiveJobParameters(unittest.TestCase): + """Test HiveJob parameters.""" + + def test_parameter_file(self): + """Test HiveJob parameters into file.""" job = pygenie.jobs.HiveJob() \ - .script('foo') \ .parameter("spaces", "this has spaces") \ .parameter("single_quotes", "test' test'") \ - .parameter("escaped_single_quotes", "Barney\\'s Adventure") \ + .parameter("escaped_single_quotes", "Barney\\\'s Adventure") \ .parameter("unicode", "\xf3\xf3\xf3") \ .parameter("number", 8) assert_equals( - job.cmd_args, - u" ".join([ - u"-d 'escaped_single_quotes=Barney\\''s Adventure'", - u"-d 'spaces=this has spaces'", - u"-d 'single_quotes=test'' test'''", - u"-d 'unicode=\xf3\xf3\xf3'", - u"-d 'number=8'", - u"-f script.hive" - ]) + '\n'.join([ + "SET hivevar:escaped_single_quotes=Barney\\\'s Adventure;", + "SET hivevar:spaces=this has spaces;", + "SET hivevar:single_quotes=test' test';", + "SET hivevar:unicode=\xf3\xf3\xf3;", + "SET hivevar:number=8;" + ]), + job._parameter_file ) @@ -238,13 +241,14 @@ def test_genie2_payload_adhoc_script(self, os_isfile, to_att): u'attachments': [ {u'name': u'hive.file1', u'data': u'file contents'}, {u'name': u'hive.file2', u'data': u'file contents'}, - {u'name': u'script.hive', u'data': u'SELECT * FROM DUAL'} + {u'name': u'script.hive', u'data': u'SELECT * FROM DUAL'}, + {u'name': u'_hive_parameters.txt', u'data': u'SET hivevar:a=b;'} ], u'clusterCriterias': [ {u'tags': [u'type:hive.cluster1']}, {u'tags': [u'type:hive']} ], - u'commandArgs': u'-d \'a=b\' -f script.hive', + u'commandArgs': u'-i _hive_parameters.txt -f script.hive', u'commandCriteria': [u'type:hive.cmd'], u'description': u'this job is to test hivejob adapter', u'disableLogArchival': True, @@ -296,13 +300,15 @@ def test_genie2_payload_file_script(self, presto_is_file, os_isfile, to_att): u'attachments': [ {u'name': u'hive.file1', u'data': u'file contents'}, {u'name': u'hive.file2', u'data': u'file contents'}, - {u'name': u'script.hql', u'data': u'file contents'} + {u'name': u'script.hql', u'data': u'file contents'}, + {u'name': u'_hive_parameters.txt', + u'data': u'SET hivevar:a=1;\nSET hivevar:b=2;'} ], u'clusterCriterias': [ {u'tags': [u'type:hive.cluster2']}, {u'tags': [u'type:hive']} ], - u'commandArgs': u'-d \'a=1\' -d \'b=2\' -f script.hql', + u'commandArgs': u'-i _hive_parameters.txt -f script.hql', u'commandCriteria': [u'type:hive.cmd.2'], u'description': u'this job is to test hivejob adapter', u'disableLogArchival': True, @@ -356,13 +362,14 @@ def test_genie3_payload_adhoc_script(self, os_isfile, file_open): u'attachments': [ (u'hive.file1', u"open file '/hive.file1'"), (u'hive.file2', u"open file '/hive.file2'"), - (u'script.hive', u'SELECT * FROM DUAL') + (u'script.hive', u'SELECT * FROM DUAL'), + (u'_hive_parameters.txt', u'SET hivevar:a=a;\nSET hivevar:b=b;') ], u'clusterCriterias': [ {u'tags': [u'type:hive.cluster-1', u'type:hive.cluster-2']}, {u'tags': [u'type:hive']} ], - u'commandArgs': u'-i properties.conf -d \'a=a\' -d \'b=b\' -f script.hive', + u'commandArgs': u'-i properties.conf -i _hive_parameters.txt -f script.hive', u'commandCriteria': [u'type:hive.cmd.1', u'type:hive.cmd.2'], u'dependencies': [u'x://properties.conf'], u'description': u'this job is to test hivejob adapter', @@ -383,7 +390,7 @@ def test_genie3_payload_adhoc_script(self, os_isfile, file_open): @patch('os.path.isfile') @patch('pygenie.jobs.hive.is_file') def test_genie3_payload_file_script(self, presto_is_file, os_isfile, file_open): - """Test PrestoJob payload for Genie 3 (file script).""" + """Test HiveJob payload for Genie 3 (file script).""" os_isfile.return_value = True presto_is_file.return_value = True @@ -420,13 +427,14 @@ def test_genie3_payload_file_script(self, presto_is_file, os_isfile, file_open): (u'hive.file1', u"open file '/hive.file1'"), (u'hive.file2', u"open file '/hive.file2'"), (u'properties.conf', u"open file '/properties.conf'"), - (u'script.hql', u"open file '/script.hql'") + (u'script.hql', u"open file '/script.hql'"), + (u'_hive_parameters.txt', u'SET hivevar:a=a;\nSET hivevar:b=b;') ], u'clusterCriterias': [ {u'tags': [u'type:hive.cluster-1', u'type:hive.cluster-2']}, {u'tags': [u'type:hive']} ], - u'commandArgs': u'-i properties.conf -d \'a=a\' -d \'b=b\' -f script.hql', + u'commandArgs': u'-i properties.conf -i _hive_parameters.txt -f script.hql', u'commandCriteria': [u'type:hive.cmd.1', u'type:hive.cmd.2'], u'dependencies': [], u'description': u'this job is to test hivejob adapter', diff --git a/genie-client/src/main/python/tests/job_tests/test_pigjob.py b/genie-client/src/main/python/tests/job_tests/test_pigjob.py index 94012d0b433..37db5d0e27a 100644 --- a/genie-client/src/main/python/tests/job_tests/test_pigjob.py +++ b/genie-client/src/main/python/tests/job_tests/test_pigjob.py @@ -123,7 +123,7 @@ def test_basic(self): spaces = "this has spaces" double_quotes = "Something: \\"Episode 189\\"" unicode = "\u0147\u0147\u0147" -escaped_single_quotes = "Barney\\'s Adventure" +escaped_single_quotes = "Barney\\'s Adventure"\ """ assert_equals( @@ -263,7 +263,7 @@ def test_genie2_payload_adhoc_script(self, os_isfile, to_att): {u'data': u'file contents', u'name': u'pig_param1.params'}, {u'data': u'file contents', u'name': u'pig_param2.params'}, {u'data': u'A = LOAD;', u'name': u'script.pig'}, - {u'data': u'param2 = "2"\nparam1 = "1"\n', u'name': u'_pig_parameters.txt'} + {u'data': u'param2 = "2"\nparam1 = "1"', u'name': u'_pig_parameters.txt'} ], u'clusterCriterias': [ {u'tags': [u'type:pig_cluster_1']}, @@ -391,7 +391,7 @@ def test_genie3_payload_adhoc_script(self, os_isfile, file_open): (u'pig_param1.params', u"open file '/pig_param1.params'"), (u'pig_param2.params', u"open file '/pig_param2.params'"), (u'script.pig', u'A = LOAD;'), - (u'_pig_parameters.txt', u'param2 = "2"\nparam1 = "1"\n') + (u'_pig_parameters.txt', u'param2 = "2"\nparam1 = "1"') ], u'clusterCriterias': [ {u'tags': [u'type:pig_cluster_1']}, From 7a087e7da583562e8c695681cedf419794981404 Mon Sep 17 00:00:00 2001 From: Tom Gianos Date: Wed, 28 Sep 2016 09:46:42 -0700 Subject: [PATCH 3/3] Move documentation snippet generation (using Spring REST docs) to the web project to avoid starting up another instance of the server during the build. Also keeps all tests consolidated and less redundancy. Speeds up build. Upgraded REST docs to 1.1.2 to get some of the new features that aren't in 1.0.x. Improved the documentation but still a lot of work to do before 3.0.0 release (#400) --- genie-docs/build.gradle | 36 -- .../genie/docs/ApplicationRestDocs.java | 548 ------------------ .../com/netflix/genie/docs/package-info.java | 25 - .../resources/application-docs.properties | 28 - genie-web/build.gradle | 34 +- .../tasks/job/JobCompletionHandlerSpec.groovy | 17 + .../tasks/job/JobCompletionServiceSpec.groovy | 17 + ...icationRestControllerIntegrationTests.java | 399 ++++++++++--- ...ClusterRestControllerIntegrationTests.java | 163 ++++-- ...CommandRestControllerIntegrationTests.java | 302 ++++++++-- .../RestControllerIntegrationTestsBase.java | 172 +++--- .../genie/web/controllers/Snippets.java | 190 ++++++ .../ConstraintDescriptions.properties | 0 .../restdocs/templates/request-fields.snippet | 5 +- gradle.properties | 2 +- settings.gradle | 2 +- 16 files changed, 1027 insertions(+), 913 deletions(-) delete mode 100644 genie-docs/build.gradle delete mode 100644 genie-docs/src/test/java/com/netflix/genie/docs/ApplicationRestDocs.java delete mode 100644 genie-docs/src/test/java/com/netflix/genie/docs/package-info.java delete mode 100644 genie-docs/src/test/resources/application-docs.properties create mode 100644 genie-web/src/test/java/com/netflix/genie/web/controllers/Snippets.java rename {genie-docs => genie-web}/src/test/resources/org/springframework/restdocs/constraints/ConstraintDescriptions.properties (100%) rename {genie-docs => genie-web}/src/test/resources/org/springframework/restdocs/templates/request-fields.snippet (57%) diff --git a/genie-docs/build.gradle b/genie-docs/build.gradle deleted file mode 100644 index a5a7acc0375..00000000000 --- a/genie-docs/build.gradle +++ /dev/null @@ -1,36 +0,0 @@ -apply plugin: 'org.asciidoctor.convert' - -ext { - snippetsDir = file('build/generated-snippets') -} - -dependencies { - /******************************* - * Compile Dependencies - *******************************/ - - /******************************* - * Provided Dependencies - *******************************/ - - /******************************* - * Runtime Dependencies - *******************************/ - - /******************************* - * Test Dependencies - *******************************/ - - testCompile(project(":genie-test")) - testCompile(project(":genie-web")) - testCompile("com.jayway.jsonpath:json-path") - testCompile("org.springframework.restdocs:spring-restdocs-mockmvc") -} - -test { - outputs.dir snippetsDir -} - -documentationTests { - outputs.dir snippetsDir -} diff --git a/genie-docs/src/test/java/com/netflix/genie/docs/ApplicationRestDocs.java b/genie-docs/src/test/java/com/netflix/genie/docs/ApplicationRestDocs.java deleted file mode 100644 index e7ef68bda25..00000000000 --- a/genie-docs/src/test/java/com/netflix/genie/docs/ApplicationRestDocs.java +++ /dev/null @@ -1,548 +0,0 @@ -/* - * - * Copyright 2015 Netflix, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ -package com.netflix.genie.docs; - -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.util.ISO8601DateFormat; -import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; -import com.google.common.collect.Lists; -import com.google.common.collect.Sets; -import com.netflix.genie.GenieWeb; -import com.netflix.genie.common.dto.Application; -import com.netflix.genie.common.dto.ApplicationStatus; -import com.netflix.genie.core.jpa.repositories.JpaApplicationRepository; -import com.netflix.genie.test.categories.DocumentationTest; -import org.hamcrest.Matchers; -import org.junit.After; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Rule; -import org.junit.Test; -import org.junit.experimental.categories.Category; -import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.SpringApplicationConfiguration; -import org.springframework.boot.test.WebIntegrationTest; -import org.springframework.hateoas.MediaTypes; -import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; -import org.springframework.restdocs.RestDocumentation; -import org.springframework.restdocs.constraints.ConstraintDescriptions; -import org.springframework.restdocs.hypermedia.HypermediaDocumentation; -import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation; -import org.springframework.restdocs.mockmvc.RestDocumentationResultHandler; -import org.springframework.restdocs.operation.preprocess.Preprocessors; -import org.springframework.restdocs.payload.FieldDescriptor; -import org.springframework.restdocs.payload.JsonFieldType; -import org.springframework.restdocs.payload.PayloadDocumentation; -import org.springframework.restdocs.request.RequestDocumentation; -import org.springframework.restdocs.snippet.Attributes; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; -import org.springframework.test.web.servlet.result.MockMvcResultHandlers; -import org.springframework.test.web.servlet.result.MockMvcResultMatchers; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; -import org.springframework.util.StringUtils; -import org.springframework.web.context.WebApplicationContext; - -import javax.servlet.RequestDispatcher; -import java.util.List; -import java.util.TimeZone; - -/** - * Used to generate the documentation for the Genie REST APIs. - * - * @author tgianos - * @since 3.0.0 - */ -@Category(DocumentationTest.class) -@ActiveProfiles({"docs"}) -@RunWith(SpringJUnit4ClassRunner.class) -@SpringApplicationConfiguration(classes = GenieWeb.class) -@WebIntegrationTest(randomPort = true) -public class ApplicationRestDocs { - - private static final String APPLICATION_API_PATH = "/api/v3/applications"; - private static ObjectMapper objectMapper; - - /** - * Where to put the generated documentation. - */ - @Rule - public final RestDocumentation restDocumentation = new RestDocumentation("build/generated-snippets"); - - @Autowired - private WebApplicationContext context; - - @Autowired - private JpaApplicationRepository jpaApplicationRepository; - - private RestDocumentationResultHandler document; - private MockMvc mockMvc; - - /** - * Set up class level variables. - */ - @BeforeClass - public static void setupClass() { - objectMapper = new ObjectMapper() - .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) - .setTimeZone(TimeZone.getTimeZone("UTC")) - .setDateFormat(new ISO8601DateFormat()) - .registerModule(new Jdk8Module()); - } - - /** - * Setup the tests. - */ - @Before - public void setUp() { - this.document = MockMvcRestDocumentation.document( - "{method-name}", - Preprocessors.preprocessRequest(Preprocessors.prettyPrint()), - Preprocessors.preprocessResponse(Preprocessors.prettyPrint()) - ); - - this.mockMvc = MockMvcBuilders - .webAppContextSetup(this.context) - .apply(MockMvcRestDocumentation.documentationConfiguration(this.restDocumentation)) - .build(); - } - - /** - * Reset for the next test. - */ - @After - public void teardown() { - this.jpaApplicationRepository.deleteAll(); - } - - /** - * Test to document error responses. - * - * @throws Exception For any error - */ - @Test - public void errorExample() throws Exception { - this.document.snippets( - PayloadDocumentation.responseFields( - PayloadDocumentation - .fieldWithPath("error") - .description("The HTTP error that occurred, e.g. `Bad Request`"), - PayloadDocumentation - .fieldWithPath("message") - .description("A description of the cause of the error"), - PayloadDocumentation - .fieldWithPath("path") - .description("The path to which the request was made"), - PayloadDocumentation - .fieldWithPath("status") - .description("The HTTP status code, e.g. `400`"), - PayloadDocumentation - .fieldWithPath("timestamp") - .description("The time, in milliseconds, at which the error occurred") - ) - ); - - this.mockMvc - .perform(MockMvcRequestBuilders - .get("/error") - .requestAttr(RequestDispatcher.ERROR_STATUS_CODE, 404) - .requestAttr(RequestDispatcher.ERROR_REQUEST_URI, APPLICATION_API_PATH + "/1") - .requestAttr( - RequestDispatcher.ERROR_MESSAGE, - "The application 'http://localhost:8080" - + APPLICATION_API_PATH - + "/1' does not exist" - ) - ) - .andDo(this.document) - .andDo(MockMvcResultHandlers.print()) - .andExpect(MockMvcResultMatchers.status().isNotFound()) - .andExpect(MockMvcResultMatchers.jsonPath("error", Matchers.is("Not Found"))) - .andExpect(MockMvcResultMatchers.jsonPath("timestamp", Matchers.is(Matchers.notNullValue()))) - .andExpect(MockMvcResultMatchers.jsonPath("status", Matchers.is(404))) - .andExpect(MockMvcResultMatchers.jsonPath("path", Matchers.is(Matchers.notNullValue()))); - } - - /** - * Document the creation process for applications. - * - * @throws Exception For any error - */ - @Test - public void createApplication() throws Exception { - final Application app = new Application.Builder("spark", "genieUser", "1.5.1", ApplicationStatus.ACTIVE) - .withDependencies(Sets.newHashSet("s3://mybucket/spark/spark-1.5.1.tar.gz")) - .withSetupFile("s3://mybucket/spark/setup-spark.sh") - .withConfigs(Sets.newHashSet("s3://mybucket/spark/spark-env.sh")) - .withDescription("Spark 1.5.1 for Genie") - .withTags(Sets.newHashSet("type:spark", "ver:1.5.1")) - .build(); - - this.document.snippets( - PayloadDocumentation.requestFields(this.getApplicationFieldDescriptors(false)) - //TODO: Add header documentation once RESTDocs supports it (in snapshot right now 10/5/15) - ); - - this.mockMvc - .perform( - MockMvcRequestBuilders.post(APPLICATION_API_PATH) - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsBytes(app)) - ) - .andExpect(MockMvcResultMatchers.status().isCreated()) - .andExpect(MockMvcResultMatchers - .header() - .string(HttpHeaders.LOCATION, Matchers.is(Matchers.notNullValue())) - ) - .andDo(this.document); - } - - /** - * Document getting an application. - * - * @throws Exception For any error - */ - @Test - public void getApplication() throws Exception { - final Application app = new Application.Builder("spark", "genieUser", "1.5.1", ApplicationStatus.ACTIVE) - .withDependencies(Sets.newHashSet("s3://mybucket/spark/spark-1.5.1.tar.gz")) - .withSetupFile("s3://mybucket/spark/setup-spark.sh") - .withConfigs(Sets.newHashSet("s3://mybucket/spark/spark-env.sh")) - .withDescription("Spark 1.5.1 for Genie") - .withTags(Sets.newHashSet("type:spark", "ver:1.5.1")) - .build(); - - final String location = this.mockMvc - .perform( - MockMvcRequestBuilders.post(APPLICATION_API_PATH) - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsBytes(app)) - ) - .andExpect(MockMvcResultMatchers.status().isCreated()) - .andExpect(MockMvcResultMatchers - .header() - .string(HttpHeaders.LOCATION, Matchers.is(Matchers.notNullValue())) - ).andReturn().getResponse().getHeader(HttpHeaders.LOCATION); - - this.document.snippets( - HypermediaDocumentation.links( - HypermediaDocumentation.linkWithRel("self").description("Location of the application"), - HypermediaDocumentation.linkWithRel("commands").description("The commands for the application") - ), - PayloadDocumentation.responseFields(this.getApplicationFieldDescriptors(true)) - ); - - this.mockMvc - .perform(MockMvcRequestBuilders.get(location).accept(MediaTypes.HAL_JSON)) - .andExpect(MockMvcResultMatchers.status().isOk()) - .andDo(this.document); - } - - /** - * Document searching for applications. - * - * @throws Exception For any error - */ - @Test - public void findApplications() throws Exception { - final Application spark151 = new Application.Builder("spark", "genieUser", "1.5.1", ApplicationStatus.ACTIVE) - .withDependencies(Sets.newHashSet("s3://mybucket/spark/spark-1.5.1.tar.gz")) - .withSetupFile("s3://mybucket/spark/setup-spark.sh") - .withConfigs(Sets.newHashSet("s3://mybucket/spark/spark-env.sh")) - .withDescription("Spark 1.5.1 for Genie") - .withTags(Sets.newHashSet("type:spark", "ver:1.5.1")) - .build(); - - final Application spark150 = new Application.Builder("spark", "genieUser", "1.5.0", ApplicationStatus.ACTIVE) - .withDependencies(Sets.newHashSet("s3://mybucket/spark/spark-1.5.0.tar.gz")) - .withSetupFile("s3://mybucket/spark/setup-spark.sh") - .withConfigs(Sets.newHashSet("s3://mybucket/spark/spark-env.sh")) - .withDescription("Spark 1.5.0 for Genie") - .withTags(Sets.newHashSet("type:spark", "ver:1.5.0")) - .build(); - - final Application spark141 = new Application.Builder("spark", "genieUser", "1.4.1", ApplicationStatus.ACTIVE) - .withDependencies(Sets.newHashSet("s3://mybucket/spark/spark-1.4.1.tar.gz")) - .withSetupFile("s3://mybucket/spark/setup-spark.sh") - .withConfigs(Sets.newHashSet("s3://mybucket/spark/spark-env.sh")) - .withDescription("Spark 1.4.1 for Genie") - .withTags(Sets.newHashSet("type:spark", "ver:1.4.1")) - .build(); - - final Application spark140 = new Application.Builder("spark", "genieUser", "1.4.0", ApplicationStatus.ACTIVE) - .withDependencies(Sets.newHashSet("s3://mybucket/spark/spark-1.4.0.tar.gz")) - .withSetupFile("s3://mybucket/spark/setup-spark.sh") - .withConfigs(Sets.newHashSet("s3://mybucket/spark/spark-env.sh")) - .withDescription("Spark 1.4.0 for Genie") - .withTags(Sets.newHashSet("type:spark", "ver:1.4.0")) - .build(); - - final Application spark131 = new Application.Builder("spark", "genieUser", "1.3.1", ApplicationStatus.ACTIVE) - .withDependencies(Sets.newHashSet("s3://mybucket/spark/spark-1.3.1.tar.gz")) - .withSetupFile("s3://mybucket/spark/setup-spark.sh") - .withConfigs(Sets.newHashSet("s3://mybucket/spark/spark-env.sh")) - .withDescription("Spark 1.3.1 for Genie") - .withTags(Sets.newHashSet("type:spark", "ver:1.3.1")) - .build(); - - final Application pig = new Application.Builder("spark", "genieUser", "0.4.0", ApplicationStatus.ACTIVE) - .withDependencies(Sets.newHashSet("s3://mybucket/pig/pig-0.15.0.tar.gz")) - .withSetupFile("s3://mybucket/pig/setup-pig.sh") - .withConfigs(Sets.newHashSet("s3://mybucket/pig/pig.properties")) - .withDescription("Pig 0.15.0 for Genie") - .withTags(Sets.newHashSet("type:pig", "ver:0.15.0")) - .build(); - - final Application hive = new Application.Builder("hive", "genieUser", "1.0.0", ApplicationStatus.ACTIVE) - .withDependencies(Sets.newHashSet("s3://mybucket/hive/hive-1.0.0.tar.gz")) - .withSetupFile("s3://mybucket/hive/setup-hive.sh") - .withConfigs( - Sets.newHashSet("s3://mybucket/hive/hive-env.sh", "s3://mybucket/hive/hive-log4j.properties") - ) - .withDescription("Hive 1.0.0 for Genie") - .withTags(Sets.newHashSet("type:hive", "ver:1.0.0")) - .build(); - - this.mockMvc - .perform( - MockMvcRequestBuilders.post(APPLICATION_API_PATH) - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsBytes(spark151)) - ) - .andExpect(MockMvcResultMatchers.status().isCreated()); - - this.mockMvc - .perform( - MockMvcRequestBuilders.post(APPLICATION_API_PATH) - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsBytes(spark150)) - ) - .andExpect(MockMvcResultMatchers.status().isCreated()); - - this.mockMvc - .perform( - MockMvcRequestBuilders.post(APPLICATION_API_PATH) - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsBytes(spark141)) - ) - .andExpect(MockMvcResultMatchers.status().isCreated()); - - this.mockMvc - .perform( - MockMvcRequestBuilders.post(APPLICATION_API_PATH) - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsBytes(spark140)) - ) - .andExpect(MockMvcResultMatchers.status().isCreated()); - - this.mockMvc - .perform( - MockMvcRequestBuilders.post(APPLICATION_API_PATH) - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsBytes(spark131)) - ) - .andExpect(MockMvcResultMatchers.status().isCreated()); - - this.mockMvc - .perform( - MockMvcRequestBuilders.post(APPLICATION_API_PATH) - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsBytes(pig)) - ) - .andExpect(MockMvcResultMatchers.status().isCreated()); - - this.mockMvc - .perform( - MockMvcRequestBuilders.post(APPLICATION_API_PATH) - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsBytes(hive)) - ) - .andExpect(MockMvcResultMatchers.status().isCreated()); - - this.document.snippets( - HypermediaDocumentation.links( - HypermediaDocumentation.linkWithRel("self").description("The current search"), - HypermediaDocumentation.linkWithRel("first").description("The first page for this search"), - HypermediaDocumentation.linkWithRel("prev").description("The previous page for this search"), - HypermediaDocumentation.linkWithRel("next").description("The next page for this search"), - HypermediaDocumentation.linkWithRel("last").description("The last page for this search") - ), - RequestDocumentation.requestParameters( - RequestDocumentation - .parameterWithName("name") - .description("The name of the applications to find."), - RequestDocumentation - .parameterWithName("userName") - .description("The user of the applications to find."), - RequestDocumentation - .parameterWithName("status") - .description("The status of the applications to find."), - RequestDocumentation - .parameterWithName("tag") - .description("The tag(s) of the applications to find."), - RequestDocumentation - .parameterWithName("page") - .description("The page number to get. Default to 0."), - RequestDocumentation - .parameterWithName("size") - .description("The size of the page to get. Default to 64."), - RequestDocumentation - .parameterWithName("sort") - .description("The fields to sort the results by. Defaults to 'updated,desc'.") - ), - PayloadDocumentation.responseFields( - PayloadDocumentation - .fieldWithPath("_embedded.applicationList") - .description("The found applications."), - PayloadDocumentation - .fieldWithPath("_links") - .description("<> to other resources."), - PayloadDocumentation - .fieldWithPath("page") - .description("The result page information."), - PayloadDocumentation - .fieldWithPath("page.size") - .description("The number of elements in this page result."), - PayloadDocumentation - .fieldWithPath("page.totalElements") - .description("The total number of elements this search result could return."), - PayloadDocumentation - .fieldWithPath("page.totalPages") - .description("The total number of pages there could be at the current page size."), - PayloadDocumentation - .fieldWithPath("page.number") - .description("The current page number.") - ) - ); - - this.mockMvc - .perform(MockMvcRequestBuilders - .get(APPLICATION_API_PATH - + "?name=spark&userName=genieUser&status=ACTIVE" - + "&tag=type:spark&page=1&size=2&sort=updated,desc") - .accept(MediaTypes.HAL_JSON) - ) - .andExpect(MockMvcResultMatchers.status().isOk()) - .andDo(this.document); - } - - private FieldDescriptor[] getApplicationFieldDescriptors(final boolean addLinks) { - final ConstraintDescriptions constraintDescriptions = new ConstraintDescriptions(Application.class); - final List descriptors = Lists.newArrayList( - PayloadDocumentation - .fieldWithPath("id") - .type(JsonFieldType.STRING) - .attributes(this.getConstraintsForField(constraintDescriptions, "id")) - .description("The id of the application. If not set the system will set one.") - .optional(), - PayloadDocumentation - .fieldWithPath("created") - .type(JsonFieldType.STRING) - .attributes(this.getConstraintsForField(constraintDescriptions, "created")) - .description("The time the application was last created. Set by system.") - .optional(), - PayloadDocumentation - .fieldWithPath("updated") - .type(JsonFieldType.STRING) - .attributes(this.getConstraintsForField(constraintDescriptions, "updated")) - .description("The time the application was last updated. Set by system.") - .optional(), - PayloadDocumentation - .fieldWithPath("name") - .attributes(this.getConstraintsForField(constraintDescriptions, "name")) - .description("The name of the application"), - PayloadDocumentation - .fieldWithPath("user") - .attributes(this.getConstraintsForField(constraintDescriptions, "user")) - .description("The user who created the application"), - PayloadDocumentation - .fieldWithPath("version") - .attributes(this.getConstraintsForField(constraintDescriptions, "version")) - .description("The version of the application"), - PayloadDocumentation - .fieldWithPath("description") - .type(JsonFieldType.STRING) - .attributes(this.getConstraintsForField(constraintDescriptions, "description")) - .description("Any description for the application.") - .optional(), - PayloadDocumentation - .fieldWithPath("type") - .type(JsonFieldType.STRING) - .attributes(this.getConstraintsForField(constraintDescriptions, "type")) - .description("The type of application this is (e.g. hadoop, presto, spark). Can be used to group.") - .optional(), - PayloadDocumentation - .fieldWithPath("status") - .attributes(this.getConstraintsForField(constraintDescriptions, "status")) - .description("The status of the application"), - PayloadDocumentation - .fieldWithPath("tags") - .type(JsonFieldType.ARRAY) - .attributes(this.getConstraintsForField(constraintDescriptions, "tags")) - .description("The tags for the application") - .optional(), - PayloadDocumentation - .fieldWithPath("dependencies") - .type(JsonFieldType.ARRAY) - .attributes(this.getConstraintsForField(constraintDescriptions, "dependencies")) - .description("The dependencies for the application") - .optional(), - PayloadDocumentation - .fieldWithPath("setupFile") - .type(JsonFieldType.STRING) - .attributes(this.getConstraintsForField(constraintDescriptions, "setupFile")) - .description("A location for any setup that needs to be done when installing") - .optional(), - PayloadDocumentation - .fieldWithPath("configs") - .type(JsonFieldType.ARRAY) - .attributes(this.getConstraintsForField(constraintDescriptions, "configs")) - .description("Any configuration files needed for the application") - .optional() - ); - - if (addLinks) { - descriptors.add( - PayloadDocumentation - .fieldWithPath("_links") - .description("<> to other resources") - ); - } - - return descriptors.toArray(new FieldDescriptor[descriptors.size()]); - } - - private Attributes.Attribute getConstraintsForField( - final ConstraintDescriptions constraints, - final String fieldName - ) { - return Attributes - .key("constraints") - .value( - StringUtils.collectionToDelimitedString( - constraints.descriptionsForProperty(fieldName), - ". " - ) - ); - } -} diff --git a/genie-docs/src/test/java/com/netflix/genie/docs/package-info.java b/genie-docs/src/test/java/com/netflix/genie/docs/package-info.java deleted file mode 100644 index da847cc71e4..00000000000 --- a/genie-docs/src/test/java/com/netflix/genie/docs/package-info.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * - * Copyright 2015 Netflix, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -/** - * Classes used to generate documentation. - * - * @author tgianos - * @since 3.0.0 - */ -package com.netflix.genie.docs; diff --git a/genie-docs/src/test/resources/application-docs.properties b/genie-docs/src/test/resources/application-docs.properties deleted file mode 100644 index 5b1c110ee37..00000000000 --- a/genie-docs/src/test/resources/application-docs.properties +++ /dev/null @@ -1,28 +0,0 @@ -## -# -# Copyright 2015 Netflix, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -## - -########################################################################### -# Spring Settings -########################################################################### - -# Datasource settings (driver class name inferred by spring boot) -spring.datasource.url=jdbc:hsqldb:mem:genie-docs-db;hsqldb.lock_file=false;shutdown=true -spring.datasource.username=SA -spring.datasource.password= - -eureka.client.enabled=false diff --git a/genie-web/build.gradle b/genie-web/build.gradle index d82431498d1..fa2b3938bd6 100644 --- a/genie-web/build.gradle +++ b/genie-web/build.gradle @@ -4,6 +4,12 @@ plugins { id "com.moowork.node" version "0.12" } +apply plugin: 'org.asciidoctor.convert' + +ext { + snippetsDir = file('build/generated-snippets') +} + dependencies { /******************************* * Compile Dependencies @@ -21,7 +27,7 @@ dependencies { compile("org.apache.httpcomponents:httpclient") // JWT JOSE implementation lib - compile("org.bitbucket.b_c:jose4j:0.5.1") + compile("org.bitbucket.b_c:jose4j:${jose4j_version}") // Spring Libs compile("org.springframework.boot:spring-boot-starter-actuator") @@ -29,7 +35,6 @@ dependencies { compile("org.springframework.boot:spring-boot-starter-hateoas") compile("org.springframework.boot:spring-boot-starter-redis") compile("org.springframework.boot:spring-boot-starter-security") - compile("org.springframework.boot:spring-boot-starter-thymeleaf") compile("org.springframework.boot:spring-boot-starter-tomcat") compile("org.springframework.boot:spring-boot-starter-web") compile("org.springframework.cloud:spring-cloud-cluster-autoconfigure:${spring_cloud_cluster_version}") @@ -43,9 +48,6 @@ dependencies { compile("org.springframework.security.oauth:spring-security-oauth2") compile("org.springframework.session:spring-session") - // Thymeleaf Extras for Spring Security - compile("org.thymeleaf.extras:thymeleaf-extras-springsecurity4") - /******************************* * Provided Dependencies *******************************/ @@ -66,6 +68,8 @@ dependencies { testCompile("com.github.springtestdbunit:spring-test-dbunit:${spring_test_dbunit_version}") testCompile("com.jayway.jsonpath:json-path") testCompile("org.dbunit:dbunit:${dbunit_version}") + testCompile("org.springframework.restdocs:spring-restdocs-core:1.1.2.RELEASE") + testCompile("org.springframework.restdocs:spring-restdocs-mockmvc:1.1.2.RELEASE") testCompile("org.springframework.security:spring-security-test") testCompile("net.sf.jtidy:jtidy:${jtidy_version}") testCompile("com.github.tomakehurst:wiremock:${wiremock_version}") @@ -75,10 +79,14 @@ license { excludes(["static/*", "*.yml", "genie-banner.txt"]) } -jar { - manifest { - attributes("Implementation-Version": version) - } +test { + outputs.dir snippetsDir +} + +asciidoctor { + attributes 'snippets': snippetsDir + inputs.dir snippetsDir + dependsOn test } processResources { @@ -105,9 +113,15 @@ bundle.dependsOn(npm_install) jar { dependsOn bundle + dependsOn asciidoctor from("${project.projectDir}/src/main/resources/static/build") { into 'static' } + from ("${asciidoctor.outputDir}/html5") { + into 'static/docs' + } + manifest { + attributes("Implementation-Version": version) + } } - diff --git a/genie-web/src/test/groovy/com/netflix/genie/web/tasks/job/JobCompletionHandlerSpec.groovy b/genie-web/src/test/groovy/com/netflix/genie/web/tasks/job/JobCompletionHandlerSpec.groovy index 84bfec5619b..c63d1fc10d3 100644 --- a/genie-web/src/test/groovy/com/netflix/genie/web/tasks/job/JobCompletionHandlerSpec.groovy +++ b/genie-web/src/test/groovy/com/netflix/genie/web/tasks/job/JobCompletionHandlerSpec.groovy @@ -1,3 +1,20 @@ +/* + * + * Copyright 2016 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ package com.netflix.genie.web.tasks.job import com.netflix.genie.common.exceptions.GenieException diff --git a/genie-web/src/test/groovy/com/netflix/genie/web/tasks/job/JobCompletionServiceSpec.groovy b/genie-web/src/test/groovy/com/netflix/genie/web/tasks/job/JobCompletionServiceSpec.groovy index ed79e520bc5..bda71b5f19d 100644 --- a/genie-web/src/test/groovy/com/netflix/genie/web/tasks/job/JobCompletionServiceSpec.groovy +++ b/genie-web/src/test/groovy/com/netflix/genie/web/tasks/job/JobCompletionServiceSpec.groovy @@ -1,3 +1,20 @@ +/* + * + * Copyright 2016 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ package com.netflix.genie.web.tasks.job import com.netflix.genie.common.dto.Job diff --git a/genie-web/src/test/java/com/netflix/genie/web/controllers/ApplicationRestControllerIntegrationTests.java b/genie-web/src/test/java/com/netflix/genie/web/controllers/ApplicationRestControllerIntegrationTests.java index 6fb08117eb5..6d1e137a225 100644 --- a/genie-web/src/test/java/com/netflix/genie/web/controllers/ApplicationRestControllerIntegrationTests.java +++ b/genie-web/src/test/java/com/netflix/genie/web/controllers/ApplicationRestControllerIntegrationTests.java @@ -33,6 +33,9 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.hateoas.MediaTypes; import org.springframework.http.MediaType; +import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation; +import org.springframework.restdocs.mockmvc.RestDocumentationResultHandler; +import org.springframework.restdocs.operation.preprocess.Preprocessors; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import org.springframework.test.web.servlet.result.MockMvcResultMatchers; @@ -52,7 +55,7 @@ public class ApplicationRestControllerIntegrationTests extends RestControllerInt private static final String ID = UUID.randomUUID().toString(); private static final String NAME = "spark"; private static final String USER = "genie"; - private static final String VERSION = "0.15.0"; + private static final String VERSION = "1.5.1"; private static final String TYPE = "spark"; private static final String DEPENDENCIES_PATH = "$.dependencies"; @@ -83,13 +86,35 @@ public void cleanup() { @Test public void canCreateApplicationWithoutId() throws Exception { Assert.assertThat(this.jpaApplicationRepository.count(), Matchers.is(0L)); - final String id = this.createApplication( - null, - NAME, - USER, - VERSION, - ApplicationStatus.ACTIVE, - TYPE + + final RestDocumentationResultHandler creationResultHandler = MockMvcRestDocumentation.document( + "{class-name}/{method-name}/{step}/", + Preprocessors.preprocessRequest(Preprocessors.prettyPrint()), + Preprocessors.preprocessResponse(Preprocessors.prettyPrint()), + Snippets.APPLICATION_REQUEST_PAYLOAD, + Snippets.LOCATION_HEADER + ); + + final String id = this.createConfigResource( + new Application + .Builder(NAME, USER, VERSION, ApplicationStatus.ACTIVE) + .withType(TYPE) + .withDependencies(Sets.newHashSet("s3://mybucket/spark/" + VERSION + "/spark.tar.gz")) + .withSetupFile("s3://mybucket/spark/" + VERSION + "/setup-spark.sh") + .withConfigs(Sets.newHashSet("s3://mybucket/spark/" + VERSION + "/spark-env.sh")) + .withDescription("Spark for Genie") + .withTags(Sets.newHashSet("type:" + TYPE, "ver:" + VERSION)) + .build(), + creationResultHandler + ); + + final RestDocumentationResultHandler getResultHandler = MockMvcRestDocumentation.document( + "{class-name}/{method-name}/{step}/", + Preprocessors.preprocessRequest(Preprocessors.prettyPrint()), + Preprocessors.preprocessResponse(Preprocessors.prettyPrint()), + Snippets.APPLICATION_RESPONSE_PAYLOAD, + Snippets.APPLICATION_LINKS, + Snippets.HAL_CONTENT_TYPE_HEADER ); this.mvc @@ -102,18 +127,36 @@ public void canCreateApplicationWithoutId() throws Exception { .andExpect(MockMvcResultMatchers.jsonPath(NAME_PATH, Matchers.is(NAME))) .andExpect(MockMvcResultMatchers.jsonPath(VERSION_PATH, Matchers.is(VERSION))) .andExpect(MockMvcResultMatchers.jsonPath(USER_PATH, Matchers.is(USER))) - .andExpect(MockMvcResultMatchers.jsonPath(DESCRIPTION_PATH, Matchers.nullValue())) - .andExpect(MockMvcResultMatchers.jsonPath(SETUP_FILE_PATH, Matchers.nullValue())) - .andExpect(MockMvcResultMatchers.jsonPath(CONFIGS_PATH, Matchers.empty())) - .andExpect(MockMvcResultMatchers.jsonPath(TAGS_PATH, Matchers.hasSize(2))) + .andExpect(MockMvcResultMatchers.jsonPath(DESCRIPTION_PATH, Matchers.is("Spark for Genie"))) + .andExpect(MockMvcResultMatchers + .jsonPath( + SETUP_FILE_PATH, + Matchers.is("s3://mybucket/spark/" + VERSION + "/setup-spark.sh") + ) + ) + .andExpect(MockMvcResultMatchers + .jsonPath( + CONFIGS_PATH, + Matchers.hasItem("s3://mybucket/spark/" + VERSION + "/spark-env.sh") + ) + ) + .andExpect(MockMvcResultMatchers.jsonPath(TAGS_PATH, Matchers.hasSize(4))) .andExpect(MockMvcResultMatchers.jsonPath(TAGS_PATH, Matchers.hasItem("genie.id:" + id))) .andExpect(MockMvcResultMatchers.jsonPath(TAGS_PATH, Matchers.hasItem("genie.name:" + NAME))) + .andExpect(MockMvcResultMatchers.jsonPath(TAGS_PATH, Matchers.hasItem("ver:" + VERSION))) + .andExpect(MockMvcResultMatchers.jsonPath(TAGS_PATH, Matchers.hasItem("type:" + TYPE))) .andExpect(MockMvcResultMatchers.jsonPath(STATUS_PATH, Matchers.is(ApplicationStatus.ACTIVE.toString()))) - .andExpect(MockMvcResultMatchers.jsonPath(DEPENDENCIES_PATH, Matchers.empty())) + .andExpect(MockMvcResultMatchers + .jsonPath( + DEPENDENCIES_PATH, + Matchers.hasItem("s3://mybucket/spark/" + VERSION + "/spark.tar.gz") + ) + ) .andExpect(MockMvcResultMatchers.jsonPath(TYPE_PATH, Matchers.is(TYPE))) .andExpect(MockMvcResultMatchers.jsonPath(LINKS_PATH + ".*", Matchers.hasSize(2))) .andExpect(MockMvcResultMatchers.jsonPath(LINKS_PATH, Matchers.hasKey(SELF_LINK_KEY))) - .andExpect(MockMvcResultMatchers.jsonPath(LINKS_PATH, Matchers.hasKey(COMMANDS_LINK_KEY))); + .andExpect(MockMvcResultMatchers.jsonPath(LINKS_PATH, Matchers.hasKey(COMMANDS_LINK_KEY))) + .andDo(getResultHandler); Assert.assertThat(this.jpaApplicationRepository.count(), Matchers.is(1L)); } @@ -126,17 +169,23 @@ public void canCreateApplicationWithoutId() throws Exception { @Test public void canCreateApplicationWithId() throws Exception { Assert.assertThat(this.jpaApplicationRepository.count(), Matchers.is(0L)); - final String id = this.createApplication( - ID, - NAME, - USER, - VERSION, - ApplicationStatus.ACTIVE, + + this.createConfigResource( + new Application + .Builder(NAME, USER, VERSION, ApplicationStatus.ACTIVE) + .withId(ID) + .withType(TYPE) + .withDependencies(Sets.newHashSet("s3://mybucket/spark/" + VERSION + "/spark.tar.gz")) + .withSetupFile("s3://mybucket/spark/" + VERSION + "/setup-spark.sh") + .withConfigs(Sets.newHashSet("s3://mybucket/spark/" + VERSION + "/spark-env.sh")) + .withDescription("Spark for Genie") + .withTags(Sets.newHashSet("type:" + TYPE, "ver:" + VERSION)) + .build(), null ); this.mvc - .perform(MockMvcRequestBuilders.get(APPLICATIONS_API + "/" + id)) + .perform(MockMvcRequestBuilders.get(APPLICATIONS_API + "/" + ID)) .andExpect(MockMvcResultMatchers.status().isOk()) .andExpect(MockMvcResultMatchers.content().contentType(MediaTypes.HAL_JSON)) .andExpect(MockMvcResultMatchers.jsonPath(ID_PATH, Matchers.is(ID))) @@ -145,15 +194,32 @@ public void canCreateApplicationWithId() throws Exception { .andExpect(MockMvcResultMatchers.jsonPath(NAME_PATH, Matchers.is(NAME))) .andExpect(MockMvcResultMatchers.jsonPath(VERSION_PATH, Matchers.is(VERSION))) .andExpect(MockMvcResultMatchers.jsonPath(USER_PATH, Matchers.is(USER))) - .andExpect(MockMvcResultMatchers.jsonPath(DESCRIPTION_PATH, Matchers.nullValue())) - .andExpect(MockMvcResultMatchers.jsonPath(SETUP_FILE_PATH, Matchers.nullValue())) - .andExpect(MockMvcResultMatchers.jsonPath(CONFIGS_PATH, Matchers.empty())) - .andExpect(MockMvcResultMatchers.jsonPath(TAGS_PATH, Matchers.hasSize(2))) + .andExpect(MockMvcResultMatchers.jsonPath(DESCRIPTION_PATH, Matchers.is("Spark for Genie"))) + .andExpect(MockMvcResultMatchers + .jsonPath( + SETUP_FILE_PATH, + Matchers.is("s3://mybucket/spark/" + VERSION + "/setup-spark.sh") + ) + ) + .andExpect(MockMvcResultMatchers + .jsonPath( + CONFIGS_PATH, + Matchers.hasItem("s3://mybucket/spark/" + VERSION + "/spark-env.sh") + ) + ) + .andExpect(MockMvcResultMatchers.jsonPath(TAGS_PATH, Matchers.hasSize(4))) .andExpect(MockMvcResultMatchers.jsonPath(TAGS_PATH, Matchers.hasItem("genie.id:" + ID))) .andExpect(MockMvcResultMatchers.jsonPath(TAGS_PATH, Matchers.hasItem("genie.name:" + NAME))) + .andExpect(MockMvcResultMatchers.jsonPath(TAGS_PATH, Matchers.hasItem("ver:" + VERSION))) + .andExpect(MockMvcResultMatchers.jsonPath(TAGS_PATH, Matchers.hasItem("type:" + TYPE))) .andExpect(MockMvcResultMatchers.jsonPath(STATUS_PATH, Matchers.is(ApplicationStatus.ACTIVE.toString()))) - .andExpect(MockMvcResultMatchers.jsonPath(DEPENDENCIES_PATH, Matchers.empty())) - .andExpect(MockMvcResultMatchers.jsonPath(TYPE_PATH, Matchers.nullValue())) + .andExpect(MockMvcResultMatchers + .jsonPath( + DEPENDENCIES_PATH, + Matchers.hasItem("s3://mybucket/spark/" + VERSION + "/spark.tar.gz") + ) + ) + .andExpect(MockMvcResultMatchers.jsonPath(TYPE_PATH, Matchers.is(TYPE))) .andExpect(MockMvcResultMatchers.jsonPath(LINKS_PATH + ".*", Matchers.hasSize(2))) .andExpect(MockMvcResultMatchers.jsonPath(LINKS_PATH, Matchers.hasKey(SELF_LINK_KEY))) .andExpect(MockMvcResultMatchers.jsonPath(LINKS_PATH, Matchers.hasKey(COMMANDS_LINK_KEY))); @@ -187,57 +253,124 @@ public void canHandleBadInputToCreateApplication() throws Exception { @Test public void canFindApplications() throws Exception { Assert.assertThat(this.jpaApplicationRepository.count(), Matchers.is(0L)); - final String id1 = UUID.randomUUID().toString(); - final String id2 = UUID.randomUUID().toString(); - final String id3 = UUID.randomUUID().toString(); - final String name1 = UUID.randomUUID().toString(); - final String name2 = UUID.randomUUID().toString(); - final String name3 = UUID.randomUUID().toString(); - final String user1 = UUID.randomUUID().toString(); - final String user2 = UUID.randomUUID().toString(); - final String user3 = UUID.randomUUID().toString(); - final String version1 = UUID.randomUUID().toString(); - final String version2 = UUID.randomUUID().toString(); - final String version3 = UUID.randomUUID().toString(); - final String type1 = UUID.randomUUID().toString(); - final String type2 = UUID.randomUUID().toString(); - final String type3 = UUID.randomUUID().toString(); - - createApplication(id1, name1, user1, version1, ApplicationStatus.ACTIVE, type1); - Thread.sleep(1000); - createApplication(id2, name2, user2, version2, ApplicationStatus.DEPRECATED, type2); - Thread.sleep(1000); - createApplication(id3, name3, user3, version3, ApplicationStatus.INACTIVE, type3); + final Application spark151 = new Application.Builder("spark", "genieUser1", "1.5.1", ApplicationStatus.ACTIVE) + .withDependencies(Sets.newHashSet("s3://mybucket/spark/spark-1.5.1.tar.gz")) + .withSetupFile("s3://mybucket/spark/setup-spark.sh") + .withConfigs(Sets.newHashSet("s3://mybucket/spark/spark-env.sh")) + .withDescription("Spark 1.5.1 for Genie") + .withTags(Sets.newHashSet("type:spark", "ver:1.5.1")) + .withType("spark") + .build(); + + final Application spark150 = new Application.Builder("spark", "genieUser2", "1.5.0", ApplicationStatus.ACTIVE) + .withDependencies(Sets.newHashSet("s3://mybucket/spark/spark-1.5.0.tar.gz")) + .withSetupFile("s3://mybucket/spark/setup-spark.sh") + .withConfigs(Sets.newHashSet("s3://mybucket/spark/spark-env.sh")) + .withDescription("Spark 1.5.0 for Genie") + .withTags(Sets.newHashSet("type:spark", "ver:1.5.0")) + .withType("spark") + .build(); + + final Application spark141 = new Application.Builder("spark", "genieUser3", "1.4.1", ApplicationStatus.INACTIVE) + .withDependencies(Sets.newHashSet("s3://mybucket/spark/spark-1.4.1.tar.gz")) + .withSetupFile("s3://mybucket/spark/setup-spark.sh") + .withConfigs(Sets.newHashSet("s3://mybucket/spark/spark-env.sh")) + .withDescription("Spark 1.4.1 for Genie") + .withTags(Sets.newHashSet("type:spark", "ver:1.4.1")) + .withType("spark") + .build(); + + final Application spark140 + = new Application.Builder("spark", "genieUser4", "1.4.0", ApplicationStatus.DEPRECATED) + .withDependencies(Sets.newHashSet("s3://mybucket/spark/spark-1.4.0.tar.gz")) + .withSetupFile("s3://mybucket/spark/setup-spark.sh") + .withConfigs(Sets.newHashSet("s3://mybucket/spark/spark-env.sh")) + .withDescription("Spark 1.4.0 for Genie") + .withTags(Sets.newHashSet("type:spark", "ver:1.4.0")) + .withType("spark") + .build(); + + final Application spark131 + = new Application.Builder("spark", "genieUser5", "1.3.1", ApplicationStatus.DEPRECATED) + .withDependencies(Sets.newHashSet("s3://mybucket/spark/spark-1.3.1.tar.gz")) + .withSetupFile("s3://mybucket/spark/setup-spark.sh") + .withConfigs(Sets.newHashSet("s3://mybucket/spark/spark-env.sh")) + .withDescription("Spark 1.3.1 for Genie") + .withTags(Sets.newHashSet("type:spark", "ver:1.3.1")) + .withType("spark") + .build(); + + final Application pig = new Application.Builder("spark", "genieUser6", "0.4.0", ApplicationStatus.ACTIVE) + .withDependencies(Sets.newHashSet("s3://mybucket/pig/pig-0.15.0.tar.gz")) + .withSetupFile("s3://mybucket/pig/setup-pig.sh") + .withConfigs(Sets.newHashSet("s3://mybucket/pig/pig.properties")) + .withDescription("Pig 0.15.0 for Genie") + .withTags(Sets.newHashSet("type:pig", "ver:0.15.0")) + .withType("pig") + .build(); + + final Application hive = new Application.Builder("hive", "genieUser7", "1.0.0", ApplicationStatus.ACTIVE) + .withDependencies(Sets.newHashSet("s3://mybucket/hive/hive-1.0.0.tar.gz")) + .withSetupFile("s3://mybucket/hive/setup-hive.sh") + .withConfigs( + Sets.newHashSet("s3://mybucket/hive/hive-env.sh", "s3://mybucket/hive/hive-log4j.properties") + ) + .withDescription("Hive 1.0.0 for Genie") + .withTags(Sets.newHashSet("type:hive", "ver:1.0.0")) + .withType("hive") + .build(); + + final String spark151Id = this.createConfigResource(spark151, null); + final String spark150Id = this.createConfigResource(spark150, null); + final String spark141Id = this.createConfigResource(spark141, null); + final String spark140Id = this.createConfigResource(spark140, null); + final String spark131Id = this.createConfigResource(spark131, null); + final String pigId = this.createConfigResource(pig, null); + final String hiveId = this.createConfigResource(hive, null); + + final RestDocumentationResultHandler documentationResultHandler = MockMvcRestDocumentation.document( + "{class-name}/{method-name}/{step}/", + Preprocessors.preprocessRequest(Preprocessors.prettyPrint()), + Preprocessors.preprocessResponse(Preprocessors.prettyPrint()), + Snippets.SEARCH_LINKS, + Snippets.APPLICATION_SEARCH_QUERY_PARAMETERS, + Snippets.APPLICATION_SEARCH_RESULT_FIELDS, + Snippets.HAL_CONTENT_TYPE_HEADER + ); // Test finding all applications this.mvc .perform(MockMvcRequestBuilders.get(APPLICATIONS_API)) .andExpect(MockMvcResultMatchers.status().isOk()) .andExpect(MockMvcResultMatchers.content().contentType(MediaTypes.HAL_JSON)) - .andExpect(MockMvcResultMatchers.jsonPath(APPLICATIONS_LIST_PATH, Matchers.hasSize(3))); + .andExpect(MockMvcResultMatchers.jsonPath(APPLICATIONS_LIST_PATH, Matchers.hasSize(7))) + .andDo(documentationResultHandler); // Limit the size this.mvc .perform(MockMvcRequestBuilders.get(APPLICATIONS_API).param("size", "2")) .andExpect(MockMvcResultMatchers.status().isOk()) .andExpect(MockMvcResultMatchers.content().contentType(MediaTypes.HAL_JSON)) - .andExpect(MockMvcResultMatchers.jsonPath(APPLICATIONS_LIST_PATH, Matchers.hasSize(2))); + .andExpect(MockMvcResultMatchers.jsonPath(APPLICATIONS_LIST_PATH, Matchers.hasSize(2))) + .andDo(documentationResultHandler); // Query by name this.mvc - .perform(MockMvcRequestBuilders.get(APPLICATIONS_API).param("name", name2)) + .perform(MockMvcRequestBuilders.get(APPLICATIONS_API).param("name", "hive")) .andExpect(MockMvcResultMatchers.status().isOk()) .andExpect(MockMvcResultMatchers.content().contentType(MediaTypes.HAL_JSON)) .andExpect(MockMvcResultMatchers.jsonPath(APPLICATIONS_LIST_PATH, Matchers.hasSize(1))) - .andExpect(MockMvcResultMatchers.jsonPath(APPLICATIONS_LIST_PATH + "[0].id", Matchers.is(id2))); + .andExpect(MockMvcResultMatchers.jsonPath(APPLICATIONS_LIST_PATH + "[0].id", Matchers.is(hiveId))) + .andDo(documentationResultHandler); // Query by user this.mvc - .perform(MockMvcRequestBuilders.get(APPLICATIONS_API).param("user", user3)) + .perform(MockMvcRequestBuilders.get(APPLICATIONS_API).param("user", "genieUser3")) .andExpect(MockMvcResultMatchers.status().isOk()) .andExpect(MockMvcResultMatchers.content().contentType(MediaTypes.HAL_JSON)) .andExpect(MockMvcResultMatchers.jsonPath(APPLICATIONS_LIST_PATH, Matchers.hasSize(1))) - .andExpect(MockMvcResultMatchers.jsonPath(APPLICATIONS_LIST_PATH + "[0].id", Matchers.is(id3))); + .andExpect(MockMvcResultMatchers.jsonPath(APPLICATIONS_LIST_PATH + "[0].id", Matchers.is(spark141Id))) + .andDo(documentationResultHandler); // Query by statuses this.mvc @@ -248,29 +381,40 @@ public void canFindApplications() throws Exception { ) .andExpect(MockMvcResultMatchers.status().isOk()) .andExpect(MockMvcResultMatchers.content().contentType(MediaTypes.HAL_JSON)) - .andExpect(MockMvcResultMatchers.jsonPath(APPLICATIONS_LIST_PATH, Matchers.hasSize(2))) - .andExpect(MockMvcResultMatchers.jsonPath(APPLICATIONS_LIST_PATH + "[0].id", Matchers.is(id2))) - .andExpect(MockMvcResultMatchers.jsonPath(APPLICATIONS_LIST_PATH + "[1].id", Matchers.is(id1))); + .andExpect(MockMvcResultMatchers.jsonPath(APPLICATIONS_LIST_PATH, Matchers.hasSize(6))) + .andExpect(MockMvcResultMatchers.jsonPath(APPLICATIONS_LIST_PATH + "[0].id", Matchers.is(hiveId))) + .andExpect(MockMvcResultMatchers.jsonPath(APPLICATIONS_LIST_PATH + "[1].id", Matchers.is(pigId))) + .andExpect(MockMvcResultMatchers.jsonPath(APPLICATIONS_LIST_PATH + "[2].id", Matchers.is(spark131Id))) + .andExpect(MockMvcResultMatchers.jsonPath(APPLICATIONS_LIST_PATH + "[3].id", Matchers.is(spark140Id))) + .andExpect(MockMvcResultMatchers.jsonPath(APPLICATIONS_LIST_PATH + "[4].id", Matchers.is(spark150Id))) + .andExpect(MockMvcResultMatchers.jsonPath(APPLICATIONS_LIST_PATH + "[5].id", Matchers.is(spark151Id))) + .andDo(documentationResultHandler); // Query by tags this.mvc - .perform(MockMvcRequestBuilders.get(APPLICATIONS_API).param("tag", "genie.id:" + id1)) + .perform(MockMvcRequestBuilders.get(APPLICATIONS_API).param("tag", "genie.id:" + spark131Id)) .andExpect(MockMvcResultMatchers.status().isOk()) .andExpect(MockMvcResultMatchers.content().contentType(MediaTypes.HAL_JSON)) .andExpect(MockMvcResultMatchers.jsonPath(APPLICATIONS_LIST_PATH, Matchers.hasSize(1))) - .andExpect(MockMvcResultMatchers.jsonPath(APPLICATIONS_LIST_PATH + "[0].id", Matchers.is(id1))); + .andExpect(MockMvcResultMatchers.jsonPath(APPLICATIONS_LIST_PATH + "[0].id", Matchers.is(spark131Id))) + .andDo(documentationResultHandler); // Query by type this.mvc - .perform(MockMvcRequestBuilders.get(APPLICATIONS_API).param("type", type2)) + .perform(MockMvcRequestBuilders.get(APPLICATIONS_API).param("type", "spark")) .andExpect(MockMvcResultMatchers.status().isOk()) .andExpect(MockMvcResultMatchers.content().contentType(MediaTypes.HAL_JSON)) - .andExpect(MockMvcResultMatchers.jsonPath(APPLICATIONS_LIST_PATH, Matchers.hasSize(1))) - .andExpect(MockMvcResultMatchers.jsonPath(APPLICATIONS_LIST_PATH + "[0].id", Matchers.is(id2))); + .andExpect(MockMvcResultMatchers.jsonPath(APPLICATIONS_LIST_PATH, Matchers.hasSize(5))) + .andExpect(MockMvcResultMatchers.jsonPath(APPLICATIONS_LIST_PATH + "[0].id", Matchers.is(spark131Id))) + .andExpect(MockMvcResultMatchers.jsonPath(APPLICATIONS_LIST_PATH + "[1].id", Matchers.is(spark140Id))) + .andExpect(MockMvcResultMatchers.jsonPath(APPLICATIONS_LIST_PATH + "[2].id", Matchers.is(spark141Id))) + .andExpect(MockMvcResultMatchers.jsonPath(APPLICATIONS_LIST_PATH + "[3].id", Matchers.is(spark150Id))) + .andExpect(MockMvcResultMatchers.jsonPath(APPLICATIONS_LIST_PATH + "[4].id", Matchers.is(spark151Id))) + .andDo(documentationResultHandler); //TODO: Add tests for sort, orderBy etc - Assert.assertThat(this.jpaApplicationRepository.count(), Matchers.is(3L)); + Assert.assertThat(this.jpaApplicationRepository.count(), Matchers.is(7L)); } /** @@ -281,7 +425,10 @@ public void canFindApplications() throws Exception { @Test public void canUpdateApplication() throws Exception { Assert.assertThat(this.jpaApplicationRepository.count(), Matchers.is(0L)); - final String id = this.createApplication(ID, NAME, USER, VERSION, ApplicationStatus.ACTIVE, null); + final String id = this.createConfigResource( + new Application.Builder(NAME, USER, VERSION, ApplicationStatus.ACTIVE).withId(ID).build(), + null + ); final String applicationResource = APPLICATIONS_API + "/" + id; final Application createdApp = objectMapper .readValue( @@ -334,7 +481,10 @@ public void canUpdateApplication() throws Exception { @Test public void canPatchApplication() throws Exception { Assert.assertThat(this.jpaApplicationRepository.count(), Matchers.is(0L)); - final String id = this.createApplication(ID, NAME, USER, VERSION, ApplicationStatus.ACTIVE, null); + final String id = this.createConfigResource( + new Application.Builder(NAME, USER, VERSION, ApplicationStatus.ACTIVE).withId(ID).build(), + null + ); final String applicationResource = APPLICATIONS_API + "/" + id; this.mvc .perform(MockMvcRequestBuilders.get(applicationResource)) @@ -369,9 +519,18 @@ public void canPatchApplication() throws Exception { @Test public void canDeleteAllApplications() throws Exception { Assert.assertThat(this.jpaApplicationRepository.count(), Matchers.is(0L)); - createApplication(null, NAME, USER, VERSION, ApplicationStatus.ACTIVE, null); - createApplication(null, NAME, USER, VERSION, ApplicationStatus.DEPRECATED, null); - createApplication(null, NAME, USER, VERSION, ApplicationStatus.INACTIVE, null); + this.createConfigResource( + new Application.Builder(NAME, USER, VERSION, ApplicationStatus.ACTIVE).build(), + null + ); + this.createConfigResource( + new Application.Builder(NAME, USER, VERSION, ApplicationStatus.DEPRECATED).build(), + null + ); + this.createConfigResource( + new Application.Builder(NAME, USER, VERSION, ApplicationStatus.INACTIVE).build(), + null + ); Assert.assertThat(this.jpaApplicationRepository.count(), Matchers.is(3L)); this.mvc @@ -402,9 +561,28 @@ public void canDeleteAnApplication() throws Exception { final String version2 = UUID.randomUUID().toString(); final String version3 = UUID.randomUUID().toString(); - createApplication(id1, name1, user1, version1, ApplicationStatus.ACTIVE, null); - createApplication(id2, name2, user2, version2, ApplicationStatus.DEPRECATED, null); - createApplication(id3, name3, user3, version3, ApplicationStatus.INACTIVE, null); + + this.createConfigResource( + new Application + .Builder(name1, user1, version1, ApplicationStatus.ACTIVE) + .withId(id1) + .build(), + null + ); + this.createConfigResource( + new Application + .Builder(name2, user2, version2, ApplicationStatus.DEPRECATED) + .withId(id2) + .build(), + null + ); + this.createConfigResource( + new Application + .Builder(name3, user3, version3, ApplicationStatus.INACTIVE) + .withId(id3) + .build(), + null + ); Assert.assertThat(this.jpaApplicationRepository.count(), Matchers.is(3L)); this.mvc @@ -426,7 +604,10 @@ public void canDeleteAnApplication() throws Exception { @Test public void canAddConfigsToApplication() throws Exception { Assert.assertThat(this.jpaApplicationRepository.count(), Matchers.is(0L)); - createApplication(ID, NAME, USER, VERSION, ApplicationStatus.ACTIVE, null); + this.createConfigResource( + new Application.Builder(NAME, USER, VERSION, ApplicationStatus.ACTIVE).withId(ID).build(), + null + ); this.canAddElementsToResource(APPLICATIONS_API + "/" + ID + "/configs"); } @@ -438,7 +619,10 @@ public void canAddConfigsToApplication() throws Exception { @Test public void canUpdateConfigsForApplication() throws Exception { Assert.assertThat(this.jpaApplicationRepository.count(), Matchers.is(0L)); - createApplication(ID, NAME, USER, VERSION, ApplicationStatus.ACTIVE, null); + this.createConfigResource( + new Application.Builder(NAME, USER, VERSION, ApplicationStatus.ACTIVE).withId(ID).build(), + null + ); this.canUpdateElementsForResource(APPLICATIONS_API + "/" + ID + "/configs"); } @@ -450,7 +634,10 @@ public void canUpdateConfigsForApplication() throws Exception { @Test public void canDeleteConfigsForApplication() throws Exception { Assert.assertThat(this.jpaApplicationRepository.count(), Matchers.is(0L)); - createApplication(ID, NAME, USER, VERSION, ApplicationStatus.ACTIVE, null); + this.createConfigResource( + new Application.Builder(NAME, USER, VERSION, ApplicationStatus.ACTIVE).withId(ID).build(), + null + ); this.canDeleteElementsFromResource(APPLICATIONS_API + "/" + ID + "/configs"); } @@ -462,7 +649,10 @@ public void canDeleteConfigsForApplication() throws Exception { @Test public void canAddDependenciesToApplication() throws Exception { Assert.assertThat(this.jpaApplicationRepository.count(), Matchers.is(0L)); - createApplication(ID, NAME, USER, VERSION, ApplicationStatus.ACTIVE, null); + this.createConfigResource( + new Application.Builder(NAME, USER, VERSION, ApplicationStatus.ACTIVE).withId(ID).build(), + null + ); this.canAddElementsToResource(APPLICATIONS_API + "/" + ID + "/dependencies"); } @@ -474,7 +664,10 @@ public void canAddDependenciesToApplication() throws Exception { @Test public void canUpdateDependenciesForApplication() throws Exception { Assert.assertThat(this.jpaApplicationRepository.count(), Matchers.is(0L)); - createApplication(ID, NAME, USER, VERSION, ApplicationStatus.ACTIVE, null); + this.createConfigResource( + new Application.Builder(NAME, USER, VERSION, ApplicationStatus.ACTIVE).withId(ID).build(), + null + ); this.canUpdateElementsForResource(APPLICATIONS_API + "/" + ID + "/dependencies"); } @@ -486,7 +679,10 @@ public void canUpdateDependenciesForApplication() throws Exception { @Test public void canDeleteDependenciesForApplication() throws Exception { Assert.assertThat(this.jpaApplicationRepository.count(), Matchers.is(0L)); - createApplication(ID, NAME, USER, VERSION, ApplicationStatus.ACTIVE, null); + this.createConfigResource( + new Application.Builder(NAME, USER, VERSION, ApplicationStatus.ACTIVE).withId(ID).build(), + null + ); this.canDeleteElementsFromResource(APPLICATIONS_API + "/" + ID + "/dependencies"); } @@ -498,7 +694,10 @@ public void canDeleteDependenciesForApplication() throws Exception { @Test public void canAddTagsToApplication() throws Exception { Assert.assertThat(this.jpaApplicationRepository.count(), Matchers.is(0L)); - createApplication(ID, NAME, USER, VERSION, ApplicationStatus.ACTIVE, null); + this.createConfigResource( + new Application.Builder(NAME, USER, VERSION, ApplicationStatus.ACTIVE).withId(ID).build(), + null + ); final String api = APPLICATIONS_API + "/" + ID + "/tags"; this.canAddTagsToResource(api, ID, NAME); } @@ -511,7 +710,10 @@ public void canAddTagsToApplication() throws Exception { @Test public void canUpdateTagsForApplication() throws Exception { Assert.assertThat(this.jpaApplicationRepository.count(), Matchers.is(0L)); - createApplication(ID, NAME, USER, VERSION, ApplicationStatus.ACTIVE, null); + this.createConfigResource( + new Application.Builder(NAME, USER, VERSION, ApplicationStatus.ACTIVE).withId(ID).build(), + null + ); final String api = APPLICATIONS_API + "/" + ID + "/tags"; this.canUpdateTagsForResource(api, ID, NAME); } @@ -524,7 +726,10 @@ public void canUpdateTagsForApplication() throws Exception { @Test public void canDeleteTagsForApplication() throws Exception { Assert.assertThat(this.jpaApplicationRepository.count(), Matchers.is(0L)); - createApplication(ID, NAME, USER, VERSION, ApplicationStatus.ACTIVE, null); + this.createConfigResource( + new Application.Builder(NAME, USER, VERSION, ApplicationStatus.ACTIVE).withId(ID).build(), + null + ); final String api = APPLICATIONS_API + "/" + ID + "/tags"; this.canDeleteTagsForResource(api, ID, NAME); } @@ -537,7 +742,10 @@ public void canDeleteTagsForApplication() throws Exception { @Test public void canDeleteTagForApplication() throws Exception { Assert.assertThat(this.jpaApplicationRepository.count(), Matchers.is(0L)); - createApplication(ID, NAME, USER, VERSION, ApplicationStatus.ACTIVE, null); + this.createConfigResource( + new Application.Builder(NAME, USER, VERSION, ApplicationStatus.ACTIVE).withId(ID).build(), + null + ); final String api = APPLICATIONS_API + "/" + ID + "/tags"; this.canDeleteTagForResource(api, ID, NAME); } @@ -549,14 +757,35 @@ public void canDeleteTagForApplication() throws Exception { */ @Test public void canGetCommandsForApplication() throws Exception { - createApplication(ID, NAME, USER, VERSION, ApplicationStatus.ACTIVE, null); + this.createConfigResource( + new Application.Builder(NAME, USER, VERSION, ApplicationStatus.ACTIVE).withId(ID).build(), + null + ); final String placeholder = UUID.randomUUID().toString(); final String command1Id = UUID.randomUUID().toString(); final String command2Id = UUID.randomUUID().toString(); final String command3Id = UUID.randomUUID().toString(); - createCommand(command1Id, placeholder, placeholder, placeholder, CommandStatus.ACTIVE, placeholder, 1000L); - createCommand(command2Id, placeholder, placeholder, placeholder, CommandStatus.INACTIVE, placeholder, 1100L); - createCommand(command3Id, placeholder, placeholder, placeholder, CommandStatus.DEPRECATED, placeholder, 1200L); + this.createConfigResource( + new Command + .Builder(placeholder, placeholder, placeholder, CommandStatus.ACTIVE, placeholder, 1000L) + .withId(command1Id) + .build(), + null + ); + this.createConfigResource( + new Command + .Builder(placeholder, placeholder, placeholder, CommandStatus.INACTIVE, placeholder, 1100L) + .withId(command2Id) + .build(), + null + ); + this.createConfigResource( + new Command + .Builder(placeholder, placeholder, placeholder, CommandStatus.DEPRECATED, placeholder, 1200L) + .withId(command3Id) + .build(), + null + ); final Set appIds = Sets.newHashSet(ID); this.mvc diff --git a/genie-web/src/test/java/com/netflix/genie/web/controllers/ClusterRestControllerIntegrationTests.java b/genie-web/src/test/java/com/netflix/genie/web/controllers/ClusterRestControllerIntegrationTests.java index 8e4e62a5e15..4ccf58b8330 100644 --- a/genie-web/src/test/java/com/netflix/genie/web/controllers/ClusterRestControllerIntegrationTests.java +++ b/genie-web/src/test/java/com/netflix/genie/web/controllers/ClusterRestControllerIntegrationTests.java @@ -21,6 +21,7 @@ import com.google.common.collect.Lists; import com.netflix.genie.common.dto.Cluster; import com.netflix.genie.common.dto.ClusterStatus; +import com.netflix.genie.common.dto.Command; import com.netflix.genie.common.dto.CommandStatus; import com.netflix.genie.core.jpa.repositories.JpaClusterRepository; import com.netflix.genie.core.jpa.repositories.JpaCommandRepository; @@ -76,7 +77,10 @@ public void cleanup() { @Test public void canCreateClusterWithoutId() throws Exception { Assert.assertThat(this.jpaClusterRepository.count(), Matchers.is(0L)); - final String id = this.createCluster(null, NAME, USER, VERSION, ClusterStatus.UP); + final String id = this.createConfigResource( + new Cluster.Builder(NAME, USER, VERSION, ClusterStatus.UP).build(), + null + ); this.mvc .perform(MockMvcRequestBuilders.get(CLUSTERS_API + "/" + id)) .andExpect(MockMvcResultMatchers.status().isOk()) @@ -105,7 +109,10 @@ public void canCreateClusterWithoutId() throws Exception { @Test public void canCreateClusterWithId() throws Exception { Assert.assertThat(this.jpaClusterRepository.count(), Matchers.is(0L)); - this.createCluster(ID, NAME, USER, VERSION, ClusterStatus.UP); + this.createConfigResource( + new Cluster.Builder(NAME, USER, VERSION, ClusterStatus.UP).withId(ID).build(), + null + ); this.mvc .perform(MockMvcRequestBuilders.get(CLUSTERS_API + "/" + ID)) .andExpect(MockMvcResultMatchers.status().isOk()) @@ -165,11 +172,20 @@ public void canFindClusters() throws Exception { final String version2 = UUID.randomUUID().toString(); final String version3 = UUID.randomUUID().toString(); - createCluster(id1, name1, user1, version1, ClusterStatus.UP); + this.createConfigResource( + new Cluster.Builder(name1, user1, version1, ClusterStatus.UP).withId(id1).build(), + null + ); Thread.sleep(1000); - createCluster(id2, name2, user2, version2, ClusterStatus.OUT_OF_SERVICE); + this.createConfigResource( + new Cluster.Builder(name2, user2, version2, ClusterStatus.OUT_OF_SERVICE).withId(id2).build(), + null + ); Thread.sleep(1000); - createCluster(id3, name3, user3, version3, ClusterStatus.TERMINATED); + this.createConfigResource( + new Cluster.Builder(name3, user3, version3, ClusterStatus.TERMINATED).withId(id3).build(), + null + ); // Test finding all clusters this.mvc @@ -228,9 +244,12 @@ public void canFindClusters() throws Exception { @Test public void canUpdateCluster() throws Exception { Assert.assertThat(this.jpaClusterRepository.count(), Matchers.is(0L)); - createCluster(ID, NAME, USER, VERSION, ClusterStatus.UP); + this.createConfigResource( + new Cluster.Builder(NAME, USER, VERSION, ClusterStatus.UP).withId(ID).build(), + null + ); final String clusterResource = CLUSTERS_API + "/" + ID; - final Cluster createdCluster = objectMapper + final Cluster createdCluster = this.objectMapper .readValue( this.mvc.perform( MockMvcRequestBuilders.get(clusterResource) @@ -261,7 +280,7 @@ public void canUpdateCluster() throws Exception { MockMvcRequestBuilders .put(clusterResource) .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsBytes(updateCluster.build())) + .content(this.objectMapper.writeValueAsBytes(updateCluster.build())) ).andExpect(MockMvcResultMatchers.status().isNoContent()); this.mvc @@ -282,7 +301,10 @@ public void canUpdateCluster() throws Exception { @Test public void canPatchCluster() throws Exception { Assert.assertThat(this.jpaClusterRepository.count(), Matchers.is(0L)); - final String id = this.createCluster(ID, NAME, USER, VERSION, ClusterStatus.UP); + final String id = this.createConfigResource( + new Cluster.Builder(NAME, USER, VERSION, ClusterStatus.UP).withId(ID).build(), + null + ); final String clusterResource = CLUSTERS_API + "/" + id; this.mvc .perform(MockMvcRequestBuilders.get(clusterResource)) @@ -317,9 +339,9 @@ public void canPatchCluster() throws Exception { @Test public void canDeleteAllClusters() throws Exception { Assert.assertThat(this.jpaClusterRepository.count(), Matchers.is(0L)); - createCluster(null, NAME, USER, VERSION, ClusterStatus.UP); - createCluster(null, NAME, USER, VERSION, ClusterStatus.OUT_OF_SERVICE); - createCluster(null, NAME, USER, VERSION, ClusterStatus.TERMINATED); + this.createConfigResource(new Cluster.Builder(NAME, USER, VERSION, ClusterStatus.UP).build(), null); + this.createConfigResource(new Cluster.Builder(NAME, USER, VERSION, ClusterStatus.OUT_OF_SERVICE).build(), null); + this.createConfigResource(new Cluster.Builder(NAME, USER, VERSION, ClusterStatus.TERMINATED).build(), null); Assert.assertThat(this.jpaClusterRepository.count(), Matchers.is(3L)); this.mvc @@ -350,9 +372,18 @@ public void canDeleteACluster() throws Exception { final String version2 = UUID.randomUUID().toString(); final String version3 = UUID.randomUUID().toString(); - createCluster(id1, name1, user1, version1, ClusterStatus.UP); - createCluster(id2, name2, user2, version2, ClusterStatus.OUT_OF_SERVICE); - createCluster(id3, name3, user3, version3, ClusterStatus.TERMINATED); + this.createConfigResource( + new Cluster.Builder(name1, user1, version1, ClusterStatus.UP).withId(id1).build(), + null + ); + this.createConfigResource( + new Cluster.Builder(name2, user2, version2, ClusterStatus.OUT_OF_SERVICE).withId(id2).build(), + null + ); + this.createConfigResource( + new Cluster.Builder(name3, user3, version3, ClusterStatus.TERMINATED).withId(id3).build(), + null + ); Assert.assertThat(this.jpaClusterRepository.count(), Matchers.is(3L)); this.mvc @@ -374,7 +405,7 @@ public void canDeleteACluster() throws Exception { @Test public void canAddConfigsToCluster() throws Exception { Assert.assertThat(this.jpaClusterRepository.count(), Matchers.is(0L)); - createCluster(ID, NAME, USER, VERSION, ClusterStatus.UP); + this.createConfigResource(new Cluster.Builder(NAME, USER, VERSION, ClusterStatus.UP).withId(ID).build(), null); this.canAddElementsToResource(CLUSTERS_API + "/" + ID + "/configs"); } @@ -386,7 +417,7 @@ public void canAddConfigsToCluster() throws Exception { @Test public void canUpdateConfigsForCluster() throws Exception { Assert.assertThat(this.jpaClusterRepository.count(), Matchers.is(0L)); - createCluster(ID, NAME, USER, VERSION, ClusterStatus.UP); + this.createConfigResource(new Cluster.Builder(NAME, USER, VERSION, ClusterStatus.UP).withId(ID).build(), null); this.canUpdateElementsForResource(CLUSTERS_API + "/" + ID + "/configs"); } @@ -398,7 +429,7 @@ public void canUpdateConfigsForCluster() throws Exception { @Test public void canDeleteConfigsForCluster() throws Exception { Assert.assertThat(this.jpaClusterRepository.count(), Matchers.is(0L)); - createCluster(ID, NAME, USER, VERSION, ClusterStatus.UP); + this.createConfigResource(new Cluster.Builder(NAME, USER, VERSION, ClusterStatus.UP).withId(ID).build(), null); this.canDeleteElementsFromResource(CLUSTERS_API + "/" + ID + "/configs"); } @@ -410,7 +441,7 @@ public void canDeleteConfigsForCluster() throws Exception { @Test public void canAddTagsToCluster() throws Exception { Assert.assertThat(this.jpaClusterRepository.count(), Matchers.is(0L)); - createCluster(ID, NAME, USER, VERSION, ClusterStatus.UP); + this.createConfigResource(new Cluster.Builder(NAME, USER, VERSION, ClusterStatus.UP).withId(ID).build(), null); final String api = CLUSTERS_API + "/" + ID + "/tags"; this.canAddTagsToResource(api, ID, NAME); } @@ -423,7 +454,7 @@ public void canAddTagsToCluster() throws Exception { @Test public void canUpdateTagsForCluster() throws Exception { Assert.assertThat(this.jpaClusterRepository.count(), Matchers.is(0L)); - createCluster(ID, NAME, USER, VERSION, ClusterStatus.UP); + this.createConfigResource(new Cluster.Builder(NAME, USER, VERSION, ClusterStatus.UP).withId(ID).build(), null); final String api = CLUSTERS_API + "/" + ID + "/tags"; this.canUpdateTagsForResource(api, ID, NAME); } @@ -436,7 +467,7 @@ public void canUpdateTagsForCluster() throws Exception { @Test public void canDeleteTagsForCluster() throws Exception { Assert.assertThat(this.jpaClusterRepository.count(), Matchers.is(0L)); - createCluster(ID, NAME, USER, VERSION, ClusterStatus.UP); + this.createConfigResource(new Cluster.Builder(NAME, USER, VERSION, ClusterStatus.UP).withId(ID).build(), null); final String api = CLUSTERS_API + "/" + ID + "/tags"; this.canDeleteTagsForResource(api, ID, NAME); } @@ -449,7 +480,7 @@ public void canDeleteTagsForCluster() throws Exception { @Test public void canDeleteTagForCluster() throws Exception { Assert.assertThat(this.jpaClusterRepository.count(), Matchers.is(0L)); - createCluster(ID, NAME, USER, VERSION, ClusterStatus.UP); + this.createConfigResource(new Cluster.Builder(NAME, USER, VERSION, ClusterStatus.UP).withId(ID).build(), null); final String api = CLUSTERS_API + "/" + ID + "/tags"; this.canDeleteTagForResource(api, ID, NAME); } @@ -461,7 +492,7 @@ public void canDeleteTagForCluster() throws Exception { */ @Test public void canAddCommandsForACluster() throws Exception { - createCluster(ID, NAME, USER, VERSION, ClusterStatus.UP); + this.createConfigResource(new Cluster.Builder(NAME, USER, VERSION, ClusterStatus.UP).withId(ID).build(), null); final String clusterCommandsAPI = CLUSTERS_API + "/" + ID + "/commands"; this.mvc .perform(MockMvcRequestBuilders.get(clusterCommandsAPI)) @@ -472,8 +503,20 @@ public void canAddCommandsForACluster() throws Exception { final String placeholder = UUID.randomUUID().toString(); final String commandId1 = UUID.randomUUID().toString(); final String commandId2 = UUID.randomUUID().toString(); - createCommand(commandId1, placeholder, placeholder, placeholder, CommandStatus.ACTIVE, placeholder, 1000L); - createCommand(commandId2, placeholder, placeholder, placeholder, CommandStatus.ACTIVE, placeholder, 2000L); + this.createConfigResource( + new Command + .Builder(placeholder, placeholder, placeholder, CommandStatus.ACTIVE, placeholder, 1000L) + .withId(commandId1) + .build(), + null + ); + this.createConfigResource( + new Command + .Builder(placeholder, placeholder, placeholder, CommandStatus.ACTIVE, placeholder, 2000L) + .withId(commandId2) + .build(), + null + ); this.mvc .perform( @@ -503,7 +546,13 @@ public void canAddCommandsForACluster() throws Exception { .andExpect(MockMvcResultMatchers.status().isPreconditionFailed()); final String commandId3 = UUID.randomUUID().toString(); - createCommand(commandId3, placeholder, placeholder, placeholder, CommandStatus.INACTIVE, placeholder, 1000L); + this.createConfigResource( + new Command + .Builder(placeholder, placeholder, placeholder, CommandStatus.INACTIVE, placeholder, 1000L) + .withId(commandId3) + .build(), + null + ); this.mvc .perform( MockMvcRequestBuilders @@ -539,7 +588,7 @@ public void canAddCommandsForACluster() throws Exception { */ @Test public void canSetCommandsForACluster() throws Exception { - createCluster(ID, NAME, USER, VERSION, ClusterStatus.UP); + this.createConfigResource(new Cluster.Builder(NAME, USER, VERSION, ClusterStatus.UP).withId(ID).build(), null); final String clusterCommandsAPI = CLUSTERS_API + "/" + ID + "/commands"; this.mvc .perform(MockMvcRequestBuilders.get(clusterCommandsAPI)) @@ -550,8 +599,20 @@ public void canSetCommandsForACluster() throws Exception { final String placeholder = UUID.randomUUID().toString(); final String commandId1 = UUID.randomUUID().toString(); final String commandId2 = UUID.randomUUID().toString(); - createCommand(commandId1, placeholder, placeholder, placeholder, CommandStatus.ACTIVE, placeholder, 4000L); - createCommand(commandId2, placeholder, placeholder, placeholder, CommandStatus.ACTIVE, placeholder, 5000L); + this.createConfigResource( + new Command + .Builder(placeholder, placeholder, placeholder, CommandStatus.ACTIVE, placeholder, 4000L) + .withId(commandId1) + .build(), + null + ); + this.createConfigResource( + new Command + .Builder(placeholder, placeholder, placeholder, CommandStatus.ACTIVE, placeholder, 5000L) + .withId(commandId2) + .build(), + null + ); this.mvc .perform( @@ -594,14 +655,26 @@ public void canSetCommandsForACluster() throws Exception { */ @Test public void canRemoveCommandsFromACluster() throws Exception { - createCluster(ID, NAME, USER, VERSION, ClusterStatus.UP); + this.createConfigResource(new Cluster.Builder(NAME, USER, VERSION, ClusterStatus.UP).withId(ID).build(), null); final String clusterCommandsAPI = CLUSTERS_API + "/" + ID + "/commands"; final String placeholder = UUID.randomUUID().toString(); final String commandId1 = UUID.randomUUID().toString(); final String commandId2 = UUID.randomUUID().toString(); - createCommand(commandId1, placeholder, placeholder, placeholder, CommandStatus.ACTIVE, placeholder, 7000L); - createCommand(commandId2, placeholder, placeholder, placeholder, CommandStatus.ACTIVE, placeholder, 8000L); + this.createConfigResource( + new Command + .Builder(placeholder, placeholder, placeholder, CommandStatus.ACTIVE, placeholder, 7000L) + .withId(commandId1) + .build(), + null + ); + this.createConfigResource( + new Command + .Builder(placeholder, placeholder, placeholder, CommandStatus.ACTIVE, placeholder, 8000L) + .withId(commandId2) + .build(), + null + ); this.mvc .perform( @@ -630,16 +703,34 @@ public void canRemoveCommandsFromACluster() throws Exception { */ @Test public void canRemoveCommandFromACluster() throws Exception { - createCluster(ID, NAME, USER, VERSION, ClusterStatus.UP); + this.createConfigResource(new Cluster.Builder(NAME, USER, VERSION, ClusterStatus.UP).withId(ID).build(), null); final String clusterCommandsAPI = CLUSTERS_API + "/" + ID + "/commands"; final String placeholder = UUID.randomUUID().toString(); final String commandId1 = UUID.randomUUID().toString(); final String commandId2 = UUID.randomUUID().toString(); final String commandId3 = UUID.randomUUID().toString(); - createCommand(commandId1, placeholder, placeholder, placeholder, CommandStatus.ACTIVE, placeholder, 1000L); - createCommand(commandId2, placeholder, placeholder, placeholder, CommandStatus.ACTIVE, placeholder, 2000L); - createCommand(commandId3, placeholder, placeholder, placeholder, CommandStatus.ACTIVE, placeholder, 3000L); + this.createConfigResource( + new Command + .Builder(placeholder, placeholder, placeholder, CommandStatus.ACTIVE, placeholder, 1000L) + .withId(commandId1) + .build(), + null + ); + this.createConfigResource( + new Command + .Builder(placeholder, placeholder, placeholder, CommandStatus.ACTIVE, placeholder, 2000L) + .withId(commandId2) + .build(), + null + ); + this.createConfigResource( + new Command + .Builder(placeholder, placeholder, placeholder, CommandStatus.ACTIVE, placeholder, 3000L) + .withId(commandId3) + .build(), + null + ); this.mvc .perform( diff --git a/genie-web/src/test/java/com/netflix/genie/web/controllers/CommandRestControllerIntegrationTests.java b/genie-web/src/test/java/com/netflix/genie/web/controllers/CommandRestControllerIntegrationTests.java index d10e6f0842f..5688787a1f1 100644 --- a/genie-web/src/test/java/com/netflix/genie/web/controllers/CommandRestControllerIntegrationTests.java +++ b/genie-web/src/test/java/com/netflix/genie/web/controllers/CommandRestControllerIntegrationTests.java @@ -19,7 +19,9 @@ import com.github.fge.jsonpatch.JsonPatch; import com.google.common.collect.Lists; +import com.netflix.genie.common.dto.Application; import com.netflix.genie.common.dto.ApplicationStatus; +import com.netflix.genie.common.dto.Cluster; import com.netflix.genie.common.dto.ClusterStatus; import com.netflix.genie.common.dto.Command; import com.netflix.genie.common.dto.CommandStatus; @@ -89,7 +91,10 @@ public void cleanup() { @Test public void canCreateCommandWithoutId() throws Exception { Assert.assertThat(this.jpaCommandRepository.count(), Matchers.is(0L)); - final String id = this.createCommand(null, NAME, USER, VERSION, CommandStatus.ACTIVE, EXECUTABLE, CHECK_DELAY); + final String id = this.createConfigResource( + new Command.Builder(NAME, USER, VERSION, CommandStatus.ACTIVE, EXECUTABLE, CHECK_DELAY).build(), + null + ); this.mvc .perform(MockMvcRequestBuilders.get(COMMANDS_API + "/" + id)) @@ -123,7 +128,10 @@ public void canCreateCommandWithoutId() throws Exception { @Test public void canCreateCommandWithId() throws Exception { Assert.assertThat(this.jpaCommandRepository.count(), Matchers.is(0L)); - createCommand(ID, NAME, USER, VERSION, CommandStatus.ACTIVE, EXECUTABLE, CHECK_DELAY); + this.createConfigResource( + new Command.Builder(NAME, USER, VERSION, CommandStatus.ACTIVE, EXECUTABLE, CHECK_DELAY).withId(ID).build(), + null + ); this.mvc .perform(MockMvcRequestBuilders.get(COMMANDS_API + "/" + ID)) @@ -191,11 +199,29 @@ public void canFindCommands() throws Exception { final String executable2 = UUID.randomUUID().toString(); final String executable3 = UUID.randomUUID().toString(); - createCommand(id1, name1, user1, version1, CommandStatus.ACTIVE, executable1, CHECK_DELAY); + this.createConfigResource( + new Command + .Builder(name1, user1, version1, CommandStatus.ACTIVE, executable1, CHECK_DELAY) + .withId(id1) + .build(), + null + ); Thread.sleep(1000); - createCommand(id2, name2, user2, version2, CommandStatus.DEPRECATED, executable2, CHECK_DELAY); + this.createConfigResource( + new Command + .Builder(name2, user2, version2, CommandStatus.DEPRECATED, executable2, CHECK_DELAY) + .withId(id2) + .build(), + null + ); Thread.sleep(1000); - createCommand(id3, name3, user3, version3, CommandStatus.INACTIVE, executable3, CHECK_DELAY); + this.createConfigResource( + new Command + .Builder(name3, user3, version3, CommandStatus.INACTIVE, executable3, CHECK_DELAY) + .withId(id3) + .build(), + null + ); // Test finding all commands this.mvc @@ -261,7 +287,13 @@ public void canFindCommands() throws Exception { @Test public void canUpdateCommand() throws Exception { Assert.assertThat(this.jpaCommandRepository.count(), Matchers.is(0L)); - createCommand(ID, NAME, USER, VERSION, CommandStatus.ACTIVE, EXECUTABLE, CHECK_DELAY); + this.createConfigResource( + new Command + .Builder(NAME, USER, VERSION, CommandStatus.ACTIVE, EXECUTABLE, CHECK_DELAY) + .withId(ID) + .build(), + null + ); final String commandResource = COMMANDS_API + "/" + ID; final Command createdCommand = objectMapper .readValue( @@ -317,7 +349,13 @@ public void canUpdateCommand() throws Exception { @Test public void canPatchCommand() throws Exception { Assert.assertThat(this.jpaCommandRepository.count(), Matchers.is(0L)); - final String id = this.createCommand(ID, NAME, USER, VERSION, CommandStatus.ACTIVE, EXECUTABLE, CHECK_DELAY); + final String id = this.createConfigResource( + new Command + .Builder(NAME, USER, VERSION, CommandStatus.ACTIVE, EXECUTABLE, CHECK_DELAY) + .withId(ID) + .build(), + null + ); final String commandResource = COMMANDS_API + "/" + id; this.mvc .perform(MockMvcRequestBuilders.get(commandResource)) @@ -352,9 +390,24 @@ public void canPatchCommand() throws Exception { @Test public void canDeleteAllCommands() throws Exception { Assert.assertThat(this.jpaCommandRepository.count(), Matchers.is(0L)); - createCommand(null, NAME, USER, VERSION, CommandStatus.ACTIVE, EXECUTABLE, CHECK_DELAY); - createCommand(null, NAME, USER, VERSION, CommandStatus.DEPRECATED, EXECUTABLE, CHECK_DELAY); - createCommand(null, NAME, USER, VERSION, CommandStatus.INACTIVE, EXECUTABLE, CHECK_DELAY); + this.createConfigResource( + new Command + .Builder(NAME, USER, VERSION, CommandStatus.ACTIVE, EXECUTABLE, CHECK_DELAY) + .build(), + null + ); + this.createConfigResource( + new Command + .Builder(NAME, USER, VERSION, CommandStatus.DEPRECATED, EXECUTABLE, CHECK_DELAY) + .build(), + null + ); + this.createConfigResource( + new Command + .Builder(NAME, USER, VERSION, CommandStatus.INACTIVE, EXECUTABLE, CHECK_DELAY) + .build(), + null + ); Assert.assertThat(this.jpaCommandRepository.count(), Matchers.is(3L)); this.mvc @@ -388,9 +441,27 @@ public void canDeleteACommand() throws Exception { final String executable2 = UUID.randomUUID().toString(); final String executable3 = UUID.randomUUID().toString(); - createCommand(id1, name1, user1, version1, CommandStatus.ACTIVE, executable1, CHECK_DELAY); - createCommand(id2, name2, user2, version2, CommandStatus.DEPRECATED, executable2, CHECK_DELAY); - createCommand(id3, name3, user3, version3, CommandStatus.INACTIVE, executable3, CHECK_DELAY); + this.createConfigResource( + new Command + .Builder(name1, user1, version1, CommandStatus.ACTIVE, executable1, CHECK_DELAY) + .withId(id1) + .build(), + null + ); + this.createConfigResource( + new Command + .Builder(name2, user2, version2, CommandStatus.ACTIVE, executable2, CHECK_DELAY) + .withId(id2) + .build(), + null + ); + this.createConfigResource( + new Command + .Builder(name3, user3, version3, CommandStatus.ACTIVE, executable3, CHECK_DELAY) + .withId(id3) + .build(), + null + ); Assert.assertThat(this.jpaCommandRepository.count(), Matchers.is(3L)); this.mvc @@ -412,7 +483,13 @@ public void canDeleteACommand() throws Exception { @Test public void canAddConfigsToCommand() throws Exception { Assert.assertThat(this.jpaCommandRepository.count(), Matchers.is(0L)); - createCommand(ID, NAME, USER, VERSION, CommandStatus.ACTIVE, EXECUTABLE, CHECK_DELAY); + this.createConfigResource( + new Command + .Builder(NAME, USER, VERSION, CommandStatus.ACTIVE, EXECUTABLE, CHECK_DELAY) + .withId(ID) + .build(), + null + ); this.canAddElementsToResource(COMMANDS_API + "/" + ID + "/configs"); } @@ -424,7 +501,13 @@ public void canAddConfigsToCommand() throws Exception { @Test public void canUpdateConfigsForCommand() throws Exception { Assert.assertThat(this.jpaCommandRepository.count(), Matchers.is(0L)); - createCommand(ID, NAME, USER, VERSION, CommandStatus.ACTIVE, EXECUTABLE, CHECK_DELAY); + this.createConfigResource( + new Command + .Builder(NAME, USER, VERSION, CommandStatus.ACTIVE, EXECUTABLE, CHECK_DELAY) + .withId(ID) + .build(), + null + ); this.canUpdateElementsForResource(COMMANDS_API + "/" + ID + "/configs"); } @@ -436,7 +519,13 @@ public void canUpdateConfigsForCommand() throws Exception { @Test public void canDeleteConfigsForCommand() throws Exception { Assert.assertThat(this.jpaCommandRepository.count(), Matchers.is(0L)); - createCommand(ID, NAME, USER, VERSION, CommandStatus.ACTIVE, EXECUTABLE, CHECK_DELAY); + this.createConfigResource( + new Command + .Builder(NAME, USER, VERSION, CommandStatus.ACTIVE, EXECUTABLE, CHECK_DELAY) + .withId(ID) + .build(), + null + ); this.canDeleteElementsFromResource(COMMANDS_API + "/" + ID + "/configs"); } @@ -448,7 +537,13 @@ public void canDeleteConfigsForCommand() throws Exception { @Test public void canAddTagsToCommand() throws Exception { Assert.assertThat(this.jpaCommandRepository.count(), Matchers.is(0L)); - createCommand(ID, NAME, USER, VERSION, CommandStatus.ACTIVE, EXECUTABLE, CHECK_DELAY); + this.createConfigResource( + new Command + .Builder(NAME, USER, VERSION, CommandStatus.ACTIVE, EXECUTABLE, CHECK_DELAY) + .withId(ID) + .build(), + null + ); final String api = COMMANDS_API + "/" + ID + "/tags"; this.canAddTagsToResource(api, ID, NAME); } @@ -461,7 +556,13 @@ public void canAddTagsToCommand() throws Exception { @Test public void canUpdateTagsForCommand() throws Exception { Assert.assertThat(this.jpaCommandRepository.count(), Matchers.is(0L)); - createCommand(ID, NAME, USER, VERSION, CommandStatus.ACTIVE, EXECUTABLE, CHECK_DELAY); + this.createConfigResource( + new Command + .Builder(NAME, USER, VERSION, CommandStatus.ACTIVE, EXECUTABLE, CHECK_DELAY) + .withId(ID) + .build(), + null + ); final String api = COMMANDS_API + "/" + ID + "/tags"; this.canUpdateTagsForResource(api, ID, NAME); } @@ -474,7 +575,13 @@ public void canUpdateTagsForCommand() throws Exception { @Test public void canDeleteTagsForCommand() throws Exception { Assert.assertThat(this.jpaCommandRepository.count(), Matchers.is(0L)); - createCommand(ID, NAME, USER, VERSION, CommandStatus.ACTIVE, EXECUTABLE, CHECK_DELAY); + this.createConfigResource( + new Command + .Builder(NAME, USER, VERSION, CommandStatus.ACTIVE, EXECUTABLE, CHECK_DELAY) + .withId(ID) + .build(), + null + ); final String api = COMMANDS_API + "/" + ID + "/tags"; this.canDeleteTagsForResource(api, ID, NAME); } @@ -487,7 +594,13 @@ public void canDeleteTagsForCommand() throws Exception { @Test public void canDeleteTagForCommand() throws Exception { Assert.assertThat(this.jpaCommandRepository.count(), Matchers.is(0L)); - createCommand(ID, NAME, USER, VERSION, CommandStatus.ACTIVE, EXECUTABLE, CHECK_DELAY); + this.createConfigResource( + new Command + .Builder(NAME, USER, VERSION, CommandStatus.ACTIVE, EXECUTABLE, CHECK_DELAY) + .withId(ID) + .build(), + null + ); final String api = COMMANDS_API + "/" + ID + "/tags"; this.canDeleteTagForResource(api, ID, NAME); } @@ -499,7 +612,13 @@ public void canDeleteTagForCommand() throws Exception { */ @Test public void canAddApplicationsForACommand() throws Exception { - createCommand(ID, NAME, USER, VERSION, CommandStatus.ACTIVE, EXECUTABLE, CHECK_DELAY); + this.createConfigResource( + new Command + .Builder(NAME, USER, VERSION, CommandStatus.ACTIVE, EXECUTABLE, CHECK_DELAY) + .withId(ID) + .build(), + null + ); final String commandApplicationsAPI = COMMANDS_API + "/" + ID + "/applications"; this.mvc .perform(MockMvcRequestBuilders.get(commandApplicationsAPI)) @@ -510,8 +629,20 @@ public void canAddApplicationsForACommand() throws Exception { final String placeholder = UUID.randomUUID().toString(); final String applicationId1 = UUID.randomUUID().toString(); final String applicationId2 = UUID.randomUUID().toString(); - createApplication(applicationId1, placeholder, placeholder, placeholder, ApplicationStatus.ACTIVE, null); - createApplication(applicationId2, placeholder, placeholder, placeholder, ApplicationStatus.ACTIVE, null); + this.createConfigResource( + new Application + .Builder(placeholder, placeholder, placeholder, ApplicationStatus.ACTIVE) + .withId(applicationId1) + .build(), + null + ); + this.createConfigResource( + new Application + .Builder(placeholder, placeholder, placeholder, ApplicationStatus.ACTIVE) + .withId(applicationId2) + .build(), + null + ); this.mvc .perform( @@ -541,7 +672,13 @@ public void canAddApplicationsForACommand() throws Exception { .andExpect(MockMvcResultMatchers.status().isPreconditionFailed()); final String applicationId3 = UUID.randomUUID().toString(); - createApplication(applicationId3, placeholder, placeholder, placeholder, ApplicationStatus.ACTIVE, null); + this.createConfigResource( + new Application + .Builder(placeholder, placeholder, placeholder, ApplicationStatus.ACTIVE) + .withId(applicationId3) + .build(), + null + ); this.mvc .perform( MockMvcRequestBuilders @@ -568,7 +705,13 @@ public void canAddApplicationsForACommand() throws Exception { */ @Test public void canSetApplicationsForACommand() throws Exception { - createCommand(ID, NAME, USER, VERSION, CommandStatus.ACTIVE, EXECUTABLE, CHECK_DELAY); + this.createConfigResource( + new Command + .Builder(NAME, USER, VERSION, CommandStatus.ACTIVE, EXECUTABLE, CHECK_DELAY) + .withId(ID) + .build(), + null + ); final String commandApplicationsAPI = COMMANDS_API + "/" + ID + "/applications"; this.mvc .perform(MockMvcRequestBuilders.get(commandApplicationsAPI)) @@ -580,9 +723,27 @@ public void canSetApplicationsForACommand() throws Exception { final String applicationId1 = UUID.randomUUID().toString(); final String applicationId2 = UUID.randomUUID().toString(); final String applicationId3 = UUID.randomUUID().toString(); - createApplication(applicationId1, placeholder, placeholder, placeholder, ApplicationStatus.ACTIVE, null); - createApplication(applicationId2, placeholder, placeholder, placeholder, ApplicationStatus.ACTIVE, null); - createApplication(applicationId3, placeholder, placeholder, placeholder, ApplicationStatus.ACTIVE, null); + this.createConfigResource( + new Application + .Builder(placeholder, placeholder, placeholder, ApplicationStatus.ACTIVE) + .withId(applicationId1) + .build(), + null + ); + this.createConfigResource( + new Application + .Builder(placeholder, placeholder, placeholder, ApplicationStatus.ACTIVE) + .withId(applicationId2) + .build(), + null + ); + this.createConfigResource( + new Application + .Builder(placeholder, placeholder, placeholder, ApplicationStatus.ACTIVE) + .withId(applicationId3) + .build(), + null + ); this.mvc .perform( @@ -665,14 +826,32 @@ public void canSetApplicationsForACommand() throws Exception { */ @Test public void canRemoveApplicationsFromACommand() throws Exception { - createCommand(ID, NAME, USER, VERSION, CommandStatus.ACTIVE, EXECUTABLE, CHECK_DELAY); + this.createConfigResource( + new Command + .Builder(NAME, USER, VERSION, CommandStatus.ACTIVE, EXECUTABLE, CHECK_DELAY) + .withId(ID) + .build(), + null + ); final String commandApplicationsAPI = COMMANDS_API + "/" + ID + "/applications"; final String placeholder = UUID.randomUUID().toString(); final String applicationId1 = UUID.randomUUID().toString(); final String applicationId2 = UUID.randomUUID().toString(); - createApplication(applicationId1, placeholder, placeholder, placeholder, ApplicationStatus.ACTIVE, null); - createApplication(applicationId2, placeholder, placeholder, placeholder, ApplicationStatus.ACTIVE, null); + this.createConfigResource( + new Application + .Builder(placeholder, placeholder, placeholder, ApplicationStatus.ACTIVE) + .withId(applicationId1) + .build(), + null + ); + this.createConfigResource( + new Application + .Builder(placeholder, placeholder, placeholder, ApplicationStatus.ACTIVE) + .withId(applicationId2) + .build(), + null + ); this.mvc .perform( @@ -701,16 +880,40 @@ public void canRemoveApplicationsFromACommand() throws Exception { */ @Test public void canRemoveApplicationFromACommand() throws Exception { - createCommand(ID, NAME, USER, VERSION, CommandStatus.ACTIVE, EXECUTABLE, CHECK_DELAY); + this.createConfigResource( + new Command + .Builder(NAME, USER, VERSION, CommandStatus.ACTIVE, EXECUTABLE, CHECK_DELAY) + .withId(ID) + .build(), + null + ); final String commandApplicationsAPI = COMMANDS_API + "/" + ID + "/applications"; final String placeholder = UUID.randomUUID().toString(); final String applicationId1 = UUID.randomUUID().toString(); final String applicationId2 = UUID.randomUUID().toString(); final String applicationId3 = UUID.randomUUID().toString(); - createApplication(applicationId1, placeholder, placeholder, placeholder, ApplicationStatus.ACTIVE, null); - createApplication(applicationId2, placeholder, placeholder, placeholder, ApplicationStatus.ACTIVE, null); - createApplication(applicationId3, placeholder, placeholder, placeholder, ApplicationStatus.ACTIVE, null); + this.createConfigResource( + new Application + .Builder(placeholder, placeholder, placeholder, ApplicationStatus.ACTIVE) + .withId(applicationId1) + .build(), + null + ); + this.createConfigResource( + new Application + .Builder(placeholder, placeholder, placeholder, ApplicationStatus.ACTIVE) + .withId(applicationId2) + .build(), + null + ); + this.createConfigResource( + new Application + .Builder(placeholder, placeholder, placeholder, ApplicationStatus.ACTIVE) + .withId(applicationId3) + .build(), + null + ); this.mvc .perform( @@ -766,14 +969,35 @@ public void canRemoveApplicationFromACommand() throws Exception { */ @Test public void canGetClustersForCommand() throws Exception { - createCommand(ID, NAME, USER, VERSION, CommandStatus.ACTIVE, EXECUTABLE, CHECK_DELAY); + this.createConfigResource( + new Command + .Builder(NAME, USER, VERSION, CommandStatus.ACTIVE, EXECUTABLE, CHECK_DELAY) + .withId(ID) + .build(), + null + ); final String placeholder = UUID.randomUUID().toString(); final String cluster1Id = UUID.randomUUID().toString(); final String cluster2Id = UUID.randomUUID().toString(); final String cluster3Id = UUID.randomUUID().toString(); - createCluster(cluster1Id, placeholder, placeholder, placeholder, ClusterStatus.UP); - createCluster(cluster2Id, placeholder, placeholder, placeholder, ClusterStatus.OUT_OF_SERVICE); - createCluster(cluster3Id, placeholder, placeholder, placeholder, ClusterStatus.TERMINATED); + this.createConfigResource( + new Cluster.Builder(placeholder, placeholder, placeholder, ClusterStatus.UP).withId(cluster1Id).build(), + null + ); + this.createConfigResource( + new Cluster + .Builder(placeholder, placeholder, placeholder, ClusterStatus.OUT_OF_SERVICE) + .withId(cluster2Id) + .build(), + null + ); + this.createConfigResource( + new Cluster + .Builder(placeholder, placeholder, placeholder, ClusterStatus.TERMINATED) + .withId(cluster3Id) + .build(), + null + ); final List commandIds = Lists.newArrayList(ID); this.mvc diff --git a/genie-web/src/test/java/com/netflix/genie/web/controllers/RestControllerIntegrationTestsBase.java b/genie-web/src/test/java/com/netflix/genie/web/controllers/RestControllerIntegrationTestsBase.java index 7cb3b049721..6fe49380696 100644 --- a/genie-web/src/test/java/com/netflix/genie/web/controllers/RestControllerIntegrationTestsBase.java +++ b/genie-web/src/test/java/com/netflix/genie/web/controllers/RestControllerIntegrationTestsBase.java @@ -23,15 +23,14 @@ import com.google.common.collect.Sets; import com.netflix.genie.GenieWeb; import com.netflix.genie.common.dto.Application; -import com.netflix.genie.common.dto.ApplicationStatus; import com.netflix.genie.common.dto.Cluster; -import com.netflix.genie.common.dto.ClusterStatus; import com.netflix.genie.common.dto.Command; -import com.netflix.genie.common.dto.CommandStatus; +import com.netflix.genie.common.dto.ConfigDTO; import com.netflix.genie.common.util.GenieDateFormat; import com.netflix.genie.test.categories.IntegrationTest; import org.hamcrest.Matchers; import org.junit.Before; +import org.junit.Rule; import org.junit.experimental.categories.Category; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; @@ -39,15 +38,19 @@ import org.springframework.boot.test.WebIntegrationTest; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; +import org.springframework.restdocs.JUnitRestDocumentation; +import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation; +import org.springframework.restdocs.mockmvc.RestDocumentationResultHandler; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.MvcResult; +import org.springframework.test.web.servlet.ResultActions; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import org.springframework.test.web.servlet.result.MockMvcResultMatchers; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.context.WebApplicationContext; +import javax.validation.constraints.NotNull; import java.util.Set; import java.util.TimeZone; import java.util.UUID; @@ -65,33 +68,43 @@ @ActiveProfiles(resolver = IntegrationTestActiveProfilesResolver.class) public abstract class RestControllerIntegrationTestsBase { - protected static final String APPLICATIONS_API = "/api/v3/applications"; - protected static final String CLUSTERS_API = "/api/v3/clusters"; - protected static final String COMMANDS_API = "/api/v3/commands"; - protected static final String JOBS_API = "/api/v3/jobs"; - - protected static final String ID_PATH = "$.id"; - protected static final String CREATED_PATH = "$.created"; - protected static final String UPDATED_PATH = "$.updated"; - protected static final String NAME_PATH = "$.name"; - protected static final String VERSION_PATH = "$.version"; - protected static final String USER_PATH = "$.user"; - protected static final String DESCRIPTION_PATH = "$.description"; - protected static final String TAGS_PATH = "$.tags"; - protected static final String SETUP_FILE_PATH = "$.setupFile"; - protected static final String STATUS_PATH = "$.status"; - protected static final String CONFIGS_PATH = "$.configs"; - protected static final String LINKS_PATH = "$._links"; - protected static final String EMBEDDED_PATH = "$._embedded"; + static final String APPLICATIONS_API = "/api/v3/applications"; + static final String CLUSTERS_API = "/api/v3/clusters"; + static final String COMMANDS_API = "/api/v3/commands"; + static final String JOBS_API = "/api/v3/jobs"; + + static final String ID_PATH = "$.id"; + static final String CREATED_PATH = "$.created"; + static final String UPDATED_PATH = "$.updated"; + static final String NAME_PATH = "$.name"; + static final String VERSION_PATH = "$.version"; + static final String USER_PATH = "$.user"; + static final String DESCRIPTION_PATH = "$.description"; + static final String TAGS_PATH = "$.tags"; + static final String SETUP_FILE_PATH = "$.setupFile"; + static final String STATUS_PATH = "$.status"; + static final String CONFIGS_PATH = "$.configs"; + static final String LINKS_PATH = "$._links"; + static final String EMBEDDED_PATH = "$._embedded"; // Link Keys - protected static final String SELF_LINK_KEY = "self"; - protected static final String COMMANDS_LINK_KEY = "commands"; - protected static final String CLUSTERS_LINK_KEY = "clusters"; - protected static final String APPLICATIONS_LINK_KEY = "applications"; - protected static final String JOBS_LINK_KEY = "jobs"; + static final String SELF_LINK_KEY = "self"; + static final String COMMANDS_LINK_KEY = "commands"; + static final String CLUSTERS_LINK_KEY = "clusters"; + static final String APPLICATIONS_LINK_KEY = "applications"; + static final String JOBS_LINK_KEY = "jobs"; - protected ObjectMapper objectMapper; + /** + * Where to put the generated documentation. + */ + @Rule + public final JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation("build/generated-snippets"); + + protected final ObjectMapper objectMapper = new ObjectMapper() + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + .setTimeZone(TimeZone.getTimeZone("UTC")) + .setDateFormat(new GenieDateFormat()) + .registerModule(new Jdk8Module()); protected MockMvc mvc; @@ -107,18 +120,11 @@ public abstract class RestControllerIntegrationTestsBase { public void setup() throws Exception { this.mvc = MockMvcBuilders .webAppContextSetup(this.context) + .apply(MockMvcRestDocumentation.documentationConfiguration(this.restDocumentation)) .build(); - - if (this.objectMapper == null) { - this.objectMapper = new ObjectMapper() - .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) - .setTimeZone(TimeZone.getTimeZone("UTC")) - .setDateFormat(new GenieDateFormat()) - .registerModule(new Jdk8Module()); - } } - protected void canAddElementsToResource(final String api) throws Exception { + void canAddElementsToResource(final String api) throws Exception { this.mvc .perform(MockMvcRequestBuilders.get(api)) .andExpect(MockMvcResultMatchers.status().isOk()) @@ -146,7 +152,7 @@ protected void canAddElementsToResource(final String api) throws Exception { .andExpect(MockMvcResultMatchers.jsonPath("$", Matchers.hasItem(element2))); } - protected void canUpdateElementsForResource(final String api) throws Exception { + void canUpdateElementsForResource(final String api) throws Exception { final String element1 = UUID.randomUUID().toString(); final String element2 = UUID.randomUUID().toString(); final Set elements = Sets.newHashSet(element1, element2); @@ -176,7 +182,7 @@ protected void canUpdateElementsForResource(final String api) throws Exception { .andExpect(MockMvcResultMatchers.jsonPath("$", Matchers.hasItem(element3))); } - protected void canDeleteElementsFromResource(final String api) throws Exception { + void canDeleteElementsFromResource(final String api) throws Exception { final String element1 = UUID.randomUUID().toString(); final String element2 = UUID.randomUUID().toString(); this.mvc @@ -199,7 +205,7 @@ protected void canDeleteElementsFromResource(final String api) throws Exception .andExpect(MockMvcResultMatchers.jsonPath("$", Matchers.empty())); } - protected void canAddTagsToResource(final String api, final String id, final String name) throws Exception { + void canAddTagsToResource(final String api, final String id, final String name) throws Exception { this.mvc .perform(MockMvcRequestBuilders.get(api)) .andExpect(MockMvcResultMatchers.status().isOk()) @@ -231,7 +237,7 @@ protected void canAddTagsToResource(final String api, final String id, final Str .andExpect(MockMvcResultMatchers.jsonPath("$", Matchers.hasItem(tag2))); } - protected void canUpdateTagsForResource(final String api, final String id, final String name) throws Exception { + void canUpdateTagsForResource(final String api, final String id, final String name) throws Exception { final String tag1 = UUID.randomUUID().toString(); final String tag2 = UUID.randomUUID().toString(); final Set tags = Sets.newHashSet(tag1, tag2); @@ -263,7 +269,7 @@ protected void canUpdateTagsForResource(final String api, final String id, final .andExpect(MockMvcResultMatchers.jsonPath("$", Matchers.hasItem(tag3))); } - protected void canDeleteTagsForResource(final String api, final String id, final String name) throws Exception { + void canDeleteTagsForResource(final String api, final String id, final String name) throws Exception { final String tag1 = UUID.randomUUID().toString(); final String tag2 = UUID.randomUUID().toString(); this.mvc @@ -288,7 +294,7 @@ protected void canDeleteTagsForResource(final String api, final String id, final .andExpect(MockMvcResultMatchers.jsonPath("$", Matchers.hasItem("genie.name:" + name))); } - protected void canDeleteTagForResource(final String api, final String id, final String name) throws Exception { + void canDeleteTagForResource(final String api, final String id, final String name) throws Exception { final String tag1 = UUID.randomUUID().toString(); final String tag2 = UUID.randomUUID().toString(); this.mvc @@ -314,74 +320,36 @@ protected void canDeleteTagForResource(final String api, final String id, final .andExpect(MockMvcResultMatchers.jsonPath("$", Matchers.hasItem(tag2))); } - protected String createApplication( - final String id, - final String name, - final String user, - final String version, - final ApplicationStatus status, - final String type + String createConfigResource( + @NotNull final R resource, + final RestDocumentationResultHandler documentationResultHandler ) throws Exception { - final Application app = new Application.Builder(name, user, version, status).withId(id).withType(type).build(); - final MvcResult result = this.mvc - .perform( - MockMvcRequestBuilders - .post(APPLICATIONS_API) - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsBytes(app)) - ) - .andExpect(MockMvcResultMatchers.status().isCreated()) - .andExpect(MockMvcResultMatchers.header().string(HttpHeaders.LOCATION, Matchers.notNullValue())) - .andReturn(); - - return this.getIdFromLocation(result.getResponse().getHeader(HttpHeaders.LOCATION)); - } + final String endpoint; + if (resource instanceof Application) { + endpoint = APPLICATIONS_API; + } else if (resource instanceof Cluster) { + endpoint = CLUSTERS_API; + } else if (resource instanceof Command) { + endpoint = COMMANDS_API; + } else { + throw new IllegalArgumentException("Unexpected type: " + resource.getClass().getCanonicalName()); + } - protected String createCluster( - final String id, - final String name, - final String user, - final String version, - final ClusterStatus status - ) throws Exception { - final Cluster cluster = new Cluster.Builder(name, user, version, status).withId(id).build(); - final MvcResult result = this.mvc + final ResultActions resultActions = this.mvc .perform( MockMvcRequestBuilders - .post(CLUSTERS_API) + .post(endpoint) .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsBytes(cluster)) + .content(objectMapper.writeValueAsBytes(resource)) ) .andExpect(MockMvcResultMatchers.status().isCreated()) - .andExpect(MockMvcResultMatchers.header().string(HttpHeaders.LOCATION, Matchers.notNullValue())) - .andReturn(); - - return this.getIdFromLocation(result.getResponse().getHeader(HttpHeaders.LOCATION)); - } + .andExpect(MockMvcResultMatchers.header().string(HttpHeaders.LOCATION, Matchers.notNullValue())); - protected String createCommand( - final String id, - final String name, - final String user, - final String version, - final CommandStatus status, - final String executable, - final long checkDelay - ) throws Exception { - final Command command - = new Command.Builder(name, user, version, status, executable, checkDelay).withId(id).build(); - final MvcResult result = this.mvc - .perform( - MockMvcRequestBuilders - .post(COMMANDS_API) - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsBytes(command)) - ) - .andExpect(MockMvcResultMatchers.status().isCreated()) - .andExpect(MockMvcResultMatchers.header().string(HttpHeaders.LOCATION, Matchers.notNullValue())) - .andReturn(); + if (documentationResultHandler != null) { + resultActions.andDo(documentationResultHandler); + } - return this.getIdFromLocation(result.getResponse().getHeader(HttpHeaders.LOCATION)); + return this.getIdFromLocation(resultActions.andReturn().getResponse().getHeader(HttpHeaders.LOCATION)); } private String getIdFromLocation(final String location) { diff --git a/genie-web/src/test/java/com/netflix/genie/web/controllers/Snippets.java b/genie-web/src/test/java/com/netflix/genie/web/controllers/Snippets.java new file mode 100644 index 00000000000..3f0bd6f714c --- /dev/null +++ b/genie-web/src/test/java/com/netflix/genie/web/controllers/Snippets.java @@ -0,0 +1,190 @@ +/* + * + * Copyright 2016 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.netflix.genie.web.controllers; + +import com.netflix.genie.common.dto.Application; +import org.springframework.hateoas.MediaTypes; +import org.springframework.http.HttpHeaders; +import org.springframework.restdocs.constraints.ConstraintDescriptions; +import org.springframework.restdocs.headers.HeaderDocumentation; +import org.springframework.restdocs.headers.ResponseHeadersSnippet; +import org.springframework.restdocs.hypermedia.HypermediaDocumentation; +import org.springframework.restdocs.hypermedia.LinksSnippet; +import org.springframework.restdocs.payload.FieldDescriptor; +import org.springframework.restdocs.payload.PayloadDocumentation; +import org.springframework.restdocs.payload.RequestFieldsSnippet; +import org.springframework.restdocs.payload.ResponseFieldsSnippet; +import org.springframework.restdocs.request.RequestDocumentation; +import org.springframework.restdocs.request.RequestParametersSnippet; +import org.springframework.restdocs.snippet.Attributes; +import org.springframework.util.StringUtils; + +/** + * Helper class for getting field descriptors for various DTOs. + * + * @author tgianos + * @since 3.0.0 + */ +final class Snippets { + + static final ResponseHeadersSnippet LOCATION_HEADER = HeaderDocumentation.responseHeaders( + HeaderDocumentation.headerWithName(HttpHeaders.LOCATION).description("The URI") + ); + + static final ResponseHeadersSnippet HAL_CONTENT_TYPE_HEADER = HeaderDocumentation.responseHeaders( + HeaderDocumentation.headerWithName(HttpHeaders.CONTENT_TYPE).description(MediaTypes.HAL_JSON_VALUE) + ); + +// static final ResponseFieldsSnippet ERROR_FIELDS = PayloadDocumentation.responseFields( +// PayloadDocumentation.fieldWithPath("error").description("The HTTP error that occurred, e.g. `Bad Request`"), +// PayloadDocumentation.fieldWithPath("message").description("A description of the cause of the error"), +// PayloadDocumentation.fieldWithPath("path").description("The path to which the request was made"), +// PayloadDocumentation.fieldWithPath("status").description("The HTTP status code, e.g. `400`"), +// PayloadDocumentation.fieldWithPath("timestamp") +// .description("The time, in milliseconds, at which the error occurred") +// ); + + static final LinksSnippet SEARCH_LINKS = HypermediaDocumentation.links( + HypermediaDocumentation.linkWithRel("self").description("The current search"), + HypermediaDocumentation.linkWithRel("first").description("The first page for this search").optional(), + HypermediaDocumentation.linkWithRel("prev").description("The previous page for this search").optional(), + HypermediaDocumentation.linkWithRel("next").description("The next page for this search").optional(), + HypermediaDocumentation.linkWithRel("last").description("The last page for this search").optional() + ); + + static final RequestParametersSnippet APPLICATION_SEARCH_QUERY_PARAMETERS = RequestDocumentation.requestParameters( + RequestDocumentation.parameterWithName("name").description("The name of the applications to find.").optional(), + RequestDocumentation.parameterWithName("user").description("The user of the applications to find.").optional(), + RequestDocumentation.parameterWithName("status").description("The status of the applications to find.") + .optional(), + RequestDocumentation.parameterWithName("tag").description("The tag(s) of the applications to find.").optional(), + RequestDocumentation.parameterWithName("type").description("The type of the applications to find.").optional(), + RequestDocumentation.parameterWithName("page").description("The page number to get. Default to 0.").optional(), + RequestDocumentation.parameterWithName("size").description("The size of the page to get. Default to 64.") + .optional(), + RequestDocumentation.parameterWithName("sort") + .description("The fields to sort the results by. Defaults to 'updated,desc'.") + .optional() + ); + static final ResponseFieldsSnippet APPLICATION_SEARCH_RESULT_FIELDS = PayloadDocumentation.responseFields( + PayloadDocumentation.fieldWithPath("_embedded.applicationList").description("The found applications."), + PayloadDocumentation.fieldWithPath("_links").description("<> to other resources."), + PayloadDocumentation.fieldWithPath("page").description("The result page information."), + PayloadDocumentation.fieldWithPath("page.size").description("The number of elements in this page result."), + PayloadDocumentation.fieldWithPath("page.totalElements") + .description("The total number of elements this search result could return."), + PayloadDocumentation.fieldWithPath("page.totalPages") + .description("The total number of pages there could be at the current page size."), + PayloadDocumentation.fieldWithPath("page.number").description("The current page number.") + ); + + static final LinksSnippet APPLICATION_LINKS = HypermediaDocumentation.links( + HypermediaDocumentation.linkWithRel("self").description("URI for this application"), + HypermediaDocumentation.linkWithRel("commands").description("Get all the commands using this application") + ); + + static final RequestFieldsSnippet APPLICATION_REQUEST_PAYLOAD + = PayloadDocumentation.requestFields(getApplicationFieldDescriptors()); + + static final ResponseFieldsSnippet APPLICATION_RESPONSE_PAYLOAD = PayloadDocumentation + .responseFields(getApplicationFieldDescriptors()) + .and( + PayloadDocumentation + .fieldWithPath("_links").description("<> to other resources") + ); + + private Snippets() { + } + + private static FieldDescriptor[] getApplicationFieldDescriptors() { + final ConstraintDescriptions constraintDescriptions = new ConstraintDescriptions(Application.class); + return new FieldDescriptor[] + { + PayloadDocumentation + .fieldWithPath("id") + .attributes(getConstraintsForField(constraintDescriptions, "id")) + .description("The id of the application. If not set the system will set one.") + .optional(), + PayloadDocumentation + .fieldWithPath("created") + .attributes(getConstraintsForField(constraintDescriptions, "created")) + .description("The time the application was last created. Set by system. ISO8601 with millis.") + .optional(), + PayloadDocumentation + .fieldWithPath("updated") + .attributes(getConstraintsForField(constraintDescriptions, "updated")) + .description("The time the application was last updated. Set by system. ISO8601 with millis.") + .optional(), + PayloadDocumentation + .fieldWithPath("name") + .attributes(getConstraintsForField(constraintDescriptions, "name")) + .description("The name of the application"), + PayloadDocumentation + .fieldWithPath("user") + .attributes(getConstraintsForField(constraintDescriptions, "user")) + .description("The user who created the application"), + PayloadDocumentation + .fieldWithPath("version") + .attributes(getConstraintsForField(constraintDescriptions, "version")) + .description("The version of the application"), + PayloadDocumentation + .fieldWithPath("description") + .attributes(getConstraintsForField(constraintDescriptions, "description")) + .description("Any description for the application.") + .optional(), + PayloadDocumentation + .fieldWithPath("type") + .attributes(getConstraintsForField(constraintDescriptions, "type")) + .description("The type of application this is (e.g. hadoop, presto, spark). Can be used to group.") + .optional(), + PayloadDocumentation + .fieldWithPath("status") + .attributes(getConstraintsForField(constraintDescriptions, "status")) + .description("The status of the application"), + PayloadDocumentation + .fieldWithPath("tags") + .attributes(getConstraintsForField(constraintDescriptions, "tags")) + .description("The tags for the application") + .optional(), + PayloadDocumentation + .fieldWithPath("dependencies") + .attributes(getConstraintsForField(constraintDescriptions, "dependencies")) + .description("The dependencies for the application") + .optional(), + PayloadDocumentation + .fieldWithPath("setupFile") + .attributes(getConstraintsForField(constraintDescriptions, "setupFile")) + .description("A location for any setup that needs to be done when installing") + .optional(), + PayloadDocumentation + .fieldWithPath("configs") + .attributes(getConstraintsForField(constraintDescriptions, "configs")) + .description("Any configuration files needed for the application") + .optional(), + }; + } + + private static Attributes.Attribute getConstraintsForField( + final ConstraintDescriptions constraints, + final String fieldName + ) { + return Attributes + .key("constraints") + .value(StringUtils.collectionToDelimitedString(constraints.descriptionsForProperty(fieldName), ". ")); + } +} diff --git a/genie-docs/src/test/resources/org/springframework/restdocs/constraints/ConstraintDescriptions.properties b/genie-web/src/test/resources/org/springframework/restdocs/constraints/ConstraintDescriptions.properties similarity index 100% rename from genie-docs/src/test/resources/org/springframework/restdocs/constraints/ConstraintDescriptions.properties rename to genie-web/src/test/resources/org/springframework/restdocs/constraints/ConstraintDescriptions.properties diff --git a/genie-docs/src/test/resources/org/springframework/restdocs/templates/request-fields.snippet b/genie-web/src/test/resources/org/springframework/restdocs/templates/request-fields.snippet similarity index 57% rename from genie-docs/src/test/resources/org/springframework/restdocs/templates/request-fields.snippet rename to genie-web/src/test/resources/org/springframework/restdocs/templates/request-fields.snippet index cd1e825c80b..b8199ce4435 100644 --- a/genie-docs/src/test/resources/org/springframework/restdocs/templates/request-fields.snippet +++ b/genie-web/src/test/resources/org/springframework/restdocs/templates/request-fields.snippet @@ -1,11 +1,12 @@ |=== -|Path|Type|Description|Constraints +|Path|Type|Description|Constraints|Optional {{#fields}} |{{path}} |{{type}} |{{description}} |{{constraints}} +|{{optional}} {{/fields}} -|=== \ No newline at end of file +|=== diff --git a/gradle.properties b/gradle.properties index e719279e64f..6d00864af80 100644 --- a/gradle.properties +++ b/gradle.properties @@ -21,7 +21,7 @@ commons_exec_version=1.3 # JWT Related Libraries -jose4j_version=0.5.1 +jose4j_version=0.5.2 # Spring Libraries NOT Covered by IO Platform spring_cloud_cluster_version=1.0.0.RELEASE diff --git a/settings.gradle b/settings.gradle index 7006b0d3ea1..079884ca5c9 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,4 +1,4 @@ rootProject.name='genie' -include 'genie-test', 'genie-common', 'genie-core', 'genie-web', 'genie-app', 'genie-docs', 'genie-client' +include 'genie-test', 'genie-common', 'genie-core', 'genie-web', 'genie-app', 'genie-client'