/*
# Licensed Materials - Property of IBM
# Copyright IBM Corp. 2015
*/
package com.ibm.streamsx.topology.test.api;
import static com.ibm.streamsx.topology.generator.operator.OpProperties.PLACEMENT;
import static com.ibm.streamsx.topology.generator.operator.OpProperties.PLACEMENT_EXPLICIT_COLOCATE_ID;
import static com.ibm.streamsx.topology.test.api.IsolateTest.getContainerIdAppend;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.junit.Ignore;
import org.junit.Test;
import com.ibm.json.java.JSONArray;
import com.ibm.json.java.JSONObject;
import com.ibm.streamsx.topology.TSink;
import com.ibm.streamsx.topology.TStream;
import com.ibm.streamsx.topology.Topology;
import com.ibm.streamsx.topology.builder.BOperator;
import com.ibm.streamsx.topology.builder.BOutputPort;
import com.ibm.streamsx.topology.builder.JOperator.JOperatorConfig;
import com.ibm.streamsx.topology.context.ContextProperties;
import com.ibm.streamsx.topology.context.Placeable;
import com.ibm.streamsx.topology.context.StreamsContext;
import com.ibm.streamsx.topology.streams.StringStreams;
import com.ibm.streamsx.topology.test.AllowAll;
import com.ibm.streamsx.topology.test.TestTopology;
import com.ibm.streamsx.topology.tester.Condition;
import com.ibm.streamsx.topology.tester.Tester;
/**
* Tests to verify Placeable
*
*/
public class PlaceableTest extends TestTopology {
@Test
public void testSimpleTagsStream() {
assumeTrue(isMainRun());
Topology t = newTopology();
TStream<String> s = t.strings("3");
testSimpleTags(s);
}
@Test
public void testSimpleTagsSink() {
assumeTrue(isMainRun());
Topology t = newTopology();
TStream<String> s = t.strings("3");
testSimpleTags(s.print());
}
private void testSimpleTags(Placeable<?> s) {
assertTrue(s.getResourceTags().isEmpty());
s.addResourceTags();
assertTrue(s.getResourceTags().isEmpty());
s.addResourceTags("ingest");
assertEquals(Collections.singleton("ingest"), s.getResourceTags());
s.addResourceTags();
assertEquals(Collections.singleton("ingest"), s.getResourceTags());
s.addResourceTags("ingest");
assertEquals(Collections.singleton("ingest"), s.getResourceTags());
Set<String> expected = new HashSet<>();
expected.add("ingest");
expected.add("database");
s.addResourceTags("database");
assertEquals(expected, s.getResourceTags());
expected.add("db2");
expected.add("sales");
s.addResourceTags("sales", "db2");
assertEquals(expected, s.getResourceTags());
}
@Test
public void testTagThenFuseStream() {
assumeTrue(isMainRun());
Topology t = newTopology();
TStream<String> s1 = t.strings("3");
TStream<String> s2 = t.strings("3");
testTagThenFuse(s1, s2);
}
@Test
public void testTagThenFuseSink() {
assumeTrue(isMainRun());
Topology t = newTopology();
TStream<String> s1 = t.strings("3");
TStream<String> s2 = t.strings("3");
testTagThenFuse(s1.print(), s2.print());
}
@Test
public void testTagThenFuseStreamSink() {
assumeTrue(isMainRun());
Topology t = newTopology();
TStream<String> s1 = t.strings("3");
TStream<String> s2 = t.strings("3");
testTagThenFuse(s1, s2.print());
}
private void testTagThenFuse(Placeable<?> s1, Placeable<?> s2) {
assertTrue(s1.getResourceTags().isEmpty());
assertTrue(s2.getResourceTags().isEmpty());
s1.addResourceTags("ingest");
s1.colocate(s2);
assertEquals(Collections.singleton("ingest"), s1.getResourceTags());
assertEquals(s1.getResourceTags(), s2.getResourceTags());
Set<String> expected = new HashSet<>();
expected.add("ingest");
expected.add("database");
s1.addResourceTags("database");
assertEquals(expected, s1.getResourceTags());
assertEquals(s1.getResourceTags(), s2.getResourceTags());
expected.add("db2");
s2.addResourceTags("db2");
assertEquals(expected, s1.getResourceTags());
assertEquals(s1.getResourceTags(), s2.getResourceTags());
}
@Test
public void testTagBothThenFuseStream() {
assumeTrue(isMainRun());
Topology t = newTopology();
TStream<String> s1 = t.strings("3");
TStream<String> s2 = t.strings("3");
testTagBothThenFuse(s1, s2);
}
@Test
public void testTagBothThenFuseSink() {
assumeTrue(isMainRun());
Topology t = newTopology();
TStream<String> s1 = t.strings("3");
TStream<String> s2 = t.strings("3");
testTagBothThenFuse(s1.print(), s2.print());
}
@Test
public void testTagBothThenFuseSinkStream() {
assumeTrue(isMainRun());
Topology t = newTopology();
TStream<String> s1 = t.strings("3");
TStream<String> s2 = t.strings("3");
testTagBothThenFuse(s1.print(), s2);
}
private void testTagBothThenFuse(Placeable<?> s1, Placeable<?> s2) {
assertTrue(s1.getResourceTags().isEmpty());
assertTrue(s2.getResourceTags().isEmpty());
s1.addResourceTags("ingest");
s2.addResourceTags("database");
s1.colocate(s2);
Set<String> expected = new HashSet<>();
expected.add("ingest");
expected.add("database");
assertEquals(expected, s1.getResourceTags());
assertEquals(s1.getResourceTags(), s2.getResourceTags());
}
@Test
public void testFuseThenTagStream() {
assumeTrue(isMainRun());
Topology t = newTopology();
TStream<String> s1 = t.strings("3");
TStream<String> s2 = t.strings("3");
testFuseThenTag(s1, s2);
}
@Test
public void testFuseThenTagSink() {
assumeTrue(isMainRun());
Topology t = newTopology();
TStream<String> s1 = t.strings("3");
TStream<String> s2 = t.strings("3");
testFuseThenTag(s1.print(), s2.print());
}
@Test
public void testFuseThenTagStreamSink() {
assumeTrue(isMainRun());
Topology t = newTopology();
TStream<String> s1 = t.strings("3");
TStream<String> s2 = t.strings("3");
testFuseThenTag(s1, s2.print());
}
private void testFuseThenTag(Placeable<?> s1, Placeable<?> s2) {
assertTrue(s1.getResourceTags().isEmpty());
assertTrue(s2.getResourceTags().isEmpty());
s1.colocate(s2);
assertTrue(s1.getResourceTags().isEmpty());
assertTrue(s2.getResourceTags().isEmpty());
assertSame(s1.addResourceTags("ingest"), s1);
assertSame(s2.addResourceTags("database"), s2);
Set<String> expected = new HashSet<>();
expected.add("ingest");
expected.add("database");
assertEquals(expected, s1.getResourceTags());
assertEquals(s1.getResourceTags(), s2.getResourceTags());
}
@Test
public void testFusing() {
assumeTrue(isMainRun());
Topology t = newTopology();
TStream<String> s1 = t.strings("3");
TStream<String> s2 = t.strings("3");
TStream<String> snf = t.strings("3");
assertTrue(s1.isPlaceable());
assertSame(s1.colocate(s2), s1);
String id1 = getFusingId(s1);
String id2 = getFusingId(s2);
assertNotNull(id1);
assertFalse(id1.isEmpty());
assertEquals(id1, id2);
TStream<String> s3 = t.strings("3");
TStream<String> s4 = t.strings("3");
TSink s5 = s4.print();
assertTrue(s5.isPlaceable());
assertSame(s3.colocate(s4, s5), s3);
assertEquals(getFusingId(s3), getFusingId(s4));
assertEquals(getFusingId(s3), getColocate(s5.operator()));
assertFalse(getFusingId(s1).equals(getFusingId(s3)));
assertNull(getFusingId(snf));
TStream<String> s6 = StringStreams.toString(s4);
s1.colocate(s6);
assertEquals(getFusingId(s1), getFusingId(s6));
}
@Test
public void testNonplaceable() {
assumeTrue(isMainRun());
Topology t = newTopology();
TStream<String> s1 = t.strings("3");
TStream<String> s2 = t.strings("3");
assertFalse(s1.union(s2).isPlaceable());
assertFalse(s1.isolate().isPlaceable());
TStream<String> sp = s1.parallel(3);
assertFalse(sp.isPlaceable());
assertFalse(sp.endParallel().isPlaceable());
}
private static String getFusingId(TStream<?> s) {
BOperator bop = ((BOutputPort) s.output()).operator();
return getColocate(bop);
}
private static String getColocate(BOperator bop) {
JSONObject placement = JOperatorConfig.getJSONItem(bop.json(), PLACEMENT);
if (placement == null)
return null;
Object ido = placement.get(PLACEMENT_EXPLICIT_COLOCATE_ID);
if (ido == null)
return null;
return ido.toString();
}
private static Set<String> getResourceTags(TStream<?> s) {
BOperator bop = ((BOutputPort) s.output()).operator();
return getResourceTags(bop);
}
private static Set<String> getResourceTags(BOperator bop) {
JSONObject placement = JOperatorConfig.getJSONItem(bop.json(), PLACEMENT);
if (placement == null)
return null;
JSONArray jat = (JSONArray) placement.get(com.ibm.streamsx.topology.generator.operator.OpProperties.PLACEMENT_RESOURCE_TAGS);
if (jat == null)
return null;
Set<String> tags = new HashSet<>();
for (Object rt : jat)
tags.add(rt.toString());
return tags;
}
@Test
public void testTags() {
assumeTrue(isMainRun());
Topology t = newTopology();
TStream<String> s1 = t.strings("3");
TStream<String> s2 = t.strings("3");
TStream<String> s3 = t.strings("3");
s1.addResourceTags();
assertNull(getResourceTags(s1));
s2.addResourceTags("A", "B");
Set<String> s2s = getResourceTags(s2);
assertEquals(2, s2s.size());
assertTrue(s2s.contains("A"));
assertTrue(s2s.contains("B"));
s3.addResourceTags("C", "D", "E");
Set<String> s3s = getResourceTags(s3);
assertEquals(3, s3s.size());
assertTrue(s3s.contains("C"));
assertTrue(s3s.contains("D"));
assertTrue(s3s.contains("E"));
s2s = getResourceTags(s2);
assertEquals(2, s2s.size());
assertTrue(s2s.contains("A"));
assertTrue(s2s.contains("B"));
s2.addResourceTags("X", "Y");
s2s = getResourceTags(s2);
assertEquals(4, s2s.size());
assertTrue(s2s.contains("A"));
assertTrue(s2s.contains("B"));
assertTrue(s2s.contains("X"));
assertTrue(s2s.contains("Y"));
// Colocating means the s1 will inherit
// s3 resource tags
s1.colocate(s3);
Set<String> s1s = getResourceTags(s1);
assertEquals(3, s1s.size());
assertTrue(s1s.contains("C"));
assertTrue(s1s.contains("D"));
assertTrue(s1s.contains("E"));
}
/**
* Test with a distributed execution with explicit
* colocation of two functions end up on the same container.
*/
@Test
public void testSimpleDistributedColocate() throws Exception {
assumeTrue(SC_OK);
assumeTrue(getTesterType() == StreamsContext.Type.DISTRIBUTED_TESTER);
Topology t = newTopology();
TStream<String> sa = t.strings("a");
TStream<String> sb = t.strings("b");
sa = sa.transform(IsolateTest.getContainerId());
sb = sb.transform(IsolateTest.getContainerId());
sa.colocate(sb);
sa = sa.isolate().filter(new AllowAll<String>());
sb = sb.isolate().filter(new AllowAll<String>());
sa = sa.union(sb);
Condition<List<String>> pes = t.getTester().stringContents(sa, "");
Condition<Long> tc = t.getTester().tupleCount(sa, 2);
complete(t.getTester(), tc, 10, TimeUnit.SECONDS);
Set<String> singlePe = new HashSet<>(pes.getResult());
assertTrue(pes.getResult().toString(), singlePe.size() == 1);
}
/**
* Test with a distributed execution with explicit
* colocation of two functions end up on the same container.
*/
@Test
@Ignore("Need to figure out how to get the tags set by the test") // TODO
public void testSimpleDistributedHostTags() throws Exception {
assumeTrue(SC_OK);
assumeTrue(getTesterType() == StreamsContext.Type.DISTRIBUTED_TESTER);
Topology t = newTopology();
TStream<String> sa = t.strings("a");
sa.addResourceTags("application");
sa = sa.filter(new AllowAll<String>());
sa.addResourceTags("application");
getConfig().put(ContextProperties.KEEP_ARTIFACTS, Boolean.TRUE);
Condition<List<String>> aout = t.getTester().stringContents(sa, "a");
complete(t.getTester(), aout, 10, TimeUnit.SECONDS);
assertTrue(aout.getResult().toString(), aout.valid());
}
@Test(expected = IllegalArgumentException.class)
public void testColocateLowLatancyNotPlaceable1() throws Exception {
assumeTrue(isMainRun());
// test current behavior of a not-placeable construct
Topology t = newTopology("testColocateLowLatancyNotPlaceable1");
TStream<String> s1 =
t.strings("a")
.modify(getContainerIdAppend())
.modify(getContainerIdAppend())
;
@SuppressWarnings("unused")
TStream<String> s2 =
t.strings("A")
.lowLatency()
.colocate(s1) // throws IAE: not placeable
.modify(getContainerIdAppend())
.modify(getContainerIdAppend())
.endLowLatency()
;
}
@Test(expected = IllegalArgumentException.class)
public void testColocateLowLatancyNotPlaceable2() throws Exception {
assumeTrue(isMainRun());
// test current behavior of a not-placeable construct
Topology t = newTopology("testColocateLowLatancyNotPlaceable2");
TStream<String> s1 =
t.strings("a")
.modify(getContainerIdAppend())
.modify(getContainerIdAppend())
;
@SuppressWarnings("unused")
TStream<String> s2 =
t.strings("A")
.lowLatency()
.modify(getContainerIdAppend())
.modify(getContainerIdAppend())
.endLowLatency()
.colocate(s1) // throws IAE: not placeable
;
}
@Test(expected = IllegalStateException.class)
@SuppressWarnings("unused")
public void testColocateLowLatancy() throws Exception {
assumeTrue(isMainRun());
// test colocate doesn't violate low latency as well as does colocate
final Topology topology = newTopology("testColocateLowLatancy");
Tester tester = topology.getTester();
TStream<String> s1 =
topology.strings("a")
.modify(getContainerIdAppend())
;
TStream<String> s2 =
topology.strings("A")
.lowLatency()
.modify(getContainerIdAppend())
.colocate(s1) // expect throw ISE: colocate in a low latency region
.modify(getContainerIdAppend())
.endLowLatency()
;
// once it's supported... (today it breaks the low latency guarantee)
// and adjust isMainRun() too
// // Given the default fuse-island behavior, expect islands to continue
// // to be fused, now both in a single container.
//
// TStream<String> all = s1.union(s2);
// all.print();
//
// Condition<Long> nTuples = tester.tupleCount(
// all.filter(new AllowAll<String>()), 2);
// Condition<List<String>> contents = tester.stringContents(
// all.filter(new AllowAll<String>()), "");
//
// complete(tester, nTuples, 10, TimeUnit.SECONDS);
//
// Set<String> ids = getContainerIds(contents.getResult());
// assertEquals("ids: "+ids, 1, ids.size());
}
@SuppressWarnings("unused")
@Test(expected = IllegalStateException.class)
public void testColocateLowLatencyRegions() throws Exception {
assumeTrue(isMainRun());
// ensure colocating two low latency regions doesn't break lowLatancy
// and colocating is achieved.
Topology t = newTopology("testColocateLowLatencyRegions");
Tester tester = t.getTester();
// getConfig().put(ContextProperties.KEEP_ARTIFACTS, true);
TStream<String> s1 =
t.strings("a")
.lowLatency()
.modify(getContainerIdAppend())
.modify(getContainerIdAppend())
;
s1
.endLowLatency()
;
TStream<String> s2 =
t.strings("A")
.lowLatency()
.modify(getContainerIdAppend())
.modify(getContainerIdAppend())
;
s2
.endLowLatency()
;
s1.colocate(s2); // expect throw ISE: colocate in a low latency region
// once it's supported... (today it breaks the low latency guarantee)
// and adjust isMainRun() too
// // Given the default fuse-island behavior, expect islands to continue
// // to be fused, now both in a single container.
//
// // Today FAILING in an interesting way.
// // There are 2 PEs:
// // - one has just the single colocated s1 and s2 modify ops
// // - the other has everything else
//
// TStream<String> all = s1.union(s2);
// all.print();
// Condition<Long> nTuples = tester.tupleCount(all.filter(new AllowAll<String>()), 2);
// Condition<List<String>> contents = tester.stringContents(
// all.filter(new AllowAll<String>()), "");
//
// complete(tester, nTuples, 10, TimeUnit.SECONDS);
//
// Set<String> ids = getContainerIds(contents.getResult());
// assertEquals("ids: "+ids, 1, ids.size());
}
@Test(expected = IllegalStateException.class)
public void testColocateIsolateViolation() throws Exception {
assumeTrue(isMainRun());
// verify s1.isolate().modify().colocate(s1) is disallowed
Topology t = newTopology("testColocateIsolateViolation");
TStream<String> s1 = t.strings("a");
s1.isolate()
.modify(getContainerIdAppend())
.colocate(s1) // throws ISE: can't colocate isolated stream with parent
;
}
}