/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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 org.apache.zeppelin.interpreter.remote; import static org.junit.Assert.*; import java.io.File; import java.io.IOException; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Properties; import org.apache.thrift.transport.TTransportException; import org.apache.zeppelin.display.AngularObjectRegistry; import org.apache.zeppelin.display.GUI; import org.apache.zeppelin.interpreter.InterpreterContext; import org.apache.zeppelin.interpreter.InterpreterContextRunner; import org.apache.zeppelin.interpreter.InterpreterGroup; import org.apache.zeppelin.interpreter.InterpreterResult; import org.apache.zeppelin.interpreter.InterpreterResult.Code; import org.apache.zeppelin.interpreter.remote.mock.MockInterpreterA; import org.apache.zeppelin.interpreter.remote.mock.MockInterpreterB; import org.apache.zeppelin.scheduler.Job; import org.apache.zeppelin.scheduler.Job.Status; import org.apache.zeppelin.scheduler.Scheduler; import org.junit.After; import org.junit.Before; import org.junit.Test; public class RemoteInterpreterTest { private InterpreterGroup intpGroup; private HashMap<String, String> env; @Before public void setUp() throws Exception { intpGroup = new InterpreterGroup(); env = new HashMap<String, String>(); env.put("ZEPPELIN_CLASSPATH", new File("./target/test-classes").getAbsolutePath()); } @After public void tearDown() throws Exception { intpGroup.close(); intpGroup.destroy(); } @Test public void testRemoteInterperterCall() throws TTransportException, IOException { Properties p = new Properties(); RemoteInterpreter intpA = new RemoteInterpreter( p, MockInterpreterA.class.getName(), new File("../bin/interpreter.sh").getAbsolutePath(), "fake", env, 10 * 1000 ); intpGroup.add(intpA); intpA.setInterpreterGroup(intpGroup); RemoteInterpreter intpB = new RemoteInterpreter( p, MockInterpreterB.class.getName(), new File("../bin/interpreter.sh").getAbsolutePath(), "fake", env, 10 * 1000 ); intpGroup.add(intpB); intpB.setInterpreterGroup(intpGroup); RemoteInterpreterProcess process = intpA.getInterpreterProcess(); process.equals(intpB.getInterpreterProcess()); assertFalse(process.isRunning()); assertEquals(0, process.getNumIdleClient()); assertEquals(0, process.referenceCount()); intpA.open(); assertTrue(process.isRunning()); assertEquals(1, process.getNumIdleClient()); assertEquals(1, process.referenceCount()); intpA.interpret("1", new InterpreterContext( "note", "id", "title", "text", new HashMap<String, Object>(), new GUI(), new AngularObjectRegistry(intpGroup.getId(), null), new LinkedList<InterpreterContextRunner>())); intpB.open(); assertEquals(2, process.referenceCount()); intpA.close(); assertEquals(1, process.referenceCount()); intpB.close(); assertEquals(0, process.referenceCount()); assertFalse(process.isRunning()); } @Test public void testRemoteInterperterErrorStatus() throws TTransportException, IOException { Properties p = new Properties(); RemoteInterpreter intpA = new RemoteInterpreter( p, MockInterpreterA.class.getName(), new File("../bin/interpreter.sh").getAbsolutePath(), "fake", env, 10 * 1000 ); intpGroup.add(intpA); intpA.setInterpreterGroup(intpGroup); intpA.open(); InterpreterResult ret = intpA.interpret("non numeric value", new InterpreterContext( "noteId", "id", "title", "text", new HashMap<String, Object>(), new GUI(), new AngularObjectRegistry(intpGroup.getId(), null), new LinkedList<InterpreterContextRunner>())); assertEquals(Code.ERROR, ret.code()); } @Test public void testRemoteSchedulerSharing() throws TTransportException, IOException { Properties p = new Properties(); RemoteInterpreter intpA = new RemoteInterpreter( p, MockInterpreterA.class.getName(), new File("../bin/interpreter.sh").getAbsolutePath(), "fake", env, 10 * 1000 ); intpGroup.add(intpA); intpA.setInterpreterGroup(intpGroup); RemoteInterpreter intpB = new RemoteInterpreter( p, MockInterpreterB.class.getName(), new File("../bin/interpreter.sh").getAbsolutePath(), "fake", env, 10 * 1000 ); intpGroup.add(intpB); intpB.setInterpreterGroup(intpGroup); intpA.open(); intpB.open(); long start = System.currentTimeMillis(); InterpreterResult ret = intpA.interpret("500", new InterpreterContext( "note", "id", "title", "text", new HashMap<String, Object>(), new GUI(), new AngularObjectRegistry(intpGroup.getId(), null), new LinkedList<InterpreterContextRunner>())); assertEquals("500", ret.message()); ret = intpB.interpret("500", new InterpreterContext( "note", "id", "title", "text", new HashMap<String, Object>(), new GUI(), new AngularObjectRegistry(intpGroup.getId(), null), new LinkedList<InterpreterContextRunner>())); assertEquals("1000", ret.message()); long end = System.currentTimeMillis(); assertTrue(end - start >= 1000); intpA.close(); intpB.close(); } @Test public void testRemoteSchedulerSharingSubmit() throws TTransportException, IOException, InterruptedException { Properties p = new Properties(); final RemoteInterpreter intpA = new RemoteInterpreter( p, MockInterpreterA.class.getName(), new File("../bin/interpreter.sh").getAbsolutePath(), "fake", env, 10 * 1000 ); intpGroup.add(intpA); intpA.setInterpreterGroup(intpGroup); final RemoteInterpreter intpB = new RemoteInterpreter( p, MockInterpreterB.class.getName(), new File("../bin/interpreter.sh").getAbsolutePath(), "fake", env, 10 * 1000 ); intpGroup.add(intpB); intpB.setInterpreterGroup(intpGroup); intpA.open(); intpB.open(); long start = System.currentTimeMillis(); Job jobA = new Job("jobA", null) { @Override public int progress() { return 0; } @Override public Map<String, Object> info() { return null; } @Override protected Object jobRun() throws Throwable { return intpA.interpret("500", new InterpreterContext( "note", "jobA", "title", "text", new HashMap<String, Object>(), new GUI(), new AngularObjectRegistry(intpGroup.getId(), null), new LinkedList<InterpreterContextRunner>())); } @Override protected boolean jobAbort() { return false; } }; intpA.getScheduler().submit(jobA); Job jobB = new Job("jobB", null) { @Override public int progress() { return 0; } @Override public Map<String, Object> info() { return null; } @Override protected Object jobRun() throws Throwable { return intpB.interpret("500", new InterpreterContext( "note", "jobB", "title", "text", new HashMap<String, Object>(), new GUI(), new AngularObjectRegistry(intpGroup.getId(), null), new LinkedList<InterpreterContextRunner>())); } @Override protected boolean jobAbort() { return false; } }; intpB.getScheduler().submit(jobB); // wait until both job finished while (jobA.getStatus() != Status.FINISHED || jobB.getStatus() != Status.FINISHED) { Thread.sleep(100); } long end = System.currentTimeMillis(); assertTrue(end - start >= 1000); assertEquals("1000", ((InterpreterResult) jobB.getReturn()).message()); intpA.close(); intpB.close(); } @Test public void testRunOrderPreserved() throws InterruptedException { Properties p = new Properties(); final RemoteInterpreter intpA = new RemoteInterpreter( p, MockInterpreterA.class.getName(), new File("../bin/interpreter.sh").getAbsolutePath(), "fake", env, 10 * 1000 ); intpGroup.add(intpA); intpA.setInterpreterGroup(intpGroup); intpA.open(); int concurrency = 3; final List<String> results = new LinkedList<String>(); Scheduler scheduler = intpA.getScheduler(); for (int i = 0; i < concurrency; i++) { final String jobId = Integer.toString(i); scheduler.submit(new Job(jobId, Integer.toString(i), null, 200) { @Override public int progress() { return 0; } @Override public Map<String, Object> info() { return null; } @Override protected Object jobRun() throws Throwable { InterpreterResult ret = intpA.interpret(getJobName(), new InterpreterContext( "note", jobId, "title", "text", new HashMap<String, Object>(), new GUI(), new AngularObjectRegistry(intpGroup.getId(), null), new LinkedList<InterpreterContextRunner>())); synchronized (results) { results.add(ret.message()); results.notify(); } return null; } @Override protected boolean jobAbort() { return false; } }); } // wait for job finished synchronized (results) { while (results.size() != concurrency) { results.wait(300); } } int i = 0; for (String result : results) { assertEquals(Integer.toString(i++), result); } assertEquals(concurrency, i); intpA.close(); } @Test public void testRunParallel() throws InterruptedException { Properties p = new Properties(); p.put("parallel", "true"); final RemoteInterpreter intpA = new RemoteInterpreter( p, MockInterpreterA.class.getName(), new File("../bin/interpreter.sh").getAbsolutePath(), "fake", env, 10 * 1000 ); intpGroup.add(intpA); intpA.setInterpreterGroup(intpGroup); intpA.open(); int concurrency = 4; final int timeToSleep = 1000; final List<String> results = new LinkedList<String>(); long start = System.currentTimeMillis(); Scheduler scheduler = intpA.getScheduler(); for (int i = 0; i < concurrency; i++) { final String jobId = Integer.toString(i); scheduler.submit(new Job(jobId, Integer.toString(i), null, 300) { @Override public int progress() { return 0; } @Override public Map<String, Object> info() { return null; } @Override protected Object jobRun() throws Throwable { String stmt = Integer.toString(timeToSleep); InterpreterResult ret = intpA.interpret(stmt, new InterpreterContext( "note", jobId, "title", "text", new HashMap<String, Object>(), new GUI(), new AngularObjectRegistry(intpGroup.getId(), null), new LinkedList<InterpreterContextRunner>())); synchronized (results) { results.add(ret.message()); results.notify(); } return stmt; } @Override protected boolean jobAbort() { return false; } }); } // wait for job finished synchronized (results) { while (results.size() != concurrency) { results.wait(300); } } long end = System.currentTimeMillis(); assertTrue(end - start < timeToSleep * concurrency); intpA.close(); } @Test public void testInterpreterGroupResetBeforeProcessStarts() { Properties p = new Properties(); RemoteInterpreter intpA = new RemoteInterpreter( p, MockInterpreterA.class.getName(), new File("../bin/interpreter.sh").getAbsolutePath(), "fake", env, 10 * 1000 ); intpA.setInterpreterGroup(intpGroup); RemoteInterpreterProcess processA = intpA.getInterpreterProcess(); intpA.setInterpreterGroup(new InterpreterGroup(intpA.getInterpreterGroup().getId())); RemoteInterpreterProcess processB = intpA.getInterpreterProcess(); assertNotSame(processA.hashCode(), processB.hashCode()); } @Test public void testInterpreterGroupResetAfterProcessFinished() { Properties p = new Properties(); RemoteInterpreter intpA = new RemoteInterpreter( p, MockInterpreterA.class.getName(), new File("../bin/interpreter.sh").getAbsolutePath(), "fake", env, 10 * 1000 ); intpA.setInterpreterGroup(intpGroup); RemoteInterpreterProcess processA = intpA.getInterpreterProcess(); intpA.open(); processA.dereference(); // intpA.close(); intpA.setInterpreterGroup(new InterpreterGroup(intpA.getInterpreterGroup().getId())); RemoteInterpreterProcess processB = intpA.getInterpreterProcess(); assertNotSame(processA.hashCode(), processB.hashCode()); } @Test public void testInterpreterGroupResetDuringProcessRunning() throws InterruptedException { Properties p = new Properties(); final RemoteInterpreter intpA = new RemoteInterpreter( p, MockInterpreterA.class.getName(), new File("../bin/interpreter.sh").getAbsolutePath(), "fake", env, 10 * 1000 ); intpGroup.add(intpA); intpA.setInterpreterGroup(intpGroup); intpA.open(); Job jobA = new Job("jobA", null) { @Override public int progress() { return 0; } @Override public Map<String, Object> info() { return null; } @Override protected Object jobRun() throws Throwable { return intpA.interpret("2000", new InterpreterContext( "note", "jobA", "title", "text", new HashMap<String, Object>(), new GUI(), new AngularObjectRegistry(intpGroup.getId(), null), new LinkedList<InterpreterContextRunner>())); } @Override protected boolean jobAbort() { return false; } }; intpA.getScheduler().submit(jobA); // wait for job started while (intpA.getScheduler().getJobsRunning().size() == 0) { Thread.sleep(100); } // restart interpreter RemoteInterpreterProcess processA = intpA.getInterpreterProcess(); intpA.close(); intpA.setInterpreterGroup(new InterpreterGroup(intpA.getInterpreterGroup().getId())); intpA.open(); RemoteInterpreterProcess processB = intpA.getInterpreterProcess(); assertNotSame(processA.hashCode(), processB.hashCode()); } @Test public void testRemoteInterpreterSharesTheSameSchedulerInstanceInTheSameGroup() { Properties p = new Properties(); RemoteInterpreter intpA = new RemoteInterpreter( p, MockInterpreterA.class.getName(), new File("../bin/interpreter.sh").getAbsolutePath(), "fake", env, 10 * 1000 ); intpGroup.add(intpA); intpA.setInterpreterGroup(intpGroup); RemoteInterpreter intpB = new RemoteInterpreter( p, MockInterpreterB.class.getName(), new File("../bin/interpreter.sh").getAbsolutePath(), "fake", env, 10 * 1000 ); intpGroup.add(intpB); intpB.setInterpreterGroup(intpGroup); intpA.open(); intpB.open(); assertEquals(intpA.getScheduler(), intpB.getScheduler()); } }