/* * Copyright © 2014-2016 Cask Data, 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 co.cask.cdap.cli; import co.cask.cdap.StandaloneTester; import co.cask.cdap.cli.util.RowMaker; import co.cask.cdap.cli.util.table.Table; import co.cask.cdap.client.DatasetTypeClient; import co.cask.cdap.client.NamespaceClient; import co.cask.cdap.client.ProgramClient; import co.cask.cdap.client.app.ConfigTestApp; import co.cask.cdap.client.app.FakeApp; import co.cask.cdap.client.app.FakeDataset; import co.cask.cdap.client.app.FakeFlow; import co.cask.cdap.client.app.FakeSpark; import co.cask.cdap.client.app.FakeWorkflow; import co.cask.cdap.client.app.PingService; import co.cask.cdap.client.app.PrefixedEchoHandler; import co.cask.cdap.common.DatasetTypeNotFoundException; import co.cask.cdap.common.ProgramNotFoundException; import co.cask.cdap.common.UnauthenticatedException; import co.cask.cdap.common.io.Locations; import co.cask.cdap.internal.test.AppJarHelper; import co.cask.cdap.proto.DatasetTypeMeta; import co.cask.cdap.proto.Id; import co.cask.cdap.proto.NamespaceMeta; import co.cask.cdap.proto.ProgramType; import co.cask.cdap.proto.StreamProperties; import co.cask.cdap.proto.WorkflowTokenDetail; import co.cask.cdap.test.XSlowTests; import co.cask.common.cli.CLI; import com.google.common.base.Charsets; import com.google.common.base.Function; import com.google.common.base.Joiner; import com.google.common.base.Splitter; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.io.Files; import com.google.gson.Gson; import org.apache.twill.filesystem.LocalLocationFactory; import org.apache.twill.filesystem.Location; import org.apache.twill.filesystem.LocationFactory; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.ClassRule; import org.junit.Test; import org.junit.experimental.categories.Category; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.BufferedWriter; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.PrintStream; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; import javax.annotation.Nullable; /** * Test for {@link CLIMain}. */ @Category(XSlowTests.class) public class CLIMainTest extends CLITestBase { @ClassRule public static final StandaloneTester STANDALONE = new StandaloneTester(); private static final Logger LOG = LoggerFactory.getLogger(CLIMainTest.class); private static final Gson GSON = new Gson(); private static final String PREFIX = "123ff1_"; private final Id.Artifact fakeArtifactId = Id.Artifact.from(Id.Namespace.DEFAULT, FakeApp.NAME, "1.0"); private final Id.Application fakeAppId = Id.Application.from(Id.Namespace.DEFAULT, FakeApp.NAME); private final Id.Workflow fakeWorkflowId = Id.Workflow.from(fakeAppId, FakeWorkflow.NAME); private final Id.Flow fakeFlowId = Id.Flow.from(fakeAppId, FakeFlow.NAME); private final Id.Program fakeSparkId = Id.Program.from(fakeAppId, ProgramType.SPARK, FakeSpark.NAME); private final Id.Service pingServiceId = Id.Service.from(fakeAppId, PingService.NAME); private final Id.Service prefixedEchoHandlerId = Id.Service.from(fakeAppId, PrefixedEchoHandler.NAME); private final Id.DatasetInstance fakeDsId = Id.DatasetInstance.from(Id.Namespace.DEFAULT, FakeApp.DS_NAME); private final Id.Stream fakeStreamId = Id.Stream.from(Id.Namespace.DEFAULT, FakeApp.STREAM_NAME); private static ProgramClient programClient; private static CLIConfig cliConfig; private static CLIMain cliMain; private static CLI cli; @BeforeClass public static void setUpClass() throws Exception { cliConfig = createCLIConfig(STANDALONE.getBaseURI()); LaunchOptions launchOptions = new LaunchOptions(LaunchOptions.DEFAULT.getUri(), true, true, false); cliMain = new CLIMain(launchOptions, cliConfig); programClient = new ProgramClient(cliConfig.getClientConfig()); cli = cliMain.getCLI(); testCommandOutputContains(cli, "connect " + STANDALONE.getBaseURI(), "Successfully connected"); testCommandOutputNotContains(cli, "list apps", FakeApp.NAME); File appJarFile = createAppJarFile(FakeApp.class); testCommandOutputContains(cli, "deploy app " + appJarFile.getAbsolutePath(), "Successfully deployed app"); if (!appJarFile.delete()) { LOG.warn("Failed to delete temporary app jar file: {}", appJarFile.getAbsolutePath()); } } @AfterClass public static void tearDownClass() throws Exception { testCommandOutputContains(cli, "delete app " + FakeApp.NAME, "Successfully deleted app"); } @Test public void testConnect() throws Exception { testCommandOutputContains(cli, "connect fakehost", "could not be reached"); testCommandOutputContains(cli, "connect " + STANDALONE.getBaseURI(), "Successfully connected"); } @Test public void testList() throws Exception { testCommandOutputContains(cli, "list apps", FakeApp.NAME); testCommandOutputContains(cli, "list dataset instances", FakeApp.DS_NAME); testCommandOutputContains(cli, "list streams", FakeApp.STREAM_NAME); testCommandOutputContains(cli, "list flows", FakeApp.FLOWS.get(0)); } @Test public void testPrompt() throws Exception { String prompt = cliMain.getPrompt(cliConfig.getConnectionConfig()); Assert.assertFalse(prompt.contains("@")); Assert.assertTrue(prompt.contains(STANDALONE.getBaseURI().getHost())); Assert.assertTrue(prompt.contains(cliConfig.getCurrentNamespace().getId())); CLIConnectionConfig oldConnectionConfig = cliConfig.getConnectionConfig(); CLIConnectionConfig authConnectionConfig = new CLIConnectionConfig( oldConnectionConfig, Id.Namespace.DEFAULT, "test-username"); cliConfig.setConnectionConfig(authConnectionConfig); prompt = cliMain.getPrompt(cliConfig.getConnectionConfig()); Assert.assertTrue(prompt.contains("test-username@")); Assert.assertTrue(prompt.contains(STANDALONE.getBaseURI().getHost())); Assert.assertTrue(prompt.contains(cliConfig.getCurrentNamespace().getId())); cliConfig.setConnectionConfig(oldConnectionConfig); } @Test public void testProgram() throws Exception { String flowId = FakeApp.FLOWS.get(0); Id.Application app = Id.Application.from(Id.Namespace.DEFAULT, FakeApp.NAME); Id.Flow flow = Id.Flow.from(app, flowId); String qualifiedFlowId = FakeApp.NAME + "." + flowId; testCommandOutputContains(cli, "start flow " + qualifiedFlowId, "Successfully started flow"); assertProgramStatus(programClient, flow, "RUNNING"); testCommandOutputContains(cli, "stop flow " + qualifiedFlowId, "Successfully stopped flow"); assertProgramStatus(programClient, flow, "STOPPED"); testCommandOutputContains(cli, "get flow status " + qualifiedFlowId, "STOPPED"); testCommandOutputContains(cli, "get flow runs " + qualifiedFlowId, "KILLED"); testCommandOutputContains(cli, "get flow live " + qualifiedFlowId, flowId); } @Test public void testAppDeploy() throws Exception { testDeploy(null); testDeploy(new ConfigTestApp.ConfigClass("testStream", "testTable")); } private void testDeploy(ConfigTestApp.ConfigClass config) throws Exception { String streamId = ConfigTestApp.DEFAULT_STREAM; String datasetId = ConfigTestApp.DEFAULT_TABLE; if (config != null) { streamId = config.getStreamName(); datasetId = config.getTableName(); } File appJarFile = createAppJarFile(ConfigTestApp.class); if (config != null) { String appConfig = GSON.toJson(config); testCommandOutputContains(cli, String.format("deploy app %s %s", appJarFile.getAbsolutePath(), appConfig), "Successfully deployed app"); } else { testCommandOutputContains(cli, String.format("deploy app %s", appJarFile.getAbsolutePath()), "Successfully"); } if (!appJarFile.delete()) { LOG.warn("Failed to delete temporary app jar file: {}", appJarFile.getAbsolutePath()); } testCommandOutputContains(cli, "list streams", streamId); testCommandOutputContains(cli, "list dataset instances", datasetId); testCommandOutputContains(cli, "delete app " + ConfigTestApp.NAME, "Successfully"); } @Test public void testStream() throws Exception { String streamId = PREFIX + "sdf123"; File file = new File(TMP_FOLDER.newFolder(), "test1.txt"); StreamProperties streamProperties = new StreamProperties(2L, null, 10, "Golden Stream"); try (BufferedWriter writer = Files.newWriter(file, Charsets.UTF_8)) { writer.write(GSON.toJson(streamProperties)); } testCommandOutputContains(cli, "create stream " + streamId + " " + file.getAbsolutePath(), "Successfully created stream"); testCommandOutputContains(cli, "describe stream " + streamId, "Golden Stream"); testCommandOutputContains(cli, "set stream description " + streamId + " 'Silver Stream'", "Successfully set stream description"); testCommandOutputContains(cli, "describe stream " + streamId, "Silver Stream"); testCommandOutputContains(cli, "delete stream " + streamId, "Successfully deleted stream"); testCommandOutputContains(cli, "create stream " + streamId, "Successfully created stream"); testCommandOutputContains(cli, "list streams", streamId); testCommandOutputNotContains(cli, "get stream " + streamId, "helloworld"); testCommandOutputContains(cli, "send stream " + streamId + " helloworld", "Successfully sent stream event"); testCommandOutputContains(cli, "get stream " + streamId, "helloworld"); testCommandOutputContains(cli, "get stream " + streamId + " -10m -0s 1", "helloworld"); testCommandOutputContains(cli, "get stream " + streamId + " -10m -0s", "helloworld"); testCommandOutputContains(cli, "get stream " + streamId + " -10m", "helloworld"); testCommandOutputContains(cli, "truncate stream " + streamId, "Successfully truncated stream"); testCommandOutputNotContains(cli, "get stream " + streamId, "helloworld"); testCommandOutputContains(cli, "set stream ttl " + streamId + " 100000", "Successfully set TTL of stream"); testCommandOutputContains(cli, "set stream notification-threshold " + streamId + " 1", "Successfully set notification threshold of stream"); testCommandOutputContains(cli, "describe stream " + streamId, "100000"); file = new File(TMP_FOLDER.newFolder(), "test2.txt"); // If the file not exist or not a file, upload should fails with an error. testCommandOutputContains(cli, "load stream " + streamId + " " + file.getAbsolutePath(), "Not a file"); testCommandOutputContains(cli, "load stream " + streamId + " " + file.getParentFile().getAbsolutePath(), "Not a file"); // Generate a file to send try (BufferedWriter writer = Files.newWriter(file, Charsets.UTF_8)) { for (int i = 0; i < 10; i++) { writer.write(String.format("%s, Event %s", i, i)); writer.newLine(); } } testCommandOutputContains(cli, "load stream " + streamId + " " + file.getAbsolutePath(), "Successfully loaded file to stream"); testCommandOutputContains(cli, "get stream " + streamId, "9, Event 9"); testCommandOutputContains(cli, "get stream-stats " + streamId, String.format("No schema found for stream '%s'", streamId)); testCommandOutputContains(cli, "set stream format " + streamId + " csv 'body string'", String.format("Successfully set format of stream '%s'", streamId)); testCommandOutputContains(cli, "execute 'show tables'", String.format("stream_%s", streamId)); testCommandOutputContains(cli, "get stream-stats " + streamId, "Analyzed 10 Stream events in the time range [0, 9223372036854775807]"); testCommandOutputContains(cli, "get stream-stats " + streamId + " limit 5 start 5 end 10", "Analyzed 0 Stream events in the time range [5, 10]"); } @Test public void testSchedule() throws Exception { String scheduleId = FakeApp.NAME + "." + FakeApp.SCHEDULE_NAME; String workflowId = FakeApp.NAME + "." + FakeWorkflow.NAME; testCommandOutputContains(cli, "get schedule status " + scheduleId, "SUSPENDED"); testCommandOutputContains(cli, "resume schedule " + scheduleId, "Successfully resumed"); testCommandOutputContains(cli, "get schedule status " + scheduleId, "SCHEDULED"); testCommandOutputContains(cli, "suspend schedule " + scheduleId, "Successfully suspended"); testCommandOutputContains(cli, "get schedule status " + scheduleId, "SUSPENDED"); testCommandOutputContains(cli, "get workflow schedules " + workflowId, FakeApp.SCHEDULE_NAME); } @Test public void testDataset() throws Exception { String datasetName = PREFIX + "sdf123lkj"; DatasetTypeClient datasetTypeClient = new DatasetTypeClient(cliConfig.getClientConfig()); DatasetTypeMeta datasetType = datasetTypeClient.list(Id.Namespace.DEFAULT).get(0); testCommandOutputContains(cli, "create dataset instance " + datasetType.getName() + " " + datasetName + " \"a=1\"", "Successfully created dataset"); testCommandOutputContains(cli, "list dataset instances", FakeDataset.class.getSimpleName()); testCommandOutputContains(cli, "get dataset instance properties " + datasetName, "\"a\":\"1\""); NamespaceClient namespaceClient = new NamespaceClient(cliConfig.getClientConfig()); Id.Namespace barspace = Id.Namespace.from("bar"); namespaceClient.create(new NamespaceMeta.Builder().setName(barspace).build()); cliConfig.setNamespace(barspace); // list of dataset instances is different in 'foo' namespace testCommandOutputNotContains(cli, "list dataset instances", FakeDataset.class.getSimpleName()); // also can not create dataset instances if the type it depends on exists only in a different namespace. Id.DatasetType datasetType1 = Id.DatasetType.from(barspace, datasetType.getName()); testCommandOutputContains(cli, "create dataset instance " + datasetType.getName() + " " + datasetName, new DatasetTypeNotFoundException(datasetType1).getMessage()); testCommandOutputContains(cli, "use namespace default", "Now using namespace 'default'"); try { testCommandOutputContains(cli, "truncate dataset instance " + datasetName, "Successfully truncated"); } finally { testCommandOutputContains(cli, "delete dataset instance " + datasetName, "Successfully deleted"); } String datasetName2 = PREFIX + "asoijm39485"; String description = "test-description-for-" + datasetName2; testCommandOutputContains(cli, "create dataset instance " + datasetType.getName() + " " + datasetName2 + " \"a=1\"" + " " + description, "Successfully created dataset"); testCommandOutputContains(cli, "list dataset instances", description); testCommandOutputContains(cli, "delete dataset instance " + datasetName2, "Successfully deleted"); } @Test public void testService() throws Exception { Id.Service service = Id.Service.from(Id.Namespace.DEFAULT, FakeApp.NAME, PrefixedEchoHandler.NAME); String qualifiedServiceId = String.format("%s.%s", FakeApp.NAME, PrefixedEchoHandler.NAME); testCommandOutputContains(cli, "start service " + qualifiedServiceId, "Successfully started service"); assertProgramStatus(programClient, service, "RUNNING"); try { testCommandOutputContains(cli, "get endpoints service " + qualifiedServiceId, "POST"); testCommandOutputContains(cli, "get endpoints service " + qualifiedServiceId, "/echo"); testCommandOutputContains(cli, "call service " + qualifiedServiceId + " POST /echo body \"testBody\"", ":testBody"); } finally { testCommandOutputContains(cli, "stop service " + qualifiedServiceId, "Successfully stopped service"); assertProgramStatus(programClient, service, "STOPPED"); } } @Test public void testRuntimeArgs() throws Exception { String qualifiedServiceId = String.format("%s.%s", FakeApp.NAME, PrefixedEchoHandler.NAME); Id.Service service = Id.Service.from(Id.Namespace.DEFAULT, FakeApp.NAME, PrefixedEchoHandler.NAME); Map<String, String> runtimeArgs = ImmutableMap.of("sdf", "bacon"); String runtimeArgsKV = Joiner.on(",").withKeyValueSeparator("=").join(runtimeArgs); testCommandOutputContains(cli, "start service " + qualifiedServiceId + " '" + runtimeArgsKV + "'", "Successfully started service"); try { assertProgramStatus(programClient, service, "RUNNING"); testCommandOutputContains(cli, "call service " + qualifiedServiceId + " POST /echo body \"testBody\"", "bacon:testBody"); testCommandOutputContains(cli, "stop service " + qualifiedServiceId, "Successfully stopped service"); assertProgramStatus(programClient, service, "STOPPED"); Map<String, String> runtimeArgs2 = ImmutableMap.of("sdf", "chickenz"); String runtimeArgs2Json = GSON.toJson(runtimeArgs2); String runtimeArgs2KV = Joiner.on(",").withKeyValueSeparator("=").join(runtimeArgs2); testCommandOutputContains(cli, "set service runtimeargs " + qualifiedServiceId + " '" + runtimeArgs2KV + "'", "Successfully set runtime args"); testCommandOutputContains(cli, "start service " + qualifiedServiceId, "Successfully started service"); testCommandOutputContains(cli, "get service runtimeargs " + qualifiedServiceId, runtimeArgs2Json); testCommandOutputContains(cli, "call service " + qualifiedServiceId + " POST /echo body \"testBody\"", "chickenz:testBody"); } finally { testCommandOutputContains(cli, "stop service " + qualifiedServiceId, "Successfully stopped service"); assertProgramStatus(programClient, service, "STOPPED"); } } @Test public void testSpark() throws Exception { String sparkId = FakeApp.SPARK.get(0); String qualifiedSparkId = FakeApp.NAME + "." + sparkId; Id.Program spark = Id.Program.from(Id.Namespace.DEFAULT, FakeApp.NAME, ProgramType.SPARK, sparkId); testCommandOutputContains(cli, "list spark", sparkId); testCommandOutputContains(cli, "start spark " + qualifiedSparkId, "Successfully started Spark"); assertProgramStatus(programClient, spark, "RUNNING"); assertProgramStatus(programClient, spark, "STOPPED"); testCommandOutputContains(cli, "get spark status " + qualifiedSparkId, "STOPPED"); testCommandOutputContains(cli, "get spark runs " + qualifiedSparkId, "COMPLETED"); testCommandOutputContains(cli, "get spark logs " + qualifiedSparkId, "HelloFakeSpark"); } @Test public void testPreferences() throws Exception { testPreferencesOutput(cli, "get preferences instance", ImmutableMap.<String, String>of()); Map<String, String> propMap = Maps.newHashMap(); propMap.put("key", "newinstance"); propMap.put("k1", "v1"); testCommandOutputContains(cli, "delete preferences instance", "successfully"); testCommandOutputContains(cli, "set preferences instance 'key=newinstance k1=v1'", "successfully"); testPreferencesOutput(cli, "get preferences instance", propMap); testPreferencesOutput(cli, "get resolved preferences instance", propMap); testCommandOutputContains(cli, "delete preferences instance", "successfully"); propMap.clear(); testPreferencesOutput(cli, "get preferences instance", propMap); propMap.put("key", "flow"); testCommandOutputContains(cli, String.format("set preferences flow 'key=flow' %s.%s", FakeApp.NAME, FakeFlow.NAME), "successfully"); testPreferencesOutput(cli, String.format("get preferences flow %s.%s", FakeApp.NAME, FakeFlow.NAME), propMap); testCommandOutputContains(cli, String.format("delete preferences flow %s.%s", FakeApp.NAME, FakeFlow.NAME), "successfully"); propMap.clear(); testPreferencesOutput(cli, String.format("get preferences app %s", FakeApp.NAME), propMap); testPreferencesOutput(cli, "get preferences namespace", propMap); testCommandOutputContains(cli, "get preferences app invalidapp", "not found"); File file = new File(TMP_FOLDER.newFolder(), "prefFile.txt"); // If the file not exist or not a file, upload should fails with an error. testCommandOutputContains(cli, "load preferences instance " + file.getAbsolutePath() + " json", "Not a file"); testCommandOutputContains(cli, "load preferences instance " + file.getParentFile().getAbsolutePath() + " json", "Not a file"); // Generate a file to load BufferedWriter writer = Files.newWriter(file, Charsets.UTF_8); try { writer.write("{'key':'somevalue'}"); } finally { writer.close(); } testCommandOutputContains(cli, "load preferences instance " + file.getAbsolutePath() + " json", "successful"); propMap.clear(); propMap.put("key", "somevalue"); testPreferencesOutput(cli, "get preferences instance", propMap); testCommandOutputContains(cli, "delete preferences namespace", "successfully"); testCommandOutputContains(cli, "delete preferences instance", "successfully"); //Try invalid Json file = new File(TMP_FOLDER.newFolder(), "badPrefFile.txt"); writer = Files.newWriter(file, Charsets.UTF_8); try { writer.write("{'key:'somevalue'}"); } finally { writer.close(); } testCommandOutputContains(cli, "load preferences instance " + file.getAbsolutePath() + " json", "invalid"); testCommandOutputContains(cli, "load preferences instance " + file.getAbsolutePath() + " xml", "Unsupported"); testCommandOutputContains(cli, "set preferences namespace 'k1=v1'", "successfully"); testCommandOutputContains(cli, "set preferences namespace 'k1=v1' name", "Error: Expected format: set preferences namespace <runtime-args>"); testCommandOutputContains(cli, "set preferences instance 'k1=v1' name", "Error: Expected format: set preferences instance <runtime-args>"); } @Test public void testNamespaces() throws Exception { final String name = PREFIX + "testNamespace"; final String description = "testDescription"; final String defaultFields = PREFIX + "defaultFields"; final String doesNotExist = "doesNotExist"; // initially only default namespace should be present NamespaceMeta defaultNs = new NamespaceMeta.Builder() .setName("default").setDescription("Default Namespace").build(); List<NamespaceMeta> expectedNamespaces = Lists.newArrayList(defaultNs); testNamespacesOutput(cli, "list namespaces", expectedNamespaces); // describe non-existing namespace testCommandOutputContains(cli, String.format("describe namespace %s", doesNotExist), String.format("Error: 'namespace:%s' was not found", doesNotExist)); // delete non-existing namespace // TODO: uncomment when fixed - this makes build hang since it requires confirmation from user // testCommandOutputContains(cli, String.format("delete namespace %s", doesNotExist), // String.format("Error: namespace '%s' was not found", doesNotExist)); // create a namespace String command = String.format("create namespace %s %s", name, description); testCommandOutputContains(cli, command, String.format("Namespace '%s' created successfully.", name)); NamespaceMeta expected = new NamespaceMeta.Builder() .setName(name).setDescription(description).build(); expectedNamespaces = Lists.newArrayList(defaultNs, expected); // list namespaces and verify testNamespacesOutput(cli, "list namespaces", expectedNamespaces); // get namespace details and verify expectedNamespaces = Lists.newArrayList(expected); command = String.format("describe namespace %s", name); testNamespacesOutput(cli, command, expectedNamespaces); // try creating a namespace with existing id command = String.format("create namespace %s", name); testCommandOutputContains(cli, command, String.format("Error: 'namespace:%s' already exists\n", name)); // create a namespace with default name and description command = String.format("create namespace %s", defaultFields); testCommandOutputContains(cli, command, String.format("Namespace '%s' created successfully.", defaultFields)); NamespaceMeta namespaceDefaultFields = new NamespaceMeta.Builder() .setName(defaultFields).setDescription("").build(); // test that there are 3 namespaces including default expectedNamespaces = Lists.newArrayList(defaultNs, namespaceDefaultFields, expected); testNamespacesOutput(cli, "list namespaces", expectedNamespaces); // describe namespace with default fields expectedNamespaces = Lists.newArrayList(namespaceDefaultFields); testNamespacesOutput(cli, String.format("describe namespace %s", defaultFields), expectedNamespaces); // delete namespace and verify // TODO: uncomment when fixed - this makes build hang since it requires confirmation from user // command = String.format("delete namespace %s", name); // testCommandOutputContains(cli, command, String.format("Namespace '%s' deleted successfully.", name)); } @Test public void testWorkflows() throws Exception { String workflow = String.format("%s.%s", FakeApp.NAME, FakeWorkflow.NAME); File doneFile = TMP_FOLDER.newFile("fake.done"); Map<String, String> runtimeArgs = ImmutableMap.of("done.file", doneFile.getAbsolutePath()); String runtimeArgsKV = Joiner.on(",").withKeyValueSeparator("=").join(runtimeArgs); testCommandOutputContains(cli, "start workflow " + workflow + " '" + runtimeArgsKV + "'", "Successfully started workflow"); assertProgramStatus(programClient, fakeWorkflowId, "STOPPED"); testCommandOutputContains(cli, "cli render as csv", "Now rendering as CSV"); String commandOutput = getCommandOutput(cli, "get workflow runs " + workflow); String[] lines = commandOutput.split("\\r?\\n"); Assert.assertEquals(2, lines.length); String[] split = lines[1].split(","); String runId = split[0]; // Test entire workflow token List<WorkflowTokenDetail.NodeValueDetail> tokenValues = new ArrayList<>(); tokenValues.add(new WorkflowTokenDetail.NodeValueDetail(FakeWorkflow.FakeAction.class.getSimpleName(), FakeWorkflow.FakeAction.TOKEN_VALUE)); tokenValues.add(new WorkflowTokenDetail.NodeValueDetail(FakeWorkflow.FakeAction.ANOTHER_FAKE_NAME, FakeWorkflow.FakeAction.TOKEN_VALUE)); testCommandOutputContains(cli, String.format("get workflow token %s %s", workflow, runId), Joiner.on(",").join(FakeWorkflow.FakeAction.TOKEN_KEY, GSON.toJson(tokenValues))); testCommandOutputNotContains(cli, String.format("get workflow token %s %s scope system", workflow, runId), Joiner.on(",").join(FakeWorkflow.FakeAction.TOKEN_KEY, GSON.toJson(tokenValues))); testCommandOutputContains( cli, String.format("get workflow token %s %s scope user key %s", workflow, runId, FakeWorkflow.FakeAction.TOKEN_KEY), Joiner.on(",").join(FakeWorkflow.FakeAction.TOKEN_KEY, GSON.toJson(tokenValues))); // Test with node name String fakeNodeValue = Joiner.on(",").join(FakeWorkflow.FakeAction.TOKEN_KEY, FakeWorkflow.FakeAction.TOKEN_VALUE); testCommandOutputContains( cli, String.format("get workflow token %s %s at node %s", workflow, runId, FakeWorkflow.FakeAction.class.getSimpleName()), fakeNodeValue); testCommandOutputNotContains( cli, String.format("get workflow token %s %s at node %s scope system", workflow, runId, FakeWorkflow.FakeAction.ANOTHER_FAKE_NAME), fakeNodeValue); testCommandOutputContains( cli, String.format("get workflow token %s %s at node %s scope user key %s", workflow, runId, FakeWorkflow.FakeAction.ANOTHER_FAKE_NAME, FakeWorkflow.FakeAction.TOKEN_KEY), fakeNodeValue); testCommandOutputContains(cli, "get workflow logs " + workflow, FakeWorkflow.FAKE_LOG); // stop workflow testCommandOutputContains(cli, "stop workflow " + workflow, String.format("400: Program '%s' is not running", fakeWorkflowId)); } @Test public void testMetadata() throws Exception { testCommandOutputContains(cli, "cli render as csv", "Now rendering as CSV"); // verify system metadata testCommandOutputContains(cli, String.format("get metadata %s scope system", fakeAppId), FakeApp.class.getSimpleName()); testCommandOutputContains(cli, String.format("get metadata-tags %s scope system", fakeWorkflowId), FakeWorkflow.FakeAction.class.getSimpleName()); testCommandOutputContains(cli, String.format("get metadata-tags %s scope system", fakeWorkflowId), FakeWorkflow.FakeAction.ANOTHER_FAKE_NAME); testCommandOutputContains(cli, String.format("get metadata-tags %s scope system", fakeDsId), "batch"); testCommandOutputContains(cli, String.format("get metadata-tags %s scope system", fakeDsId), "explore"); testCommandOutputContains(cli, String.format("get metadata-tags %s scope system", fakeStreamId), FakeApp.STREAM_NAME); testCommandOutputContains(cli, String.format("add metadata-properties %s appKey=appValue", fakeAppId), "Successfully added metadata properties"); testCommandOutputContains(cli, String.format("get metadata-properties %s", fakeAppId), "appKey,appValue"); testCommandOutputContains(cli, String.format("add metadata-tags %s 'wfTag1 wfTag2'", fakeWorkflowId), "Successfully added metadata tags"); String output = getCommandOutput(cli, String.format("get metadata-tags %s", fakeWorkflowId)); List<String> lines = Arrays.asList(output.split("\\r?\\n")); Assert.assertTrue(lines.contains("wfTag1") && lines.contains("wfTag2")); testCommandOutputContains(cli, String.format("add metadata-properties %s dsKey=dsValue", fakeDsId), "Successfully added metadata properties"); testCommandOutputContains(cli, String.format("get metadata-properties %s", fakeDsId), "dsKey,dsValue"); testCommandOutputContains(cli, String.format("add metadata-tags %s 'streamTag1 streamTag2'", fakeStreamId), "Successfully added metadata tags"); output = getCommandOutput(cli, String.format("get metadata-tags %s", fakeStreamId)); lines = Arrays.asList(output.split("\\r?\\n")); Assert.assertTrue(lines.contains("streamTag1") && lines.contains("streamTag2")); // test search testCommandOutputContains(cli, String.format("search metadata %s filtered by target-type artifact", FakeApp.class.getSimpleName()), fakeArtifactId.toString()); testCommandOutputContains(cli, "search metadata appKey:appValue", fakeAppId.toString()); testCommandOutputContains(cli, "search metadata fake* filtered by target-type app", fakeAppId.toString()); output = getCommandOutput(cli, "search metadata fake* filtered by target-type program"); lines = Arrays.asList(output.split("\\r?\\n")); List<String> expected = ImmutableList.of("Entity", fakeWorkflowId.toString(), fakeSparkId.toString(), fakeFlowId.toString()); Assert.assertTrue(lines.containsAll(expected) && expected.containsAll(lines)); testCommandOutputContains(cli, "search metadata fake* filtered by target-type dataset", fakeDsId.toString()); testCommandOutputContains(cli, "search metadata fake* filtered by target-type stream", fakeStreamId.toString()); testCommandOutputContains(cli, String.format("search metadata %s", FakeApp.SCHEDULE_NAME), fakeAppId.toString()); testCommandOutputContains(cli, String.format("search metadata %s", FakeApp.STREAM_SCHEDULE_NAME), fakeAppId.toString()); testCommandOutputContains(cli, String.format("search metadata %s filtered by target-type app", PingService.NAME), fakeAppId.toString()); testCommandOutputContains(cli, String.format("search metadata %s filtered by target-type program", PrefixedEchoHandler.NAME), prefixedEchoHandlerId.toString()); testCommandOutputContains(cli, "search metadata batch* filtered by target-type dataset", fakeDsId.toString()); testCommandOutputNotContains(cli, "search metadata batchwritable filtered by target-type dataset", fakeDsId.toString()); testCommandOutputContains(cli, "search metadata bat* filtered by target-type dataset", fakeDsId.toString()); output = getCommandOutput(cli, "search metadata batch filtered by target-type program"); lines = Arrays.asList(output.split("\\r?\\n")); expected = ImmutableList.of("Entity", fakeSparkId.toString(), fakeWorkflowId.toString()); Assert.assertTrue(lines.containsAll(expected) && expected.containsAll(lines)); output = getCommandOutput(cli, "search metadata realtime filtered by target-type program"); lines = Arrays.asList(output.split("\\r?\\n")); expected = ImmutableList.of("Entity", fakeFlowId.toString(), pingServiceId.toString(), prefixedEchoHandlerId.toString()); Assert.assertTrue(lines.containsAll(expected) && expected.containsAll(lines)); output = getCommandOutput(cli, "search metadata fake* filtered by target-type dataset,stream"); lines = Arrays.asList(output.split("\\r?\\n")); expected = ImmutableList.of("Entity", fakeDsId.toString(), fakeStreamId.toString()); Assert.assertTrue(lines.containsAll(expected) && expected.containsAll(lines)); output = getCommandOutput(cli, "search metadata fake* filtered by target-type dataset,stream,app"); lines = Arrays.asList(output.split("\\r?\\n")); expected = ImmutableList.of("Entity", fakeDsId.toString(), fakeStreamId.toString(), fakeAppId.toString()); Assert.assertTrue(lines.containsAll(expected) && expected.containsAll(lines)); } private static File createAppJarFile(Class<?> cls) throws IOException { File tmpFolder = TMP_FOLDER.newFolder(); LocationFactory locationFactory = new LocalLocationFactory(tmpFolder); Location deploymentJar = AppJarHelper.createDeploymentJar(locationFactory, cls); File appJarFile = new File(tmpFolder, String.format("%s-1.0.%d.jar", cls.getSimpleName(), System.currentTimeMillis())); Files.copy(Locations.newInputSupplier(deploymentJar), appJarFile); return appJarFile; } protected void assertProgramStatus(ProgramClient programClient, Id.Program programId, String programStatus, int tries) throws IOException, ProgramNotFoundException, UnauthenticatedException { String status; int numTries = 0; do { status = programClient.getStatus(programId); numTries++; try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { // NO-OP } } while (!status.equals(programStatus) && numTries <= tries); Assert.assertEquals(programStatus, status); } protected void assertProgramStatus(ProgramClient programClient, Id.Program programId, String programStatus) throws IOException, ProgramNotFoundException, UnauthenticatedException { assertProgramStatus(programClient, programId, programStatus, 180); } private static void testNamespacesOutput(CLI cli, String command, final List<NamespaceMeta> expected) throws Exception { ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); PrintStream output = new PrintStream(outputStream); Table table = Table.builder() .setHeader("name", "description") .setRows(expected, new RowMaker<NamespaceMeta>() { @Override public List<?> makeRow(NamespaceMeta object) { return Lists.newArrayList(object.getName(), object.getDescription()); } }).build(); cliMain.getTableRenderer().render(cliConfig, output, table); final String expectedOutput = outputStream.toString(); testCommand(cli, command, new Function<String, Void>() { @Nullable @Override public Void apply(@Nullable String output) { Assert.assertNotNull(output); Assert.assertEquals(expectedOutput, output); return null; } }); } private static void testPreferencesOutput(CLI cli, String command, final Map<String, String> expected) throws Exception { testCommand(cli, command, new Function<String, Void>() { @Nullable @Override public Void apply(@Nullable String output) { Assert.assertNotNull(output); Map<String, String> outputMap = Splitter.on(System.lineSeparator()) .omitEmptyStrings().withKeyValueSeparator("=").split(output); Assert.assertEquals(expected, outputMap); return null; } }); } }