/**
   * Main method for launching a {@link TwillContainerService} which runs a {@link
   * org.apache.twill.api.TwillRunnable}.
   */
  public static void main(final String[] args) throws Exception {
    // Try to load the secure store from localized file, which AM requested RM to localize it for
    // this container.
    loadSecureStore();

    String zkConnectStr = System.getenv(EnvKeys.TWILL_ZK_CONNECT);
    File twillSpecFile = new File(Constants.Files.TWILL_SPEC);
    RunId appRunId = RunIds.fromString(System.getenv(EnvKeys.TWILL_APP_RUN_ID));
    RunId runId = RunIds.fromString(System.getenv(EnvKeys.TWILL_RUN_ID));
    String runnableName = System.getenv(EnvKeys.TWILL_RUNNABLE_NAME);
    int instanceId = Integer.parseInt(System.getenv(EnvKeys.TWILL_INSTANCE_ID));
    int instanceCount = Integer.parseInt(System.getenv(EnvKeys.TWILL_INSTANCE_COUNT));

    ZKClientService zkClientService = createZKClient(zkConnectStr);
    ZKDiscoveryService discoveryService = new ZKDiscoveryService(zkClientService);

    ZKClient appRunZkClient = getAppRunZKClient(zkClientService, appRunId);

    TwillSpecification twillSpec = loadTwillSpec(twillSpecFile);
    renameLocalFiles(twillSpec.getRunnables().get(runnableName));

    TwillRunnableSpecification runnableSpec =
        twillSpec.getRunnables().get(runnableName).getRunnableSpecification();
    ContainerInfo containerInfo = new EnvContainerInfo();
    Arguments arguments = decodeArgs();
    BasicTwillContext context =
        new BasicTwillContext(
            runId,
            appRunId,
            containerInfo.getHost(),
            arguments.getRunnableArguments().get(runnableName).toArray(new String[0]),
            arguments.getArguments().toArray(new String[0]),
            runnableSpec,
            instanceId,
            discoveryService,
            discoveryService,
            appRunZkClient,
            instanceCount,
            containerInfo.getMemoryMB(),
            containerInfo.getVirtualCores());

    ZKClient containerZKClient = getContainerZKClient(zkClientService, appRunId, runnableName);
    Configuration conf = new YarnConfiguration(new HdfsConfiguration(new Configuration()));
    Service service =
        new TwillContainerService(
            context,
            containerInfo,
            containerZKClient,
            runId,
            runnableSpec,
            getClassLoader(),
            createAppLocation(conf));
    new TwillContainerMain()
        .doMain(
            service,
            new LogFlushService(),
            zkClientService,
            new TwillZKPathService(containerZKClient, runId));
  }
  @Test
  public void testStateTransition()
      throws InterruptedException, ExecutionException, TimeoutException {
    InMemoryZKServer zkServer = InMemoryZKServer.builder().build();
    zkServer.startAndWait();

    try {
      final String namespace =
          Joiner.on('/').join("/twill", RunIds.generate(), "runnables", "Runner1");

      final ZKClientService zkClient =
          ZKClientService.Builder.of(zkServer.getConnectionStr()).build();
      zkClient.startAndWait();
      zkClient.create(namespace, null, CreateMode.PERSISTENT).get();

      try {
        JsonObject content = new JsonObject();
        content.addProperty("containerId", "container-123");
        content.addProperty("host", "localhost");

        RunId runId = RunIds.generate();
        final Semaphore semaphore = new Semaphore(0);
        ZKServiceDecorator service =
            new ZKServiceDecorator(
                ZKClients.namespace(zkClient, namespace),
                runId,
                Suppliers.ofInstance(content),
                new AbstractIdleService() {
                  @Override
                  protected void startUp() throws Exception {
                    Preconditions.checkArgument(
                        semaphore.tryAcquire(5, TimeUnit.SECONDS), "Fail to start");
                  }

                  @Override
                  protected void shutDown() throws Exception {
                    Preconditions.checkArgument(
                        semaphore.tryAcquire(5, TimeUnit.SECONDS), "Fail to stop");
                  }
                });

        final String runnablePath = namespace + "/" + runId.getId();
        final AtomicReference<String> stateMatch = new AtomicReference<String>("STARTING");
        watchDataChange(zkClient, runnablePath + "/state", semaphore, stateMatch);
        Assert.assertEquals(Service.State.RUNNING, service.start().get(5, TimeUnit.SECONDS));

        stateMatch.set("STOPPING");
        Assert.assertEquals(Service.State.TERMINATED, service.stop().get(5, TimeUnit.SECONDS));

      } finally {
        zkClient.stopAndWait();
      }
    } finally {
      zkServer.stopAndWait();
    }
  }