Repository: incubator-slider
Updated Branches:
  refs/heads/develop e23893cdd -> 522983bd9


SLIDER-763. Add install client command to slider


Project: http://git-wip-us.apache.org/repos/asf/incubator-slider/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-slider/commit/522983bd
Tree: http://git-wip-us.apache.org/repos/asf/incubator-slider/tree/522983bd
Diff: http://git-wip-us.apache.org/repos/asf/incubator-slider/diff/522983bd

Branch: refs/heads/develop
Commit: 522983bd9a760110697ccbd93dfd5c29872a1656
Parents: e23893c
Author: Sumit Mohanty <[email protected]>
Authored: Thu Feb 19 13:38:12 2015 -0800
Committer: Sumit Mohanty <[email protected]>
Committed: Thu Feb 19 13:40:51 2015 -0800

----------------------------------------------------------------------
 .../slider-pkg/clientInstallConfig-default.json |   8 +
 .../command-logger/slider-pkg/metainfo.xml      |  10 +
 .../slider-pkg/package/scripts/cl_client.py     |  48 +++
 .../slider-pkg/package/scripts/client_params.py |  29 ++
 .../storm/clientInstallConfig-default.json      |   6 +
 app-packages/storm/metainfo.xml                 |  10 +
 app-packages/storm/package/files/storm-slider   |  63 ++++
 .../storm/package/files/storm-slider.py         | 370 +++++++++++++++++++
 .../storm/package/scripts/client_params.py      |  28 ++
 .../storm/package/scripts/storm_client.py       |  56 +++
 .../package/templates/storm-slider-env.sh.j2    |  38 ++
 app-packages/storm/src/assembly/storm.xml       |   7 +
 .../org/apache/slider/client/SliderClient.java  |  66 +++-
 .../apache/slider/client/SliderClientAPI.java   |  14 +-
 .../slider/common/params/ActionClientArgs.java  |  65 ++++
 .../slider/common/params/ActionPackageArgs.java |   4 +-
 .../apache/slider/common/params/Arguments.java  |   3 +-
 .../apache/slider/common/params/ClientArgs.java |  19 +-
 .../slider/common/params/SliderActions.java     |   3 +
 .../providers/AbstractClientProvider.java       |  20 +
 .../providers/agent/AgentClientProvider.java    | 304 ++++++++++++++-
 .../providers/agent/AgentProviderService.java   |   7 +-
 .../test_command_log/client-config.json         |   7 +
 .../agent/actions/TestActionPackage.groovy      |  20 +-
 .../agent/TestAgentClientProvider2.java         | 173 ++++++++-
 .../lifecycle/AgentClientInstallIT.groovy       |  79 ++++
 .../clusters/remote/slider/slider-client.xml    |  10 +-
 27 files changed, 1434 insertions(+), 33 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-slider/blob/522983bd/app-packages/command-logger/slider-pkg/clientInstallConfig-default.json
----------------------------------------------------------------------
diff --git 
a/app-packages/command-logger/slider-pkg/clientInstallConfig-default.json 
b/app-packages/command-logger/slider-pkg/clientInstallConfig-default.json
new file mode 100644
index 0000000..f0b84a5
--- /dev/null
+++ b/app-packages/command-logger/slider-pkg/clientInstallConfig-default.json
@@ -0,0 +1,8 @@
+{ 
+  "schema":"http://example.org/specification/v2.0.0";, 
+  "global":{
+    "client_root":"{app_install_dir}",
+    "application_id": "application_id_1"
+  }
+}
+

http://git-wip-us.apache.org/repos/asf/incubator-slider/blob/522983bd/app-packages/command-logger/slider-pkg/metainfo.xml
----------------------------------------------------------------------
diff --git a/app-packages/command-logger/slider-pkg/metainfo.xml 
b/app-packages/command-logger/slider-pkg/metainfo.xml
index 5de2c37..eb02f6a 100644
--- a/app-packages/command-logger/slider-pkg/metainfo.xml
+++ b/app-packages/command-logger/slider-pkg/metainfo.xml
@@ -36,6 +36,16 @@
           <timeout>600</timeout>
         </commandScript>
       </component>
+
+      <component>
+        <name>COMMAND_LOGGER_CLIENT</name>
+        <category>CLIENT</category>
+        <commandScript>
+          <script>scripts/cl_client.py</script>
+          <scriptType>PYTHON</scriptType>
+          <timeout>600</timeout>
+        </commandScript>
+      </component>
     </components>
 
     <osSpecifics>

http://git-wip-us.apache.org/repos/asf/incubator-slider/blob/522983bd/app-packages/command-logger/slider-pkg/package/scripts/cl_client.py
----------------------------------------------------------------------
diff --git 
a/app-packages/command-logger/slider-pkg/package/scripts/cl_client.py 
b/app-packages/command-logger/slider-pkg/package/scripts/cl_client.py
new file mode 100644
index 0000000..73a2675
--- /dev/null
+++ b/app-packages/command-logger/slider-pkg/package/scripts/cl_client.py
@@ -0,0 +1,48 @@
+#!/usr/bin/env python
+"""
+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.
+
+"""
+
+import sys
+import os
+from resource_management import *
+
+
+class Client(Script):
+  def install(self, env):
+    import client_params
+    self.install_packages(env)
+    File(format(client_params.client_root + '/operations.log'),
+       mode=0755,
+       content=Template('operations.log.j2', container_id = 
client_params.container_id, application_id = client_params.application_id)
+    )
+
+  def configure(self, env):
+    pass
+
+  def start(self, env):
+    pass
+
+  def stop(self, env):
+    pass
+
+  def status(self, env):
+    pass
+
+if __name__ == "__main__":
+  Client().execute()

http://git-wip-us.apache.org/repos/asf/incubator-slider/blob/522983bd/app-packages/command-logger/slider-pkg/package/scripts/client_params.py
----------------------------------------------------------------------
diff --git 
a/app-packages/command-logger/slider-pkg/package/scripts/client_params.py 
b/app-packages/command-logger/slider-pkg/package/scripts/client_params.py
new file mode 100644
index 0000000..4d5a4ff
--- /dev/null
+++ b/app-packages/command-logger/slider-pkg/package/scripts/client_params.py
@@ -0,0 +1,29 @@
+#!/usr/bin/env python
+"""
+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.
+
+"""
+from resource_management import *
+
+config = Script.get_config()
+
+app_install_dir = config['configurations']['global']['app_install_dir']
+client_root = config['configurations']['global']['client_root']
+
+container_id = config['configurations']['global']['container_id']
+application_id = config['configurations']['global']['application_id']
+

http://git-wip-us.apache.org/repos/asf/incubator-slider/blob/522983bd/app-packages/storm/clientInstallConfig-default.json
----------------------------------------------------------------------
diff --git a/app-packages/storm/clientInstallConfig-default.json 
b/app-packages/storm/clientInstallConfig-default.json
new file mode 100644
index 0000000..898439d
--- /dev/null
+++ b/app-packages/storm/clientInstallConfig-default.json
@@ -0,0 +1,6 @@
+{ 
+  "schema":"http://example.org/specification/v2.0.0";, 
+  "global":{
+    "client_root":"{app_install_dir}/apache-storm-${pkg.version}"
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-slider/blob/522983bd/app-packages/storm/metainfo.xml
----------------------------------------------------------------------
diff --git a/app-packages/storm/metainfo.xml b/app-packages/storm/metainfo.xml
index d2625f4..cc96628 100644
--- a/app-packages/storm/metainfo.xml
+++ b/app-packages/storm/metainfo.xml
@@ -125,6 +125,16 @@
           <timeout>600</timeout>
         </commandScript>
       </component>
+
+      <component>
+        <name>STORM_CLIENT</name>
+        <category>CLIENT</category>
+        <commandScript>
+          <script>scripts/storm_client.py</script>
+          <scriptType>PYTHON</scriptType>
+          <timeout>600</timeout>
+        </commandScript>
+      </component>
     </components>
 
     <osSpecifics>

http://git-wip-us.apache.org/repos/asf/incubator-slider/blob/522983bd/app-packages/storm/package/files/storm-slider
----------------------------------------------------------------------
diff --git a/app-packages/storm/package/files/storm-slider 
b/app-packages/storm/package/files/storm-slider
new file mode 100644
index 0000000..af492bd
--- /dev/null
+++ b/app-packages/storm/package/files/storm-slider
@@ -0,0 +1,63 @@
+#!/bin/bash
+#
+# Copyright 2014 The Apache Software Foundation
+#
+# 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.
+#
+
+# Resolve links - $0 may be a softlink
+PRG="${0}"
+
+while [ -h "${PRG}" ]; do
+  ls=`ls -ld "${PRG}"`
+  link=`expr "$ls" : '.*-> \(.*\)$'`
+  if expr "$link" : '/.*' > /dev/null; then
+    PRG="$link"
+  else
+    PRG=`dirname "${PRG}"`/"$link"
+  fi
+done
+
+# find python >= 2.6
+if [ -a /usr/bin/python2.6 ]; then
+  PYTHON=/usr/bin/python2.6
+fi
+
+if [ -z "$PYTHON" ]; then
+  PYTHON=/usr/bin/python
+fi
+
+# check for version
+majversion=`$PYTHON -V 2>&1 | awk '{print $2}' | cut -d'.' -f1`
+minversion=`$PYTHON -V 2>&1 | awk '{print $2}' | cut -d'.' -f2`
+numversion=$(( 10 * $majversion + $minversion))
+if (( $numversion < 26 )); then
+  echo "Need python version > 2.6"
+  exit 1
+fi
+
+STORM_BIN_DIR=`dirname ${PRG}`
+STORM_BASE_DIR=`cd ${STORM_BIN_DIR}/..;pwd`
+export STORM_BASE_DIR
+
+export STORM_CONF_DIR="${STORM_CONF_DIR:-$STORM_BASE_DIR/conf}"
+
+if [ -f "${STORM_CONF_DIR}/storm-slider-env.sh" ]; then
+  . "${STORM_CONF_DIR}/storm-slider-env.sh"
+fi
+
+$PYTHON ${STORM_BIN_DIR}/storm-slider.py $@

http://git-wip-us.apache.org/repos/asf/incubator-slider/blob/522983bd/app-packages/storm/package/files/storm-slider.py
----------------------------------------------------------------------
diff --git a/app-packages/storm/package/files/storm-slider.py 
b/app-packages/storm/package/files/storm-slider.py
new file mode 100644
index 0000000..cbe441d
--- /dev/null
+++ b/app-packages/storm/package/files/storm-slider.py
@@ -0,0 +1,370 @@
+#!/usr/bin/python
+# 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.
+
+
+import os
+from os.path import expanduser
+import sys, tempfile
+import json
+import subprocess as sub
+import re
+import shlex
+
+def is_windows():
+    return sys.platform.startswith('win')
+
+def identity(x):
+    return x
+
+def cygpath(x):
+    command = ["cygpath", "-wp", x]
+    p = sub.Popen(command,stdout=sub.PIPE)
+    output, errors = p.communicate()
+    lines = output.split("\n")
+    return lines[0]
+
+if sys.platform == "cygwin":
+    normclasspath = cygpath
+else:
+    normclasspath = identity
+
+SLIDER_DIR = os.getenv('SLIDER_HOME', None)
+
+if  SLIDER_DIR == None or (not os.path.exists(SLIDER_DIR)):
+    print "Unable to find SLIDER_HOME. Please configure SLIDER_HOME before 
running storm-slider"
+    sys.exit(1)
+
+USER_CONF_DIR = os.path.expanduser("~/.storm")
+
+if os.getenv('STORM_BASE_DIR', None) != None:
+    STORM_DIR = os.getenv('STORM_BASE_DIR', None)
+elif os.getenv('STORM_HOME', None) != None:
+    STORM_DIR = os.getenv('STORM_HOME', None)
+else:
+    print "Either STORM_BASE_DIR or STORM_HOME must be set."
+    sys.exit(1)
+
+CMD_OPTS = {}
+CONFIG_OPTS = []
+JAR_JVM_OPTS = shlex.split(os.getenv('STORM_JAR_JVM_OPTS', ''))
+pid = os.getpid()
+CONFFILE = os.path.join(tempfile.gettempdir(),"storm."+str(pid)+".json")
+SLIDER_CLIENT_CONF = os.path.join(SLIDER_DIR,'conf','slider-client.xml')
+SLIDER_CMD = os.path.join(SLIDER_DIR,'bin','slider.py')
+JAVA_HOME = os.getenv('JAVA_HOME', None)
+JAVA_CMD= 'java' if not JAVA_HOME else os.path.join(JAVA_HOME, 'bin', 'java')
+if is_windows():
+    JAVA_CMD = JAVA_CMD + '.exe'
+STORM_THRIFT_TRANSPORT_KEY = "storm.thrift.transport"
+NIMBUS_HOST_KEY = "nimbus.host"
+NIMBUS_THRIFT_PORT_KEY = "nimbus.thrift.port"
+
+if not os.path.isfile(JAVA_CMD):
+    print "Unable to find "+JAVA_CMD+" please check JAVA_HOME"
+    sys.exit(1)
+
+def get_config_opts():
+    global CONFIG_OPTS
+    return "-Dstorm.options=" + (','.join(CONFIG_OPTS)).replace(' ', "%%%%")
+
+def get_jars_full(adir):
+    files = os.listdir(adir)
+    ret = []
+    for f in files:
+        if f.endswith(".jar"):
+            ret.append(os.path.join(adir, f))
+    return ret
+
+def get_classpath(extrajars):
+    ret = (get_jars_full(os.path.join(STORM_DIR ,"lib")))
+    ret.extend(extrajars)
+
+    sep = ";" if is_windows() else ":"
+    return normclasspath(sep.join(ret))
+
+def print_remoteconfvalue(name):
+    """Syntax: [storm-slider --app remoteconfvalue conf-name]
+
+    Prints out the value for conf-name in the cluster's Storm configs.
+    This command must be run on a cluster machine.
+    """
+    storm_conf = storm_conf_values([name])
+    for conf in storm_conf:
+        print conf
+
+def parse_args(string):
+    r"""Takes a string of whitespace-separated tokens and parses it into a 
list.
+    Whitespace inside tokens may be quoted with single quotes, double quotes or
+    backslash (similar to command-line arguments in bash).
+
+    >>> parse_args(r'''"a a" 'b b' c\ c "d'd" 'e"e' 'f\'f' "g\"g" "i""i" 
'j''j' k" "k l' l' mm n\\n''')
+    ['a a', 'b b', 'c c', "d'd", 'e"e', "f'f", 'g"g', 'ii', 'jj', 'k k', 'l 
l', 'mm', r'n\n']
+    """
+    re_split = re.compile(r'''((?:
+        [^\s"'\\] |
+        "(?: [^"\\] | \\.)*" |
+        '(?: [^'\\] | \\.)*' |
+        \\.
+    )+)''', re.VERBOSE)
+    args = re_split.split(string)[1::2]
+    args = [re.compile(r'"((?:[^"\\]|\\.)*)"').sub('\\1', x) for x in args]
+    args = [re.compile(r"'((?:[^'\\]|\\.)*)'").sub('\\1', x) for x in args]
+    return [re.compile(r'\\(.)').sub('\\1', x) for x in args]
+
+def exec_storm_class(klass, jvmtype="-server", jvmopts=[], extrajars=[], 
args=[], fork=False):
+    global CONFFILE
+    storm_log_dir = os.path.join(STORM_DIR, "logs")
+    all_args = [
+        "java", jvmtype, get_config_opts(),
+        "-Dstorm.home=" + STORM_DIR,
+        "-cp", get_classpath(extrajars),
+    ] + jvmopts + [klass] + list(args)
+    print "Running: " + " ".join(all_args)
+    if is_windows():
+        sub.call([JAVA_CMD] + all_args[1:])
+    else:
+        os.execvp(JAVA_CMD, all_args) # replaces the current process and never 
returns
+
+def jar(jarfile, klass, *args):
+    """Syntax: [storm-slider --app jar topology-jar-path class ...]
+
+    Runs the main method of class with the specified arguments.
+    The storm jars and configs in ~/.storm are put on the classpath.
+    The process is configured so that StormSubmitter
+    (http://nathanmarz.github.com/storm/doc/backtype/storm/StormSubmitter.html)
+    will upload the jar at topology-jar-path when the topology is submitted.
+    """
+    exec_storm_class(
+        klass,
+        jvmtype="-client",
+        extrajars=[jarfile, USER_CONF_DIR, os.path.join(STORM_DIR ,"bin")],
+        args=args,
+        jvmopts=JAR_JVM_OPTS + ["-Dstorm.jar=" + jarfile])
+
+def kill(*args):
+    """Syntax: [storm-slider --app kill topology-name [-w wait-time-secs]]
+
+    Kills the topology with the name topology-name. Storm will
+    first deactivate the topology's spouts for the duration of
+    the topology's message timeout to allow all messages currently
+    being processed to finish processing. Storm will then shutdown
+    the workers and clean up their state. You can override the length
+    of time Storm waits between deactivation and shutdown with the -w flag.
+    """
+    exec_storm_class(
+        "backtype.storm.command.kill_topology",
+        args=args,
+        jvmtype="-client",
+        extrajars=[USER_CONF_DIR, os.path.join(STORM_DIR , "bin")])
+
+def activate(*args):
+    """Syntax: [storm-slider --app activate topology-name]
+
+    Activates the specified topology's spouts.
+    """
+    exec_storm_class(
+        "backtype.storm.command.activate",
+        args=args,
+        jvmtype="-client",
+        extrajars=[USER_CONF_DIR, os.path.join(STORM_DIR , "bin")])
+
+def listtopos(*args):
+    """Syntax: [storm-slider --app list]
+
+    List the running topologies and their statuses.
+    """
+    exec_storm_class(
+        "backtype.storm.command.list",
+        args=args,
+        jvmtype="-client",
+        extrajars=[USER_CONF_DIR, os.path.join(STORM_DIR , "bin")])
+
+def deactivate(*args):
+    """Syntax: [storm-slider --app deactivate topology-name]
+
+    Deactivates the specified topology's spouts.
+    """
+    exec_storm_class(
+        "backtype.storm.command.deactivate",
+        args=args,
+        jvmtype="-client",
+        extrajars=[USER_CONF_DIR, os.path.join(STORM_DIR , "bin")])
+
+def rebalance(*args):
+    """Syntax: [storm-slider --app rebalance topology-name [-w wait-time-secs] 
[-n new-num-workers] [-e component=parallelism]*]
+
+    Sometimes you may wish to spread out where the workers for a topology
+    are running. For example, let's say you have a 10 node cluster running
+    4 workers per node, and then let's say you add another 10 nodes to
+    the cluster. You may wish to have Storm spread out the workers for the
+    running topology so that each node runs 2 workers. One way to do this
+    is to kill the topology and resubmit it, but Storm provides a "rebalance"
+    command that provides an easier way to do this.
+
+    Rebalance will first deactivate the topology for the duration of the
+    message timeout (overridable with the -w flag) and then redistribute
+    the workers evenly around the cluster. The topology will then return to
+    its previous state of activation (so a deactivated topology will still
+    be deactivated and an activated topology will go back to being activated).
+
+    The rebalance command can also be used to change the parallelism of a 
running topology.
+    Use the -n and -e switches to change the number of workers or number of 
executors of a component
+    respectively.
+    """
+    exec_storm_class(
+        "backtype.storm.command.rebalance",
+        args=args,
+        jvmtype="-client",
+        extrajars=[USER_CONF_DIR, os.path.join(STORM_DIR,"bin")])
+
+def version():
+    """Syntax: [storm-slider --app version]
+    Prints the version number of this Storm release.
+    """
+    releasefile = os.path.join(STORM_DIR, "RELEASE")
+    if os.path.exists(releasefile):
+        print open(releasefile).readline().strip()
+    else:
+        print "Unknown"
+
+def quicklinks():
+    """Syntax: [storm-slider --app  version]
+    Prints the quicklinks information of storm-slider registry"
+    """
+    global CMD_OPTS
+    all_args = ["slider", "registry", "--getconf", "quicklinks","--format", 
"json", "--name"]
+    if 'app_name' in CMD_OPTS.keys():
+        all_args.append(CMD_OPTS['app_name'])
+    else:
+        print_usage()
+        sys.exit(1)
+
+    if 'user' in CMD_OPTS.keys():
+        all_args.append( "--user "+CMD_OPTS['user'])
+
+    #os.spawnvp(os.P_WAIT,SLIDER_CMD, all_args)
+    cmd = [SLIDER_CMD] + all_args[1:]
+    if is_windows():
+        cmd = ['python'] + cmd
+
+    sub.call(cmd)
+
+def get_storm_config_json():
+    global CMD_OPTS
+    all_args = ["slider.py", "registry", "--getconf", "storm-site","--format", 
"json", "--dest", CONFFILE, "--name"]
+    if 'app_name' in CMD_OPTS.keys():
+       all_args.append(CMD_OPTS['app_name'])
+    else:
+        print_usage()
+        sys.exit(1)
+
+    if 'user' in CMD_OPTS.keys():
+        all_args.append( "--user "+CMD_OPTS['user'])
+
+    #os.spawnvp(os.P_WAIT,SLIDER_CMD, all_args)
+    cmd = [SLIDER_CMD] + all_args[1:]
+
+    if is_windows():
+        cmd = ['python'] + cmd
+
+    sub.call(cmd)
+    if not os.path.exists(CONFFILE):
+        print "Failed to read slider deployed storm config"
+        sys.exit(1)
+
+def storm_conf_values(keys):
+    file = open(CONFFILE,"r")
+    data = json.load(file)
+    storm_args = []
+    for key in keys:
+        if data.has_key(key):
+            storm_args.append(key+"="+data[key])
+    return storm_args
+
+def print_commands():
+    """Print all client commands and link to documentation"""
+    print "Commands:\n\t",  "\n\t".join(sorted(COMMANDS.keys()))
+    print "\nHelp:", "\n\thelp", "\n\thelp <command>"
+
+def print_usage(command=None):
+    """Print one help message or list of available commands"""
+    if command != None:
+        if COMMANDS.has_key(command):
+            print (COMMANDS[command].__doc__ or
+                   "No documentation provided for <%s>" % command)
+        else:
+            print "<%s> is not a valid command" % command
+    else:
+        print "Please provide yarn app name followed by command"
+        print "storm-slider --app --user"
+        print_commands()
+
+
+def unknown_command(*args):
+    print "Unknown command: [storm-slider %s]" % ' '.join(sys.argv[1:])
+    print_usage()
+
+COMMANDS = {"jar": jar, "kill": kill, "remoteconfvalue": print_remoteconfvalue,
+            "activate": activate, "deactivate": deactivate, "rebalance": 
rebalance, "help": print_usage,
+            "list": listtopos, "version": version, "quicklinks" : quicklinks}
+
+def parse_config(config_list):
+    global CONFIG_OPTS
+    if len(config_list) > 0:
+        for config in config_list:
+            CONFIG_OPTS.append(config)
+
+def parse_config_opts(args):
+    curr = args[:]
+    curr.reverse()
+    global CMD_OPTS
+    global CONFIG_OPTS
+    args_list = []
+    while len(curr) > 0:
+        token = curr.pop()
+        if token == "--app":
+            CMD_OPTS['app_name'] = curr.pop() if (len(curr) != 0) else None
+        elif token == "--user":
+            CMD_OPTS['user'] =  curr.pop() if (len(curr) != 0) else None
+        elif token == "-c" and len(curr) != 0:
+            CONFIG_OPTS.append(curr.pop())
+        else:
+            args_list.append(token)
+    return args_list
+
+def main():
+    args = parse_config_opts(sys.argv[1:])
+    if len(args) < 1:
+        print_usage()
+        sys.exit(-1)
+    COMMAND = args[0]
+    ARGS = args[1:]
+    if (COMMAND != 'help'):
+        get_storm_config_json()
+        storm_conf = 
storm_conf_values([NIMBUS_HOST_KEY,NIMBUS_THRIFT_PORT_KEY,STORM_THRIFT_TRANSPORT_KEY])
+        parse_config(storm_conf)
+    try:
+        (COMMANDS.get(COMMAND, unknown_command))(*ARGS)
+    except:
+        print "Unexpected error:", sys.exc_info()[0]
+        raise
+    finally:
+        if (os.path.isfile(CONFFILE)):
+            os.remove(CONFFILE)
+
+if __name__ == "__main__":
+    main()

http://git-wip-us.apache.org/repos/asf/incubator-slider/blob/522983bd/app-packages/storm/package/scripts/client_params.py
----------------------------------------------------------------------
diff --git a/app-packages/storm/package/scripts/client_params.py 
b/app-packages/storm/package/scripts/client_params.py
new file mode 100644
index 0000000..924a202
--- /dev/null
+++ b/app-packages/storm/package/scripts/client_params.py
@@ -0,0 +1,28 @@
+#!/usr/bin/env python
+"""
+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.
+
+"""
+from resource_management import *
+
+config = Script.get_config()
+
+app_install_dir = config['configurations']['global']['app_install_dir']
+client_root = config['configurations']['global']['client_root']
+
+slider_home_dir = config['configurations']['global']['slider_home_dir']
+java64_home = config['configurations']['global']['java64_home']

http://git-wip-us.apache.org/repos/asf/incubator-slider/blob/522983bd/app-packages/storm/package/scripts/storm_client.py
----------------------------------------------------------------------
diff --git a/app-packages/storm/package/scripts/storm_client.py 
b/app-packages/storm/package/scripts/storm_client.py
new file mode 100644
index 0000000..6d36edd
--- /dev/null
+++ b/app-packages/storm/package/scripts/storm_client.py
@@ -0,0 +1,56 @@
+#!/usr/bin/env python
+"""
+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.
+
+"""
+
+import sys
+from resource_management import *
+from storm import storm
+from service import service
+
+class Client(Script):
+  def install(self, env):
+    import client_params
+    self.install_packages(env)
+    File(format(client_params.client_root + '/bin/storm-slider'),
+       content=StaticFile("storm-slider"),
+       mode=0755
+    )
+    File(format(client_params.client_root + '/bin/storm-slider.py'),
+       content=StaticFile("storm-slider.py"),
+       mode=0755
+    )
+    File(format(client_params.client_root + '/conf/storm-slider-env.sh'),
+       mode=0755,
+       content=Template('storm-slider-env.sh.j2', java64_home = 
client_params.java64_home, slider_home_dir = client_params.slider_home_dir)
+    )
+
+  def configure(self, env):
+    pass
+
+  def start(self, env):
+    pass
+
+  def stop(self, env):
+    pass
+
+  def status(self, env):
+    pass
+
+if __name__ == "__main__":
+  Client().execute()

http://git-wip-us.apache.org/repos/asf/incubator-slider/blob/522983bd/app-packages/storm/package/templates/storm-slider-env.sh.j2
----------------------------------------------------------------------
diff --git a/app-packages/storm/package/templates/storm-slider-env.sh.j2 
b/app-packages/storm/package/templates/storm-slider-env.sh.j2
new file mode 100644
index 0000000..8022a4b
--- /dev/null
+++ b/app-packages/storm/package/templates/storm-slider-env.sh.j2
@@ -0,0 +1,38 @@
+{#
+# 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.
+#}
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#/*
+# * 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.
+# */
+export JAVA_HOME={{java64_home}}
+export SLIDER_HOME={{slider_home_dir}}

http://git-wip-us.apache.org/repos/asf/incubator-slider/blob/522983bd/app-packages/storm/src/assembly/storm.xml
----------------------------------------------------------------------
diff --git a/app-packages/storm/src/assembly/storm.xml 
b/app-packages/storm/src/assembly/storm.xml
index f7dcf13..6a6e750 100644
--- a/app-packages/storm/src/assembly/storm.xml
+++ b/app-packages/storm/src/assembly/storm.xml
@@ -42,6 +42,12 @@
       <fileMode>0755</fileMode>
     </file>
     <file>
+      <source>clientInstall-default.json</source>
+      <outputDirectory>/</outputDirectory>
+      <filtered>true</filtered>
+      <fileMode>0755</fileMode>
+    </file>
+    <file>
       <source>metainfo.xml</source>
       <outputDirectory>/</outputDirectory>
       <filtered>true</filtered>
@@ -65,6 +71,7 @@
         <exclude>target/**</exclude>
         <exclude>appConfig-default.json</exclude>
         <exclude>appConfig-secured-default.json</exclude>
+        <exclude>clientInstall-default.json</exclude>
         <exclude>metainfo.xml</exclude>
       </excludes>
       <fileMode>0755</fileMode>

http://git-wip-us.apache.org/repos/asf/incubator-slider/blob/522983bd/slider-core/src/main/java/org/apache/slider/client/SliderClient.java
----------------------------------------------------------------------
diff --git 
a/slider-core/src/main/java/org/apache/slider/client/SliderClient.java 
b/slider-core/src/main/java/org/apache/slider/client/SliderClient.java
index 5cbe945..77b5e96 100644
--- a/slider-core/src/main/java/org/apache/slider/client/SliderClient.java
+++ b/slider-core/src/main/java/org/apache/slider/client/SliderClient.java
@@ -78,6 +78,7 @@ import org.apache.slider.common.SliderKeys;
 import org.apache.slider.common.SliderXmlConfKeys;
 import org.apache.slider.common.params.AbstractActionArgs;
 import org.apache.slider.common.params.AbstractClusterBuildingActionArgs;
+import org.apache.slider.common.params.ActionClientArgs;
 import org.apache.slider.common.params.ActionDiagnosticArgs;
 import org.apache.slider.common.params.ActionExistsArgs;
 import org.apache.slider.common.params.ActionInstallKeytabArgs;
@@ -172,6 +173,8 @@ import java.io.StringWriter;
 import java.io.Writer;
 import java.net.InetSocketAddress;
 import java.net.URISyntaxException;
+import java.nio.charset.Charset;
+import java.nio.file.Files;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -204,7 +207,7 @@ public class SliderClient extends 
AbstractSliderLaunchedService implements RunSe
   
   private String deployedClusterName;
   /**
-   * Cluster opaerations against the deployed cluster -will be null
+   * Cluster operations against the deployed cluster -will be null
    * if no bonding has yet taken place
    */
   private SliderClusterOperations sliderClusterOperations;
@@ -385,6 +388,8 @@ public class SliderClient extends 
AbstractSliderLaunchedService implements RunSe
     // actions
     if (ACTION_PACKAGE.equals(action)) {
       exitCode = actionPackage(serviceArgs.getActionPackageArgs());
+    } else if (ACTION_CLIENT.equals(action)) {
+      exitCode = actionClient(serviceArgs.getActionClientArgs());
     } else if (ACTION_INSTALL_PACKAGE.equals(action)) {
       exitCode = actionInstallPkg(serviceArgs.getActionInstallPackageArgs());
     } else if (ACTION_INSTALL_KEYTAB.equals(action)) {
@@ -869,6 +874,65 @@ public class SliderClient extends 
AbstractSliderLaunchedService implements RunSe
   }
 
   @Override
+  public int actionClient(ActionClientArgs clientInfo) throws
+      SliderException,
+      IOException {
+
+    if(!clientInfo.install) {
+      throw new BadCommandArgumentsException(
+          "Only install command is supported for the client.\n"
+          + CommonArgs.usage(serviceArgs, ACTION_CLIENT));
+    }
+
+    if (clientInfo.installLocation == null) {
+      throw new BadCommandArgumentsException(
+          "A valid install location must be provided for the client.\n"
+          + CommonArgs.usage(serviceArgs, ACTION_CLIENT));
+    } else {
+      if (!clientInfo.installLocation.exists()) {
+        throw new BadCommandArgumentsException("Install path does not exist at 
" +
+                                               
clientInfo.installLocation.getAbsolutePath());
+      }
+      if (!clientInfo.installLocation.isDirectory()) {
+        throw new BadCommandArgumentsException("Install path is not a valid 
directory " +
+                                               
clientInfo.installLocation.getAbsolutePath());
+      }
+    }
+
+    File pkgFile;
+    if (StringUtils.isEmpty(clientInfo.packageURI)) {
+      throw new BadCommandArgumentsException("A valid application package 
location required.");
+    } else {
+      pkgFile = new File(clientInfo.packageURI);
+      if (!pkgFile.exists() || pkgFile.isDirectory()) {
+        throw new BadCommandArgumentsException("Unable to access supplied pkg 
file at " +
+                                               pkgFile.getAbsolutePath());
+      }
+    }
+
+    JSONObject config = null;
+    if(clientInfo.clientConfig != null) {
+      try {
+        byte[] encoded = Files.readAllBytes(clientInfo.clientConfig.toPath());
+        config = new JSONObject(new String(encoded, Charset.defaultCharset()));
+      }catch(JSONException jsonEx) {
+        log.error("Unable to read supplied config", jsonEx);
+        throw new SliderException("Invalid configuration. Must be a valid json 
file.", jsonEx);
+      }
+    }
+
+    // Only INSTALL is supported
+    AbstractClientProvider provider = 
createClientProvider(SliderProviderFactory.DEFAULT_CLUSTER_TYPE);
+    provider.processClientOperation(sliderFileSystem,
+                                    "INSTALL",
+                                    clientInfo.installLocation,
+                                    pkgFile,
+                                    config);
+    return EXIT_SUCCESS;
+  }
+
+
+  @Override
   public int actionPackage(ActionPackageArgs actionPackageInfo)
       throws YarnException, IOException {
     if (actionPackageInfo.install) {

http://git-wip-us.apache.org/repos/asf/incubator-slider/blob/522983bd/slider-core/src/main/java/org/apache/slider/client/SliderClientAPI.java
----------------------------------------------------------------------
diff --git 
a/slider-core/src/main/java/org/apache/slider/client/SliderClientAPI.java 
b/slider-core/src/main/java/org/apache/slider/client/SliderClientAPI.java
index 4fd4136..efb1f7f 100644
--- a/slider-core/src/main/java/org/apache/slider/client/SliderClientAPI.java
+++ b/slider-core/src/main/java/org/apache/slider/client/SliderClientAPI.java
@@ -26,6 +26,7 @@ import org.apache.hadoop.yarn.exceptions.YarnException;
 import org.apache.slider.api.types.SliderInstanceDescription;
 import org.apache.slider.common.params.AbstractClusterBuildingActionArgs;
 import org.apache.slider.common.params.ActionAMSuicideArgs;
+import org.apache.slider.common.params.ActionClientArgs;
 import org.apache.slider.common.params.ActionDiagnosticArgs;
 import org.apache.slider.common.params.ActionEchoArgs;
 import org.apache.slider.common.params.ActionFlexArgs;
@@ -125,6 +126,17 @@ public interface SliderClientAPI extends Service {
       throws YarnException, IOException;
 
   /**
+   * Perform client operations such as install or configure
+   *
+   * @param clientInfo the arguments needed for client operations
+   *
+   * @throws SliderException bad arguments.
+   * @throws IOException problems related to package and destination folders
+   */
+  int actionClient(ActionClientArgs clientInfo)
+      throws IOException, SliderException;
+
+  /**
    * Managing slider application package
    *
    * @param pkgInfo the arguments needed to upload, delete or list the package
@@ -283,7 +295,7 @@ public interface SliderClientAPI extends Service {
   /**
    * diagnostic operation
    *
-   * @param diagosticArgs diagnostic Arguments
+   * @param diagnosticArgs diagnostic Arguments
    * @return 0 for success, -1 for some issues that aren't errors, just
    *         failures to retrieve information (e.g. no application name
    *         specified)

http://git-wip-us.apache.org/repos/asf/incubator-slider/blob/522983bd/slider-core/src/main/java/org/apache/slider/common/params/ActionClientArgs.java
----------------------------------------------------------------------
diff --git 
a/slider-core/src/main/java/org/apache/slider/common/params/ActionClientArgs.java
 
b/slider-core/src/main/java/org/apache/slider/common/params/ActionClientArgs.java
new file mode 100644
index 0000000..4154c9f
--- /dev/null
+++ 
b/slider-core/src/main/java/org/apache/slider/common/params/ActionClientArgs.java
@@ -0,0 +1,65 @@
+/*
+ * 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.slider.common.params;
+
+import com.beust.jcommander.Parameter;
+import com.beust.jcommander.Parameters;
+
+import java.io.File;
+
+@Parameters(commandNames = {SliderActions.ACTION_CLIENT},
+    commandDescription = SliderActions.DESCRIBE_ACTION_CLIENT)
+
+public class ActionClientArgs extends AbstractActionArgs {
+
+  @Override
+  public String getActionName() {
+    return SliderActions.ACTION_CLIENT;
+  }
+
+  @Parameter(names = {ARG_INSTALL},
+      description = "Install client")
+  public boolean install;
+
+  @Parameter(names = {ARG_PACKAGE},
+      description = "Path to app package")
+  public String packageURI;
+
+  @Parameter(names = {ARG_DEST},
+      description = "The location where to install the client")
+  public File installLocation;
+
+  @Parameter(names = {ARG_CONFIG},
+      description = "Client configuration")
+  public File clientConfig;
+
+  /**
+   * Get the min #of params expected
+   *
+   * @return the min number of params in the {@link #parameters} field
+   */
+  public int getMinParams() {
+    return 0;
+  }
+
+  @Override
+  public int getMaxParams() {
+    return 1;
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-slider/blob/522983bd/slider-core/src/main/java/org/apache/slider/common/params/ActionPackageArgs.java
----------------------------------------------------------------------
diff --git 
a/slider-core/src/main/java/org/apache/slider/common/params/ActionPackageArgs.java
 
b/slider-core/src/main/java/org/apache/slider/common/params/ActionPackageArgs.java
index d5bd293..e98aba1 100644
--- 
a/slider-core/src/main/java/org/apache/slider/common/params/ActionPackageArgs.java
+++ 
b/slider-core/src/main/java/org/apache/slider/common/params/ActionPackageArgs.java
@@ -22,7 +22,7 @@ import com.beust.jcommander.Parameter;
 import com.beust.jcommander.Parameters;
 
 @Parameters(commandNames = {SliderActions.ACTION_PACKAGE},
-            commandDescription = SliderActions.DESCRIBE_ACTION_INSTALL_PACKAGE)
+            commandDescription = SliderActions.DESCRIBE_ACTION_PACKAGE)
 
 public class ActionPackageArgs extends AbstractActionArgs {
 
@@ -31,7 +31,7 @@ public class ActionPackageArgs extends AbstractActionArgs {
     return SliderActions.ACTION_PACKAGE;
   }
 
-  @Parameter(names = {ARG_PKGINSTALL},
+  @Parameter(names = {ARG_INSTALL},
       description = "Install package operation")
   public boolean install;
 

http://git-wip-us.apache.org/repos/asf/incubator-slider/blob/522983bd/slider-core/src/main/java/org/apache/slider/common/params/Arguments.java
----------------------------------------------------------------------
diff --git 
a/slider-core/src/main/java/org/apache/slider/common/params/Arguments.java 
b/slider-core/src/main/java/org/apache/slider/common/params/Arguments.java
index 511d01e..789b285 100644
--- a/slider-core/src/main/java/org/apache/slider/common/params/Arguments.java
+++ b/slider-core/src/main/java/org/apache/slider/common/params/Arguments.java
@@ -81,7 +81,7 @@ public interface Arguments {
   String ARG_PACKAGE = "--package";
   String ARG_PATH = "--path";
   String ARG_PKGDELETE = "--delete";
-  String ARG_PKGINSTALL = "--install";
+  String ARG_INSTALL = "--install";
   String ARG_PKGINSTANCES = "--instances";
   String ARG_PKGLIST = "--list";
   String ARG_PROVIDER = "--provider";
@@ -105,6 +105,7 @@ public interface Arguments {
   String ARG_ZKHOSTS = "--zkhosts";
   String ARG_ZKPATH = "--zkpath";
   String ARG_ZKPORT = "--zkport";
+  String ARG_CONFIG = "--config";
 
 
   /**

http://git-wip-us.apache.org/repos/asf/incubator-slider/blob/522983bd/slider-core/src/main/java/org/apache/slider/common/params/ClientArgs.java
----------------------------------------------------------------------
diff --git 
a/slider-core/src/main/java/org/apache/slider/common/params/ClientArgs.java 
b/slider-core/src/main/java/org/apache/slider/common/params/ClientArgs.java
index fc466a1..fcc1b81 100644
--- a/slider-core/src/main/java/org/apache/slider/common/params/ClientArgs.java
+++ b/slider-core/src/main/java/org/apache/slider/common/params/ClientArgs.java
@@ -58,6 +58,7 @@ public class ClientArgs extends CommonArgs {
   private final ActionHelpArgs actionHelpArgs = new ActionHelpArgs();
   private final ActionInstallPackageArgs actionInstallPackageArgs = new 
ActionInstallPackageArgs();
   private final ActionPackageArgs actionPackageArgs = new ActionPackageArgs();
+  private final ActionClientArgs actionClientArgs = new ActionClientArgs();
   private final ActionInstallKeytabArgs actionInstallKeytabArgs = new 
ActionInstallKeytabArgs();
   private final ActionKeytabArgs actionKeytabArgs = new ActionKeytabArgs();
   private final ActionKillContainerArgs actionKillContainerArgs =
@@ -96,6 +97,7 @@ public class ClientArgs extends CommonArgs {
         actionHelpArgs,
         actionInstallPackageArgs,
         actionPackageArgs,
+        actionClientArgs,
         actionInstallKeytabArgs,
         actionKeytabArgs,
         actionKillContainerArgs,
@@ -141,17 +143,15 @@ public class ClientArgs extends CommonArgs {
     return actionBuildArgs;
   }
 
-  public ActionInstallPackageArgs getActionInstallPackageArgs() {
-    return actionInstallPackageArgs; }
+  public ActionInstallPackageArgs getActionInstallPackageArgs() { return 
actionInstallPackageArgs; }
 
-  public ActionPackageArgs getActionPackageArgs() {
-    return actionPackageArgs; }
+  public ActionClientArgs getActionClientArgs() { return actionClientArgs; }
 
-  public ActionInstallKeytabArgs getActionInstallKeytabArgs() {
-    return actionInstallKeytabArgs; }
+  public ActionPackageArgs getActionPackageArgs() { return actionPackageArgs; }
 
-  public ActionKeytabArgs getActionKeytabArgs() {
-    return actionKeytabArgs; }
+  public ActionInstallKeytabArgs getActionInstallKeytabArgs() { return 
actionInstallKeytabArgs; }
+
+  public ActionKeytabArgs getActionKeytabArgs() { return actionKeytabArgs; }
 
   public ActionUpdateArgs getActionUpdateArgs() {
     return actionUpdateArgs;
@@ -257,6 +257,9 @@ public class ClientArgs extends CommonArgs {
     } else if (SliderActions.ACTION_PACKAGE.equals(action)) {
       bindCoreAction(actionPackageArgs);
 
+    } else if (SliderActions.ACTION_CLIENT.equals(action)) {
+      bindCoreAction(actionClientArgs);
+
     } else if (SliderActions.ACTION_INSTALL_KEYTAB.equals(action)) {
       bindCoreAction(actionInstallKeytabArgs);
 

http://git-wip-us.apache.org/repos/asf/incubator-slider/blob/522983bd/slider-core/src/main/java/org/apache/slider/common/params/SliderActions.java
----------------------------------------------------------------------
diff --git 
a/slider-core/src/main/java/org/apache/slider/common/params/SliderActions.java 
b/slider-core/src/main/java/org/apache/slider/common/params/SliderActions.java
index e58ad2d..2599927 100644
--- 
a/slider-core/src/main/java/org/apache/slider/common/params/SliderActions.java
+++ 
b/slider-core/src/main/java/org/apache/slider/common/params/SliderActions.java
@@ -48,6 +48,7 @@ public interface SliderActions {
   String ACTION_INSTALL_PACKAGE = "install-package";
   String ACTION_PACKAGE = "package";
   String ACTION_INSTALL_KEYTAB = "install-keytab";
+  String ACTION_CLIENT = "client";
   String ACTION_KEYTAB = "keytab";
   String DESCRIBE_ACTION_AM_SUICIDE =
     "Tell the Slider Application Master to simulate a process failure by 
terminating itself";
@@ -86,6 +87,8 @@ public interface SliderActions {
   String DESCRIBE_ACTION_VERSION =
                         "Print the Slider version information";
   String DESCRIBE_ACTION_INSTALL_PACKAGE = "Install the application package in 
the home directory under sub-folder packages";
+  String DESCRIBE_ACTION_PACKAGE = "Install/list/delete application packages 
and list app instances that use this package";
+  String DESCRIBE_ACTION_CLIENT = "Install the application client in the 
specified directory";
   String DESCRIBE_ACTION_INSTALL_KEYTAB = "Install the Kerberos keytab file in 
the sub-folder 'keytabs' of the user's Slider base directory";
   String DESCRIBE_ACTION_KEYTAB = "Manage a Kerberos keytab file (install, 
delete, list) in the sub-folder 'keytabs' of the user's Slider base directory";
   String DESCRIBE_ACTION_DIAGNOSTIC = "Diagnose the configuration of the 
running slider application and slider client";

http://git-wip-us.apache.org/repos/asf/incubator-slider/blob/522983bd/slider-core/src/main/java/org/apache/slider/providers/AbstractClientProvider.java
----------------------------------------------------------------------
diff --git 
a/slider-core/src/main/java/org/apache/slider/providers/AbstractClientProvider.java
 
b/slider-core/src/main/java/org/apache/slider/providers/AbstractClientProvider.java
index f89f842..b5aafd4 100644
--- 
a/slider-core/src/main/java/org/apache/slider/providers/AbstractClientProvider.java
+++ 
b/slider-core/src/main/java/org/apache/slider/providers/AbstractClientProvider.java
@@ -28,9 +28,11 @@ import org.apache.slider.core.conf.MapOperations;
 import org.apache.slider.core.exceptions.BadClusterStateException;
 import org.apache.slider.core.exceptions.SliderException;
 import org.apache.slider.core.launch.AbstractLauncher;
+import org.codehaus.jettison.json.JSONObject;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.io.File;
 import java.io.IOException;
 import java.util.Collections;
 import java.util.List;
@@ -218,4 +220,22 @@ public abstract class AbstractClientProvider extends 
Configured {
     return Collections.emptySet();
   }
 
+  /**
+   * Process client operations for applications such as install, configure
+   * @param fileSystem
+   * @param operation
+   * @param config
+   * @param clientPackage
+   * @param clientInstallPath
+   * @throws SliderException
+   */
+  public void processClientOperation(SliderFileSystem fileSystem,
+                                     String operation,
+                                     File clientInstallPath,
+                                     File clientPackage,
+                                     JSONObject config)
+      throws SliderException {
+    throw new SliderException("Provider does not support client operations.");
+  }
+
 }

http://git-wip-us.apache.org/repos/asf/incubator-slider/blob/522983bd/slider-core/src/main/java/org/apache/slider/providers/agent/AgentClientProvider.java
----------------------------------------------------------------------
diff --git 
a/slider-core/src/main/java/org/apache/slider/providers/agent/AgentClientProvider.java
 
b/slider-core/src/main/java/org/apache/slider/providers/agent/AgentClientProvider.java
index 0ef8a33..94d4c97 100644
--- 
a/slider-core/src/main/java/org/apache/slider/providers/agent/AgentClientProvider.java
+++ 
b/slider-core/src/main/java/org/apache/slider/providers/agent/AgentClientProvider.java
@@ -18,7 +18,12 @@
 
 package org.apache.slider.providers.agent;
 
+import com.google.common.io.Files;
+import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
+import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
+import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream;
 import org.apache.commons.lang.StringUtils;
+import org.apache.commons.lang.exception.ExceptionUtils;
 import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.fs.Path;
 import org.apache.slider.api.InternalKeys;
@@ -38,16 +43,34 @@ import org.apache.slider.providers.ProviderUtils;
 import org.apache.slider.providers.agent.application.metadata.Application;
 import org.apache.slider.providers.agent.application.metadata.Component;
 import org.apache.slider.providers.agent.application.metadata.Metainfo;
+import org.apache.slider.providers.agent.application.metadata.MetainfoParser;
+import org.codehaus.jettison.json.JSONException;
+import org.codehaus.jettison.json.JSONObject;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.FileWriter;
 import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.charset.Charset;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
 
 /** This class implements  the client-side aspects of the agent deployer */
 public class AgentClientProvider extends AbstractClientProvider
@@ -229,7 +252,7 @@ public class AgentClientProvider extends 
AbstractClientProvider
       Path agentPath = new Path(tempPath.getParent(), 
AgentKeys.PROVIDER_AGENT);
       log.info("Automatically uploading the agent tarball at {}", agentPath);
       fileSystem.getFileSystem().mkdirs(agentPath);
-      if(ProviderUtils.addAgentTar(this, AGENT_TAR, fileSystem, agentPath)) {
+      if (ProviderUtils.addAgentTar(this, AGENT_TAR, fileSystem, agentPath)) {
         instanceDefinition.getInternalOperations().set(
             InternalKeys.INTERNAL_APPLICATION_IMAGE_PATH,
             new Path(agentPath, AGENT_TAR).toUri());
@@ -249,7 +272,7 @@ public class AgentClientProvider extends 
AbstractClientProvider
       throw new SliderException("Error retrieving metainfo", e);
     }
 
-    if(metainfo == null) {
+    if (metainfo == null) {
       log.error("Error retrieving metainfo from {}", appDef);
       throw new SliderException("Error parsing metainfo file, possibly bad 
structure.");
     }
@@ -262,4 +285,281 @@ public class AgentClientProvider extends 
AbstractClientProvider
 
     return tags;
   }
+
+  @Override
+  public void processClientOperation(SliderFileSystem fileSystem,
+                                     String operation,
+                                     File clientInstallPath,
+                                     File appPackage,
+                                     JSONObject config) throws SliderException 
{
+    // create temp folder
+    // create sub-folders app_pkg, agent_pkg, command
+    File tmpDir = Files.createTempDir();
+    log.info("Command is being executed at {}", tmpDir.getAbsolutePath());
+    File appPkgDir = new File(tmpDir, "app_pkg");
+    appPkgDir.mkdir();
+
+    File agentPkgDir = new File(tmpDir, "agent_pkg");
+    agentPkgDir.mkdir();
+
+    File cmdDir = new File(tmpDir, "command");
+    cmdDir.mkdir();
+
+    Metainfo metaInfo = null;
+    JSONObject defaultConfig = null;
+    try {
+      // expand app package into /app_pkg
+      ZipInputStream zipInputStream = null;
+      try {
+        zipInputStream = new ZipInputStream(new FileInputStream(appPackage));
+        {
+          ZipEntry zipEntry = zipInputStream.getNextEntry();
+          while (zipEntry != null) {
+            if ("metainfo.xml".equals(zipEntry.getName())) {
+              int size = (int) zipEntry.getSize();
+              if (size != -1) {
+                log.info("Reading {} of size {}", zipEntry.getName(),
+                         zipEntry.getSize());
+                byte[] content = new byte[size];
+                int offset = 0;
+                while (offset < size) {
+                  offset += zipInputStream.read(content, offset, size - 
offset);
+                }
+                metaInfo = new MetainfoParser().parse(new 
ByteArrayInputStream(content));
+              }
+            } else if 
("clientInstallConfig-default.json".equals(zipEntry.getName())) {
+              int size = (int) zipEntry.getSize();
+              if (size != -1) {
+                log.info("Reading {} of size {}", zipEntry.getName(),
+                         zipEntry.getSize());
+                byte[] content = new byte[size];
+                int offset = 0;
+                while (offset < size) {
+                  offset += zipInputStream.read(content, offset, size - 
offset);
+                }
+                try {
+                  defaultConfig = new JSONObject(new String(content, 
Charset.defaultCharset()));
+                } catch (JSONException jex) {
+                  throw new SliderException("Unable to read default client 
config.", jex);
+                }
+              }
+            }
+            String filePath = appPkgDir + File.separator + zipEntry.getName();
+            if (!zipEntry.isDirectory()) {
+              extractFile(zipInputStream, filePath);
+            } else {
+              File dir = new File(filePath);
+              dir.mkdir();
+            }
+            zipInputStream.closeEntry();
+            zipEntry = zipInputStream.getNextEntry();
+          }
+        }
+      } finally {
+        zipInputStream.close();
+      }
+
+      if (metaInfo == null) {
+        throw new SliderException("Not a valid app package. Could not read 
metainfo.xml.");
+      }
+
+      expandAgentTar(agentPkgDir);
+
+      JSONObject commandJson = getCommandJson(defaultConfig, config, metaInfo, 
clientInstallPath);
+      FileWriter file = new FileWriter(new File(cmdDir, "command.json"));
+      try {
+        file.write(commandJson.toString());
+
+      } catch (IOException e) {
+        e.printStackTrace();
+      } finally {
+        file.flush();
+        file.close();
+      }
+
+      String client_script = null;
+      for (Component component : metaInfo.getApplication().getComponents()) {
+        if (component.getCategory().equals("CLIENT")) {
+          client_script = component.getCommandScript().getScript();
+          log.info("Installing CLIENT {} using script {}", 
component.getName(), client_script);
+          break;
+        }
+      }
+
+      if (SliderUtils.isUnset(client_script)) {
+        throw new SliderException("No valid CLIENT component found. Aborting 
install.");
+      }
+
+      runCommand(appPkgDir, agentPkgDir, cmdDir, client_script);
+
+    } catch (IOException ioex) {
+      log.warn("Error while executing INSTALL command {}", ioex.getMessage());
+      throw new SliderException("INSTALL client failed.");
+    }
+  }
+
+  protected void runCommand(
+      File appPkgDir,
+      File agentPkgDir,
+      File cmdDir,
+      String clientScript) throws SliderException {
+    int exitCode = 0;
+    Exception exp = null;
+    try {
+      String clientScriptPath = appPkgDir.getAbsolutePath() + File.separator + 
"package" +
+                                File.separator + clientScript;
+      List<String> command = Arrays.asList(AgentKeys.PYTHON_EXE,
+                                           "-S",
+                                           clientScriptPath,
+                                           "INSTALL",
+                                           cmdDir.getAbsolutePath() + 
File.separator + "command.json",
+                                           appPkgDir.getAbsolutePath() + 
File.separator + "package",
+                                           cmdDir.getAbsolutePath() + 
File.separator + "command-out.json",
+                                           "DEBUG");
+      ProcessBuilder pb = new ProcessBuilder(command);
+      log.info("Command: " + StringUtils.join(pb.command(), " "));
+      pb.environment().put(SliderKeys.PYTHONPATH,
+                           agentPkgDir.getAbsolutePath()
+                           + File.separator + "slider-agent:"
+                           + agentPkgDir.getAbsolutePath()
+                           + File.separator + "slider-agent/jinja2");
+      log.info("{}={}", SliderKeys.PYTHONPATH, 
pb.environment().get(SliderKeys.PYTHONPATH));
+
+      Process proc = pb.start();
+      InputStream stderr = proc.getErrorStream();
+      InputStream stdout = proc.getInputStream();
+      BufferedReader stdOutReader = new BufferedReader(new 
InputStreamReader(stdout));
+      BufferedReader stdErrReader = new BufferedReader(new 
InputStreamReader(stderr));
+
+      proc.waitFor();
+
+      String line;
+      while ((line = stdOutReader.readLine()) != null) {
+        log.info("Stdout: " + line);
+      }
+      while ((line = stdErrReader.readLine()) != null) {
+        log.info("Stderr: " + line);
+      }
+
+      exitCode = proc.exitValue();
+      log.info("Exit value is {}", exitCode);
+    } catch (IOException e) {
+      exp = e;
+    } catch (InterruptedException e) {
+      exp = e;
+    }
+
+    if (exitCode != 0) {
+      throw new SliderException("INSTALL client failed with exit code " + 
exitCode);
+    }
+
+    if (exp != null) {
+      log.error("Error while executing INSTALL command {}. Stack trace {}",
+                exp.getMessage(),
+                ExceptionUtils.getStackTrace(exp));
+      throw new SliderException("INSTALL client failed.", exp);
+    }
+  }
+
+  private void expandAgentTar(File agentPkgDir) throws IOException {
+    String libDirProp =
+        System.getProperty(SliderKeys.PROPERTY_LIB_DIR);
+    File tarFile = new File(libDirProp, SliderKeys.AGENT_TAR);
+    TarArchiveInputStream tarIn = new TarArchiveInputStream(
+        new GzipCompressorInputStream(
+            new BufferedInputStream(
+                new FileInputStream(tarFile)
+            )
+        )
+    );
+    try {
+      TarArchiveEntry tarEntry = tarIn.getNextTarEntry();
+      while (tarEntry != null) {
+        File destPath = new File(agentPkgDir, tarEntry.getName());
+        if (tarEntry.isDirectory()) {
+          destPath.mkdirs();
+        } else {
+          destPath.createNewFile();
+          byte[] byteToRead = new byte[1024];
+          BufferedOutputStream buffOut =
+              new BufferedOutputStream(new FileOutputStream(destPath));
+          try {
+            int len;
+            while ((len = tarIn.read(byteToRead)) != -1) {
+              buffOut.write(byteToRead, 0, len);
+            }
+          } finally {
+            buffOut.close();
+          }
+        }
+        tarEntry = tarIn.getNextTarEntry();
+      }
+    } finally {
+      tarIn.close();
+    }
+  }
+
+  protected JSONObject getCommandJson(JSONObject defaultConfig,
+                                      JSONObject inputConfig,
+                                      Metainfo metainfo,
+                                      File clientInstallPath) throws 
SliderException {
+    try {
+      JSONObject pkgList = new JSONObject();
+      pkgList.put(AgentKeys.PACKAGE_LIST,
+                  
AgentProviderService.getPackageListFromApplication(metainfo.getApplication()));
+      JSONObject obj = new JSONObject();
+      obj.put("hostLevelParams", pkgList);
+
+      JSONObject configuration = new JSONObject();
+      JSONObject global = new JSONObject();
+      global.put("app_install_dir", clientInstallPath.getAbsolutePath());
+
+      if (defaultConfig != null) {
+        readConfigEntries(defaultConfig, clientInstallPath, global);
+      }
+      if (inputConfig != null) {
+        readConfigEntries(inputConfig, clientInstallPath, global);
+      }
+
+      configuration.put("global", global);
+      obj.put("configurations", configuration);
+      return obj;
+    } catch (JSONException jex) {
+      log.warn("Error while executing INSTALL command {}", jex.getMessage());
+      throw new SliderException("INSTALL client failed.");
+    }
+  }
+
+  private void readConfigEntries(JSONObject inpConfig,
+                                 File clientInstallPath,
+                                 JSONObject globalConfig)
+      throws JSONException {
+    JSONObject globalSection = inpConfig.getJSONObject("global");
+    Iterator it = globalSection.keys();
+    while (it.hasNext()) {
+      String key = (String) it.next();
+      String value = globalSection.getString(key);
+      if (SliderUtils.isSet(value)) {
+        value = value.replace("{app_install_dir}", 
clientInstallPath.getAbsolutePath());
+      }
+      if (globalConfig.has(key)) {
+        // last one wins
+        globalConfig.remove(key);
+      }
+      globalConfig.put(key, value);
+    }
+  }
+
+  private void extractFile(ZipInputStream zipInputStream, String filePath) 
throws IOException {
+    BufferedOutputStream output = new BufferedOutputStream(new 
FileOutputStream(filePath));
+    try {
+      byte[] bytesRead = new byte[4096];
+      int read = 0;
+      while ((read = zipInputStream.read(bytesRead)) != -1) {
+        output.write(bytesRead, 0, read);
+      }
+    } finally {
+      output.close();
+    }
+  }
 }

http://git-wip-us.apache.org/repos/asf/incubator-slider/blob/522983bd/slider-core/src/main/java/org/apache/slider/providers/agent/AgentProviderService.java
----------------------------------------------------------------------
diff --git 
a/slider-core/src/main/java/org/apache/slider/providers/agent/AgentProviderService.java
 
b/slider-core/src/main/java/org/apache/slider/providers/agent/AgentProviderService.java
index 0730beb..60236a5 100644
--- 
a/slider-core/src/main/java/org/apache/slider/providers/agent/AgentProviderService.java
+++ 
b/slider-core/src/main/java/org/apache/slider/providers/agent/AgentProviderService.java
@@ -1663,11 +1663,10 @@ public class AgentProviderService extends 
AbstractProviderService implements
     response.addExecutionCommand(cmd);
   }
 
-  private String getPackageList() {
+  protected static String getPackageListFromApplication(Application 
application) {
     String pkgFormatString = "{\"type\":\"%s\",\"name\":\"%s\"}";
     String pkgListFormatString = "[%s]";
     List<String> packages = new ArrayList();
-    Application application = getMetainfo().getApplication();
     if (application != null) {
       List<OSSpecific> osSpecifics = application.getOSSpecifics();
       if (osSpecifics != null && osSpecifics.size() > 0) {
@@ -1688,6 +1687,10 @@ public class AgentProviderService extends 
AbstractProviderService implements
     }
   }
 
+  private String getPackageList() {
+    return getPackageListFromApplication(getMetainfo().getApplication());
+  }
+
   private void prepareExecutionCommand(ExecutionCommand cmd) {
     cmd.setTaskId(taskId.incrementAndGet());
     cmd.setCommandId(cmd.getTaskId() + "-1");

http://git-wip-us.apache.org/repos/asf/incubator-slider/blob/522983bd/slider-core/src/test/app_packages/test_command_log/client-config.json
----------------------------------------------------------------------
diff --git 
a/slider-core/src/test/app_packages/test_command_log/client-config.json 
b/slider-core/src/test/app_packages/test_command_log/client-config.json
new file mode 100644
index 0000000..c4cdac6
--- /dev/null
+++ b/slider-core/src/test/app_packages/test_command_log/client-config.json
@@ -0,0 +1,7 @@
+{
+  "schema": "http://example.org/specification/v2.0.0";,
+  "global": {
+    "java_home": "/usr/jdk64/jdk1.7.0_67",
+    "container_id": "cid_1"
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-slider/blob/522983bd/slider-core/src/test/groovy/org/apache/slider/agent/actions/TestActionPackage.groovy
----------------------------------------------------------------------
diff --git 
a/slider-core/src/test/groovy/org/apache/slider/agent/actions/TestActionPackage.groovy
 
b/slider-core/src/test/groovy/org/apache/slider/agent/actions/TestActionPackage.groovy
index b0b832a..fa6145e 100644
--- 
a/slider-core/src/test/groovy/org/apache/slider/agent/actions/TestActionPackage.groovy
+++ 
b/slider-core/src/test/groovy/org/apache/slider/agent/actions/TestActionPackage.groovy
@@ -52,7 +52,7 @@ class TestActionPackage extends AgentMiniClusterTestBase {
           //varargs list of command line params
           [
               SliderActions.ACTION_PACKAGE,
-              Arguments.ARG_PKGINSTALL
+              Arguments.ARG_INSTALL
           ],
       )
       fail("expected an exception, got a status code " + 
launcher.serviceExitCode)
@@ -70,7 +70,7 @@ class TestActionPackage extends AgentMiniClusterTestBase {
           //varargs list of command line params
           [
               SliderActions.ACTION_PACKAGE,
-              Arguments.ARG_PKGINSTALL,
+              Arguments.ARG_INSTALL,
               Arguments.ARG_NAME, "hbase"
           ],
       )
@@ -89,7 +89,7 @@ class TestActionPackage extends AgentMiniClusterTestBase {
           //varargs list of command line params
           [
               SliderActions.ACTION_PACKAGE,
-              Arguments.ARG_PKGINSTALL,
+              Arguments.ARG_INSTALL,
               Arguments.ARG_NAME, "hbase",
               Arguments.ARG_PACKAGE, "src/test/resources/log4j.properties",
           ],
@@ -100,7 +100,7 @@ class TestActionPackage extends AgentMiniClusterTestBase {
           //varargs list of command line params
           [
               SliderActions.ACTION_PACKAGE,
-              Arguments.ARG_PKGINSTALL,
+              Arguments.ARG_INSTALL,
               Arguments.ARG_NAME, "hbase",
               Arguments.ARG_PACKAGE, "src/test/resources/log4j.properties",
           ],
@@ -120,7 +120,7 @@ class TestActionPackage extends AgentMiniClusterTestBase {
           //varargs list of command line params
           [
               SliderActions.ACTION_PACKAGE,
-              Arguments.ARG_PKGINSTALL,
+              Arguments.ARG_INSTALL,
               Arguments.ARG_NAME, "hbase",
               Arguments.ARG_PACKAGE, "unlikely_to_be_a_file_path",
           ],
@@ -140,7 +140,7 @@ class TestActionPackage extends AgentMiniClusterTestBase {
           //varargs list of command line params
           [
               SliderActions.ACTION_PACKAGE,
-              Arguments.ARG_PKGINSTALL,
+              Arguments.ARG_INSTALL,
               Arguments.ARG_NAME, "hbase",
               Arguments.ARG_PACKAGE, "src/test/resources/log4j.properties",
           ],
@@ -151,7 +151,7 @@ class TestActionPackage extends AgentMiniClusterTestBase {
           //varargs list of command line params
           [
               SliderActions.ACTION_PACKAGE,
-              Arguments.ARG_PKGINSTALL,
+              Arguments.ARG_INSTALL,
               Arguments.ARG_NAME, "hbase",
               Arguments.ARG_PACKAGE, "src/test/resources/log4j.properties",
               Arguments.ARG_REPLACE_PKG
@@ -171,7 +171,7 @@ class TestActionPackage extends AgentMiniClusterTestBase {
           //varargs list of command line params
           [
               SliderActions.ACTION_PACKAGE,
-              Arguments.ARG_PKGINSTALL,
+              Arguments.ARG_INSTALL,
               Arguments.ARG_PKGLIST
           ],
       )
@@ -189,7 +189,7 @@ class TestActionPackage extends AgentMiniClusterTestBase {
           //varargs list of command line params
           [
               SliderActions.ACTION_PACKAGE,
-              Arguments.ARG_PKGINSTALL,
+              Arguments.ARG_INSTALL,
               Arguments.ARG_PKGINSTANCES
           ],
       )
@@ -207,7 +207,7 @@ class TestActionPackage extends AgentMiniClusterTestBase {
           //varargs list of command line params
           [
               SliderActions.ACTION_PACKAGE,
-              Arguments.ARG_PKGINSTALL,
+              Arguments.ARG_INSTALL,
               Arguments.ARG_NAME, "storm",
               Arguments.ARG_PACKAGE, "src/test/resources/log4j.properties",
           ],

http://git-wip-us.apache.org/repos/asf/incubator-slider/blob/522983bd/slider-core/src/test/java/org/apache/slider/providers/agent/TestAgentClientProvider2.java
----------------------------------------------------------------------
diff --git 
a/slider-core/src/test/java/org/apache/slider/providers/agent/TestAgentClientProvider2.java
 
b/slider-core/src/test/java/org/apache/slider/providers/agent/TestAgentClientProvider2.java
index 0ff613a..1e4d834 100644
--- 
a/slider-core/src/test/java/org/apache/slider/providers/agent/TestAgentClientProvider2.java
+++ 
b/slider-core/src/test/java/org/apache/slider/providers/agent/TestAgentClientProvider2.java
@@ -16,15 +16,27 @@
  */
 package org.apache.slider.providers.agent;
 
+import org.apache.commons.io.IOUtils;
 import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.fs.FileSystem;
 import org.apache.hadoop.fs.Path;
+import org.apache.log4j.BasicConfigurator;
 import org.apache.slider.api.InternalKeys;
+import org.apache.slider.client.SliderClient;
+import org.apache.slider.common.params.ActionClientArgs;
 import org.apache.slider.common.tools.SliderFileSystem;
 import org.apache.slider.core.conf.AggregateConf;
 import org.apache.slider.core.conf.ConfTree;
+import org.apache.slider.core.exceptions.BadCommandArgumentsException;
+import org.apache.slider.core.exceptions.SliderException;
 import org.apache.slider.providers.ProviderUtils;
+import org.apache.slider.providers.agent.application.metadata.Application;
+import org.apache.slider.providers.agent.application.metadata.Metainfo;
+import org.apache.slider.providers.agent.application.metadata.OSPackage;
+import org.apache.slider.providers.agent.application.metadata.OSSpecific;
+import org.codehaus.jettison.json.JSONObject;
 import org.junit.Assert;
+import org.junit.BeforeClass;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TemporaryFolder;
@@ -35,6 +47,12 @@ import org.powermock.modules.junit4.PowerMockRunner;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+
 import static org.easymock.EasyMock.anyObject;
 import static org.easymock.EasyMock.expect;
 
@@ -42,13 +60,19 @@ import static org.easymock.EasyMock.expect;
  *
  */
 @RunWith(PowerMockRunner.class)
-@PrepareForTest(ProviderUtils.class)
+@PrepareForTest({ProviderUtils.class, ProcessBuilder.class, 
AgentClientProvider.class})
 public class TestAgentClientProvider2 {
   protected static final Logger log =
       LoggerFactory.getLogger(TestAgentClientProvider2.class);
   @Rule
   public TemporaryFolder folder = new TemporaryFolder();
 
+  @BeforeClass
+  public static void initialize() {
+    BasicConfigurator.resetConfiguration();
+    BasicConfigurator.configure();
+  }
+
   @Test
   public void testPrepareAMAndConfigForLaunch() throws Exception {
     AgentClientProvider provider = new AgentClientProvider(null);
@@ -85,4 +109,151 @@ public class TestAgentClientProvider2 {
     PowerMock.verify(sfs, fs, ProviderUtils.class);
     
Assert.assertTrue(tree.global.containsKey(InternalKeys.INTERNAL_APPLICATION_IMAGE_PATH));
   }
+
+  @Test
+  public void testGetCommandJson() throws Exception {
+    AgentClientProvider provider = new AgentClientProvider(null);
+    JSONObject defaultConfig = null;
+
+    JSONObject inputConfig = new JSONObject();
+    JSONObject global = new JSONObject();
+    global.put("a", "b");
+    global.put("d", "{app_install_dir}/d");
+    inputConfig.put("global", global);
+
+    Metainfo metainfo = new Metainfo();
+    Application app = new Application();
+    metainfo.setApplication(app);
+    OSSpecific osSpecific = new OSSpecific();
+    osSpecific.setOsType("any");
+    app.addOSSpecific(osSpecific);
+    OSPackage pkg = new OSPackage();
+    osSpecific.addOSPackage(pkg);
+    pkg.setName("app.tar");
+    pkg.setType("tarball");
+
+    File clientInstallPath = new File("/tmp/file1");
+
+    JSONObject output = provider.getCommandJson(defaultConfig,
+                                                inputConfig,
+                                                metainfo,
+                                                clientInstallPath);
+    JSONObject outConfigs = output.getJSONObject("configurations");
+    Assert.assertNotNull(outConfigs);
+    JSONObject outGlobal = outConfigs.getJSONObject("global");
+    Assert.assertNotNull(outGlobal);
+    Assert.assertEquals("b", outGlobal.getString("a"));
+    Assert.assertEquals("/tmp/file1/d", outGlobal.getString("d"));
+
+    defaultConfig = new JSONObject();
+    global = new JSONObject();
+    global.put("a1", "b2");
+    global.put("a", "b-not");
+    global.put("d1", "{app_install_dir}/d");
+    defaultConfig.put("global", global);
+
+    output = provider.getCommandJson(defaultConfig,
+                                     inputConfig,
+                                     metainfo,
+                                     clientInstallPath);
+    outConfigs = output.getJSONObject("configurations");
+    Assert.assertNotNull(outConfigs);
+    outGlobal = outConfigs.getJSONObject("global");
+    Assert.assertNotNull(outGlobal);
+    Assert.assertEquals("b", outGlobal.getString("a"));
+    Assert.assertEquals("/tmp/file1/d", outGlobal.getString("d"));
+    Assert.assertEquals("b2", outGlobal.getString("a1"));
+    Assert.assertEquals("/tmp/file1/d", outGlobal.getString("d1"));
+  }
+
+
+  @Test
+  public void testRunCommand() throws Exception {
+    AgentClientProvider provider = new AgentClientProvider(null);
+    File appPkgDir = new File("/tmp/pkg");
+    File agentPkgDir = new File("/tmp/agt");
+    File cmdDir = new File("/tmp/cmd");
+    String client_script = "scripts/abc.py";
+
+    List<String> commands =
+        Arrays.asList("python", "-S", "/tmp/pkg/package/scripts/abc.py", 
"INSTALL", "/tmp/cmd/command.json",
+                      "/tmp/pkg/package", "/tmp/cmd/command-out.json", 
"DEBUG");
+    ProcessBuilder pbMock = PowerMock.createMock(ProcessBuilder.class);
+    Process procMock = PowerMock.createMock(Process.class);
+    PowerMock.expectNew(ProcessBuilder.class, commands).andReturn(pbMock);
+
+    expect(pbMock.environment()).andReturn(new HashMap<String, 
String>()).anyTimes();
+    expect(pbMock.start()).andReturn(procMock);
+    expect(pbMock.command()).andReturn(new ArrayList<String>());
+    expect(procMock.waitFor()).andReturn(0);
+    expect(procMock.exitValue()).andReturn(0);
+    
expect(procMock.getErrorStream()).andReturn(IOUtils.toInputStream("stderr", 
"UTF-8"));
+    
expect(procMock.getInputStream()).andReturn(IOUtils.toInputStream("stdout", 
"UTF-8"));
+
+    PowerMock.replayAll();
+
+    provider.runCommand(appPkgDir,
+                        agentPkgDir,
+                        cmdDir,
+                        client_script);
+    PowerMock.verifyAll();
+  }
+
+  @Test
+  public void testSliderClientForInstallFailures() throws Exception {
+    SliderClient client = new SliderClient();
+    client.bindArgs(new Configuration(), "client", "--dest", 
"a_random_path/none", "--package", "a_random_pkg.zip");
+    ActionClientArgs args = new ActionClientArgs();
+    args.install = false;
+    try {
+      client.actionClient(args);
+    }catch(BadCommandArgumentsException e) {
+      log.info(e.getMessage());
+      Assert.assertTrue(e.getMessage().contains("Only install command is 
supported for the client"));
+    }
+
+    args.install = true;
+    try {
+      client.actionClient(args);
+    }catch(BadCommandArgumentsException e) {
+      log.info(e.getMessage());
+      Assert.assertTrue(e.getMessage().contains("A valid install location must 
be provided for the client"));
+    }
+
+    File tmpFile = File.createTempFile("del", "");
+    File dest = new File(tmpFile.getParentFile(), tmpFile.getName() + "dir");
+    args.installLocation = dest;
+    try {
+      client.actionClient(args);
+    }catch(BadCommandArgumentsException e) {
+      log.info(e.getMessage());
+      Assert.assertTrue(e.getMessage().contains("Install path does not exist 
at"));
+    }
+
+    dest.mkdir();
+    try {
+      client.actionClient(args);
+    }catch(BadCommandArgumentsException e) {
+      log.info(e.getMessage());
+      Assert.assertTrue(e.getMessage().contains("A valid application package 
location required"));
+    }
+
+    tmpFile = File.createTempFile("del", ".zip");
+    args.packageURI = tmpFile.toString();
+    args.clientConfig = tmpFile;
+    try {
+      client.actionClient(args);
+    }catch(SliderException e) {
+      log.info(e.getMessage());
+      Assert.assertTrue(e.getMessage().contains("Invalid configuration. Must 
be a valid json file"));
+    }
+
+    args.clientConfig = null;
+    try {
+      client.actionClient(args);
+    }catch(SliderException e) {
+      log.info(e.getMessage());
+      Assert.assertTrue(e.getMessage().contains("Not a valid app package. 
Could not read metainfo.xml"));
+    }
+  }
 }

http://git-wip-us.apache.org/repos/asf/incubator-slider/blob/522983bd/slider-funtest/src/test/groovy/org/apache/slider/funtest/lifecycle/AgentClientInstallIT.groovy
----------------------------------------------------------------------
diff --git 
a/slider-funtest/src/test/groovy/org/apache/slider/funtest/lifecycle/AgentClientInstallIT.groovy
 
b/slider-funtest/src/test/groovy/org/apache/slider/funtest/lifecycle/AgentClientInstallIT.groovy
new file mode 100644
index 0000000..ef75821
--- /dev/null
+++ 
b/slider-funtest/src/test/groovy/org/apache/slider/funtest/lifecycle/AgentClientInstallIT.groovy
@@ -0,0 +1,79 @@
+/*
+ * 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.slider.funtest.lifecycle
+
+import groovy.io.FileType
+import groovy.transform.CompileStatic
+import groovy.util.logging.Slf4j
+import org.apache.hadoop.yarn.api.records.YarnApplicationState
+import org.apache.slider.api.ClusterDescription
+import org.apache.slider.api.StatusKeys
+import org.apache.slider.client.SliderClient
+import org.apache.slider.common.SliderExitCodes
+import org.apache.slider.common.SliderXmlConfKeys
+import org.apache.slider.common.params.Arguments
+import org.apache.slider.common.params.SliderActions
+import org.apache.slider.funtest.framework.AgentCommandTestBase
+import org.apache.slider.funtest.framework.FuntestProperties
+import org.apache.slider.funtest.framework.SliderShell
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+
+@CompileStatic
+@Slf4j
+public class AgentClientInstallIT extends AgentCommandTestBase
+  implements FuntestProperties, Arguments, SliderExitCodes, SliderActions {
+
+
+  @Test
+  public void testAgentClientInstall() throws Throwable {
+
+    describe "Install command logger client"
+    File zipFileName = new File(TEST_APP_PKG_DIR, 
TEST_APP_PKG_FILE).canonicalFile
+    File tmpFile = File.createTempFile("del", "");
+    File dest = new File(tmpFile.getParentFile(), tmpFile.getName() + "dir");
+    String CLIENT_CONFIG =
+        
"../slider-core/src/test/app_packages/test_command_log/client-config.json"
+
+    dest.mkdir()
+
+    SliderShell shell = slider(EXIT_SUCCESS,
+        [
+            ACTION_CLIENT,
+            "--install",
+            ARG_DEST, dest.canonicalPath,
+            ARG_PACKAGE, zipFileName.absolutePath,
+            ARG_CONFIG, CLIENT_CONFIG
+        ])
+    logShell(shell)
+
+    def list = []
+
+    dest.eachFileRecurse (FileType.FILES) { file ->
+      list << file.toString()
+    }
+
+    String expectedFile1 = new File(dest, "operations.log").toString();
+    String expectedFile2 = new File(new File(dest, "command-logger-app"), 
"operations.log").toString();
+
+    assert list.contains(expectedFile1)
+    assert list.contains(expectedFile2)
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-slider/blob/522983bd/src/test/clusters/remote/slider/slider-client.xml
----------------------------------------------------------------------
diff --git a/src/test/clusters/remote/slider/slider-client.xml 
b/src/test/clusters/remote/slider/slider-client.xml
index f8d88eb..41dc32e 100644
--- a/src/test/clusters/remote/slider/slider-client.xml
+++ b/src/test/clusters/remote/slider/slider-client.xml
@@ -31,22 +31,22 @@
 
   <property>
     <name>slider.zookeeper.quorum</name>
-    <value>c6403.ambari.apache.org:2181</value>
+    <value>c6401.ambari.apache.org:2181</value>
   </property>
 
   <property>
     <name>yarn.resourcemanager.address</name>
-    <value>c6403.ambari.apache.org:8050</value>
+    <value>c6401.ambari.apache.org:8050</value>
   </property>
 
   <property>
     <name>yarn.resourcemanager.scheduler.address</name>
-    <value>c6403.ambari.apache.org:8030</value>
+    <value>c6401.ambari.apache.org:8030</value>
   </property>
 
   <property>
     <name>fs.defaultFS</name>
-    <value>hdfs://c6403.ambari.apache.org:8020</value>
+    <value>hdfs://c6401.ambari.apache.org:8020</value>
   </property>
 
   <property>
@@ -74,7 +74,7 @@
 
   <property>
     <name>slider.test.agent.tar</name>
-    
<value>hdfs://c6403.ambari.apache.org:8020/slider/agent/slider-agent.tar.gz</value>
+    
<value>hdfs://c6401.ambari.apache.org:8020/slider/agent/slider-agent.tar.gz</value>
   </property>
 
 </configuration>

Reply via email to