/*
* 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 gobblin.service.modules.orchestration;
import java.io.File;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Collection;
import java.util.List;
import java.util.Properties;
import org.apache.commons.io.FileUtils;
import org.apache.hadoop.fs.Path;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testng.Assert;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import com.google.common.base.Optional;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.typesafe.config.Config;
import gobblin.runtime.api.FlowSpec;
import gobblin.runtime.api.Spec;
import gobblin.runtime.api.SpecCompiler;
import gobblin.runtime.api.SpecExecutorInstanceProducer;
import gobblin.runtime.api.TopologySpec;
import gobblin.runtime.app.ServiceBasedAppLauncher;
import gobblin.runtime.spec_catalog.FlowCatalog;
import gobblin.runtime.spec_catalog.TopologyCatalog;
import gobblin.runtime.spec_executorInstance.InMemorySpecExecutorInstanceProducer;
import gobblin.service.modules.flow.IdentityFlowToJobSpecCompiler;
import gobblin.util.ConfigUtils;
import gobblin.util.PathUtils;
public class OrchestratorTest {
private static final Logger logger = LoggerFactory.getLogger(TopologyCatalog.class);
private static Gson gson = new GsonBuilder().setPrettyPrinting().create();
private static final String SPEC_STORE_PARENT_DIR = "/tmp/orchestrator/";
private static final String SPEC_DESCRIPTION = "Test Orchestrator";
private static final String SPEC_VERSION = "1";
private static final String TOPOLOGY_SPEC_STORE_DIR = "/tmp/orchestrator/topologyTestSpecStore";
private static final String FLOW_SPEC_STORE_DIR = "/tmp/orchestrator/flowTestSpecStore";
private ServiceBasedAppLauncher serviceLauncher;
private TopologyCatalog topologyCatalog;
private TopologySpec topologySpec;
private FlowCatalog flowCatalog;
private FlowSpec flowSpec;
private Orchestrator orchestrator;
@BeforeClass
public void setup() throws Exception {
cleanUpDir(TOPOLOGY_SPEC_STORE_DIR);
cleanUpDir(FLOW_SPEC_STORE_DIR);
Properties orchestratorProperties = new Properties();
Properties topologyProperties = new Properties();
topologyProperties.put("specStore.fs.dir", TOPOLOGY_SPEC_STORE_DIR);
Properties flowProperties = new Properties();
flowProperties.put("specStore.fs.dir", FLOW_SPEC_STORE_DIR);
this.serviceLauncher = new ServiceBasedAppLauncher(orchestratorProperties, "OrchestratorCatalogTest");
this.topologyCatalog = new TopologyCatalog(ConfigUtils.propertiesToConfig(topologyProperties),
Optional.of(logger));
this.serviceLauncher.addService(topologyCatalog);
this.flowCatalog = new FlowCatalog(ConfigUtils.propertiesToConfig(flowProperties),
Optional.of(logger));
this.serviceLauncher.addService(flowCatalog);
this.orchestrator = new Orchestrator(ConfigUtils.propertiesToConfig(orchestratorProperties),
Optional.of(this.topologyCatalog), Optional.of(logger));
this.topologyCatalog.addListener(orchestrator);
this.flowCatalog.addListener(orchestrator);
// Start application
this.serviceLauncher.start();
// Create Spec to play with
this.topologySpec = initTopologySpec();
this.flowSpec = initFlowSpec();
}
private void cleanUpDir(String dir) throws Exception {
File specStoreDir = new File(dir);
if (specStoreDir.exists()) {
FileUtils.deleteDirectory(specStoreDir);
}
}
private TopologySpec initTopologySpec() {
Properties properties = new Properties();
properties.put("specStore.fs.dir", TOPOLOGY_SPEC_STORE_DIR);
properties.put("specExecInstance.capabilities", "source:destination");
Config config = ConfigUtils.propertiesToConfig(properties);
SpecExecutorInstanceProducer specExecutorInstanceProducer = new InMemorySpecExecutorInstanceProducer(config);
TopologySpec.Builder topologySpecBuilder = TopologySpec.builder(computeTopologySpecURI(SPEC_STORE_PARENT_DIR,
TOPOLOGY_SPEC_STORE_DIR))
.withConfig(config)
.withDescription(SPEC_DESCRIPTION)
.withVersion(SPEC_VERSION)
.withSpecExecutorInstanceProducer(specExecutorInstanceProducer);
return topologySpecBuilder.build();
}
private FlowSpec initFlowSpec() {
Properties properties = new Properties();
properties.put("specStore.fs.dir", FLOW_SPEC_STORE_DIR);
properties.put("specExecInstance.capabilities", "source:destination");
properties.put("gobblin.flow.sourceIdentifier", "source");
properties.put("gobblin.flow.destinationIdentifier", "destination");
Config config = ConfigUtils.propertiesToConfig(properties);
SpecExecutorInstanceProducer specExecutorInstanceProducer = new InMemorySpecExecutorInstanceProducer(config);
FlowSpec.Builder flowSpecBuilder = null;
try {
flowSpecBuilder = FlowSpec.builder(computeTopologySpecURI(SPEC_STORE_PARENT_DIR,
FLOW_SPEC_STORE_DIR))
.withConfig(config)
.withDescription(SPEC_DESCRIPTION)
.withVersion(SPEC_VERSION)
.withTemplate(new URI("templateURI"));
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
return flowSpecBuilder.build();
}
public URI computeTopologySpecURI(String parent, String current) {
// Make sure this is relative
URI uri = PathUtils.relativizePath(new Path(current), new Path(parent)).toUri();
return uri;
}
@AfterClass
public void cleanUp() throws Exception {
// Shutdown Catalog
this.serviceLauncher.stop();
File specStoreDir = new File(SPEC_STORE_PARENT_DIR);
if (specStoreDir.exists()) {
FileUtils.deleteDirectory(specStoreDir);
}
}
@Test
public void createTopologySpec() {
IdentityFlowToJobSpecCompiler specCompiler = (IdentityFlowToJobSpecCompiler) this.orchestrator.getSpecCompiler();
// List Current Specs
Collection<Spec> specs = topologyCatalog.getSpecs();
logger.info("[Before Create] Number of specs: " + specs.size());
int i=0;
for (Spec spec : specs) {
TopologySpec topologySpec = (TopologySpec) spec;
logger.info("[Before Create] Spec " + i++ + ": " + gson.toJson(topologySpec));
}
// Make sure TopologyCatalog is empty
Assert.assertTrue(specs.size() == 0, "Spec store should be empty before addition");
// Make sure TopologyCatalog Listener is empty
Assert.assertTrue(specCompiler.getTopologySpecMap().size() == 0, "SpecCompiler should not know about any Topology "
+ "before addition");
// Create and add Spec
this.topologyCatalog.put(topologySpec);
// List Specs after adding
specs = topologyCatalog.getSpecs();
logger.info("[After Create] Number of specs: " + specs.size());
i = 0;
for (Spec spec : specs) {
topologySpec = (TopologySpec) spec;
logger.info("[After Create] Spec " + i++ + ": " + gson.toJson(topologySpec));
}
// Make sure TopologyCatalog has the added Topology
Assert.assertTrue(specs.size() == 1, "Spec store should contain 1 Spec after addition");
// Make sure TopologyCatalog Listener knows about added Topology
Assert.assertTrue(specCompiler.getTopologySpecMap().size() == 1, "SpecCompiler should contain 1 Spec after addition");
}
@Test (dependsOnMethods = "createTopologySpec")
public void createFlowSpec() throws Exception {
// Since only 1 Topology with 1 SpecExecutorInstanceProducer has been added in previous test
// .. it should be available and responsible for our new FlowSpec
IdentityFlowToJobSpecCompiler specCompiler = (IdentityFlowToJobSpecCompiler) this.orchestrator.getSpecCompiler();
SpecExecutorInstanceProducer sei = specCompiler.getTopologySpecMap().values().iterator().next().getSpecExecutorInstanceProducer();
// List Current Specs
Collection<Spec> specs = flowCatalog.getSpecs();
logger.info("[Before Create] Number of specs: " + specs.size());
int i=0;
for (Spec spec : specs) {
FlowSpec flowSpec = (FlowSpec) spec;
logger.info("[Before Create] Spec " + i++ + ": " + gson.toJson(flowSpec));
}
// Make sure FlowCatalog is empty
Assert.assertTrue(specs.size() == 0, "Spec store should be empty before addition");
// Make sure FlowCatalog Listener is empty
Assert.assertTrue(((List)(sei.listSpecs().get())).size() == 0, "SpecExecutorInstanceProducer should not know about "
+ "any Flow before addition");
// Create and add Spec
this.flowCatalog.put(flowSpec);
// List Specs after adding
specs = flowCatalog.getSpecs();
logger.info("[After Create] Number of specs: " + specs.size());
i = 0;
for (Spec spec : specs) {
flowSpec = (FlowSpec) spec;
logger.info("[After Create] Spec " + i++ + ": " + gson.toJson(flowSpec));
}
// Make sure FlowCatalog has the added Flow
Assert.assertTrue(specs.size() == 1, "Spec store should contain 1 Spec after addition");
// Orchestrator is a no-op listener for any new FlowSpecs
Assert.assertTrue(((List)(sei.listSpecs().get())).size() == 0, "SpecExecutorInstanceProducer should contain 0 "
+ "Spec after addition");
}
@Test (dependsOnMethods = "createFlowSpec")
public void deleteFlowSpec() throws Exception {
// Since only 1 Flow has been added in previous test it should be available
IdentityFlowToJobSpecCompiler specCompiler = (IdentityFlowToJobSpecCompiler) this.orchestrator.getSpecCompiler();
SpecExecutorInstanceProducer sei = specCompiler.getTopologySpecMap().values().iterator().next().getSpecExecutorInstanceProducer();
// List Current Specs
Collection<Spec> specs = flowCatalog.getSpecs();
logger.info("[Before Delete] Number of specs: " + specs.size());
int i=0;
for (Spec spec : specs) {
FlowSpec flowSpec = (FlowSpec) spec;
logger.info("[Before Delete] Spec " + i++ + ": " + gson.toJson(flowSpec));
}
// Make sure FlowCatalog has the previously added Flow
Assert.assertTrue(specs.size() == 1, "Spec store should contain 1 Flow that was added in last test");
// Orchestrator is a no-op listener for any new FlowSpecs, so no FlowSpecs should be around
int specsInSEI = ((List)(sei.listSpecs().get())).size();
Assert.assertTrue(specsInSEI == 0, "SpecExecutorInstanceProducer should contain 0 "
+ "Spec after addition because Orchestrator is a no-op listener for any new FlowSpecs");
// Remove the flow
this.flowCatalog.remove(flowSpec.getUri());
// List Specs after adding
specs = flowCatalog.getSpecs();
logger.info("[After Delete] Number of specs: " + specs.size());
i = 0;
for (Spec spec : specs) {
flowSpec = (FlowSpec) spec;
logger.info("[After Delete] Spec " + i++ + ": " + gson.toJson(flowSpec));
}
// Make sure FlowCatalog has the Flow removed
Assert.assertTrue(specs.size() == 0, "Spec store should not contain Spec after deletion");
// Make sure FlowCatalog Listener knows about the deletion
specsInSEI = ((List)(sei.listSpecs().get())).size();
Assert.assertTrue(specsInSEI == 0, "SpecExecutorInstanceProducer should not contain "
+ "Spec after deletion");
}
}