diff --git a/README.rst b/README.rst
index f3d4a9af..f62d9010 100644
--- a/README.rst
+++ b/README.rst
@@ -7,12 +7,11 @@ Team and repository tags
 
 .. Change things from this point on
 
-Python bindings to the Senlin Clustering API
-============================================
+OpenStackClient Plugin for Senlin Clustering Service
+====================================================
 
 This is a client library for Senlin built on the Senlin clustering API. It
-provides a Python API (the ``senlinclient`` module) and a command-line tool
-(``senlin``).
+provides a plugin for the openstackclient command-line tool.
 
 Development takes place via the usual OpenStack processes as outlined in the
 `developer guide <https://docs.openstack.org/infra/manual/developers.html>`_.
diff --git a/doc/source/cli/index.rst b/doc/source/cli/index.rst
deleted file mode 100644
index dfdcb88c..00000000
--- a/doc/source/cli/index.rst
+++ /dev/null
@@ -1,1723 +0,0 @@
-.. ###################################################
-.. ##  WARNING  ######################################
-.. ##############  WARNING  ##########################
-.. ##########################  WARNING  ##############
-.. ######################################  WARNING  ##
-.. ###################################################
-.. ###################################################
-.. ##
-.. This file is tool-generated. Do not edit manually.
-.. https://docs.openstack.org/doc-contrib-guide/
-.. doc-tools/cli-reference.html
-..                                                  ##
-.. ##  WARNING  ######################################
-.. ##############  WARNING  ##########################
-.. ##########################  WARNING  ##############
-.. ######################################  WARNING  ##
-.. ###################################################
-
-===============================================
-Clustering service (senlin) command-line client
-===============================================
-
-The senlin client is the command-line interface (CLI) for
-the Clustering service (senlin) API and its extensions.
-
-This chapter documents :command:`senlin` version ``1.3.0``.
-
-For help on a specific :command:`senlin` command, enter:
-
-.. code-block:: console
-
-   $ senlin help COMMAND
-
-.. _senlin_command_usage:
-
-senlin usage
-~~~~~~~~~~~~
-
-.. code-block:: console
-
-   usage: senlin [--version] [-d] [-v] [--api-timeout API_TIMEOUT]
-                 [--senlin-api-version SENLIN_API_VERSION]
-                 [--os-auth-plugin AUTH_PLUGIN] [--os-auth-url AUTH_URL]
-                 [--os-project-id PROJECT_ID] [--os-project-name PROJECT_NAME]
-                 [--os-tenant-id TENANT_ID] [--os-tenant-name TENANT_NAME]
-                 [--os-domain-id DOMAIN_ID] [--os-domain-name DOMAIN_NAME]
-                 [--os-project-domain-id PROJECT_DOMAIN_ID]
-                 [--os-project-domain-name PROJECT_DOMAIN_NAME]
-                 [--os-user-domain-id USER_DOMAIN_ID]
-                 [--os-user-domain-name USER_DOMAIN_NAME]
-                 [--os-username USERNAME] [--os-user-id USER_ID]
-                 [--os-password PASSWORD] [--os-trust-id TRUST_ID]
-                 [--os-cacert CA_BUNDLE_FILE | --verify | --insecure]
-                 [--os-token TOKEN] [--os-access-info ACCESS_INFO]
-                 [--os-profile HMAC_KEY]
-                 <subcommand> ...
-
-**Subcommands:**
-
-``action-list``
-  List actions.
-
-``action-show``
-  Show detailed info about the specified action.
-
-``build-info``
-  Retrieve build information.
-
-``cluster-check``
-  Check the cluster(s).
-
-``cluster-collect``
-  Collect attributes across a cluster.
-
-``cluster-create``
-  Create the cluster.
-
-``cluster-delete``
-  Delete the cluster(s).
-
-``cluster-list``
-  List the user's clusters.
-
-``cluster-node-add``
-  Add specified nodes to cluster.
-
-``cluster-node-del``
-  Delete specified nodes from cluster.
-
-``cluster-node-list``
-  List nodes from cluster.
-
-``cluster-node-replace``
-  Replace the nodes in cluster with specified nodes.
-
-``cluster-policy-attach``
-  Attach policy to cluster.
-
-``cluster-policy-detach``
-  Detach policy from cluster.
-
-``cluster-policy-list``
-  List policies from cluster.
-
-``cluster-policy-show``
-  Show a specific policy that is bound to the specified
-  cluster.
-
-``cluster-policy-update``
-  Update a policy's properties on a cluster.
-
-``cluster-recover``
-  Recover the cluster(s).
-
-``cluster-resize``
-  Resize a cluster.
-
-``cluster-run``
-  Run shell scripts on all nodes of a cluster.
-
-``cluster-scale-in``
-  Scale in a cluster by the specified number of nodes.
-
-``cluster-scale-out``
-  Scale out a cluster by the specified number of nodes.
-
-``cluster-show``
-  Show details of the cluster.
-
-``cluster-update``
-  Update the cluster.
-
-``event-list``
-  List events.
-
-``event-show``
-  Describe the event.
-
-``node-check``
-  Check the node(s).
-
-``node-create``
-  Create the node.
-
-``node-delete``
-  Delete the node(s).
-
-``node-list``
-  Show list of nodes.
-
-``node-recover``
-  Recover the node(s).
-
-``node-show``
-  Show detailed info about the specified node.
-
-``node-update``
-  Update the node.
-
-``policy-create``
-  Create a policy.
-
-``policy-delete``
-  Delete policy(s).
-
-``policy-list``
-  List policies that meet the criteria.
-
-``policy-show``
-  Show the policy details.
-
-``policy-type-list``
-  List the available policy types.
-
-``policy-type-show``
-  Get the details about a policy type.
-
-``policy-update``
-  Update a policy.
-
-``policy-validate``
-  Validate a policy spec.
-
-``profile-create``
-  Create a profile.
-
-``profile-delete``
-  Delete profile(s).
-
-``profile-list``
-  List profiles that meet the criteria.
-
-``profile-show``
-  Show the profile details.
-
-``profile-type-list``
-  List the available profile types.
-
-``profile-type-show``
-  Get the details about a profile type.
-
-``profile-update``
-  Update a profile.
-
-``profile-validate``
-  Validate a profile.
-
-``receiver-create``
-  Create a receiver.
-
-``receiver-delete``
-  Delete receiver(s).
-
-``receiver-list``
-  List receivers that meet the criteria.
-
-``receiver-show``
-  Show the receiver details.
-
-``bash-completion``
-  Prints all of the commands and options to stdout.
-
-``help``
-  Display help about this program or one of its
-  subcommands.
-
-.. _senlin_command_options:
-
-senlin optional arguments
-~~~~~~~~~~~~~~~~~~~~~~~~~
-
-``--version``
-  Shows the client version and exits.
-
-``-d, --debug``
-  Defaults to ``env[SENLINCLIENT_DEBUG]``.
-
-``-v, --verbose``
-  Print more verbose output.
-
-``--api-timeout API_TIMEOUT``
-  Number of seconds to wait for an API response,
-  defaults to system socket timeout
-
-``--senlin-api-version SENLIN_API_VERSION``
-  Version number for Senlin API to use, Default to "1".
-
-``--os-auth-plugin AUTH_PLUGIN``
-  Authentication plugin, default to ``env[OS_AUTH_PLUGIN]``
-
-``--os-auth-url AUTH_URL``
-  Defaults to ``env[OS_AUTH_URL]``
-
-``--os-project-id PROJECT_ID``
-  Defaults to ``env[OS_PROJECT_ID]``.
-
-``--os-project-name PROJECT_NAME``
-  Defaults to ``env[OS_PROJECT_NAME]``.
-
-``--os-tenant-id TENANT_ID``
-  Defaults to ``env[OS_TENANT_ID]``.
-
-``--os-tenant-name TENANT_NAME``
-  Defaults to ``env[OS_TENANT_NAME]``.
-
-``--os-domain-id DOMAIN_ID``
-  Domain ID for scope of authorization, defaults to
-  ``env[OS_DOMAIN_ID]``.
-
-``--os-domain-name DOMAIN_NAME``
-  Domain name for scope of authorization, defaults to
-  ``env[OS_DOMAIN_NAME]``.
-
-``--os-project-domain-id PROJECT_DOMAIN_ID``
-  Project domain ID for scope of authorization, defaults
-  to ``env[OS_PROJECT_DOMAIN_ID]``.
-
-``--os-project-domain-name PROJECT_DOMAIN_NAME``
-  Project domain name for scope of authorization,
-  defaults to ``env[OS_PROJECT_DOMAIN_NAME]``.
-
-``--os-user-domain-id USER_DOMAIN_ID``
-  User domain ID for scope of authorization, defaults to
-  ``env[OS_USER_DOMAIN_ID]``.
-
-``--os-user-domain-name USER_DOMAIN_NAME``
-  User domain name for scope of authorization, defaults
-  to ``env[OS_USER_DOMAIN_NAME]``.
-
-``--os-username USERNAME``
-  Defaults to ``env[OS_USERNAME]``.
-
-``--os-user-id USER_ID``
-  Defaults to ``env[OS_USER_ID]``.
-
-``--os-password PASSWORD``
-  Defaults to ``env[OS_PASSWORD]``
-
-``--os-trust-id TRUST_ID``
-  Defaults to ``env[OS_TRUST_ID]``
-
-``--os-cacert CA_BUNDLE_FILE``
-  Path of CA TLS certificate(s) used to verify the
-  remote server's certificate. Without this option
-  senlin looks for the default system CA certificates.
-
-``--verify``
-  Verify server certificate (default)
-
-``--insecure``
-  Explicitly allow senlinclient to perform "insecure
-  SSL" (HTTPS) requests. The server's certificate will
-  not be verified against any certificate authorities.
-  This option should be used with caution.
-
-``--os-token TOKEN``
-  A string token to bootstrap the Keystone database,
-  defaults to ``env[OS_TOKEN]``
-
-``--os-access-info ACCESS_INFO``
-  Access info, defaults to ``env[OS_ACCESS_INFO]``
-
-``--os-profile HMAC_KEY``
-  HMAC key to use for encrypting context data for
-  performance profiling of operation. This key should be
-  the value of HMAC key configured in osprofiler
-  middleware in senlin, it is specified in the paste
-  deploy configuration (/etc/senlin/api-paste.ini).
-  Without the key, profiling will not be triggered even
-  if osprofiler is enabled on server side.
-
-.. _senlin_action-list:
-
-senlin action-list
-------------------
-
-.. code-block:: console
-
-   usage: senlin action-list [-f <"KEY1=VALUE1;KEY2=VALUE2...">] [-o <KEY:DIR>]
-                             [-l <LIMIT>] [-m <ID>] [-g] [-F]
-
-List actions.
-
-**Optional arguments:**
-
-``-f <"KEY1=VALUE1;KEY2=VALUE2...">, --filters <"KEY1=VALUE1;KEY2=VALUE2...">``
-  Filter parameters to apply on returned actions. This
-  can be specified multiple times, or once with
-  parameters separated by a semicolon.
-
-``-o <KEY:DIR>, --sort <KEY:DIR>``
-  Sorting option which is a string containing a list of
-  keys separated by commas. Each key can be optionally
-  appended by a sort direction (:asc or :desc)
-
-``-l <LIMIT>, --limit <LIMIT>``
-  Limit the number of actions returned.
-
-``-m <ID>, --marker <ID>``
-  Only return actions that appear after the given node
-  ID.
-
-``-g, --global-project``
-  Whether actions from all projects should be listed.
-  Default to False. Setting this to True may demand for
-  an admin privilege.
-
-``-F, --full-id``
-  Print full IDs in list.
-
-.. _senlin_action-show:
-
-senlin action-show
-------------------
-
-.. code-block:: console
-
-   usage: senlin action-show <ACTION>
-
-Show detailed info about the specified action.
-
-**Positional arguments:**
-
-``<ACTION>``
-  Name or ID of the action to show the details for.
-
-.. _senlin_build-info:
-
-senlin build-info
------------------
-
-.. code-block:: console
-
-   usage: senlin build-info
-
-Retrieve build information.
-
-.. _senlin_cluster-check:
-
-senlin cluster-check
---------------------
-
-.. code-block:: console
-
-   usage: senlin cluster-check <CLUSTER> [<CLUSTER> ...]
-
-Check the cluster(s).
-
-**Positional arguments:**
-
-``<CLUSTER>``
-  ID or name of cluster(s) to operate on.
-
-.. _senlin_cluster-collect:
-
-senlin cluster-collect
-----------------------
-
-.. code-block:: console
-
-   usage: senlin cluster-collect -p <PATH> [-L] [-F] <CLUSTER>
-
-Collect attributes across a cluster.
-
-**Positional arguments:**
-
-``<CLUSTER>``
-  Name or ID of cluster(s) to operate on.
-
-**Optional arguments:**
-
-``-p <PATH>, --path <PATH>``
-  A Json path string specifying the attribute to
-  collect.
-
-``-L, --list``
-  Print a full list that contains both node ids and
-  attribute values instead of values only. Default is
-  False.
-
-``-F, --full-id``
-  Print full IDs in list.
-
-.. _senlin_cluster-create:
-
-senlin cluster-create
----------------------
-
-.. code-block:: console
-
-   usage: senlin cluster-create -p <PROFILE> [-n <MIN-SIZE>] [-m <MAX-SIZE>]
-                                [-c <DESIRED-CAPACITY>] [-t <TIMEOUT>]
-                                [-M <"KEY1=VALUE1;KEY2=VALUE2...">]
-                                <CLUSTER_NAME>
-
-Create the cluster.
-
-**Positional arguments:**
-
-``<CLUSTER_NAME>``
-  Name of the cluster to create.
-
-**Optional arguments:**
-
-``-p <PROFILE>, --profile <PROFILE>``
-  Profile Id or name used for this cluster.
-
-``-n <MIN-SIZE>, --min-size <MIN-SIZE>``
-  Min size of the cluster. Default to 0.
-
-``-m <MAX-SIZE>, --max-size <MAX-SIZE>``
-  Max size of the cluster. Default to -1, means
-  unlimited.
-
-``-c <DESIRED-CAPACITY>, --desired-capacity <DESIRED-CAPACITY>``
-  Desired capacity of the cluster. Default to min_size
-  if min_size is specified else 0.
-
-``-t <TIMEOUT>, --timeout <TIMEOUT>``
-  Cluster creation timeout in seconds.
-
-``-M <"KEY1=VALUE1;KEY2=VALUE2...">, --metadata <"KEY1=VALUE1;KEY2=VALUE2...">``
-  Metadata values to be attached to the cluster. This
-  can
-  be
-  specified
-  multiple
-  times,
-  or
-  once
-  with
-  key-value
-  pairs
-  separated
-  by
-  a
-  semicolon.
-
-.. _senlin_cluster-delete:
-
-senlin cluster-delete
----------------------
-
-.. code-block:: console
-
-   usage: senlin cluster-delete <CLUSTER> [<CLUSTER> ...]
-
-Delete the cluster(s).
-
-**Positional arguments:**
-
-``<CLUSTER>``
-  Name or ID of cluster(s) to delete.
-
-.. _senlin_cluster-list:
-
-senlin cluster-list
--------------------
-
-.. code-block:: console
-
-   usage: senlin cluster-list [-f <"KEY1=VALUE1;KEY2=VALUE2...">] [-o <KEY:DIR>]
-                              [-l <LIMIT>] [-m <ID>] [-g] [-F]
-
-List the user's clusters.
-
-**Optional arguments:**
-
-``-f <"KEY1=VALUE1;KEY2=VALUE2...">, --filters <"KEY1=VALUE1;KEY2=VALUE2...">``
-  Filter parameters to apply on returned clusters. This
-  can be specified multiple times, or once with
-  parameters separated by a semicolon.
-
-``-o <KEY:DIR>, --sort <KEY:DIR>``
-  Sorting option which is a string containing a list of
-  keys separated by commas. Each key can be optionally
-  appended by a sort direction (:asc or :desc)
-
-``-l <LIMIT>, --limit <LIMIT>``
-  Limit the number of clusters returned.
-
-``-m <ID>, --marker <ID>``
-  Only return clusters that appear after the given
-  cluster ID.
-
-``-g, --global-project``
-  Indicate that the cluster list should include clusters
-  from all projects. This option is subject to access
-  policy checking. Default is False.
-
-``-F, --full-id``
-  Print full IDs in list.
-
-.. _senlin_cluster-node-add:
-
-senlin cluster-node-add
------------------------
-
-.. code-block:: console
-
-   usage: senlin cluster-node-add -n <NODES> <CLUSTER>
-
-Add specified nodes to cluster.
-
-**Positional arguments:**
-
-``<CLUSTER>``
-  Name or ID of cluster to operate on.
-
-**Optional arguments:**
-
-``-n <NODES>, --nodes <NODES>``
-  ID of nodes to be added; multiple nodes can be
-  separated with ","
-
-.. _senlin_cluster-node-del:
-
-senlin cluster-node-del
------------------------
-
-.. code-block:: console
-
-   usage: senlin cluster-node-del -n <NODES> [-d <BOOLEAN>] <CLUSTER>
-
-Delete specified nodes from cluster.
-
-**Positional arguments:**
-
-``<CLUSTER>``
-  Name or ID of cluster to operate on.
-
-**Optional arguments:**
-
-``-n <NODES>, --nodes <NODES>``
-  ID of nodes to be deleted; multiple nodes can be
-  separated with ",".
-
-``-d <BOOLEAN>, --destroy-after-deletion <BOOLEAN>``
-  Whether nodes should be destroyed after deleted.
-  Default is False.
-
-.. _senlin_cluster-node-list:
-
-senlin cluster-node-list
-------------------------
-
-.. code-block:: console
-
-   usage: senlin cluster-node-list [-f <"KEY1=VALUE1;KEY2=VALUE2...">]
-                                   [-l <LIMIT>] [-m <ID>] [-F]
-                                   <CLUSTER>
-
-List nodes from cluster.
-
-**Positional arguments:**
-
-``<CLUSTER>``
-  Name or ID of cluster to nodes from.
-
-**Optional arguments:**
-
-``-f <"KEY1=VALUE1;KEY2=VALUE2...">, --filters <"KEY1=VALUE1;KEY2=VALUE2...">``
-  Filter parameters to apply on returned nodes. This can
-  be specified multiple times, or once with parameters
-  separated by a semicolon.
-
-``-l <LIMIT>, --limit <LIMIT>``
-  Limit the number of nodes returned.
-
-``-m <ID>, --marker <ID>``
-  Only return nodes that appear after the given node ID.
-
-``-F, --full-id``
-  Print full IDs in list.
-
-.. _senlin_cluster-node-replace:
-
-senlin cluster-node-replace
----------------------------
-
-.. code-block:: console
-
-   usage: senlin cluster-node-replace -n <OLD_NODE1=NEW_NODE1> <CLUSTER>
-
-Replace the nodes in cluster with specified nodes.
-
-**Positional arguments:**
-
-``<CLUSTER>``
-  Name or ID of cluster to operate on.
-
-**Optional arguments:**
-
-``-n <OLD_NODE1=NEW_NODE1>, --nodes <OLD_NODE1=NEW_NODE1>``
-  OLD_NODE is the name or ID of a node to be replaced,
-  NEW_NODE is the name or ID of a node as replacement.
-  This can be specified multiple times, or once with
-  node-pairs separated by a comma ','.
-
-.. _senlin_cluster-policy-attach:
-
-senlin cluster-policy-attach
-----------------------------
-
-.. code-block:: console
-
-   usage: senlin cluster-policy-attach -p <POLICY> [-e <BOOLEAN>] <NAME or ID>
-
-Attach policy to cluster.
-
-**Positional arguments:**
-
-``<NAME or ID>``
-  Name or ID of cluster to operate on.
-
-**Optional arguments:**
-
-``-p <POLICY>, --policy <POLICY>``
-  ID or name of policy to be attached.
-
-``-e <BOOLEAN>, --enabled <BOOLEAN>``
-  Whether the policy should be enabled once attached.
-  Default to enabled.
-
-.. _senlin_cluster-policy-detach:
-
-senlin cluster-policy-detach
-----------------------------
-
-.. code-block:: console
-
-   usage: senlin cluster-policy-detach -p <POLICY> <NAME or ID>
-
-Detach policy from cluster.
-
-**Positional arguments:**
-
-``<NAME or ID>``
-  Name or ID of cluster to operate on.
-
-**Optional arguments:**
-
-``-p <POLICY>, --policy <POLICY>``
-  ID or name of policy to be detached.
-
-.. _senlin_cluster-policy-list:
-
-senlin cluster-policy-list
---------------------------
-
-.. code-block:: console
-
-   usage: senlin cluster-policy-list [-f <"KEY1=VALUE1;KEY2=VALUE2...">]
-                                     [-o <SORT_STRING>] [-F]
-                                     <CLUSTER>
-
-List policies from cluster.
-
-**Positional arguments:**
-
-``<CLUSTER>``
-  Name or ID of cluster to query on.
-
-**Optional arguments:**
-
-``-f <"KEY1=VALUE1;KEY2=VALUE2...">, --filters <"KEY1=VALUE1;KEY2=VALUE2...">``
-  Filter parameters to apply on returned results. This
-  can be specified multiple times, or once with
-  parameters separated by a semicolon.
-
-``-o <SORT_STRING>, --sort <SORT_STRING>``
-  Sorting option which is a string containing a list of
-  keys separated by commas. Each key can be optionally
-  appended by a sort direction (:asc or :desc)
-
-``-F, --full-id``
-  Print full IDs in list.
-
-.. _senlin_cluster-policy-show:
-
-senlin cluster-policy-show
---------------------------
-
-.. code-block:: console
-
-   usage: senlin cluster-policy-show -p <POLICY> <CLUSTER>
-
-Show a specific policy that is bound to the specified cluster.
-
-**Positional arguments:**
-
-``<CLUSTER>``
-  ID or name of the cluster to query on.
-
-**Optional arguments:**
-
-``-p <POLICY>, --policy <POLICY>``
-  ID or name of the policy to query on.
-
-.. _senlin_cluster-policy-update:
-
-senlin cluster-policy-update
-----------------------------
-
-.. code-block:: console
-
-   usage: senlin cluster-policy-update -p <POLICY> [-e <BOOLEAN>] <NAME or ID>
-
-Update a policy's properties on a cluster.
-
-**Positional arguments:**
-
-``<NAME or ID>``
-  Name or ID of cluster to operate on.
-
-**Optional arguments:**
-
-``-p <POLICY>, --policy <POLICY>``
-  ID or name of policy to be updated.
-
-``-e <BOOLEAN>, --enabled <BOOLEAN>``
-  Whether the policy should be enabled.
-
-.. _senlin_cluster-recover:
-
-senlin cluster-recover
-----------------------
-
-.. code-block:: console
-
-   usage: senlin cluster-recover <CLUSTER> [<CLUSTER> ...]
-
-Recover the cluster(s).
-
-**Positional arguments:**
-
-``<CLUSTER>``
-  ID or name of cluster(s) to operate on.
-
-.. _senlin_cluster-resize:
-
-senlin cluster-resize
----------------------
-
-.. code-block:: console
-
-   usage: senlin cluster-resize [-c <CAPACITY>] [-a <ADJUSTMENT>]
-                                [-p <PERCENTAGE>] [-t <MIN_STEP>] [-s] [-n MIN]
-                                [-m MAX]
-                                <CLUSTER>
-
-Resize a cluster.
-
-**Positional arguments:**
-
-``<CLUSTER>``
-  Name or ID of cluster to operate on.
-
-**Optional arguments:**
-
-``-c <CAPACITY>, --capacity <CAPACITY>``
-  The desired number of nodes of the cluster.
-
-``-a <ADJUSTMENT>, --adjustment <ADJUSTMENT>``
-  A positive integer meaning the number of nodes to add,
-  or a negative integer indicating the number of nodes
-  to remove.
-
-``-p <PERCENTAGE>, --percentage <PERCENTAGE>``
-  A value that is interpreted as the percentage of size
-  adjustment. This value can be positive or negative.
-
-``-t <MIN_STEP>, --min-step <MIN_STEP>``
-  An integer specifying the number of nodes for
-  adjustment when <PERCENTAGE> is specified.
-
-``-s, --strict A``
-  boolean specifying whether the resize should be
-  performed on a best-effort basis when the new capacity
-  may go beyond size constraints.
-
-``-n MIN, --min-size MIN``
-  New lower bound of cluster size.
-
-``-m MAX, --max-size MAX``
-  New upper bound of cluster size. A value of -1
-  indicates no upper limit on cluster size.
-
-.. _senlin_cluster-run:
-
-senlin cluster-run
-------------------
-
-.. code-block:: console
-
-   usage: senlin cluster-run [-p <PORT>] [-t ADDRESS_TYPE] [-n <NETWORK>] [-6]
-                             [-u <USER>] [-i IDENTITY_FILE] [-O SSH_OPTIONS] -s
-                             <FILE>
-                             <CLUSTER>
-
-Run shell scripts on all nodes of a cluster.
-
-**Positional arguments:**
-
-``<CLUSTER>``
-  Name or ID of the cluster.
-
-**Optional arguments:**
-
-``-p <PORT>, --port <PORT>``
-  Optional flag to indicate the port to use
-  (Default=22).
-
-``-t ADDRESS_TYPE, --address-type ADDRESS_TYPE``
-  Optional flag to indicate which IP type to use.
-  Possible values includes 'fixed' and 'floating' (the
-  Default).
-
-``-n <NETWORK>, --network <NETWORK>``
-  Network to use for the ssh.
-
-``-6, --ipv6``
-  Optional flag to indicate whether to use an IPv6
-  address attached to a server. (Defaults to IPv4
-  address)
-
-``-u <USER>, --user <USER>``
-  Login to use.
-
-``-i IDENTITY_FILE, --identity-file IDENTITY_FILE``
-  Private key file, same as the '-i' option to the ssh
-  command.
-
-``-O SSH_OPTIONS, --ssh-options SSH_OPTIONS``
-  Extra options to pass to ssh. see: man ssh.
-
-``-s <FILE>, --script <FILE>``
-  Script file to run.
-
-.. _senlin_cluster-scale-in:
-
-senlin cluster-scale-in
------------------------
-
-.. code-block:: console
-
-   usage: senlin cluster-scale-in [-c <COUNT>] <CLUSTER>
-
-Scale in a cluster by the specified number of nodes.
-
-**Positional arguments:**
-
-``<CLUSTER>``
-  Name or ID of cluster to operate on.
-
-**Optional arguments:**
-
-``-c <COUNT>, --count <COUNT>``
-  Number of nodes to be deleted from the specified
-  cluster.
-
-.. _senlin_cluster-scale-out:
-
-senlin cluster-scale-out
-------------------------
-
-.. code-block:: console
-
-   usage: senlin cluster-scale-out [-c <COUNT>] <CLUSTER>
-
-Scale out a cluster by the specified number of nodes.
-
-**Positional arguments:**
-
-``<CLUSTER>``
-  Name or ID of cluster to operate on.
-
-**Optional arguments:**
-
-``-c <COUNT>, --count <COUNT>``
-  Number of nodes to be added to the specified cluster.
-
-.. _senlin_cluster-show:
-
-senlin cluster-show
--------------------
-
-.. code-block:: console
-
-   usage: senlin cluster-show <CLUSTER>
-
-Show details of the cluster.
-
-**Positional arguments:**
-
-``<CLUSTER>``
-  Name or ID of cluster to show.
-
-.. _senlin_cluster-update:
-
-senlin cluster-update
----------------------
-
-.. code-block:: console
-
-   usage: senlin cluster-update [-p <PROFILE>] [-P <BOOLEAN>] [-t <TIMEOUT>]
-                                [-M <"KEY1=VALUE1;KEY2=VALUE2...">] [-n <NAME>]
-                                <CLUSTER>
-
-Update the cluster.
-
-**Positional arguments:**
-
-``<CLUSTER>``
-  Name or ID of cluster to be updated.
-
-**Optional arguments:**
-
-``-p <PROFILE>, --profile <PROFILE>``
-  ID or name of new profile to use.
-
-``-P <BOOLEAN>, --profile-only <BOOLEAN>``
-  Whether the cluster should be updated profile only. If
-  false, it will be applied to all existing nodes. If
-  true, any newly created nodes will use the new
-  profile, but existing nodes will not be changed.
-  Default is False.
-
-``-t <TIMEOUT>, --timeout <TIMEOUT>``
-  New timeout (in seconds) value for the cluster.
-
-``-M <"KEY1=VALUE1;KEY2=VALUE2...">, --metadata <"KEY1=VALUE1;KEY2=VALUE2...">``
-  Metadata values to be attached to the cluster. This
-  can
-  be
-  specified
-  multiple
-  times,
-  or
-  once
-  with
-  key-value
-  pairs
-  separated
-  by
-  a
-  semicolon.
-  Use
-  '{}'
-  can
-  clean metadata
-
-``-n <NAME>, --name <NAME>``
-  New name for the cluster to update.
-
-.. _senlin_event-list:
-
-senlin event-list
------------------
-
-.. code-block:: console
-
-   usage: senlin event-list [-f <"KEY1=VALUE1;KEY2=VALUE2...">] [-l <LIMIT>]
-                            [-m <ID>] [-o <KEY:DIR>] [-g] [-F]
-
-List events.
-
-**Optional arguments:**
-
-``-f <"KEY1=VALUE1;KEY2=VALUE2...">, --filters <"KEY1=VALUE1;KEY2=VALUE2...">``
-  Filter parameters to apply on returned events. This
-  can be specified multiple times, or once with
-  parameters separated by a semicolon.
-
-``-l <LIMIT>, --limit <LIMIT>``
-  Limit the number of events returned.
-
-``-m <ID>, --marker <ID>``
-  Only return events that appear after the given event
-  ID.
-
-``-o <KEY:DIR>, --sort <KEY:DIR>``
-  Sorting option which is a string containing a list of
-  keys separated by commas. Each key can be optionally
-  appended by a sort direction (:asc or :desc)
-
-``-g, --global-project``
-  Whether events from all projects should be listed.
-  Default to False. Setting this to True may demand for
-  an admin privilege.
-
-``-F, --full-id``
-  Print full IDs in list.
-
-.. _senlin_event-show:
-
-senlin event-show
------------------
-
-.. code-block:: console
-
-   usage: senlin event-show <EVENT>
-
-Describe the event.
-
-**Positional arguments:**
-
-``<EVENT>``
-  ID of event to display details for.
-
-.. _senlin_node-check:
-
-senlin node-check
------------------
-
-.. code-block:: console
-
-   usage: senlin node-check <NODE> [<NODE> ...]
-
-Check the node(s).
-
-**Positional arguments:**
-
-``<NODE>``
-  ID or name of node(s) to check.
-
-.. _senlin_node-create:
-
-senlin node-create
-------------------
-
-.. code-block:: console
-
-   usage: senlin node-create -p <PROFILE> [-c <CLUSTER>] [-r <ROLE>]
-                             [-M <"KEY1=VALUE1;KEY2=VALUE2...">]
-                             <NODE_NAME>
-
-Create the node.
-
-**Positional arguments:**
-
-``<NODE_NAME>``
-  Name of the node to create.
-
-**Optional arguments:**
-
-``-p <PROFILE>, --profile <PROFILE>``
-  Profile Id or name used for this node.
-
-``-c <CLUSTER>, --cluster <CLUSTER>``
-  Cluster Id for this node.
-
-``-r <ROLE>, --role <ROLE>``
-  Role for this node in the specific cluster.
-
-``-M <"KEY1=VALUE1;KEY2=VALUE2...">, --metadata <"KEY1=VALUE1;KEY2=VALUE2...">``
-  Metadata values to be attached to the node. This can
-  be specified multiple times, or once with key-value
-  pairs separated by a semicolon.
-
-.. _senlin_node-delete:
-
-senlin node-delete
-------------------
-
-.. code-block:: console
-
-   usage: senlin node-delete <NODE> [<NODE> ...]
-
-Delete the node(s).
-
-**Positional arguments:**
-
-``<NODE>``
-  Name or ID of node(s) to delete.
-
-.. _senlin_node-list:
-
-senlin node-list
-----------------
-
-.. code-block:: console
-
-   usage: senlin node-list [-c <CLUSTER>] [-f <"KEY1=VALUE1;KEY2=VALUE2...">]
-                           [-o <KEY:DIR>] [-l <LIMIT>] [-m <ID>] [-g] [-F]
-
-Show list of nodes.
-
-**Optional arguments:**
-
-``-c <CLUSTER>, --cluster <CLUSTER>``
-  ID or name of cluster from which nodes are to be
-  listed.
-
-``-f <"KEY1=VALUE1;KEY2=VALUE2...">, --filters <"KEY1=VALUE1;KEY2=VALUE2...">``
-  Filter parameters to apply on returned nodes. This can
-  be specified multiple times, or once with parameters
-  separated by a semicolon.
-
-``-o <KEY:DIR>, --sort <KEY:DIR>``
-  Sorting option which is a string containing a list of
-  keys separated by commas. Each key can be optionally
-  appended by a sort direction (:asc or :desc)
-
-``-l <LIMIT>, --limit <LIMIT>``
-  Limit the number of nodes returned.
-
-``-m <ID>, --marker <ID>``
-  Only return nodes that appear after the given node ID.
-
-``-g, --global-project``
-  Indicate that this node list should include nodes from
-  all projects. This option is subject to access policy
-  checking. Default is False.
-
-``-F, --full-id``
-  Print full IDs in list.
-
-.. _senlin_node-recover:
-
-senlin node-recover
--------------------
-
-.. code-block:: console
-
-   usage: senlin node-recover [-c <BOOLEAN>] <NODE> [<NODE> ...]
-
-Recover the node(s).
-
-**Positional arguments:**
-
-``<NODE>``
-  ID or name of node(s) to recover.
-
-**Optional arguments:**
-
-``-c <BOOLEAN>, --check <BOOLEAN>``
-  Whether the node(s) should check physical resource
-  status before doing node recover.Default is false
-
-.. _senlin_node-show:
-
-senlin node-show
-----------------
-
-.. code-block:: console
-
-   usage: senlin node-show [-D] <NODE>
-
-Show detailed info about the specified node.
-
-**Positional arguments:**
-
-``<NODE>``
-  Name or ID of the node to show the details for.
-
-**Optional arguments:**
-
-``-D, --details``
-  Include physical object details.
-
-.. _senlin_node-update:
-
-senlin node-update
-------------------
-
-.. code-block:: console
-
-   usage: senlin node-update [-n <NAME>] [-p <PROFILE ID>] [-r <ROLE>]
-                             [-M <"KEY1=VALUE1;KEY2=VALUE2...">]
-                             <NODE>
-
-Update the node.
-
-**Positional arguments:**
-
-``<NODE>``
-  Name or ID of node to update.
-
-**Optional arguments:**
-
-``-n <NAME>, --name <NAME>``
-  New name for the node.
-
-``-p <PROFILE ID>, --profile <PROFILE ID>``
-  ID or name of new profile to use.
-
-``-r <ROLE>, --role <ROLE>``
-  Role for this node in the specific cluster.
-
-``-M <"KEY1=VALUE1;KEY2=VALUE2...">, --metadata <"KEY1=VALUE1;KEY2=VALUE2...">``
-  Metadata values to be attached to the node. This can
-  be specified multiple times, or once with key-value
-  pairs separated by a semicolon. Use '{}' can clean
-  metadata
-
-.. _senlin_policy-create:
-
-senlin policy-create
---------------------
-
-.. code-block:: console
-
-   usage: senlin policy-create -s <SPEC_FILE> <NAME>
-
-Create a policy.
-
-**Positional arguments:**
-
-``<NAME>``
-  Name of the policy to create.
-
-**Optional arguments:**
-
-``-s <SPEC_FILE>, --spec-file <SPEC_FILE>``
-  The spec file used to create the policy.
-
-.. _senlin_policy-delete:
-
-senlin policy-delete
---------------------
-
-.. code-block:: console
-
-   usage: senlin policy-delete <POLICY> [<POLICY> ...]
-
-Delete policy(s).
-
-**Positional arguments:**
-
-``<POLICY>``
-  Name or ID of policy(s) to delete.
-
-.. _senlin_policy-list:
-
-senlin policy-list
-------------------
-
-.. code-block:: console
-
-   usage: senlin policy-list [-f <"KEY1=VALUE1;KEY2=VALUE2...">] [-l <LIMIT>]
-                             [-m <ID>] [-o <KEY:DIR>] [-g] [-F]
-
-List policies that meet the criteria.
-
-**Optional arguments:**
-
-``-f <"KEY1=VALUE1;KEY2=VALUE2...">, --filters <"KEY1=VALUE1;KEY2=VALUE2...">``
-  Filter parameters to apply on returned policies. This
-  can be specified multiple times, or once with
-  parameters separated by a semicolon.
-
-``-l <LIMIT>, --limit <LIMIT>``
-  Limit the number of policies returned.
-
-``-m <ID>, --marker <ID>``
-  Only return policies that appear after the given ID.
-
-``-o <KEY:DIR>, --sort <KEY:DIR>``
-  Sorting option which is a string containing a list of
-  keys separated by commas. Each key can be optionally
-  appended by a sort direction (:asc or :desc)
-
-``-g, --global-project``
-  Indicate that the list should include policies from
-  all projects. This option is subject to access policy
-  checking. Default is False.
-
-``-F, --full-id``
-  Print full IDs in list.
-
-.. _senlin_policy-show:
-
-senlin policy-show
-------------------
-
-.. code-block:: console
-
-   usage: senlin policy-show <POLICY>
-
-Show the policy details.
-
-**Positional arguments:**
-
-``<POLICY>``
-  Name or ID of the policy to be shown.
-
-.. _senlin_policy-type-list:
-
-senlin policy-type-list
------------------------
-
-.. code-block:: console
-
-   usage: senlin policy-type-list
-
-List the available policy types.
-
-.. _senlin_policy-type-show:
-
-senlin policy-type-show
------------------------
-
-.. code-block:: console
-
-   usage: senlin policy-type-show [-F <FORMAT>] <TYPE_NAME>
-
-Get the details about a policy type.
-
-**Positional arguments:**
-
-``<TYPE_NAME>``
-  Policy type to retrieve.
-
-**Optional arguments:**
-
-``-F <FORMAT>, --format <FORMAT>``
-  The template output format, one of: yaml, json.
-
-.. _senlin_policy-update:
-
-senlin policy-update
---------------------
-
-.. code-block:: console
-
-   usage: senlin policy-update [-n <NAME>] <POLICY>
-
-Update a policy.
-
-**Positional arguments:**
-
-``<POLICY>``
-  Name of the policy to be updated.
-
-**Optional arguments:**
-
-``-n <NAME>, --name <NAME>``
-  New name of the policy to be updated.
-
-.. _senlin_policy-validate:
-
-senlin policy-validate
-----------------------
-
-.. code-block:: console
-
-   usage: senlin policy-validate -s <SPEC_FILE>
-
-Validate a policy spec.
-
-**Optional arguments:**
-
-``-s <SPEC_FILE>, --spec-file <SPEC_FILE>``
-  The spec file of the policy to be validated.
-
-.. _senlin_profile-create:
-
-senlin profile-create
----------------------
-
-.. code-block:: console
-
-   usage: senlin profile-create -s <SPEC FILE>
-                                [-M <"KEY1=VALUE1;KEY2=VALUE2...">]
-                                <PROFILE_NAME>
-
-Create a profile.
-
-**Positional arguments:**
-
-``<PROFILE_NAME>``
-  Name of the profile to create.
-
-**Optional arguments:**
-
-``-s <SPEC FILE>, --spec-file <SPEC FILE>``
-  The spec file used to create the profile.
-
-``-M <"KEY1=VALUE1;KEY2=VALUE2...">, --metadata <"KEY1=VALUE1;KEY2=VALUE2...">``
-  Metadata values to be attached to the profile. This
-  can
-  be
-  specified
-  multiple
-  times,
-  or
-  once
-  with
-  key-value
-  pairs
-  separated
-  by
-  a
-  semicolon.
-
-.. _senlin_profile-delete:
-
-senlin profile-delete
----------------------
-
-.. code-block:: console
-
-   usage: senlin profile-delete <PROFILE> [<PROFILE> ...]
-
-Delete profile(s).
-
-**Positional arguments:**
-
-``<PROFILE>``
-  Name or ID of profile(s) to delete.
-
-.. _senlin_profile-list:
-
-senlin profile-list
--------------------
-
-.. code-block:: console
-
-   usage: senlin profile-list [-f <"KEY1=VALUE1;KEY2=VALUE2...">] [-l <LIMIT>]
-                              [-m <ID>] [-o <KEY:DIR>] [-g] [-F]
-
-List profiles that meet the criteria.
-
-**Optional arguments:**
-
-``-f <"KEY1=VALUE1;KEY2=VALUE2...">, --filters <"KEY1=VALUE1;KEY2=VALUE2...">``
-  Filter parameters to apply on returned profiles. This
-  can be specified multiple times, or once with
-  parameters separated by a semicolon.
-
-``-l <LIMIT>, --limit <LIMIT>``
-  Limit the number of profiles returned.
-
-``-m <ID>, --marker <ID>``
-  Only return profiles that appear after the given ID.
-
-``-o <KEY:DIR>, --sort <KEY:DIR>``
-  Sorting option which is a string containing a list of
-  keys separated by commas. Each key can be optionally
-  appended by a sort direction (:asc or :desc)
-
-``-g, --global-project``
-  Indicate that the list should include profiles from
-  all projects. This option is subject to access policy
-  checking. Default is False.
-
-``-F, --full-id``
-  Print full IDs in list.
-
-.. _senlin_profile-show:
-
-senlin profile-show
--------------------
-
-.. code-block:: console
-
-   usage: senlin profile-show <PROFILE>
-
-Show the profile details.
-
-**Positional arguments:**
-
-``<PROFILE>``
-  Name or ID of profile to show.
-
-.. _senlin_profile-type-list:
-
-senlin profile-type-list
-------------------------
-
-.. code-block:: console
-
-   usage: senlin profile-type-list
-
-List the available profile types.
-
-.. _senlin_profile-type-show:
-
-senlin profile-type-show
-------------------------
-
-.. code-block:: console
-
-   usage: senlin profile-type-show [-F <FORMAT>] <TYPE_NAME>
-
-Get the details about a profile type.
-
-**Positional arguments:**
-
-``<TYPE_NAME>``
-  Profile type to retrieve.
-
-**Optional arguments:**
-
-``-F <FORMAT>, --format <FORMAT>``
-  The template output format, one of: yaml, json.
-
-.. _senlin_profile-update:
-
-senlin profile-update
----------------------
-
-.. code-block:: console
-
-   usage: senlin profile-update [-n <NAME>] [-M <"KEY1=VALUE1;KEY2=VALUE2...">]
-                                <PROFILE_ID>
-
-Update a profile.
-
-**Positional arguments:**
-
-``<PROFILE_ID>``
-  Name or ID of the profile to update.
-
-**Optional arguments:**
-
-``-n <NAME>, --name <NAME>``
-  The new name for the profile.
-
-``-M <"KEY1=VALUE1;KEY2=VALUE2...">, --metadata <"KEY1=VALUE1;KEY2=VALUE2...">``
-  Metadata values to be attached to the profile. This
-  can
-  be
-  specified
-  multiple
-  times,
-  or
-  once
-  with
-  key-value
-  pairs
-  separated
-  by
-  a
-  semicolon.
-  Use
-  '{}'
-  can
-  clean metadata
-
-.. _senlin_profile-validate:
-
-senlin profile-validate
------------------------
-
-.. code-block:: console
-
-   usage: senlin profile-validate -s <SPEC FILE>
-
-Validate a profile.
-
-**Optional arguments:**
-
-``-s <SPEC FILE>, --spec-file <SPEC FILE>``
-  The spec file of the profile to be validated.
-
-.. _senlin_receiver-create:
-
-senlin receiver-create
-----------------------
-
-.. code-block:: console
-
-   usage: senlin receiver-create [-t <TYPE>] [-c <CLUSTER>] [-a <ACTION>]
-                                 [-P <"KEY1=VALUE1;KEY2=VALUE2...">]
-                                 <NAME>
-
-Create a receiver.
-
-**Positional arguments:**
-
-``<NAME>``
-  Name of the receiver to create.
-
-**Optional arguments:**
-
-``-t <TYPE>, --type <TYPE>``
-  Type of the receiver to create. Receiver type can be
-  "webhook" or "message". Default to "webhook".
-
-``-c <CLUSTER>, --cluster <CLUSTER>``
-  Targeted cluster for this receiver. Required if
-  receiver type is webhook.
-
-``-a <ACTION>, --action <ACTION>``
-  Name or ID of the targeted action to be triggered.
-  Required if receiver type is webhook.
-
-``-P <"KEY1=VALUE1;KEY2=VALUE2...">, --params <"KEY1=VALUE1;KEY2=VALUE2...">``
-  A dictionary of parameters that will be passed to
-  target action when the receiver is triggered.
-
-.. _senlin_receiver-delete:
-
-senlin receiver-delete
-----------------------
-
-.. code-block:: console
-
-   usage: senlin receiver-delete <RECEIVER> [<RECEIVER> ...]
-
-Delete receiver(s).
-
-**Positional arguments:**
-
-``<RECEIVER>``
-  Name or ID of receiver(s) to delete.
-
-.. _senlin_receiver-list:
-
-senlin receiver-list
---------------------
-
-.. code-block:: console
-
-   usage: senlin receiver-list [-f <"KEY1=VALUE1;KEY2=VALUE2...">] [-l <LIMIT>]
-                               [-m <ID>] [-o <KEY:DIR>] [-g] [-F]
-
-List receivers that meet the criteria.
-
-**Optional arguments:**
-
-``-f <"KEY1=VALUE1;KEY2=VALUE2...">, --filters <"KEY1=VALUE1;KEY2=VALUE2...">``
-  Filter parameters to apply on returned receivers. This
-  can be specified multiple times, or once with
-  parameters separated by a semicolon.
-
-``-l <LIMIT>, --limit <LIMIT>``
-  Limit the number of receivers returned.
-
-``-m <ID>, --marker <ID>``
-  Only return receivers that appear after the given ID.
-
-``-o <KEY:DIR>, --sort <KEY:DIR>``
-  Sorting option which is a string containing a list of
-  keys separated by commas. Each key can be optionally
-  appended by a sort direction (:asc or :desc)
-
-``-g, --global-project``
-  Indicate that the list should include receivers from
-  all projects. This option is subject to access policy
-  checking. Default is False.
-
-``-F, --full-id``
-  Print full IDs in list.
-
-.. _senlin_receiver-show:
-
-senlin receiver-show
---------------------
-
-.. code-block:: console
-
-   usage: senlin receiver-show <RECEIVER>
-
-Show the receiver details.
-
-**Positional arguments:**
-
-``<RECEIVER>``
-  Name or ID of the receiver to show.
-
diff --git a/doc/source/conf.py b/doc/source/conf.py
index 49b3e603..d29233ac 100644
--- a/doc/source/conf.py
+++ b/doc/source/conf.py
@@ -247,11 +247,7 @@ latex_documents = [
 
 # One entry per manual page. List of tuples
 # (source start file, name, description, authors, manual section).
-man_pages = [
-    ('man/senlin', 'senlin',
-     u'Command line reference for Senlin',
-     [u'Senlin Developers'], 1)
-]
+# man_pages = []
 
 # If true, show URL addresses after external links.
 # man_show_urls = False
diff --git a/doc/source/index.rst b/doc/source/index.rst
index 2cd3d310..5b76f575 100644
--- a/doc/source/index.rst
+++ b/doc/source/index.rst
@@ -8,7 +8,6 @@ Contents:
    :maxdepth: 2
 
    install/index
-   cli/index
 
 
 Indices and tables
diff --git a/doc/source/man/senlin.rst b/doc/source/man/senlin.rst
deleted file mode 100644
index 6023c15c..00000000
--- a/doc/source/man/senlin.rst
+++ /dev/null
@@ -1,82 +0,0 @@
-:orphan:
-
-======
-senlin
-======
-
-.. program:: senlin
-
-SYNOPSIS
-========
-
-  `senlin` [options] <command> [command-options]
-
-  `senlin help`
-
-  `senlin help` <command>
-
-
-DESCRIPTION
-===========
-
-`senlin` is a command line client for controlling OpenStack Senlin.
-
-Before the `senlin` command is issued, ensure the environment contains
-the necessary variables so that the CLI can pass user credentials to
-the server.
-See `Getting Credentials for a CLI`  section of `OpenStack CLI Guide`
-for more info.
-
-
-OPTIONS
-=======
-
-To get a list of available commands and options run::
-
-    senlin help
-
-To get usage and options of a command run::
-
-    senlin help <command>
-
-
-EXAMPLES
-========
-
-Get information about profile-create command::
-
-    senlin help profile-create
-
-List available profiles::
-
-    senlin profile-list
-
-List available clusters::
-
-    senlin cluster-list
-
-Create a profile::
-
-    senlin profile-create -s profile.spec myprofile
-
-View profile information::
-
-    senlin profile-show myprofile
-
-Create a cluster::
-
-    senlin cluster-create -p myprofile -n 2 mycluster
-
-List events::
-
-    senlin event-list
-
-Delete a cluster::
-
-    senlin cluster-delete mycluster
-
-BUGS
-====
-
-Senlin client is hosted in Launchpad so you can view current bugs
-at https://bugs.launchpad.net/python-senlinclient/.
diff --git a/senlinclient/cliargs.py b/senlinclient/cliargs.py
deleted file mode 100644
index e2f28dc1..00000000
--- a/senlinclient/cliargs.py
+++ /dev/null
@@ -1,180 +0,0 @@
-# Licensed 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 argparse
-
-from senlinclient.common.i18n import _
-from senlinclient.common import utils
-
-
-def add_global_identity_args(parser):
-    parser.add_argument(
-        '--os-auth-plugin', dest='auth_plugin', metavar='AUTH_PLUGIN',
-        default=utils.env('OS_AUTH_PLUGIN', default=None),
-        help=_('Authentication plugin, default to env[OS_AUTH_PLUGIN]'))
-
-    parser.add_argument(
-        '--os-auth-url', dest='auth_url', metavar='AUTH_URL',
-        default=utils.env('OS_AUTH_URL'),
-        help=_('Defaults to env[OS_AUTH_URL]'))
-
-    parser.add_argument(
-        '--os-project-id', dest='project_id', metavar='PROJECT_ID',
-        default=utils.env('OS_PROJECT_ID'),
-        help=_('Defaults to env[OS_PROJECT_ID].'))
-
-    parser.add_argument(
-        '--os-project-name', dest='project_name', metavar='PROJECT_NAME',
-        default=utils.env('OS_PROJECT_NAME'),
-        help=_('Defaults to env[OS_PROJECT_NAME].'))
-
-    parser.add_argument(
-        '--os-tenant-id', dest='tenant_id', metavar='TENANT_ID',
-        default=utils.env('OS_TENANT_ID'),
-        help=_('Defaults to env[OS_TENANT_ID].'))
-
-    parser.add_argument(
-        '--os-tenant-name', dest='tenant_name', metavar='TENANT_NAME',
-        default=utils.env('OS_TENANT_NAME'),
-        help=_('Defaults to env[OS_TENANT_NAME].'))
-
-    parser.add_argument(
-        '--os-domain-id', dest='domain_id', metavar='DOMAIN_ID',
-        default=utils.env('OS_DOMAIN_ID'),
-        help=_('Domain ID for scope of authorization, defaults to '
-               'env[OS_DOMAIN_ID].'))
-
-    parser.add_argument(
-        '--os-domain-name', dest='domain_name', metavar='DOMAIN_NAME',
-        default=utils.env('OS_DOMAIN_NAME'),
-        help=_('Domain name for scope of authorization, defaults to '
-               'env[OS_DOMAIN_NAME].'))
-
-    parser.add_argument(
-        '--os-project-domain-id', dest='project_domain_id',
-        metavar='PROJECT_DOMAIN_ID',
-        default=utils.env('OS_PROJECT_DOMAIN_ID'),
-        help=_('Project domain ID for scope of authorization, defaults to '
-               'env[OS_PROJECT_DOMAIN_ID].'))
-
-    parser.add_argument(
-        '--os-project-domain-name', dest='project_domain_name',
-        metavar='PROJECT_DOMAIN_NAME',
-        default=utils.env('OS_PROJECT_DOMAIN_NAME'),
-        help=_('Project domain name for scope of authorization, defaults to '
-               'env[OS_PROJECT_DOMAIN_NAME].'))
-
-    parser.add_argument(
-        '--os-user-domain-id', dest='user_domain_id',
-        metavar='USER_DOMAIN_ID',
-        default=utils.env('OS_USER_DOMAIN_ID'),
-        help=_('User domain ID for scope of authorization, defaults to '
-               'env[OS_USER_DOMAIN_ID].'))
-
-    parser.add_argument(
-        '--os-user-domain-name', dest='user_domain_name',
-        metavar='USER_DOMAIN_NAME',
-        default=utils.env('OS_USER_DOMAIN_NAME'),
-        help=_('User domain name for scope of authorization, defaults to '
-               'env[OS_USER_DOMAIN_NAME].'))
-
-    parser.add_argument(
-        '--os-username', dest='username', metavar='USERNAME',
-        default=utils.env('OS_USERNAME'),
-        help=_('Defaults to env[OS_USERNAME].'))
-
-    parser.add_argument(
-        '--os-user-id', dest='user_id', metavar='USER_ID',
-        default=utils.env('OS_USER_ID'),
-        help=_('Defaults to env[OS_USER_ID].'))
-
-    parser.add_argument(
-        '--os-password', dest='password', metavar='PASSWORD',
-        default=utils.env('OS_PASSWORD'),
-        help=_('Defaults to env[OS_PASSWORD]'))
-
-    parser.add_argument(
-        '--os-trust-id', dest='trust_id', metavar='TRUST_ID',
-        default=utils.env('OS_TRUST_ID'),
-        help=_('Defaults to env[OS_TRUST_ID]'))
-
-    verify_group = parser.add_mutually_exclusive_group()
-
-    verify_group.add_argument(
-        '--os-cacert', dest='verify', metavar='CA_BUNDLE_FILE',
-        default=utils.env('OS_CACERT', default=True),
-        help=_('Path of CA TLS certificate(s) used to verify the remote '
-               'server\'s certificate. Without this option senlin looks '
-               'for the default system CA certificates.'))
-
-    verify_group.add_argument(
-        '--verify',
-        action='store_true',
-        help=_('Verify server certificate (default)'))
-
-    verify_group.add_argument(
-        '--insecure', dest='verify', action='store_false',
-        help=_('Explicitly allow senlinclient to perform "insecure SSL" '
-               '(HTTPS) requests. The server\'s certificate will not be '
-               'verified against any certificate authorities. This '
-               'option should be used with caution.'))
-
-    parser.add_argument(
-        '--os-token', dest='token', metavar='TOKEN',
-        default=utils.env('OS_TOKEN', default=None),
-        help=_('A string token to bootstrap the Keystone database, defaults '
-               'to env[OS_TOKEN]'))
-
-    parser.add_argument(
-        '--os-access-info', dest='access_info', metavar='ACCESS_INFO',
-        default=utils.env('OS_ACCESS_INFO'),
-        help=_('Access info, defaults to env[OS_ACCESS_INFO]'))
-
-#    parser.add_argument(
-#        '--os-cert',
-#        help=_('Path of certificate file to use in SSL connection. This '
-#               'file can optionally be prepended with the private key.'))
-#
-#    parser.add_argument(
-#        '--os-key',
-#        help=_('Path of client key to use in SSL connection. This option is '
-#               'not necessary if your key is prepended to your cert file.'))
-
-
-def add_global_args(parser, version):
-    # GLOBAL ARGUMENTS
-    parser.add_argument(
-        '-h', '--help', action='store_true',
-        help=argparse.SUPPRESS)
-
-    parser.add_argument(
-        '--version', action='version', version=version,
-        help=_("Shows the client version and exits."))
-
-    parser.add_argument(
-        '-d', '--debug', action='store_true',
-        default=bool(utils.env('SENLINCLIENT_DEBUG')),
-        help=_('Defaults to env[SENLINCLIENT_DEBUG].'))
-
-    parser.add_argument(
-        '-v', '--verbose', action="store_true", default=False,
-        help=_("Print more verbose output."))
-
-    parser.add_argument(
-        '--api-timeout',
-        help=_('Number of seconds to wait for an API response, '
-               'defaults to system socket timeout'))
-
-    parser.add_argument(
-        '--senlin-api-version',
-        default=utils.env('SENLIN_API_VERSION', default='1'),
-        help=_('Version number for Senlin API to use, Default to "1".'))
diff --git a/senlinclient/client.py b/senlinclient/client.py
deleted file mode 100644
index 3d9c751f..00000000
--- a/senlinclient/client.py
+++ /dev/null
@@ -1,23 +0,0 @@
-# Licensed 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 senlinclient.common import utils
-
-
-def Client(api_ver, *args, **kwargs):
-    """Import versioned client module.
-
-    :param api_ver: API version required.
-    """
-    module = utils.import_versioned_module(api_ver, 'client')
-    cls = getattr(module, 'Client')
-    return cls(*args, **kwargs)
diff --git a/senlinclient/common/utils.py b/senlinclient/common/utils.py
index 39c20e25..5f51661d 100644
--- a/senlinclient/common/utils.py
+++ b/senlinclient/common/utils.py
@@ -12,13 +12,9 @@
 
 from __future__ import print_function
 
-import os
-import sys
 
 from heatclient.common import template_utils
 from oslo_serialization import jsonutils
-from oslo_utils import encodeutils
-from oslo_utils import importutils
 import prettytable
 import six
 import yaml
@@ -27,46 +23,6 @@ from senlinclient.common import exc
 from senlinclient.common.i18n import _
 
 
-supported_formats = {
-    "json": lambda x: jsonutils.dumps(x, indent=2),
-    "yaml": lambda x: yaml.safe_dump(x, default_flow_style=False)
-}
-
-
-def arg(*args, **kwargs):
-    """Decorator for CLI args."""
-
-    def _decorator(func):
-        if not hasattr(func, 'arguments'):
-            func.arguments = []
-
-        if (args, kwargs) not in func.arguments:
-            func.arguments.insert(0, (args, kwargs))
-
-        return func
-
-    return _decorator
-
-
-def env(*args, **kwargs):
-    """Returns the first environment variable set.
-
-    If all are empty, defaults to '' or keyword arg `default`.
-    """
-    for arg in args:
-        value = os.environ.get(arg)
-        if value:
-            return value
-    return kwargs.get('default', '')
-
-
-def import_versioned_module(version, submodule=None):
-    module = 'senlinclient.v%s' % version
-    if submodule:
-        module = '.'.join((module, submodule))
-    return importutils.import_module(module)
-
-
 def format_nested_dict(d, fields, column_names):
     if d is None:
         return ''
@@ -99,95 +55,6 @@ def list_formatter(record):
     return '\n'.join(record or [])
 
 
-def _print_list(objs, fields, formatters=None, sortby_index=0,
-                mixed_case_fields=None, field_labels=None):
-    """Print a list of objects as a table, one row per object.
-
-    :param objs: iterable of :class:`Resource`
-    :param fields: attributes that correspond to columns, in order
-    :param formatters: `dict` of callables for field formatting
-    :param sortby_index: index of the field for sorting table rows
-    :param mixed_case_fields: fields corresponding to object attributes that
-        have mixed case names (e.g., 'serverId')
-    :param field_labels: Labels to use in the heading of the table, default to
-        fields.
-    """
-    formatters = formatters or {}
-    mixed_case_fields = mixed_case_fields or []
-    field_labels = field_labels or fields
-    if len(field_labels) != len(fields):
-        raise ValueError(_("Field labels list %(labels)s has different number "
-                           "of elements than fields list %(fields)s"),
-                         {'labels': field_labels, 'fields': fields})
-
-    if sortby_index is None:
-        kwargs = {}
-    else:
-        kwargs = {'sortby': field_labels[sortby_index]}
-    pt = prettytable.PrettyTable(field_labels)
-    pt.align = 'l'
-
-    for o in objs:
-        row = []
-        for field in fields:
-            if field in formatters:
-                data = formatters[field](o)
-            else:
-                if field in mixed_case_fields:
-                    field_name = field.replace(' ', '_')
-                else:
-                    field_name = field.lower().replace(' ', '_')
-                data = getattr(o, field_name, '')
-            if data is None:
-                data = '-'
-            row.append(data)
-        pt.add_row(row)
-
-    if six.PY3:
-        return encodeutils.safe_encode(pt.get_string(**kwargs)).decode()
-    else:
-        return encodeutils.safe_encode(pt.get_string(**kwargs))
-
-
-def print_list(objs, fields, formatters=None, sortby_index=0,
-               mixed_case_fields=None, field_labels=None):
-    # This wrapper is needed because sdk may yield a generator that will
-    # escape the exception catching previously
-    if not objs:
-        objs = []
-
-    try:
-        res = _print_list(objs, fields, formatters=formatters,
-                          sortby_index=sortby_index,
-                          mixed_case_fields=mixed_case_fields,
-                          field_labels=field_labels)
-        print(res)
-    except Exception as ex:
-        exc.parse_exception(ex)
-
-
-def print_dict(d, formatters=None):
-    formatters = formatters or {}
-    pt = prettytable.PrettyTable(['Property', 'Value'],
-                                 caching=False, print_empty=False)
-    pt.align = 'l'
-
-    for field in d.keys():
-        if field in formatters:
-            data = formatters[field](d[field])
-        else:
-            data = d[field]
-        if data is None:
-            data = '-'
-        pt.add_row([field, data])
-
-    content = pt.get_string(sortby='Property')
-    if six.PY3:
-        print(encodeutils.safe_encode(content).decode())
-    else:
-        print(encodeutils.safe_encode(content))
-
-
 def print_action_result(rid, res):
     if res[0] == "OK":
         output = _("accepted by action %s") % res[1]
@@ -281,18 +148,3 @@ def process_stack_spec(spec):
     }
 
     return new_spec
-
-
-def format_output(output, format='yaml'):
-    fmt = format.lower()
-    try:
-        return supported_formats[fmt](output)
-    except KeyError:
-        raise exc.HTTPUnsupported(_('The format(%s) is unsupported.')
-                                  % fmt)
-
-
-def exit(msg=''):
-    if msg:
-        print(msg, file=sys.stderr)
-    sys.exit(1)
diff --git a/senlinclient/shell.py b/senlinclient/shell.py
deleted file mode 100644
index 057fb6c1..00000000
--- a/senlinclient/shell.py
+++ /dev/null
@@ -1,312 +0,0 @@
-# Licensed 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.
-
-"""
-Command-line interface to the Senlin clustering API.
-"""
-
-from __future__ import print_function
-
-import argparse
-import logging
-import sys
-
-from oslo_utils import encodeutils
-from oslo_utils import importutils
-import six
-
-import senlinclient
-from senlinclient import cliargs
-from senlinclient import client as senlin_client
-from senlinclient.common import exc
-from senlinclient.common.i18n import _
-from senlinclient.common import utils
-
-osprofiler_profiler = importutils.try_import("osprofiler.profiler")
-USER_AGENT = 'python-senlinclient'
-LOG = logging.getLogger(__name__)
-
-
-class HelpFormatter(argparse.HelpFormatter):
-    def start_section(self, heading):
-        # Title-case the headings
-        heading = '%s%s' % (heading[0].upper(), heading[1:])
-        super(HelpFormatter, self).start_section(heading)
-
-
-class SenlinShell(object):
-    def _setup_logging(self, debug):
-        log_lvl = logging.DEBUG if debug else logging.WARNING
-        logging.basicConfig(format="%(levelname)s (%(module)s) %(message)s",
-                            level=log_lvl)
-        logging.getLogger('iso8601').setLevel(logging.WARNING)
-        logging.getLogger('urllib3.connectionpool').setLevel(logging.WARNING)
-
-    def _setup_verbose(self, verbose):
-        if verbose:
-            exc.verbose = 1
-
-    def _find_actions(self, subparsers, actions_module):
-        for attr in (a for a in dir(actions_module) if a.startswith('do_')):
-            command = attr[3:].replace('_', '-')
-            callback = getattr(actions_module, attr)
-
-            # get callback documentation string
-            desc = callback.__doc__ or ''
-            help = desc.strip().split('\n')[0]
-            arguments = getattr(callback, 'arguments', [])
-
-            subparser = subparsers.add_parser(command,
-                                              help=help,
-                                              description=desc,
-                                              add_help=False,
-                                              formatter_class=HelpFormatter)
-
-            subparser.add_argument('-h', '--help',
-                                   action='help',
-                                   help=argparse.SUPPRESS)
-
-            for (args, kwargs) in arguments:
-                subparser.add_argument(*args, **kwargs)
-            subparser.set_defaults(func=callback)
-
-            self.subcommands[command] = subparser
-
-    def do_bash_completion(self, args):
-        """Prints all of the commands and options to stdout.
-
-        The senlin.bash_completion script doesn't have to hard code them.
-        """
-        commands = set()
-        options = set()
-        for sc_str, sc in self.subcommands.items():
-            if sc_str == 'bash-completion':
-                continue
-
-            commands.add(sc_str)
-            for option in list(sc._optionals._option_string_actions):
-                options.add(option)
-
-        print(' '.join(commands | options))
-
-    def add_profiler_args(self, parser):
-        if osprofiler_profiler:
-            parser.add_argument(
-                '--os-profile',
-                metavar='HMAC_KEY',
-                default=utils.env('OS_PROFILE'),
-                help=_('HMAC key to use for encrypting context data for '
-                       'performance profiling of operation. This key should '
-                       'be the value of HMAC key configured in '
-                       'senlin configuration (/etc/senlin/senlin.conf). '
-                       'Without the key, profiling will not be triggered '
-                       'even if osprofiler is enabled on server side.'))
-
-    def get_subcommand_parser(self, base_parser, version):
-        parser = base_parser
-
-        self.subcommands = {}
-        subparsers = parser.add_subparsers(metavar='<subcommand>')
-        submodule = utils.import_versioned_module(version, 'shell')
-        self._find_actions(subparsers, submodule)
-        self._find_actions(subparsers, self)
-
-        return parser
-
-    @utils.arg('command', metavar='<subcommand>', nargs='?',
-               help=_('Display help for <subcommand>.'))
-    def do_help(self, args):
-        """Display help about this program or one of its subcommands."""
-        if getattr(args, 'command', None):
-            if args.command in self.subcommands:
-                self.subcommands[args.command].print_help()
-            else:
-                raise exc.CommandError("'%s' is not a valid subcommand" %
-                                       args.command)
-        else:
-            self.parser.print_help()
-
-    def _check_identity_arguments(self, args):
-        # TODO(Qiming): validate the token authentication path and the trust
-        # authentication path
-
-        if not args.auth_url:
-            msg = _('You must provide an auth url via --os-auth-url (or '
-                    ' env[OS_AUTH_URL])')
-            raise exc.CommandError(msg)
-
-        # username or user_id or token must be specified
-        if not (args.username or args.user_id or args.token):
-            msg = _('You must provide a user name, a user_id or a '
-                    'token for authentication')
-            raise exc.CommandError(msg)
-
-        # if both username and user_id are specified, user_id takes precedence
-        if (args.username and args.user_id):
-            msg = _('Both user name and user ID are specified, Senlin will '
-                    'use user ID for authentication')
-            print(_('WARNING: %s') % msg)
-
-        if 'v3' in args.auth_url:
-            if (args.username and not args.user_id):
-                if not (args.user_domain_id or args.user_domain_name):
-                    msg = _('Either user domain ID (--user-domain-id / '
-                            'env[OS_USER_DOMAIN_ID]) or user domain name '
-                            '(--user-domain-name / env[OS_USER_DOMAIN_NAME]) '
-                            'must be specified, because user name may not be '
-                            'unique.')
-                    raise exc.CommandError(msg)
-
-        # password is needed if username or user_id is present
-        if (args.username or args.user_id) and not (args.password):
-            msg = _('You must provide a password for user %s') % (
-                args.username or args.user_id)
-            raise exc.CommandError(msg)
-
-        # project name or ID is needed, or else sdk may find the wrong project
-        if (not (args.project_id or args.project_name or args.tenant_id or
-                 args.tenant_name)):
-            if not (args.user_id):
-                msg = _('Either project/tenant ID or project/tenant name '
-                        'must be specified, or else Senlin cannot know '
-                        'which project to use.')
-                raise exc.CommandError(msg)
-            else:
-                msg = _('Neither project ID nor project name is specified. '
-                        'Senlin will use user\'s default project which may '
-                        'result in authentication error.')
-                print(_('WARNING: %s') % msg)
-
-        # both project name and ID are specified, ID takes precedence
-        if ((args.project_id or args.tenant_id) and
-                (args.project_name or args.tenant_name)):
-            msg = _('Both project/tenant name and project/tenant ID are '
-                    'specified, Senlin will use project ID for '
-                    'authentication')
-            print(_('WARNING: %s') % msg)
-
-        # project name may not be unique
-        if 'v3' in args.auth_url:
-            if (not (args.project_id or args.tenant_id) and
-                    (args.project_name or args.tenant_name) and
-                    not (args.project_domain_id or args.project_domain_name)):
-                msg = _('Either project domain ID (--project-domain-id / '
-                        'env[OS_PROJECT_DOMAIN_ID]) orr project domain name '
-                        '(--project-domain-name / '
-                        'env[OS_PROJECT_DOMAIN_NAME]) must be specified, '
-                        'because project/tenant name may not be unique.')
-                raise exc.CommandError(msg)
-
-    def _setup_senlin_client(self, api_ver, args):
-        """Create senlin client using given args."""
-        kwargs = {
-            'auth_plugin': args.auth_plugin or 'password',
-            'auth_url': args.auth_url,
-            'project_name': args.project_name or args.tenant_name,
-            'project_id': args.project_id or args.tenant_id,
-            'domain_name': args.domain_name,
-            'domain_id': args.domain_id,
-            'project_domain_name': args.project_domain_name,
-            'project_domain_id': args.project_domain_id,
-            'user_domain_name': args.user_domain_name,
-            'user_domain_id': args.user_domain_id,
-            'username': args.username,
-            'user_id': args.user_id,
-            'password': args.password,
-            'verify': args.verify,
-            'token': args.token,
-            'trust_id': args.trust_id,
-        }
-
-        return senlin_client.Client('1', user_agent=USER_AGENT, **kwargs)
-
-    def main(self, argv):
-        # Parse args once to find version
-        parser = argparse.ArgumentParser(
-            prog='senlin',
-            description=__doc__.strip(),
-            epilog=_('Type "senlin help <COMMAND>" for help on a specific '
-                     'command.'),
-            add_help=False,
-            formatter_class=HelpFormatter,
-        )
-
-        cliargs.add_global_args(parser, version=senlinclient.__version__)
-        cliargs.add_global_identity_args(parser)
-        self.add_profiler_args(parser)
-        base_parser = parser
-
-        (options, args) = base_parser.parse_known_args(argv)
-
-        self._setup_logging(options.debug)
-        self._setup_verbose(options.verbose)
-
-        # build available subcommands based on version
-        api_ver = options.senlin_api_version
-        LOG.info(api_ver)
-        subcommand_parser = self.get_subcommand_parser(base_parser, api_ver)
-        self.parser = subcommand_parser
-
-        # Handle top-level --help/-h before attempting to parse
-        # a command off the command line
-        if not args and options.help or not argv:
-            self.do_help(options)
-            return 0
-
-        # Parse args again and call whatever callback was selected
-        args = subcommand_parser.parse_args(argv)
-
-        # Short-circuit and deal with help command right away.
-        if args.func == self.do_help:
-            self.do_help(args)
-            return 0
-        elif args.func == self.do_bash_completion:
-            self.do_bash_completion(args)
-            return 0
-
-        # Check if identity information are sufficient
-        self._check_identity_arguments(args)
-
-        # Setup Senlin client connection
-        sc = self._setup_senlin_client(api_ver, args)
-
-        os_profile = osprofiler_profiler and options.os_profile
-        if os_profile:
-            osprofiler_profiler.init(options.os_profile)
-
-        args.func(sc.service, args)
-
-        if os_profile:
-            trace_id = osprofiler_profiler.get().get_base_id()
-            print(_("Trace ID: %s") % trace_id)
-            print(_("To display trace use next command:\n"
-                  "osprofiler trace show --html %s ") % trace_id)
-
-
-def main(args=None):
-    try:
-        if args is None:
-            args = sys.argv[1:]
-
-        SenlinShell().main(args)
-    except KeyboardInterrupt:
-        print(_("... terminating senlin client"), file=sys.stderr)
-        return 130
-    except Exception as e:
-        if '--debug' in args or '-d' in args:
-            raise
-        else:
-            print(encodeutils.safe_encode(six.text_type(e)), file=sys.stderr)
-        return 1
-
-if __name__ == "__main__":
-    sys.exit(main())
diff --git a/senlinclient/tests/unit/test_cliargs.py b/senlinclient/tests/unit/test_cliargs.py
deleted file mode 100644
index 89833f38..00000000
--- a/senlinclient/tests/unit/test_cliargs.py
+++ /dev/null
@@ -1,74 +0,0 @@
-# Licensed 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 mock
-import testtools
-
-from senlinclient import cliargs
-
-
-class TestCliArgs(testtools.TestCase):
-
-    def test_add_global_identity_args(self):
-        parser = mock.Mock()
-
-        cliargs.add_global_identity_args(parser)
-        expected = [
-            '--os-auth-plugin',
-            '--os-auth-url',
-            '--os-project-id',
-            '--os-project-name',
-            '--os-tenant-id',
-            '--os-tenant-name',
-            '--os-domain-id',
-            '--os-domain-name',
-            '--os-project-domain-id',
-            '--os-project-domain-name',
-            '--os-user-domain-id',
-            '--os-user-domain-name',
-            '--os-username',
-            '--os-user-id',
-            '--os-password',
-            '--os-trust-id',
-            '--os-token',
-            '--os-access-info',
-        ]
-
-        options = [arg[0][0] for arg in parser.add_argument.call_args_list]
-        self.assertEqual(expected, options)
-
-        parser.add_mutually_exclusive_group.assert_called_once_with()
-        group = parser.add_mutually_exclusive_group.return_value
-
-        verify_opts = [arg[0][0] for arg in group.add_argument.call_args_list]
-        verify_args = [
-            '--os-cacert',
-            '--verify',
-            '--insecure'
-        ]
-        self.assertEqual(verify_args, verify_opts)
-
-    def test_add_global_args(self):
-        parser = mock.Mock()
-
-        cliargs.add_global_args(parser, '1')
-        expected = [
-            '-h',
-            '--version',
-            '-d',
-            '-v',
-            '--api-timeout',
-            '--senlin-api-version'
-        ]
-
-        options = [arg[0][0] for arg in parser.add_argument.call_args_list]
-        self.assertEqual(expected, options)
diff --git a/senlinclient/tests/unit/test_client.py b/senlinclient/tests/unit/test_client.py
deleted file mode 100644
index 097a56c1..00000000
--- a/senlinclient/tests/unit/test_client.py
+++ /dev/null
@@ -1,39 +0,0 @@
-# Licensed 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 mock
-import testtools
-
-from senlinclient import client as sc
-from senlinclient.common import utils
-
-
-class FakeClient(object):
-
-    def __init__(self, session):
-        super(FakeClient, self).__init__()
-        self.session = session
-
-
-class ClientTest(testtools.TestCase):
-
-    @mock.patch.object(utils, 'import_versioned_module')
-    def test_client_init(self, mock_import):
-        the_module = mock.Mock()
-        the_module.Client = FakeClient
-        mock_import.return_value = the_module
-        session = mock.Mock()
-
-        res = sc.Client('FAKE_VER', session)
-
-        mock_import.assert_called_once_with('FAKE_VER', 'client')
-        self.assertIsInstance(res, FakeClient)
diff --git a/senlinclient/tests/unit/test_shell.py b/senlinclient/tests/unit/test_shell.py
deleted file mode 100644
index 337e7cfb..00000000
--- a/senlinclient/tests/unit/test_shell.py
+++ /dev/null
@@ -1,356 +0,0 @@
-# Licensed 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 argparse
-import logging
-import sys
-
-import mock
-import six
-from six.moves import builtins
-import testtools
-
-from senlinclient import client as senlin_client
-from senlinclient.common import exc
-from senlinclient.common.i18n import _
-from senlinclient.common import sdk
-from senlinclient.common import utils
-from senlinclient import shell
-from senlinclient.tests.unit import fakes
-
-
-class HelpFormatterTest(testtools.TestCase):
-
-    def test_start_section(self):
-        fmtr = shell.HelpFormatter('senlin')
-        res = fmtr.start_section(('heading', 'text1', 30))
-        self.assertIsNone(res)
-        h = fmtr._current_section.heading
-        self.assertEqual("HEADING('text1', 30)", h)
-
-
-class TestArgs(testtools.TestCase):
-
-    def __init__(self):
-        self.auth_url = 'http://fakeurl/v3'
-        self.auth_plugin = 'test_plugin'
-        self.username = 'test_user_name'
-        self.user_id = 'test_user_id'
-        self.token = 'test_token'
-        self.project_id = 'test_project_id'
-        self.project_name = 'test_project_name'
-        self.tenant_id = 'test_tenant_id'
-        self.tenant_name = 'test_tenant_name'
-        self.password = 'test_password'
-        self.user_domain_id = 'test_user_domain_id'
-        self.user_domain_name = 'test_user_domain_name'
-        self.project_domain_id = 'test_project_domain_id'
-        self.project_domain_name = 'test_project_domain_name'
-        self.domain_name = 'test_domain_name'
-        self.domain_id = 'test_domain_id'
-        self.verify = 'test_verify'
-        self.user_preferences = 'test_preferences'
-        self.trust_id = 'test_trust'
-
-
-class ShellTest(testtools.TestCase):
-
-    def setUp(self):
-        super(ShellTest, self).setUp()
-
-    def SHELL(self, func, *args, **kwargs):
-        orig_out = sys.stdout
-        sys.stdout = six.StringIO()
-        func(*args, **kwargs)
-        output = sys.stdout.getvalue()
-        sys.stdout.close()
-        sys.stdout = orig_out
-
-        return output
-
-    @mock.patch.object(logging, 'basicConfig')
-    @mock.patch.object(logging, 'getLogger')
-    def test_setup_logging_debug(self, x_get, x_config):
-        sh = shell.SenlinShell()
-        sh._setup_logging(True)
-
-        x_config.assert_called_once_with(
-            format="%(levelname)s (%(module)s) %(message)s",
-            level=logging.DEBUG)
-        mock_calls = [
-            mock.call('iso8601'),
-            mock.call().setLevel(logging.WARNING),
-            mock.call('urllib3.connectionpool'),
-            mock.call().setLevel(logging.WARNING),
-        ]
-        x_get.assert_has_calls(mock_calls)
-
-    @mock.patch.object(logging, 'basicConfig')
-    @mock.patch.object(logging, 'getLogger')
-    def test_setup_logging_no_debug(self, x_get, x_config):
-        sh = shell.SenlinShell()
-        sh._setup_logging(False)
-
-        x_config.assert_called_once_with(
-            format="%(levelname)s (%(module)s) %(message)s",
-            level=logging.WARNING)
-        mock_calls = [
-            mock.call('iso8601'),
-            mock.call().setLevel(logging.WARNING),
-            mock.call('urllib3.connectionpool'),
-            mock.call().setLevel(logging.WARNING),
-        ]
-        x_get.assert_has_calls(mock_calls)
-
-    def test_setup_verbose(self):
-        sh = shell.SenlinShell()
-        sh._setup_verbose(True)
-        self.assertEqual(1, exc.verbose)
-
-        sh._setup_verbose(False)
-        self.assertEqual(1, exc.verbose)
-
-    def test_find_actions(self):
-        sh = shell.SenlinShell()
-        sh.subcommands = {}
-        subparsers = mock.Mock()
-        x_subparser1 = mock.Mock()
-        x_subparser2 = mock.Mock()
-        x_add_parser = mock.Mock(side_effect=[x_subparser1, x_subparser2])
-        subparsers.add_parser = x_add_parser
-
-        # subparsers.add_parser = mock.Mock(return_value=x_subparser)
-        sh._find_actions(subparsers, fakes)
-
-        self.assertEqual({'command-bar': x_subparser1,
-                          'command-foo': x_subparser2},
-                         sh.subcommands)
-        add_calls = [
-            mock.call('command-bar', help='This is the command doc.',
-                      description='This is the command doc.',
-                      add_help=False,
-                      formatter_class=shell.HelpFormatter),
-            mock.call('command-foo', help='Pydoc for command foo.',
-                      description='Pydoc for command foo.',
-                      add_help=False,
-                      formatter_class=shell.HelpFormatter),
-        ]
-        x_add_parser.assert_has_calls(add_calls)
-
-        calls_1 = [
-            mock.call('-h', '--help', action='help',
-                      help=argparse.SUPPRESS),
-            mock.call('-F', '--flag', metavar='<FLAG>',
-                      help='Flag desc.'),
-            mock.call('arg1', metavar='<ARG1>',
-                      help='Arg1 desc')
-        ]
-        x_subparser1.add_argument.assert_has_calls(calls_1)
-        x_subparser1.set_defaults.assert_called_once_with(
-            func=fakes.do_command_bar)
-
-        calls_2 = [
-            mock.call('-h', '--help', action='help',
-                      help=argparse.SUPPRESS),
-        ]
-        x_subparser2.add_argument.assert_has_calls(calls_2)
-        x_subparser2.set_defaults.assert_called_once_with(
-            func=fakes.do_command_foo)
-
-    def test_do_bash_completion(self):
-        sh = shell.SenlinShell()
-        sc1 = mock.Mock()
-        sc2 = mock.Mock()
-        sc1._optionals._option_string_actions = ('A1', 'A2', 'C')
-        sc2._optionals._option_string_actions = ('B1', 'B2', 'C')
-        sh.subcommands = {
-            'command-foo': sc1,
-            'command-bar': sc2,
-            'bash-completion': None,
-        }
-
-        output = self.SHELL(sh.do_bash_completion, None)
-
-        output = output.split('\n')[0]
-        output_list = output.split(' ')
-        for option in ('A1', 'A2', 'C', 'B1', 'B2',
-                       'command-foo', 'command-bar'):
-            self.assertIn(option, output_list)
-
-    def test_do_add_profiler_args(self):
-        sh = shell.SenlinShell()
-        parser = mock.Mock()
-
-        sh.add_profiler_args(parser)
-        if shell.osprofiler_profiler:
-            self.assertEqual(1, parser.add_argument.call_count)
-        else:
-            self.assertEqual(0, parser.add_argument.call_count)
-
-    @mock.patch.object(utils, 'import_versioned_module')
-    @mock.patch.object(shell.SenlinShell, '_find_actions')
-    def test_get_subcommand_parser(self, x_find, x_import):
-        x_base = mock.Mock()
-        x_module = mock.Mock()
-        x_import.return_value = x_module
-        sh = shell.SenlinShell()
-
-        res = sh.get_subcommand_parser(x_base, 'v100')
-
-        self.assertEqual(x_base, res)
-        x_base.add_subparsers.assert_called_once_with(
-            metavar='<subcommand>')
-        x_subparsers = x_base.add_subparsers.return_value
-        x_import.assert_called_once_with('v100', 'shell')
-        find_calls = [
-            mock.call(x_subparsers, x_module),
-            mock.call(x_subparsers, sh)
-        ]
-
-        x_find.assert_has_calls(find_calls)
-
-    @mock.patch.object(argparse.ArgumentParser, 'print_help')
-    def test_do_help(self, mock_print):
-        sh = shell.SenlinShell()
-        args = mock.Mock()
-        args.command = mock.Mock()
-        sh.subcommands = {args.command: argparse.ArgumentParser}
-        sh.do_help(args)
-        self.assertTrue(mock_print.called)
-
-        sh.subcommands = {}
-        ex = self.assertRaises(exc.CommandError,
-                               sh.do_help, args)
-        msg = _("'%s' is not a valid subcommand") % args.command
-        self.assertEqual(msg, six.text_type(ex))
-
-    @mock.patch.object(builtins, 'print')
-    def test_check_identity_arguments(self, mock_print):
-        sh = shell.SenlinShell()
-        # auth_url is not specified.
-        args = TestArgs()
-        args.auth_url = None
-        ex = self.assertRaises(exc.CommandError,
-                               sh._check_identity_arguments, args)
-        msg = _('You must provide an auth url via --os-auth-url (or '
-                ' env[OS_AUTH_URL])')
-        self.assertEqual(msg, six.text_type(ex))
-        # username, user_id and token are not specified.
-        args = TestArgs()
-        args.username = None
-        args.user_id = None
-        args.token = None
-        msg = _('You must provide a user name, a user_id or a '
-                'token for authentication')
-        ex = self.assertRaises(exc.CommandError,
-                               sh._check_identity_arguments, args)
-        self.assertEqual(msg, six.text_type(ex))
-        # Both username and user_id are specified.
-        args = TestArgs()
-        args.project_id = None
-        args.tenant_id = None
-        sh._check_identity_arguments(args)
-        msg = _('WARNING: Both user name and user ID are specified, '
-                'Senlin will use user ID for authentication')
-        mock_print.assert_called_with(msg)
-
-        # 'v3' in auth_url but neither user_domain_id nor user_domain_name
-        # is specified.
-        args = TestArgs()
-        args.user_id = None
-        args.user_domain_id = None
-        args.user_domain_name = None
-        msg = _('Either user domain ID (--user-domain-id / '
-                'env[OS_USER_DOMAIN_ID]) or user domain name '
-                '(--user-domain-name / env[OS_USER_DOMAIN_NAME]) '
-                'must be specified, because user name may not be '
-                'unique.')
-        ex = self.assertRaises(exc.CommandError,
-                               sh._check_identity_arguments, args)
-        self.assertEqual(msg, six.text_type(ex))
-        # user_id, project_id, project_name, tenant_id and tenant_name are all
-        # not specified.
-        args = TestArgs()
-        args.project_id = None
-        args.project_name = None
-        args.tenant_id = None
-        args.tenant_name = None
-        args.user_id = None
-        msg = _('Either project/tenant ID or project/tenant name '
-                'must be specified, or else Senlin cannot know '
-                'which project to use.')
-        ex = self.assertRaises(exc.CommandError,
-                               sh._check_identity_arguments, args)
-        self.assertEqual(msg, six.text_type(ex))
-        args.user_id = 'test_user_id'
-        sh._check_identity_arguments(args)
-        msg = _('Neither project ID nor project name is specified. '
-                'Senlin will use user\'s default project which may '
-                'result in authentication error.')
-        mock_print.assert_called_with(_('WARNING: %s') % msg)
-
-        # Both project_name and project_id are specified
-        args = TestArgs()
-        args.user_id = None
-        sh._check_identity_arguments(args)
-        msg = _('Both project/tenant name and project/tenant ID are '
-                'specified, Senlin will use project ID for '
-                'authentication')
-        mock_print.assert_called_with(_('WARNING: %s') % msg)
-        # Project name may not be unique
-        args = TestArgs()
-        args.user_id = None
-        args.project_id = None
-        args.tenant_id = None
-        args.project_domain_id = None
-        args.project_domain_name = None
-        msg = _('Either project domain ID (--project-domain-id / '
-                'env[OS_PROJECT_DOMAIN_ID]) orr project domain name '
-                '(--project-domain-name / '
-                'env[OS_PROJECT_DOMAIN_NAME]) must be specified, '
-                'because project/tenant name may not be unique.')
-        ex = self.assertRaises(exc.CommandError,
-                               sh._check_identity_arguments, args)
-        self.assertEqual(msg, six.text_type(ex))
-
-    @mock.patch.object(sdk, 'create_connection')
-    def test_setup_senlinclient(self, mock_conn):
-        USER_AGENT = 'python-senlinclient'
-        args = TestArgs()
-        kwargs = {
-            'auth_plugin': args.auth_plugin,
-            'auth_url': args.auth_url,
-            'project_name': args.project_name or args.tenant_name,
-            'project_id': args.project_id or args.tenant_id,
-            'domain_name': args.domain_name,
-            'domain_id': args.domain_id,
-            'project_domain_name': args.project_domain_name,
-            'project_domain_id': args.project_domain_id,
-            'user_domain_name': args.user_domain_name,
-            'user_domain_id': args.user_domain_id,
-            'username': args.username,
-            'user_id': args.user_id,
-            'password': args.password,
-            'verify': args.verify,
-            'token': args.token,
-            'trust_id': args.trust_id,
-        }
-        sh = shell.SenlinShell()
-        conn = mock.Mock()
-        mock_conn.return_value = conn
-        conn.session = mock.Mock()
-        sh._setup_senlin_client('1', args)
-        mock_conn.assert_called_once_with(prof=None, user_agent=USER_AGENT,
-                                          **kwargs)
-        client = mock.Mock()
-        senlin_client.Client = mock.Mock(return_value=client)
-        self.assertEqual(client, sh._setup_senlin_client('1', args))
diff --git a/senlinclient/tests/unit/test_utils.py b/senlinclient/tests/unit/test_utils.py
index f961c733..c51811ea 100644
--- a/senlinclient/tests/unit/test_utils.py
+++ b/senlinclient/tests/unit/test_utils.py
@@ -10,9 +10,6 @@
 # License for the specific language governing permissions and limitations
 # under the License.
 
-import collections
-import sys
-
 from heatclient.common import template_utils
 import mock
 import six
@@ -23,21 +20,7 @@ from senlinclient.common.i18n import _
 from senlinclient.common import utils
 
 
-class CaptureStdout(object):
-    """Context manager for capturing stdout from statements in its block."""
-    def __enter__(self):
-        self.real_stdout = sys.stdout
-        self.stringio = six.StringIO()
-        sys.stdout = self.stringio
-        return self
-
-    def __exit__(self, *args):
-        sys.stdout = self.real_stdout
-        self.stringio.seek(0)
-        self.read = self.stringio.read
-
-
-class shellTest(testtools.TestCase):
+class UtilTest(testtools.TestCase):
 
     def test_format_parameter(self):
         params = ['status=ACTIVE;name=cluster1']
@@ -114,137 +97,3 @@ class shellTest(testtools.TestCase):
     def test_list_formatter_with_empty_list(self):
         params = []
         self.assertEqual('', utils.list_formatter(params))
-
-
-class PrintListTestCase(testtools.TestCase):
-
-    def test_print_list_with_list(self):
-        Row = collections.namedtuple('Row', ['foo', 'bar'])
-        to_print = [Row(foo='fake_foo1', bar='fake_bar2'),
-                    Row(foo='fake_foo2', bar='fake_bar1')]
-        with CaptureStdout() as cso:
-            utils.print_list(to_print, ['foo', 'bar'])
-        # Output should be sorted by the first key (foo)
-        self.assertEqual("""\
-+-----------+-----------+
-| foo       | bar       |
-+-----------+-----------+
-| fake_foo1 | fake_bar2 |
-| fake_foo2 | fake_bar1 |
-+-----------+-----------+
-""", cso.read())
-
-    def test_print_list_with_None_string(self):
-        Row = collections.namedtuple('Row', ['foo', 'bar'])
-        to_print = [Row(foo='fake_foo1', bar='None'),
-                    Row(foo='fake_foo2', bar='fake_bar1')]
-        with CaptureStdout() as cso:
-            utils.print_list(to_print, ['foo', 'bar'])
-        # Output should be sorted by the first key (foo)
-        self.assertEqual("""\
-+-----------+-----------+
-| foo       | bar       |
-+-----------+-----------+
-| fake_foo1 | None      |
-| fake_foo2 | fake_bar1 |
-+-----------+-----------+
-""", cso.read())
-
-    def test_print_list_with_None_data(self):
-        Row = collections.namedtuple('Row', ['foo', 'bar'])
-        to_print = [Row(foo='fake_foo1', bar=None),
-                    Row(foo='fake_foo2', bar='fake_bar1')]
-        with CaptureStdout() as cso:
-            utils.print_list(to_print, ['foo', 'bar'])
-        # Output should be sorted by the first key (foo)
-        self.assertEqual("""\
-+-----------+-----------+
-| foo       | bar       |
-+-----------+-----------+
-| fake_foo1 | -         |
-| fake_foo2 | fake_bar1 |
-+-----------+-----------+
-""", cso.read())
-
-    def test_print_list_with_list_sortby(self):
-        Row = collections.namedtuple('Row', ['foo', 'bar'])
-        to_print = [Row(foo='fake_foo1', bar='fake_bar2'),
-                    Row(foo='fake_foo2', bar='fake_bar1')]
-        with CaptureStdout() as cso:
-            utils.print_list(to_print, ['foo', 'bar'], sortby_index=1)
-        # Output should be sorted by the first key (bar)
-        self.assertEqual("""\
-+-----------+-----------+
-| foo       | bar       |
-+-----------+-----------+
-| fake_foo2 | fake_bar1 |
-| fake_foo1 | fake_bar2 |
-+-----------+-----------+
-""", cso.read())
-
-    def test_print_list_with_list_no_sort(self):
-        Row = collections.namedtuple('Row', ['foo', 'bar'])
-        to_print = [Row(foo='fake_foo2', bar='fake_bar1'),
-                    Row(foo='fake_foo1', bar='fake_bar2')]
-        with CaptureStdout() as cso:
-            utils.print_list(to_print, ['foo', 'bar'], sortby_index=None)
-        # Output should be in the order given
-        self.assertEqual("""\
-+-----------+-----------+
-| foo       | bar       |
-+-----------+-----------+
-| fake_foo2 | fake_bar1 |
-| fake_foo1 | fake_bar2 |
-+-----------+-----------+
-""", cso.read())
-
-    def test_print_list_with_generator(self):
-        Row = collections.namedtuple('Row', ['foo', 'bar'])
-
-        def gen_rows():
-            for row in [Row(foo='fake_foo1', bar='fake_bar2'),
-                        Row(foo='fake_foo2', bar='fake_bar1')]:
-                yield row
-        with CaptureStdout() as cso:
-            utils.print_list(gen_rows(), ['foo', 'bar'])
-        self.assertEqual("""\
-+-----------+-----------+
-| foo       | bar       |
-+-----------+-----------+
-| fake_foo1 | fake_bar2 |
-| fake_foo2 | fake_bar1 |
-+-----------+-----------+
-""", cso.read())
-
-
-class PrintDictTestCase(testtools.TestCase):
-
-    def test_print_dict(self):
-        data = {'foo': 'fake_foo', 'bar': 'fake_bar'}
-        with CaptureStdout() as cso:
-            utils.print_dict(data)
-        # Output should be sorted by the Property
-        self.assertEqual("""\
-+----------+----------+
-| Property | Value    |
-+----------+----------+
-| bar      | fake_bar |
-| foo      | fake_foo |
-+----------+----------+
-""", cso.read())
-
-    def test_print_dict_with_None_data(self):
-        Row = collections.namedtuple('Row', ['foo', 'bar'])
-        to_print = [Row(foo='fake_foo1', bar=None),
-                    Row(foo='fake_foo2', bar='fake_bar1')]
-        with CaptureStdout() as cso:
-            utils.print_list(to_print, ['foo', 'bar'])
-        # Output should be sorted by the first key (foo)
-        self.assertEqual("""\
-+-----------+-----------+
-| foo       | bar       |
-+-----------+-----------+
-| fake_foo1 | -         |
-| fake_foo2 | fake_bar1 |
-+-----------+-----------+
-""", cso.read())
diff --git a/senlinclient/tests/unit/v1/test_shell.py b/senlinclient/tests/unit/v1/test_shell.py
deleted file mode 100644
index 0e639d3e..00000000
--- a/senlinclient/tests/unit/v1/test_shell.py
+++ /dev/null
@@ -1,1952 +0,0 @@
-# Licensed 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 copy
-import fixtures
-import subprocess
-
-import mock
-from openstack import exceptions as oexc
-import six
-import testtools
-
-from senlinclient.common import exc
-from senlinclient.common.i18n import _
-from senlinclient.common import utils
-from senlinclient.v1 import shell as sh
-
-
-class ShellTest(testtools.TestCase):
-
-    def setUp(self):
-        super(ShellTest, self).setUp()
-        self.profile_args = {
-            'spec_file': mock.Mock(),
-            'name': 'stack_spec',
-            'metadata': {'user': 'demo'}
-        }
-        self.profile_spec = {
-            'type': 'os.heat.stack',
-            'version': 1.0,
-            'properties': {
-                'name': 'stack1',
-                'template': {"Template": "data"}
-            }
-        }
-        self.patch('senlinclient.v1.shell.show_deprecated')
-
-    # NOTE(pshchelo): this overrides the testtools.TestCase.patch method
-    # that does simple monkey-patching in favor of mock's patching
-    def patch(self, target, **kwargs):
-        mockfixture = self.useFixture(fixtures.MockPatch(target, **kwargs))
-        return mockfixture.mock
-
-    def _make_args(self, args):
-        """Convert a dict to an object."""
-        class Args(object):
-            def __init__(self, entries):
-                self.__dict__.update(entries)
-
-        return Args(args)
-
-    @mock.patch.object(utils, 'print_dict')
-    def test_do_build_info(self, mock_print):
-        service = mock.Mock()
-        result = mock.Mock()
-        service.get_build_info.return_value = result
-        sh.do_build_info(service)
-        formatters = {
-            'api': utils.json_formatter,
-            'engine': utils.json_formatter,
-        }
-        mock_print.assert_called_once_with(
-            {'api': result.api, 'engine': result.engine},
-            formatters=formatters)
-        service.get_build_info.assert_called_once_with()
-
-    @mock.patch.object(utils, 'print_list')
-    def test_do_profile_type_list(self, mock_print):
-        service = mock.Mock()
-        mock_type = mock.Mock(
-            support_status={
-                "1.0": [
-                    {
-                        "status": "SUPPORTED",
-                        "since": "2016.10"
-                    }
-                ]
-            }
-        )
-        mock_type.name = "fake_type"
-        types = [mock_type]
-        service.profile_types.return_value = types
-
-        sh.do_profile_type_list(service)
-
-        mock_print.assert_called_once_with(
-            mock.ANY,
-            ['name', 'version', 'support_status'],
-            sortby_index=0)
-        self.assertTrue(service.profile_types.called)
-
-    @mock.patch.object(utils, 'format_output')
-    def test_do_profile_type_show(self, mock_format):
-        service = mock.Mock()
-        fake_pt = mock.Mock()
-        fake_pt.to_dict.return_value = {'foo': 'bar'}
-        service.get_profile_type = mock.Mock(return_value=fake_pt)
-        args_dict = {
-            'format': 'json',
-            'type_name': 'os.nova.server'
-        }
-        args = self._make_args(args_dict)
-        sh.do_profile_type_show(service, args)
-        mock_format.assert_called_with({'foo': 'bar'}, format=args.format)
-        service.get_profile_type.assert_called_with('os.nova.server')
-        args.format = None
-        sh.do_profile_type_show(service, args)
-        mock_format.assert_called_with({'foo': 'bar'})
-
-    def test_do_profile_type_show_type_not_found(self):
-        service = mock.Mock()
-        args = {
-            'type_name': 'wrong_type',
-            'format': 'json'
-        }
-        args = self._make_args(args)
-        ex = oexc.ResourceNotFound
-        service.get_profile_type = mock.Mock(side_effect=ex)
-        ex = self.assertRaises(exc.CommandError,
-                               sh.do_profile_type_show,
-                               service, args)
-        self.assertEqual(_('Profile Type not found: wrong_type'),
-                         six.text_type(ex))
-
-    @mock.patch.object(utils, 'format_output')
-    def test_do_profile_type_operations(self, mock_format):
-        service = mock.Mock()
-        fake_pto = {'foo': 'bar'}
-        service.list_profile_type_operations = mock.Mock(return_value=fake_pto)
-        args_dict = {
-            'format': 'json',
-            'type_name': 'os.nova.server-1.0'
-        }
-        args = self._make_args(args_dict)
-        sh.do_profile_type_ops(service, args)
-        mock_format.assert_called_with({'foo': 'bar'}, format=args.format)
-        service.list_profile_type_operations.assert_called_with(
-            'os.nova.server-1.0')
-        args.format = None
-        sh.do_profile_type_ops(service, args)
-        mock_format.assert_called_with({'foo': 'bar'})
-
-    def test_do_profile_type_operations_type_not_found(self):
-        service = mock.Mock()
-        args = {
-            'type_name': 'wrong_type',
-            'format': 'json'
-        }
-        args = self._make_args(args)
-        ex = oexc.ResourceNotFound
-        service.list_profile_type_operations = mock.Mock(side_effect=ex)
-        ex = self.assertRaises(exc.CommandError,
-                               sh.do_profile_type_ops,
-                               service, args)
-        self.assertEqual(_('Profile Type not found: wrong_type'),
-                         six.text_type(ex))
-
-    @mock.patch.object(utils, 'print_list')
-    def test_do_profile_list(self, mock_print):
-        service = mock.Mock()
-        profiles = mock.Mock()
-        service.profiles.return_value = profiles
-        fields = ['id', 'name', 'type', 'created_at', 'project_id']
-        args = {
-            'limit': 20,
-            'marker': 'mark_id',
-            'sort': 'key:dir',
-            'global_project': True,
-            'filters': ['name=stack_spec']
-        }
-        queries = copy.deepcopy(args)
-        del queries['filters']
-        queries['name'] = 'stack_spec'
-        formatters = {}
-        args = self._make_args(args)
-        args.full_id = True
-        sh.do_profile_list(service, args)
-        service.profiles.assert_called_once_with(**queries)
-        mock_print.assert_called_with(profiles, fields, formatters=formatters,
-                                      sortby_index=None)
-
-        args.sort = None
-        sh.do_profile_list(service, args)
-        mock_print.assert_called_with(profiles, fields, formatters=formatters,
-                                      sortby_index=1)
-
-    @mock.patch.object(utils, 'nested_dict_formatter')
-    @mock.patch.object(utils, 'print_dict')
-    def test_show_profile(self, mock_print, mock_dict):
-        service = mock.Mock()
-        profile = mock.Mock()
-        profile_id = mock.Mock()
-        service.get_profile.return_value = profile
-        pro_to_dict = mock.Mock()
-        profile.to_dict.return_value = pro_to_dict
-        json_formatter = mock.Mock()
-        utils.json_formatter = json_formatter
-        dict_formatter = mock.Mock()
-        mock_dict.return_value = dict_formatter
-        formatters = {
-            'metadata': json_formatter,
-            'spec': dict_formatter
-        }
-        sh._show_profile(service, profile_id)
-        service.get_profile.assert_called_once_with(profile_id)
-        mock_dict.assert_called_once_with(['type', 'version', 'properties'],
-                                          ['property', 'value'])
-        mock_print.assert_called_once_with(pro_to_dict, formatters=formatters)
-
-    def test_show_profile_not_found(self):
-        service = mock.Mock()
-        ex = oexc.ResourceNotFound
-        service.get_profile.side_effect = ex
-        profile_id = 'wrong_id'
-        ex = self.assertRaises(exc.CommandError,
-                               sh._show_profile,
-                               service, profile_id)
-        self.assertEqual(_('Profile not found: wrong_id'), six.text_type(ex))
-        service.get_profile.assert_called_once_with(profile_id)
-
-    @mock.patch.object(sh, '_show_profile')
-    @mock.patch.object(utils, 'format_parameters')
-    @mock.patch.object(utils, 'process_stack_spec')
-    @mock.patch.object(utils, 'get_spec_content')
-    def test_do_profile_create(self, mock_get, mock_proc, mock_format,
-                               mock_show):
-        args = copy.deepcopy(self.profile_args)
-        args = self._make_args(args)
-        spec = copy.deepcopy(self.profile_spec)
-        mock_get.return_value = spec
-        stack_properties = mock.Mock()
-        mock_proc.return_value = stack_properties
-        mock_format.return_value = {'user': 'demo'}
-        params = {
-            'name': 'stack_spec',
-            'spec': spec,
-            'metadata': {'user': 'demo'},
-        }
-        service = mock.Mock()
-        profile = mock.Mock()
-        profile_id = mock.Mock()
-        profile.id = profile_id
-        service.create_profile.return_value = profile
-
-        sh.do_profile_create(service, args)
-
-        mock_get.assert_called_once_with(args.spec_file)
-        mock_proc.assert_called_once_with(self.profile_spec['properties'])
-        mock_format.assert_called_once_with(args.metadata)
-        service.create_profile.assert_called_once_with(**params)
-        mock_show.assert_called_once_with(service, profile_id)
-
-        # Miss 'type' key in spec file
-        del spec['type']
-        ex = self.assertRaises(exc.CommandError,
-                               sh.do_profile_create,
-                               service, args)
-        self.assertEqual(_("Missing 'type' key in spec file."),
-                         six.text_type(ex))
-        # Miss 'version' key in spec file
-        spec['type'] = 'os.heat.stack'
-        del spec['version']
-        ex = self.assertRaises(exc.CommandError,
-                               sh.do_profile_create,
-                               service, args)
-        self.assertEqual(_("Missing 'version' key in spec file."),
-                         six.text_type(ex))
-        # Miss 'properties' key in spec file
-        spec['version'] = 1.0
-        del spec['properties']
-        ex = self.assertRaises(exc.CommandError,
-                               sh.do_profile_create,
-                               service, args)
-        self.assertEqual(_("Missing 'properties' key in spec file."),
-                         six.text_type(ex))
-
-    @mock.patch.object(sh, '_show_profile')
-    def test_do_profile_show(self, mock_show):
-        service = mock.Mock()
-        args = {'id': 'profile_id'}
-        args = self._make_args(args)
-        sh.do_profile_show(service, args)
-        mock_show.assert_called_once_with(service, args.id)
-
-    @mock.patch.object(sh, '_show_profile')
-    @mock.patch.object(utils, 'format_parameters')
-    def test_do_profile_update(self, mock_format, mock_show):
-        args = copy.deepcopy(self.profile_args)
-        args = self._make_args(args)
-        mock_format.return_value = {'user': 'demo'}
-        service = mock.Mock()
-        profile = mock.Mock()
-        profile_id = mock.Mock()
-        profile.id = profile_id
-        args.id = 'FAKE_ID'
-        service.get_profile.return_value = profile
-
-        sh.do_profile_update(service, args)
-
-        mock_format.assert_called_once_with(args.metadata)
-        service.get_profile.assert_called_once_with('FAKE_ID')
-        params = {
-            'name': 'stack_spec',
-            'metadata': {'user': 'demo'},
-        }
-        service.update_profile.assert_called_once_with(profile, **params)
-        mock_show.assert_called_once_with(service, profile_id)
-
-    @mock.patch.object(utils, 'format_parameters')
-    def test_do_profile_update_not_found(self, mock_format):
-        service = mock.Mock()
-        args = copy.deepcopy(self.profile_args)
-        args = self._make_args(args)
-        args.id = 'FAKE_ID'
-        ex = oexc.ResourceNotFound
-        service.get_profile.side_effect = ex
-        ex = self.assertRaises(exc.CommandError,
-                               sh.do_profile_update,
-                               service, args)
-        self.assertEqual(_('Profile not found: FAKE_ID'),
-                         six.text_type(ex))
-        mock_format.assert_called_once_with(args.metadata)
-
-    def test_do_profile_delete(self):
-        service = mock.Mock()
-        args = {'id': ['profile_id']}
-        args = self._make_args(args)
-        sh.do_profile_delete(service, args)
-        service.delete_profile.assert_called_with('profile_id', False)
-
-    def test_do_profile_delete_not_found(self):
-        service = mock.Mock()
-        args = {'id': ['profile1', 'profile2']}
-        args = self._make_args(args)
-        sh.do_profile_delete(service, args)
-        service.delete_profile.side_effect = oexc.ResourceNotFound
-        ex = self.assertRaises(exc.CommandError,
-                               sh.do_profile_delete,
-                               service, args)
-        msg = _("Failed to delete some of the specified profile(s).")
-        self.assertEqual(msg, six.text_type(ex))
-        service.delete_profile.assert_called_with('profile2', False)
-
-    @mock.patch.object(utils, 'process_stack_spec')
-    @mock.patch.object(utils, 'get_spec_content')
-    def test_do_profile_validate(self, mock_get, mock_proc):
-        args = self._make_args({'spec_file': mock.Mock()})
-        spec = copy.deepcopy(self.profile_spec)
-        mock_get.return_value = spec
-        params = {
-            'spec': spec,
-        }
-        service = mock.Mock()
-        profile = mock.Mock()
-        profile.to_dict.return_value = {}
-        service.validate_profile.return_value = profile
-
-        sh.do_profile_validate(service, args)
-
-        service.validate_profile.assert_called_once_with(**params)
-
-        # Miss 'type' key in spec file
-        del spec['type']
-        ex = self.assertRaises(exc.CommandError,
-                               sh.do_profile_validate,
-                               service, args)
-        self.assertEqual(_("Missing 'type' key in spec file."),
-                         six.text_type(ex))
-        # Miss 'version' key in spec file
-        spec['type'] = 'os.heat.stack'
-        del spec['version']
-        ex = self.assertRaises(exc.CommandError,
-                               sh.do_profile_validate,
-                               service, args)
-        self.assertEqual(_("Missing 'version' key in spec file."),
-                         six.text_type(ex))
-        # Miss 'properties' key in spec file
-        spec['version'] = 1.0
-        del spec['properties']
-        ex = self.assertRaises(exc.CommandError,
-                               sh.do_profile_validate,
-                               service, args)
-        self.assertEqual(_("Missing 'properties' key in spec file."),
-                         six.text_type(ex))
-
-    @mock.patch.object(utils, 'print_list')
-    def test_do_policy_type_list(self, mock_print):
-        service = mock.Mock()
-        args = mock.Mock()
-        mock_type = mock.Mock(
-            support_status={
-                "1.0": [
-                    {
-                        "status": "SUPPORTED",
-                        "since": "2016.10"
-                    }
-                ]
-            }
-        )
-        mock_type.name = "fake_type"
-        types = [mock_type]
-        service.policy_types.return_value = types
-
-        sh.do_policy_type_list(service, args)
-
-        mock_print.assert_called_once_with(
-            mock.ANY,
-            ['name', 'version', 'support_status'],
-            sortby_index=0)
-        self.assertTrue(service.policy_types.called)
-
-    @mock.patch.object(utils, 'format_output')
-    def test_do_policy_type_show(self, mock_format):
-        service = mock.Mock()
-        args = {
-            'type_name': 'senlin.policy.deletion',
-            'format': 'yaml'
-        }
-        args = self._make_args(args)
-        res = mock.Mock()
-        pt = mock.Mock()
-        res.to_dict.return_value = pt
-        service.get_policy_type.return_value = res
-        sh.do_policy_type_show(service, args)
-        mock_format.assert_called_with(pt, format=args.format)
-
-        # no format attribute
-        args = {
-            'type_name': 'senlin.policy.deletion',
-            'format': None
-        }
-        args = self._make_args(args)
-        service.get_policy_type.return_value = res
-        sh.do_policy_type_show(service, args)
-        mock_format.assert_called_with(pt)
-
-    def test_do_policy_type_show_not_found(self):
-        service = mock.Mock()
-        args = {'type_name': 'BAD'}
-        args = self._make_args(args)
-
-        service.get_policy_type.side_effect = oexc.ResourceNotFound
-        ex = self.assertRaises(exc.CommandError,
-                               sh.do_policy_type_show, service, args)
-        msg = _('Policy type not found: BAD')
-        self.assertEqual(msg, six.text_type(ex))
-
-    @mock.patch.object(utils, 'print_list')
-    def test_do_receiver_list(self, mock_print):
-        service = mock.Mock()
-        params = {
-            'limit': 10,
-            'marker': 'fake_id',
-            'sort': 'key:dir',
-            'filters': ['filter_key=filter_value'],
-            'global_project': False,
-            'full_id': False,
-        }
-        fields = ['id', 'name', 'type', 'cluster_id', 'action', 'created_at']
-        args = self._make_args(params)
-        queries = copy.deepcopy(params)
-        del queries['filters']
-        queries['filter_key'] = 'filter_value'
-        r1 = mock.Mock()
-        r1.id = '01234567-abcd-efgh'
-        r1.cluster_id = 'abcdefgh-abcd-efgh'
-        receivers = [r1]
-        service.receivers.return_value = receivers
-        formatters = {
-            'id': mock.ANY,
-            'cluster_id': mock.ANY
-        }
-        sh.do_receiver_list(service, args)
-        mock_print.assert_called_with(receivers, fields,
-                                      formatters=formatters,
-                                      sortby_index=None)
-        # full_id is requested
-        args.full_id = True
-        sh.do_receiver_list(service, args)
-        mock_print.assert_called_with(receivers, fields,
-                                      formatters={},
-                                      sortby_index=None)
-
-        # default sorting
-        args.sort = None
-        sh.do_receiver_list(service, args)
-        mock_print.assert_called_with(receivers, fields,
-                                      formatters={},
-                                      sortby_index=0)
-
-    @mock.patch.object(utils, 'print_dict')
-    def test_show_receiver(self, mock_print):
-        service = mock.Mock()
-        receiver = mock.Mock()
-        receiver_id = '01234567-abcd-abcd-abcdef'
-        receiver.id = receiver_id
-        service.get_receiver.return_value = receiver
-        receiver_dict = mock.Mock()
-        receiver.to_dict.return_value = receiver_dict
-        sh._show_receiver(service, receiver_id)
-        formatters = {
-            'actor': utils.json_formatter,
-            'params': utils.json_formatter,
-            'channel': utils.json_formatter,
-        }
-        service.get_receiver.assert_called_once_with(receiver_id)
-        mock_print.assert_called_once_with(receiver_dict,
-                                           formatters=formatters)
-
-    def test_show_receiver_not_found(self):
-        service = mock.Mock()
-        receiver = mock.Mock()
-        receiver_id = 'wrong_id'
-        receiver.id = receiver_id
-
-        service.get_receiver.side_effect = oexc.ResourceNotFound
-        ex = self.assertRaises(exc.CommandError,
-                               sh._show_receiver, service, receiver_id)
-        self.assertEqual(_('Receiver not found: wrong_id'), six.text_type(ex))
-
-    @mock.patch.object(sh, '_show_receiver')
-    def test_do_receiver_show(self, mock_show):
-        service = mock.Mock()
-        args = {'id': 'receiver_id'}
-        args = self._make_args(args)
-        sh.do_receiver_show(service, args)
-        mock_show.assert_called_once_with(service,
-                                          receiver_id='receiver_id')
-
-    @mock.patch.object(sh, '_show_receiver')
-    def test_do_receiver_create_webhook(self, mock_show):
-        service = mock.Mock()
-        args = {
-            'name': 'receiver1',
-            'type': 'webhook',
-            'cluster': 'cluster1',
-            'action': 'CLUSTER_SCALE_IN',
-            'params': []
-        }
-        args = self._make_args(args)
-        params = {
-            'name': 'receiver1',
-            'type': 'webhook',
-            'cluster_id': 'cluster1',
-            'action': 'CLUSTER_SCALE_IN',
-            'params': {}
-        }
-        receiver = mock.Mock()
-        receiver.id = 'FAKE_ID'
-        service.create_receiver.return_value = receiver
-        sh.do_receiver_create(service, args)
-        service.create_receiver.assert_called_once_with(**params)
-        mock_show.assert_called_once_with(service, 'FAKE_ID')
-
-    def test_do_receiver_create_webhook_failed(self):
-        service = mock.Mock()
-        args = {
-            'name': 'receiver1',
-            'type': 'webhook',
-            'cluster': None,
-            'action': None,
-            'params': []
-        }
-        args = self._make_args(args)
-        ex = self.assertRaises(exc.CommandError,
-                               sh.do_receiver_create, service, args)
-        msg = _("cluster and action parameters are required to create webhook"
-                " type of receiver.")
-        self.assertEqual(msg, six.text_type(ex))
-
-    @mock.patch.object(sh, '_show_receiver')
-    def test_do_receiver_create_non_webhook(self, mock_show):
-        service = mock.Mock()
-        args = {
-            'name': 'receiver1',
-            'type': 'foo',
-            'cluster': None,
-            'action': None,
-            'params': []
-        }
-        args = self._make_args(args)
-        params = {
-            'name': 'receiver1',
-            'type': 'foo',
-            'cluster_id': None,
-            'action': None,
-            'params': {}
-        }
-        receiver = mock.Mock()
-        receiver.id = 'FAKE_ID'
-        service.create_receiver.return_value = receiver
-        sh.do_receiver_create(service, args)
-        service.create_receiver.assert_called_once_with(**params)
-        mock_show.assert_called_once_with(service, 'FAKE_ID')
-
-    @mock.patch.object(sh, '_show_receiver')
-    def test_do_receiver_update(self, mock_show):
-        service = mock.Mock()
-        args = {
-            'name': 'receiver2',
-            'id': 'receiver_id',
-            'action': 'CLUSTER_SCALE_OUT',
-            'params': ['key1=value1;key2=value2']
-        }
-        args = self._make_args(args)
-        params = {
-            'name': 'receiver2',
-            'action': 'CLUSTER_SCALE_OUT',
-            'params': {'key1': 'value1', 'key2': 'value2'}
-        }
-        receiver = mock.Mock()
-        receiver.id = 'receiver_id'
-        service.get_receiver.return_value = receiver
-        sh.do_receiver_update(service, args)
-        service.get_receiver.assert_called_once_with('receiver_id')
-        service.update_receiver.assert_called_once_with(
-            receiver, **params)
-        mock_show(service, receiver_id=receiver.id)
-
-    def test_do_receiver_delete(self):
-        service = mock.Mock()
-        args = {'id': ['FAKE']}
-        args = self._make_args(args)
-        service.delete_receiver = mock.Mock()
-        sh.do_receiver_delete(service, args)
-        service.delete_receiver.assert_called_once_with('FAKE', False)
-
-    def test_do_receiver_delete_not_found(self):
-        service = mock.Mock()
-        args = {'id': ['receiver_id']}
-        args = self._make_args(args)
-
-        service.delete_receiver.side_effect = oexc.ResourceNotFound
-        ex = self.assertRaises(exc.CommandError,
-                               sh.do_receiver_delete, service, args)
-        msg = _("Failed to delete some of the specified receiver(s).")
-        self.assertEqual(msg, six.text_type(ex))
-
-    @mock.patch.object(utils, 'print_list')
-    def test_do_policy_list(self, mock_print):
-        service = mock.Mock()
-        fields = ['id', 'name', 'type', 'created_at']
-        args = {
-            'limit': 20,
-            'marker': 'fake_id',
-            'sort': 'name',
-            'global_project': False,
-            'full_id': True,
-            'filters': ['name=stack_spec']
-        }
-        args = self._make_args(args)
-        queries = {
-            'limit': 20,
-            'marker': 'fake_id',
-            'sort': 'name',
-            'global_project': False,
-            'name': 'stack_spec',
-        }
-        policies = mock.Mock()
-        service.policies.return_value = policies
-        formatters = {}
-        sh.do_policy_list(service, args)
-        service.policies.assert_called_once_with(**queries)
-        mock_print.assert_called_once_with(
-            policies, fields, formatters=formatters, sortby_index=None)
-        mock_print.reset_mock()
-
-        args.sort = None
-        sh.do_policy_list(service, args)
-        mock_print.assert_called_once_with(
-            policies, fields, formatters=formatters, sortby_index=1)
-
-    @mock.patch.object(utils, 'print_dict')
-    def test_show_policy(self, mock_print):
-        service = mock.Mock()
-        formatters = {
-            'metadata': utils.json_formatter,
-            'spec': utils.json_formatter,
-        }
-        policy_id = 'fake_policy_id'
-        policy = mock.Mock()
-        policy.id = policy_id
-        service.get_policy.return_value = policy
-        policy_dict = mock.Mock()
-        policy.to_dict.return_value = policy_dict
-        sh._show_policy(service, policy_id)
-        mock_print.assert_called_once_with(policy_dict,
-                                           formatters=formatters)
-
-        # policy not found
-        ex = oexc.ResourceNotFound
-        service.get_policy.side_effect = ex
-        ex = self.assertRaises(exc.CommandError,
-                               sh._show_policy,
-                               service, policy_id)
-        msg = _('Policy not found: fake_policy_id')
-        self.assertEqual(msg, six.text_type(ex))
-
-    @mock.patch.object(sh, '_show_policy')
-    @mock.patch.object(utils, 'get_spec_content')
-    def test_do_policy_create(self, mock_get, mock_show):
-        service = mock.Mock()
-        spec = mock.Mock()
-        mock_get.return_value = spec
-        args = {
-            'name': 'new_policy',
-            'spec_file': 'policy_file',
-        }
-        args = self._make_args(args)
-        attrs = {
-            'name': 'new_policy',
-            'spec': spec,
-        }
-        policy = mock.Mock()
-        policy.id = 'policy_id'
-        service.create_policy.return_value = policy
-        sh.do_policy_create(service, args)
-        mock_get.assert_called_once_with(args.spec_file)
-        service.create_policy.assert_called_once_with(**attrs)
-        mock_show.assert_called_once_with(service, policy.id)
-
-    @mock.patch.object(sh, '_show_policy')
-    def test_do_policy_show(self, mock_show):
-        service = mock.Mock()
-        args = {'id': 'policy_id'}
-        args = self._make_args(args)
-        sh.do_policy_show(service, args)
-        mock_show.assert_called_once_with(service, policy_id='policy_id')
-
-    @mock.patch.object(sh, '_show_policy')
-    def test_do_policy_update(self, mock_show):
-        service = mock.Mock()
-        args = {
-            'name': 'deletion_policy',
-            'id': 'policy_id',
-        }
-        args = self._make_args(args)
-        params = {
-            'name': 'deletion_policy',
-        }
-        policy = mock.Mock()
-        service.get_policy.return_value = policy
-        policy.id = 'policy_id'
-        sh.do_policy_update(service, args)
-        service.get_policy.assert_called_once_with('policy_id')
-        service.update_policy.assert_called_once_with(
-            policy, **params)
-        mock_show(service, policy_id=policy.id)
-
-    def test_do_policy_delete(self):
-        service = mock.Mock()
-        args = {'id': ['policy_id']}
-        args = self._make_args(args)
-        service.delete_policy = mock.Mock()
-        sh.do_policy_delete(service, args)
-        service.delete_policy.assert_called_once_with('policy_id', False)
-
-    def test_do_policy_delete_not_found(self):
-        service = mock.Mock()
-        args = {'id': ['policy_id']}
-        args = self._make_args(args)
-
-        service.delete_policy.side_effect = oexc.ResourceNotFound
-        ex = self.assertRaises(exc.CommandError,
-                               sh.do_policy_delete, service, args)
-        msg = _("Failed to delete some of the specified policy(s).")
-        self.assertEqual(msg, six.text_type(ex))
-
-    @mock.patch.object(utils, 'get_spec_content')
-    def test_do_policy_validate(self, mock_get):
-        service = mock.Mock()
-        spec = mock.Mock()
-        mock_get.return_value = spec
-        args = {
-            'spec_file': 'policy_file',
-        }
-        args = self._make_args({'spec_file': 'policy_file'})
-        attrs = {
-            'spec': spec,
-        }
-        policy = mock.Mock()
-        policy.to_dict.return_value = {}
-        service.validate_policy.return_value = policy
-        sh.do_policy_validate(service, args)
-        mock_get.assert_called_once_with(args.spec_file)
-        service.validate_policy.assert_called_once_with(**attrs)
-
-    @mock.patch.object(utils, 'print_list')
-    def test_do_cluster_list(self, mock_print):
-        service = mock.Mock()
-        fields = ['id', 'name', 'status', 'created_at', 'updated_at']
-        args = {
-            'limit': 20,
-            'marker': 'fake_id',
-            'sort': 'key:dir',
-            'global_project': False,
-            'filters': ['status=ACTIVE'],
-        }
-        queries = copy.deepcopy(args)
-        del queries['filters']
-        queries['status'] = 'ACTIVE'
-        args = self._make_args(args)
-        clusters = mock.Mock()
-        service.clusters.return_value = clusters
-        args.full_id = True
-        formatters = {}
-        sh.do_cluster_list(service, args)
-        service.clusters.assert_called_once_with(**queries)
-        mock_print.assert_called_once_with(clusters, fields,
-                                           formatters=formatters,
-                                           sortby_index=None)
-        args.sort = None
-        sh.do_cluster_list(service, args)
-        mock_print.assert_called_with(clusters, fields,
-                                      formatters={}, sortby_index=3)
-
-    @mock.patch.object(utils, 'print_dict')
-    def test_show_cluster(self, mock_print):
-        service = mock.Mock()
-        cluster_id = 'cluster_id'
-        cluster = mock.Mock()
-        cluster.id = cluster_id
-        service.get_cluster.return_value = cluster
-        formatters = {
-            'config': utils.json_formatter,
-            'metadata': utils.json_formatter,
-            'node_ids': utils.list_formatter,
-        }
-        cluster_dict = mock.Mock()
-        cluster.to_dict.return_value = cluster_dict
-        sh._show_cluster(service, cluster_id)
-        mock_print.assert_called_once_with(cluster_dict, formatters=formatters)
-
-    @mock.patch.object(sh, '_show_cluster')
-    def test_do_cluster_create(self, mock_show):
-        service = mock.Mock()
-        args = {
-            'name': 'CLUSTER1',
-            'profile': 'profile1',
-            'min_size': 1,
-            'max_size': 10,
-            'desired_capacity': 5,
-            'metadata': ['user=demo'],
-            'config': {},
-            'timeout': 200,
-        }
-        attrs = copy.deepcopy(args)
-        attrs['profile_id'] = args['profile']
-        args = self._make_args(args)
-        del attrs['profile']
-        attrs['metadata'] = {'user': 'demo'}
-        cluster = mock.Mock()
-        service.create_cluster.return_value = cluster
-        cluster.id = 'cluster_id'
-        sh.do_cluster_create(service, args)
-        service.create_cluster.assert_called_once_with(**attrs)
-        mock_show.assert_called_once_with(service, 'cluster_id')
-
-    def test_do_cluster_delete(self):
-        service = mock.Mock()
-        args = {'id': ['CID'], 'force_delete': False}
-        args = self._make_args(args)
-        service.delete_cluster = mock.Mock()
-        sh.do_cluster_delete(service, args)
-        service.delete_cluster.assert_called_once_with('CID', False, False)
-
-    def test_do_cluster_delete_force(self):
-        service = mock.Mock()
-        args = {'id': ['CID'], 'force_delete': True}
-        args = self._make_args(args)
-        service.delete_cluster = mock.Mock()
-        sh.do_cluster_delete(service, args)
-        service.delete_cluster.assert_called_once_with('CID', True, False)
-
-    @mock.patch('subprocess.Popen')
-    def test__run_script(self, mock_proc):
-        x_proc = mock.Mock(returncode=0)
-        x_stdout = 'OUTPUT'
-        x_stderr = 'ERROR'
-        x_proc.communicate.return_value = (x_stdout, x_stderr)
-        mock_proc.return_value = x_proc
-
-        addr = {
-            'private': [
-                {
-                    'OS-EXT-IPS:type': 'floating',
-                    'version': 4,
-                    'addr': '1.2.3.4',
-                }
-            ]
-        }
-        output = {}
-
-        sh._run_script('NODE_ID', addr, 'private', 'floating', 22, 'john',
-                       False, 'identity_path', 'echo foo', '-f bar',
-                       output=output)
-        mock_proc.assert_called_once_with(
-            ['ssh', '-4', '-p22', '-i identity_path', '-f bar', 'john@1.2.3.4',
-             'echo foo'],
-            stdout=subprocess.PIPE)
-        self.assertEqual(
-            {'status': 'SUCCEEDED (0)', 'output': 'OUTPUT', 'error': 'ERROR'},
-            output)
-
-    def test__run_script_network_not_found(self):
-        addr = {'foo': 'bar'}
-        output = {}
-
-        sh._run_script('NODE_ID', addr, 'private', 'floating', 22, 'john',
-                       False, 'identity_path', 'echo foo', '-f bar',
-                       output=output)
-        self.assertEqual(
-            {'status': 'FAILED',
-             'reason': "Node 'NODE_ID' is not attached to network 'private'."
-             },
-            output)
-
-    def test__run_script_more_than_one_network(self):
-        addr = {'foo': 'bar', 'koo': 'tar'}
-        output = {}
-
-        sh._run_script('NODE_ID', addr, '', 'floating', 22, 'john',
-                       False, 'identity_path', 'echo foo', '-f bar',
-                       output=output)
-        self.assertEqual(
-            {'status': 'FAILED',
-             'reason': "Node 'NODE_ID' is attached to more than one "
-                       "network. Please pick the network to use."},
-            output)
-
-    def test__run_script_no_network(self):
-        addr = {}
-        output = {}
-
-        sh._run_script('NODE_ID', addr, '', 'floating', 22, 'john',
-                       False, 'identity_path', 'echo foo', '-f bar',
-                       output=output)
-
-        self.assertEqual(
-            {'status': 'FAILED',
-             'reason': "Node 'NODE_ID' is not attached to any network."},
-            output)
-
-    def test__run_script_no_matching_address(self):
-        addr = {
-            'private': [
-                {
-                    'OS-EXT-IPS:type': 'fixed',
-                    'version': 4,
-                    'addr': '1.2.3.4',
-                }
-            ]
-        }
-        output = {}
-
-        sh._run_script('NODE_ID', addr, 'private', 'floating', 22, 'john',
-                       False, 'identity_path', 'echo foo', '-f bar',
-                       output=output)
-        self.assertEqual(
-            {'status': 'FAILED',
-             'reason': "No address that would match network 'private' and "
-                       "type 'floating' of IPv4 has been found for node "
-                       "'NODE_ID'."},
-            output)
-
-    def test__run_script_more_than_one_address(self):
-        addr = {
-            'private': [
-                {
-                    'OS-EXT-IPS:type': 'fixed',
-                    'version': 4,
-                    'addr': '1.2.3.4',
-                },
-                {
-                    'OS-EXT-IPS:type': 'fixed',
-                    'version': 4,
-                    'addr': '5.6.7.8',
-                },
-            ]
-        }
-
-        output = {}
-
-        sh._run_script('NODE_ID', addr, 'private', 'fixed', 22, 'john',
-                       False, 'identity_path', 'echo foo', '-f bar',
-                       output=output)
-        self.assertEqual(
-            {'status': 'FAILED',
-             'reason': "More than one IPv4 fixed address found."},
-            output)
-
-    @mock.patch('threading.Thread')
-    @mock.patch.object(sh, '_run_script')
-    def test_do_cluster_run(self, mock_run, mock_thread):
-        service = mock.Mock()
-        args = {
-            'script': 'script_name',
-            'network': 'network_name',
-            'address_type': 'fixed',
-            'port': 22,
-            'user': 'root',
-            'ipv6': False,
-            'identity_file': 'identity_filename',
-            'ssh_options': '-f oo',
-        }
-        args = self._make_args(args)
-        args.id = 'CID'
-        addr1 = {'addresses': 'ADDR CONTENT 1'}
-        addr2 = {'addresses': 'ADDR CONTENT 2'}
-        attributes = [
-            mock.Mock(node_id='NODE1', attr_value=addr1),
-            mock.Mock(node_id='NODE2', attr_value=addr2)
-        ]
-        service.collect_cluster_attrs.return_value = attributes
-
-        th1 = mock.Mock()
-        th2 = mock.Mock()
-        mock_thread.side_effect = [th1, th2]
-        fake_script = 'blah blah'
-        with mock.patch('senlinclient.v1.shell.open',
-                        mock.mock_open(read_data=fake_script)) as mock_open:
-            sh.do_cluster_run(service, args)
-
-        service.collect_cluster_attrs.assert_called_once_with(
-            args.id, 'details')
-        mock_open.assert_called_once_with('script_name', 'r')
-        mock_thread.assert_has_calls([
-            mock.call(target=mock_run,
-                      args=('NODE1', 'ADDR CONTENT 1', 'network_name',
-                            'fixed', 22, 'root', False, 'identity_filename',
-                            'blah blah', '-f oo'),
-                      kwargs={'output': {}}),
-            mock.call(target=mock_run,
-                      args=('NODE2', 'ADDR CONTENT 2', 'network_name',
-                            'fixed', 22, 'root', False, 'identity_filename',
-                            'blah blah', '-f oo'),
-                      kwargs={'output': {}})
-        ])
-        th1.start.assert_called_once_with()
-        th2.start.assert_called_once_with()
-        th1.join.assert_called_once_with()
-        th2.join.assert_called_once_with()
-
-    @mock.patch.object(sh, '_show_cluster')
-    def test_do_cluster_update(self, mock_show):
-        service = mock.Mock()
-        args = {
-            'profile': 'test_profile',
-            'profile_only': 'false',
-            'name': 'CLUSTER1',
-            'metadata': ['user=demo'],
-            'timeout': 100,
-        }
-        attrs = copy.deepcopy(args)
-        attrs['metadata'] = {'user': 'demo'}
-        attrs['profile_id'] = 'test_profile'
-        attrs['profile_only'] = False
-        del attrs['profile']
-        args = self._make_args(args)
-        args.id = 'CID'
-        cluster = mock.Mock()
-        cluster.id = 'CID'
-        service.get_cluster.return_value = cluster
-        service.update_cluster = mock.Mock()
-
-        sh.do_cluster_update(service, args)
-
-        service.get_cluster.assert_called_once_with('CID')
-        service.update_cluster.assert_called_once_with(cluster, **attrs)
-        mock_show.assert_called_once_with(service, 'CID')
-
-    @mock.patch.object(sh, '_show_cluster')
-    def test_do_cluster_show(self, mock_show):
-        service = mock.Mock()
-        args = {'id': 'cluster_id'}
-        args = self._make_args(args)
-        sh.do_cluster_show(service, args)
-        mock_show.assert_called_once_with(service, 'cluster_id')
-
-    @mock.patch.object(utils, 'print_list')
-    def test_do_cluster_node_list(self, mock_print):
-        service = mock.Mock()
-        args = {
-            'id': 'cluster_id',
-            'limit': 20,
-            'marker': 'marker_id',
-            'filters': ['status=ACTIVE'],
-        }
-        queries = copy.deepcopy(args)
-        queries['cluster_id'] = args['id']
-        del queries['id']
-        del queries['filters']
-        queries['status'] = 'ACTIVE'
-        args = self._make_args(args)
-        args.full_id = True
-        nodes = mock.Mock()
-        service.nodes.return_value = nodes
-        formatters = {}
-        fields = ['id', 'name', 'index', 'status', 'physical_id', 'created_at']
-        sh.do_cluster_node_list(service, args)
-        service.nodes.assert_called_once_with(**queries)
-        mock_print.assert_called_once_with(nodes, fields,
-                                           formatters=formatters,
-                                           sortby_index=5)
-
-    def test_do_cluster_node_add(self):
-        service = mock.Mock()
-        args = {
-            'id': 'cluster_id',
-            'nodes': 'node1,node2'
-        }
-        args = self._make_args(args)
-        node_ids = ['node1', 'node2']
-        resp = {'action': 'CLUSTER_NODE_ADD'}
-        service.cluster_add_nodes.return_value = resp
-        sh.do_cluster_node_add(service, args)
-        service.cluster_add_nodes.assert_called_once_with(
-            'cluster_id', node_ids)
-
-    def test_do_cluster_node_del(self):
-        service = mock.Mock()
-        args = {
-            'id': 'cluster_id',
-            'nodes': 'node1,node2',
-            'destroy_after_deletion': False
-        }
-        args = self._make_args(args)
-        node_ids = ['node1', 'node2']
-        resp = {'action': 'CLUSTER_NODE_DEL'}
-        service.cluster_del_nodes.return_value = resp
-
-        sh.do_cluster_node_del(service, args)
-
-        service.cluster_del_nodes.assert_called_once_with(
-            'cluster_id',
-            node_ids,
-            destroy_after_deletion=False)
-
-    def test_do_cluster_resize(self):
-        service = mock.Mock()
-        args = {
-            'id': 'cluster_id',
-            'capacity': 2,
-            'adjustment': 1,
-            'percentage': 50.0,
-            'min_size': 1,
-            'max_size': 10,
-            'min_step': 1,
-            'strict': True,
-        }
-        args = self._make_args(args)
-        ex = self.assertRaises(exc.CommandError,
-                               sh.do_cluster_resize,
-                               service, args)
-        msg = _("Only one of 'capacity', 'adjustment' and "
-                "'percentage' can be specified.")
-        self.assertEqual(msg, six.text_type(ex))
-
-        # capacity
-        args.adjustment = None
-        args.percentage = None
-        args.min_step = None
-        action_args = {
-            'adjustment_type': 'EXACT_CAPACITY',
-            'number': 2,
-            'min_size': 1,
-            'max_size': 10,
-            'strict': True,
-            'min_step': None,
-        }
-        resp = {'action': 'action_id'}
-        service.cluster_resize.return_value = resp
-        sh.do_cluster_resize(service, args)
-        service.cluster_resize.assert_called_with('cluster_id', **action_args)
-
-        # capacity is smaller than 0
-        args.capacity = -1
-        ex = self.assertRaises(exc.CommandError,
-                               sh.do_cluster_resize,
-                               service, args)
-        msg = _('Cluster capacity must be larger than '
-                'or equal to zero.')
-        self.assertEqual(msg, six.text_type(ex))
-
-        # adjustment
-        args.capacity = None
-        args.percentage = None
-        args.adjustment = 1
-        action_args['adjustment_type'] = 'CHANGE_IN_CAPACITY'
-        action_args['number'] = 1
-        sh.do_cluster_resize(service, args)
-        service.cluster_resize.assert_called_with('cluster_id', **action_args)
-
-        # adjustment is 0
-        args.adjustment = 0
-        ex = self.assertRaises(exc.CommandError,
-                               sh.do_cluster_resize,
-                               service, args)
-        msg = _('Adjustment cannot be zero.')
-        self.assertEqual(msg, six.text_type(ex))
-
-        # percentage
-        args.capacity = None
-        args.percentage = 50.0
-        args.adjustment = None
-        action_args['adjustment_type'] = 'CHANGE_IN_PERCENTAGE'
-        action_args['number'] = 50.0
-        sh.do_cluster_resize(service, args)
-        service.cluster_resize.assert_called_with('cluster_id', **action_args)
-
-        # percentage is 0
-        args.percentage = 0
-        ex = self.assertRaises(exc.CommandError,
-                               sh.do_cluster_resize,
-                               service, args)
-        msg = _('Percentage cannot be zero.')
-        self.assertEqual(msg, six.text_type(ex))
-
-        # min_step is not None while percentage is None
-        args.capacity = 2
-        args.percentage = None
-        args.adjustment = None
-        args.min_step = 1
-        ex = self.assertRaises(exc.CommandError,
-                               sh.do_cluster_resize,
-                               service, args)
-        msg = _('Min step is only used with percentage.')
-        self.assertEqual(msg, six.text_type(ex))
-
-        # min_size < 0
-        args.capacity = 2
-        args.percentage = None
-        args.adjustment = None
-        args.min_step = None
-        args.min_size = -1
-        ex = self.assertRaises(exc.CommandError,
-                               sh.do_cluster_resize,
-                               service, args)
-        msg = _('Min size cannot be less than zero.')
-        self.assertEqual(msg, six.text_type(ex))
-
-        # max_size < min_size
-        args.capacity = 5
-        args.percentage = None
-        args.adjustment = None
-        args.min_step = None
-        args.min_size = 5
-        args.max_size = 4
-        ex = self.assertRaises(exc.CommandError,
-                               sh.do_cluster_resize,
-                               service, args)
-        msg = _('Min size cannot be larger than max size.')
-        self.assertEqual(msg, six.text_type(ex))
-
-        # min_size > capacity
-        args.capacity = 5
-        args.percentage = None
-        args.adjustment = None
-        args.min_step = None
-        args.min_size = 6
-        args.max_size = 8
-        ex = self.assertRaises(exc.CommandError,
-                               sh.do_cluster_resize,
-                               service, args)
-        msg = _('Min size cannot be larger than the specified capacity')
-        self.assertEqual(msg, six.text_type(ex))
-
-        # max_size < capacity
-        args.capacity = 5
-        args.percentage = None
-        args.adjustment = None
-        args.min_step = None
-        args.min_size = 1
-        args.max_size = 4
-        ex = self.assertRaises(exc.CommandError,
-                               sh.do_cluster_resize,
-                               service, args)
-        msg = _('Max size cannot be less than the specified capacity.')
-        self.assertEqual(msg, six.text_type(ex))
-
-    def test_do_cluster_scale_out(self):
-        service = mock.Mock()
-        args = {
-            'id': 'cluster_id',
-            'count': 3,
-        }
-        args = self._make_args(args)
-        resp = {'action': 'action_id'}
-        service.cluster_scale_out.return_value = resp
-        sh.do_cluster_scale_out(service, args)
-        service.cluster_scale_out.assert_called_once_with(
-            'cluster_id', 3)
-
-    def test_do_cluster_scale_in(self):
-        service = mock.Mock()
-        args = {
-            'id': 'cluster_id',
-            'count': 3,
-        }
-        args = self._make_args(args)
-        resp = {'action': 'action_id'}
-        service.cluster_scale_in.return_value = resp
-
-        sh.do_cluster_scale_in(service, args)
-
-        service.cluster_scale_in.assert_called_once_with('cluster_id', 3)
-
-    @mock.patch.object(utils, 'print_list')
-    def test_do_cluster_policy_list(self, mock_print):
-        fields = ['policy_id', 'policy_name', 'policy_type', 'is_enabled']
-        service = mock.Mock()
-        args = {
-            'id': 'C1',
-            'filters': ['enabled=True'],
-            'sort': 'enabled:asc',
-            'full_id': True,
-        }
-        args = self._make_args(args)
-        queries = {
-            'sort': 'enabled:asc',
-            'enabled': 'True',
-        }
-        cluster = mock.Mock()
-        cluster.id = 'C1'
-        service.get_cluster.return_value = cluster
-        policies = mock.Mock()
-        service.cluster_policies.return_value = policies
-        sh.do_cluster_policy_list(service, args)
-        service.get_cluster.assert_called_once_with('C1')
-        service.cluster_policies.assert_called_once_with('C1', **queries)
-        formatters = {}
-        mock_print.assert_called_once_with(policies, fields,
-                                           formatters=formatters,
-                                           sortby_index=None)
-
-    @mock.patch.object(utils, 'print_dict')
-    def test_do_cluster_policy_show(self, mock_print):
-        class Binding(object):
-            def to_dict(self):
-                pass
-
-        service = mock.Mock()
-        args = {
-            'id': 'CC',
-            'policy': 'PP',
-        }
-        args = self._make_args(args)
-        binding = Binding()
-        service.get_cluster_policy.return_value = binding
-        sh.do_cluster_policy_show(service, args)
-        service.get_cluster_policy.assert_called_once_with('PP', 'CC')
-        mock_print.assert_called_once_with(binding.to_dict())
-
-    def test_do_cluster_policy_attach(self):
-        service = mock.Mock()
-        args = {
-            'id': 'C1',
-            'policy': 'P1',
-            'enabled': True,
-        }
-        args = self._make_args(args)
-        kwargs = {
-            'enabled': True,
-        }
-        service.cluster_attach_policy.return_value = {'action': 'action_id'}
-        sh.do_cluster_policy_attach(service, args)
-        service.cluster_attach_policy.assert_called_once_with('C1', 'P1',
-                                                              **kwargs)
-
-    def test_do_cluster_policy_detach(self):
-        args = {
-            'id': 'CC',
-            'policy': 'PP'
-        }
-        service = mock.Mock()
-        args = self._make_args(args)
-        resp = {'action': 'action_id'}
-        service.cluster_detach_policy.return_value = resp
-        sh.do_cluster_policy_detach(service, args)
-        service.cluster_detach_policy.assert_called_once_with('CC', 'PP')
-
-    def test_do_cluster_policy_update(self):
-        service = mock.Mock()
-        args = {
-            'id': 'C1',
-            'policy': 'policy1',
-            'enabled': True,
-        }
-        args = self._make_args(args)
-        kwargs = {
-            'enabled': True,
-        }
-        service.cluster_update_policy.return_value = {'action': 'action_id'}
-
-        sh.do_cluster_policy_update(service, args)
-
-        service.cluster_update_policy.assert_called_once_with('C1', 'policy1',
-                                                              **kwargs)
-
-    def test_do_cluster_check(self):
-        service = mock.Mock()
-        args = self._make_args({'id': ['cluster1']})
-        service.check_cluster = mock.Mock()
-        service.check_cluster.return_value = {'action': 'action_id'}
-        sh.do_cluster_check(service, args)
-
-        service.check_cluster.assert_called_once_with('cluster1')
-
-    def test_do_cluster_recover(self):
-        service = mock.Mock()
-        args = {
-            'id': ['cluster1'],
-            'check': 'false'
-        }
-        args = self._make_args(args)
-        params = {
-            'check': False
-        }
-        service.recover_cluster = mock.Mock()
-        service.recover_cluster.return_value = {'action': 'action_id'}
-
-        sh.do_cluster_recover(service, args)
-
-        service.recover_cluster.assert_called_once_with('cluster1', **params)
-
-    def test_do_cluster_collect(self):
-        service = mock.Mock()
-        args = self._make_args({
-            'path': 'path.to.attr',
-            'list': False,
-            'full_id': False,
-            'id': 'cluster1'
-        })
-        service.collect_cluster_attrs = mock.Mock(
-            return_value=[mock.Mock(node_id='FAKE1', attr_value='VALUE1')]
-        )
-
-        sh.do_cluster_collect(service, args)
-
-        service.collect_cluster_attrs.assert_called_once_with(
-            'cluster1', 'path.to.attr')
-
-    @mock.patch.object(utils, 'print_list')
-    def test_do_cluster_collect_as_list(self, mock_print):
-        service = mock.Mock()
-        args = self._make_args({
-            'path': 'path.to.attr',
-            'list': True,
-            'full_id': True,
-            'id': 'cluster1'
-        })
-        attrs = [mock.Mock(node_id='FAKE1', attr_value='VALUE1')]
-        fields = ['node_id', 'attr_value']
-        formatters = {'attr_value': mock.ANY}
-        service.collect_cluster_attrs = mock.Mock(return_value=attrs)
-
-        sh.do_cluster_collect(service, args)
-
-        service.collect_cluster_attrs.assert_called_once_with(
-            'cluster1', 'path.to.attr')
-        mock_print.assert_called_once_with(attrs, fields,
-                                           formatters=formatters)
-
-    @mock.patch.object(utils, 'print_list')
-    def test_do_cluster_collect_as_list_with_shortid(self, mock_print):
-        service = mock.Mock()
-        args = self._make_args({
-            'path': 'path.to.attr',
-            'list': True,
-            'full_id': False,
-            'id': 'cluster1'
-        })
-        attrs = [mock.Mock(node_id='FAKE1', attr_value='VALUE1')]
-        fields = ['node_id', 'attr_value']
-        formatters = {'node_id': mock.ANY, 'attr_value': mock.ANY}
-        service.collect_cluster_attrs = mock.Mock(return_value=attrs)
-
-        sh.do_cluster_collect(service, args)
-
-        service.collect_cluster_attrs.assert_called_once_with(
-            'cluster1', 'path.to.attr')
-        mock_print.assert_called_once_with(attrs, fields,
-                                           formatters=formatters)
-
-    def test_do_cluster_op(self):
-        service = mock.Mock()
-        args = {
-            'id': 'cluster1',
-            'operation': 'dance',
-            'params': ['style=tango']
-        }
-        args = self._make_args(args)
-        attrs = {
-            'style': 'tango'
-        }
-        service.perform_operation_on_cluster = mock.Mock()
-
-        sh.do_cluster_op(service, args)
-
-        service.perform_operation_on_cluster.assert_called_once_with(
-            'cluster1', 'dance', **attrs)
-
-    def test_do_cluster_op_not_found(self):
-        service = mock.Mock()
-        ex = exc.HTTPNotFound
-        service.perform_operation_on_cluster.side_effect = ex
-        args = {
-            'id': 'cluster1',
-            'operation': 'swim',
-            'params': []
-        }
-        args = self._make_args(args)
-
-        ex = self.assertRaises(exc.CommandError,
-                               sh.do_cluster_op, service, args)
-        msg = _('Cluster "cluster1" is not found')
-        self.assertEqual(msg, six.text_type(ex))
-
-    @mock.patch.object(utils, 'print_list')
-    def test_do_node_list(self, mock_print):
-        service = mock.Mock()
-        fields = ['id', 'name', 'index', 'status', 'cluster_id', 'physical_id',
-                  'profile_name', 'created_at', 'updated_at', 'project_id']
-        args = {
-            'cluster': 'cluster1',
-            'sort': 'name:asc',
-            'limit': 20,
-            'marker': 'marker_id',
-            'global_project': True,
-            'filters': ['status=active'],
-            'full_id': True,
-        }
-        queries = {
-            'cluster_id': 'cluster1',
-            'sort': 'name:asc',
-            'limit': 20,
-            'marker': 'marker_id',
-            'global_project': True,
-            'status': 'active',
-        }
-        args = self._make_args(args)
-        nodes = mock.Mock()
-        service.nodes.return_value = nodes
-        formatters = {}
-        sh.do_node_list(service, args)
-        mock_print.assert_called_once_with(nodes, fields,
-                                           formatters=formatters,
-                                           sortby_index=None)
-        service.nodes.assert_called_once_with(**queries)
-
-    @mock.patch.object(utils, 'print_dict')
-    @mock.patch.object(utils, 'nested_dict_formatter')
-    def test_show_node(self, mock_nested, mock_print):
-        service = mock.Mock()
-        node_id = 'node1'
-        node = mock.Mock()
-        service.get_node.return_value = node
-        formatters = {
-            'metadata': utils.json_formatter,
-            'data': utils.json_formatter,
-            'dependents': utils.json_formatter,
-        }
-        data = mock.Mock()
-        node.to_dict.return_value = data
-
-        sh._show_node(service, node_id, show_details=False)
-
-        service.get_node.assert_called_once_with(node_id, details=False)
-        mock_print.assert_called_once_with(data, formatters=formatters)
-
-    @mock.patch.object(sh, '_show_node')
-    def test_do_node_create(self, mock_show):
-        args = {
-            'name': 'node1',
-            'cluster': 'cluster1',
-            'profile': 'profile1',
-            'role': 'master',
-            'metadata': ['user=demo'],
-        }
-        args = self._make_args(args)
-        attrs = {
-            'name': 'node1',
-            'cluster_id': 'cluster1',
-            'profile_id': 'profile1',
-            'role': 'master',
-            'metadata': {'user': 'demo'},
-        }
-        service = mock.Mock()
-        node = mock.Mock()
-        node.id = 'node_id'
-        service.create_node.return_value = node
-        sh.do_node_create(service, args)
-        service.create_node.assert_called_once_with(**attrs)
-        mock_show.assert_called_once_with(service, 'node_id')
-
-    @mock.patch.object(sh, '_show_node')
-    def test_do_node_adopt(self, mock_show):
-        args = {
-            'identity': 'fake-resoruce-id',
-            'name': 'adopt-node1',
-            'role': 'master',
-            'metadata': ['user=demo'],
-            'snapshot': None,
-            'overrides': '{"networks": [{"network": "fake-net-name"}]}',
-            'type': 'os.nova.server-1.0',
-            'preview': False
-        }
-        args = self._make_args(args)
-        attrs = {
-            'identity': 'fake-resoruce-id',
-            'name': 'adopt-node1',
-            'role': 'master',
-            'metadata': {'user': 'demo'},
-            'overrides': {'networks': [{'network': 'fake-net-name'}]},
-            'snapshot': None,
-            'type': 'os.nova.server-1.0',
-        }
-        service = mock.Mock()
-        node = mock.Mock()
-        node.id = 'node_id'
-        service.adopt_node.return_value = node
-        sh.do_node_adopt(service, args)
-        service.adopt_node.assert_called_once_with(**attrs)
-        mock_show.assert_called_once_with(service, 'node_id')
-
-    @mock.patch.object(utils, 'print_dict')
-    @mock.patch.object(utils, 'nested_dict_formatter')
-    def test_do_node_adopt_preview(self, mock_nest, mock_print):
-        args = {
-            'identity': 'fake-resoruce-id',
-            'snapshot': None,
-            'overrides': '{"networks": [{"network": "fake-net-name"}]}',
-            'type': 'os.nova.server-1.0',
-            'preview': True
-        }
-        args = self._make_args(args)
-        attrs = {
-            'identity': 'fake-resoruce-id',
-            'overrides': {'networks': [{'network': 'fake-net-name'}]},
-            'snapshot': None,
-            'type': 'os.nova.server-1.0',
-        }
-
-        fake_preview = {
-            "node_profile": {
-                "node_preview": {
-                    "properties": {
-                    },
-                    "type": "os.nova.server",
-                    "version": "1.0"}
-            }
-        }
-
-        service = mock.Mock()
-        service.adopt_node.return_value = fake_preview
-        sh.do_node_adopt(service, args)
-        service.adopt_node.assert_called_once_with(True, **attrs)
-
-        formatters = {}
-        formatters['node_preview'] = utils.nested_dict_formatter(
-            ['type', 'version', 'properties'],
-            ['property', 'value'])
-        mock_print.assert_called_once_with(fake_preview['node_profile'],
-                                           formatters=formatters)
-
-    @mock.patch.object(sh, '_show_node')
-    def test_do_node_show(self, mock_show):
-        service = mock.Mock()
-        args = {
-            'id': 'node1',
-            'details': False
-        }
-        args = self._make_args(args)
-        sh.do_node_show(service, args)
-        mock_show.assert_called_once_with(service, 'node1', False)
-
-    @mock.patch.object(sh, '_show_node')
-    def test_do_node_update(self, mock_show):
-        service = mock.Mock()
-        args = {
-            'id': 'node_id',
-            'name': 'node1',
-            'role': 'master',
-            'profile': 'profile1',
-            'metadata': ['user=demo'],
-        }
-        args = self._make_args(args)
-        attrs = {
-            'name': 'node1',
-            'role': 'master',
-            'profile_id': 'profile1',
-            'metadata': {'user': 'demo'},
-        }
-        node = mock.Mock()
-        node.id = 'node_id'
-        service.get_node.return_value = node
-        sh.do_node_update(service, args)
-        service.get_node.assert_called_once_with('node_id')
-        service.update_node.assert_called_once_with(node, **attrs)
-        mock_show.assert_called_once_with(service, 'node_id')
-
-    def test_do_node_delete(self):
-        service = mock.Mock()
-        args = self._make_args({'id': ['node1'], 'force_delete': False})
-        service.delete_node = mock.Mock()
-
-        sh.do_node_delete(service, args)
-
-        service.delete_node.assert_called_once_with('node1', False, False)
-
-    def test_do_node_delete_force(self):
-        service = mock.Mock()
-        args = self._make_args({'id': ['node1'], 'force_delete': True})
-        service.delete_node = mock.Mock()
-
-        sh.do_node_delete(service, args)
-
-        service.delete_node.assert_called_once_with('node1', True, False)
-
-    def test_do_node_check(self):
-        service = mock.Mock()
-        args = self._make_args({'id': ['node1']})
-        service.check_node = mock.Mock()
-
-        sh.do_node_check(service, args)
-
-        service.check_node.assert_called_once_with('node1')
-
-    def test_do_node_check_not_found(self):
-        service = mock.Mock()
-        ex = exc.HTTPNotFound
-        service.check_node.side_effect = ex
-
-        args = self._make_args({'id': ['node1']})
-        ex = self.assertRaises(exc.CommandError,
-                               sh.do_node_check, service, args)
-        msg = _('Failed to check some of the specified nodes.')
-        self.assertEqual(msg, six.text_type(ex))
-
-    def test_do_node_recover(self):
-        service = mock.Mock()
-        args = {
-            'id': ['node1'],
-            'check': 'false'
-        }
-        args = self._make_args(args)
-        attrs = {
-            'check': False
-        }
-        service.check_node = mock.Mock()
-
-        sh.do_node_recover(service, args)
-
-        service.recover_node.assert_called_once_with('node1', **attrs)
-
-    def test_do_node_recover_not_found(self):
-        service = mock.Mock()
-        ex = exc.HTTPNotFound
-        service.recover_node.side_effect = ex
-        args = {
-            'id': ['node1'],
-            'check': 'false'
-        }
-        args = self._make_args(args)
-
-        ex = self.assertRaises(exc.CommandError,
-                               sh.do_node_recover, service, args)
-        msg = _('Failed to recover some of the specified nodes.')
-        self.assertEqual(msg, six.text_type(ex))
-
-    def test_do_node_op(self):
-        service = mock.Mock()
-        args = {
-            'id': 'node1',
-            'operation': 'dance',
-            'params': ['style=tango']
-        }
-        args = self._make_args(args)
-        attrs = {
-            'style': 'tango'
-        }
-        service.perform_operation_on_node = mock.Mock()
-
-        sh.do_node_op(service, args)
-
-        service.perform_operation_on_node.assert_called_once_with(
-            'node1', 'dance', **attrs)
-
-    def test_do_node_op_not_found(self):
-        service = mock.Mock()
-        ex = exc.HTTPNotFound
-        service.perform_operation_on_node.side_effect = ex
-        args = {
-            'id': 'node1',
-            'operation': 'swim',
-            'params': []
-        }
-        args = self._make_args(args)
-
-        ex = self.assertRaises(exc.CommandError,
-                               sh.do_node_op, service, args)
-        msg = _('Node "node1" is not found')
-        self.assertEqual(msg, six.text_type(ex))
-
-    @mock.patch.object(utils, 'print_list')
-    def test_do_event_list(self, mock_print):
-        service = mock.Mock()
-        fields = ['id', 'generated_at', 'obj_type', 'obj_id', 'obj_name',
-                  'action', 'status', 'level', 'cluster_id', 'project_id']
-        field_labels = ['id', 'timestamp', 'obj_type', 'obj_id', 'obj_name',
-                        'action', 'status', 'level', 'cluster_id',
-                        'project_id']
-
-        args = {
-            'sort': 'timestamp:asc',
-            'limit': 20,
-            'marker': 'marker_id',
-            'global_project': True,
-            'filters': ['action=NODE_DELETE'],
-            'full_id': True,
-        }
-        queries = copy.deepcopy(args)
-        del queries['full_id']
-        del queries['filters']
-        queries['action'] = 'NODE_DELETE'
-        args = self._make_args(args)
-        formatters = {}
-        sortby_index = None
-        events = mock.Mock()
-        service.events.return_value = events
-
-        sh.do_event_list(service, args)
-
-        service.events.assert_called_once_with(**queries)
-        mock_print.assert_called_once_with(events, fields,
-                                           formatters=formatters,
-                                           sortby_index=sortby_index,
-                                           field_labels=field_labels)
-
-    @mock.patch.object(utils, 'print_dict')
-    def test_do_event_show(self, mock_print):
-        class FakeEvent(object):
-            def to_dict(self):
-                pass
-
-        service = mock.Mock()
-        args = {
-            'id': 'event_id'
-        }
-        args = self._make_args(args)
-
-        event = FakeEvent()
-        service.get_event.return_value = event
-        sh.do_event_show(service, args)
-        service.get_event.assert_called_once_with('event_id')
-        mock_print.assert_called_once_with(event.to_dict())
-
-    def test_do_event_show_not_found(self):
-        service = mock.Mock()
-        args = self._make_args({'id': 'FAKE'})
-        # event not found
-        ex = exc.CommandError
-        service.get_event.side_effect = oexc.ResourceNotFound
-        ex = self.assertRaises(ex,
-                               sh.do_event_show,
-                               service, args)
-
-    @mock.patch.object(utils, 'print_list')
-    def test_do_action_list(self, mock_print):
-        service = mock.Mock()
-        fields = ['id', 'name', 'action', 'status', 'target_id', 'depends_on',
-                  'depended_by', 'created_at']
-        args = {
-            'sort': 'status',
-            'limit': 20,
-            'marker': 'marker_id',
-            'global_project': True,
-        }
-        queries = copy.deepcopy(args)
-        args = self._make_args(args)
-        args.filters = ['status=ACTIVE']
-        queries['status'] = 'ACTIVE'
-        actions = mock.Mock()
-        service.actions.return_value = actions
-        formatters = {
-            'depends_on': mock.ANY,
-            'depended_by': mock.ANY
-        }
-        args.full_id = True
-        sortby_index = None
-        sh.do_action_list(service, args)
-        service.actions.assert_called_once_with(**queries)
-        mock_print.assert_called_once_with(actions, fields,
-                                           formatters=formatters,
-                                           sortby_index=sortby_index)
-
-    @mock.patch.object(utils, 'print_dict')
-    def test_do_action_show(self, mock_print):
-        class FakeAction(object):
-            def to_dict(self):
-                pass
-
-        service = mock.Mock()
-        args = self._make_args({'id': 'action_id'})
-
-        action = FakeAction()
-        service.get_action.return_value = action
-        formatters = {
-            'inputs': utils.json_formatter,
-            'outputs': utils.json_formatter,
-            'metadata': utils.json_formatter,
-            'data': utils.json_formatter,
-            'depends_on': utils.list_formatter,
-            'depended_by': utils.list_formatter,
-        }
-        sh.do_action_show(service, args)
-        service.get_action.assert_called_once_with('action_id')
-        mock_print.assert_called_once_with(action.to_dict(),
-                                           formatters=formatters)
-
-    def test_do_action_show_not_found(self):
-        service = mock.Mock()
-        args = self._make_args({'id': 'fake_id'})
-
-        service.get_action.side_effect = oexc.ResourceNotFound
-        ex = self.assertRaises(exc.CommandError,
-                               sh.do_action_show,
-                               service, args)
-        msg = _('Action not found: fake_id')
-        self.assertEqual(msg, six.text_type(ex))
-
-    @mock.patch.object(utils, 'print_list')
-    def test_do_service_list(self, mock_print):
-        service = mock.Mock()
-        fields = ['binary', 'host', 'status', 'state', 'updated_at',
-                  'disabled_reason']
-
-        result = mock.Mock()
-        service.services.return_value = result
-        formatters = {}
-        sh.do_service_list(service)
-        mock_print.assert_called_once_with(result, fields,
-                                           formatters=formatters)
diff --git a/senlinclient/v1/shell.py b/senlinclient/v1/shell.py
deleted file mode 100644
index 79501ed5..00000000
--- a/senlinclient/v1/shell.py
+++ /dev/null
@@ -1,1883 +0,0 @@
-# Licensed 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 logging
-import subprocess
-import threading
-import time
-
-from openstack import exceptions as sdk_exc
-from oslo_utils import strutils
-import six
-
-from senlinclient.common import exc
-from senlinclient.common.i18n import _
-from senlinclient.common import utils
-
-logger = logging.getLogger(__name__)
-
-
-def show_deprecated(deprecated, recommended):
-    logger.warning(
-        ('"%(old)s" is deprecated and will be removed by Apr 2017, '
-         'please use "%(new)s" instead.'),
-        {'old': deprecated, 'new': recommended})
-
-
-def do_build_info(service, args=None):
-    """Retrieve build information."""
-    show_deprecated('senlin build-info', 'openstack cluster build info')
-    result = service.get_build_info()
-    info = {'api': result.api, 'engine': result.engine}
-
-    formatters = {
-        'api': utils.json_formatter,
-        'engine': utils.json_formatter,
-    }
-    utils.print_dict(info, formatters=formatters)
-
-
-# PROFILE TYPES
-
-def do_profile_type_list(service, args=None):
-    """List the available profile types."""
-    show_deprecated('senlin profile-type-list',
-                    'openstack cluster profile type list')
-
-    class _ProfileType(object):
-
-        def __init__(self, name, version, status):
-            self.name = name
-            self.version = version
-            self.support_status = status
-
-    fields = ['name', 'version', 'support_status']
-    types = service.profile_types()
-    results = []
-    for t in types:
-        for v in t.support_status.keys():
-            ss = '\n'.join([' since '.join((item['status'], item['since']))
-                           for item in t.support_status[v]])
-            results.append(_ProfileType(t.name, v, ss))
-
-    utils.print_list(results, fields, sortby_index=0)
-
-
-@utils.arg('type_name', metavar='<TYPE_NAME>',
-           help=_('Profile type to retrieve.'))
-@utils.arg('-F', '--format', metavar='<FORMAT>',
-           choices=utils.supported_formats.keys(),
-           help=_("The template output format, one of: %s.")
-                 % ', '.join(utils.supported_formats.keys()))
-def do_profile_type_show(service, args):
-    """Get the details about a profile type."""
-    show_deprecated('senlin profile-type-show',
-                    'openstack cluster profile type show')
-    try:
-        res = service.get_profile_type(args.type_name)
-    except sdk_exc.ResourceNotFound:
-        raise exc.CommandError(
-            _('Profile Type not found: %s') % args.type_name)
-
-    pt = res.to_dict()
-
-    if args.format:
-        print(utils.format_output(pt, format=args.format))
-    else:
-        print(utils.format_output(pt))
-
-
-@utils.arg('type_name', metavar='<TYPE_NAME>',
-           help=_('Profile type to retrieve.'))
-@utils.arg('-F', '--format', metavar='<FORMAT>',
-           choices=utils.supported_formats.keys(),
-           help=_("The template output format, one of: %s.")
-                 % ', '.join(utils.supported_formats.keys()))
-def do_profile_type_ops(service, args):
-    """Show the operations about a policy type."""
-    show_deprecated('senlin profile-type-ops',
-                    'openstack cluster profile type ops')
-    try:
-        ops = service.list_profile_type_operations(args.type_name)
-    except sdk_exc.ResourceNotFound:
-        raise exc.CommandError(_('Profile Type not found: %s')
-                               % args.type_name)
-
-    if args.format:
-        print(utils.format_output(ops, format=args.format))
-    else:
-        print(utils.format_output(ops))
-
-
-# PROFILES
-
-@utils.arg('-f', '--filters', metavar='<"KEY1=VALUE1;KEY2=VALUE2...">',
-           help=_('Filter parameters to apply on returned profiles. '
-                  'This can be specified multiple times, or once with '
-                  'parameters separated by a semicolon.'),
-           action='append')
-@utils.arg('-l', '--limit', metavar='<LIMIT>',
-           help=_('Limit the number of profiles returned.'))
-@utils.arg('-m', '--marker', metavar='<ID>',
-           help=_('Only return profiles that appear after the given ID.'))
-@utils.arg('-o', '--sort', metavar='<KEY:DIR>',
-           help=_('Sorting option which is a string containing a list of keys '
-                  'separated by commas. Each key can be optionally appended '
-                  'by a sort direction (:asc or :desc)'))
-@utils.arg('-g', '--global-project', default=False, action="store_true",
-           help=_('Indicate that the list should include profiles from'
-                  ' all projects. This option is subject to access policy '
-                  'checking. Default is False.'))
-@utils.arg('-F', '--full-id', default=False, action="store_true",
-           help=_('Print full IDs in list.'))
-def do_profile_list(service, args=None):
-    """List profiles that meet the criteria."""
-    show_deprecated('senlin profile-list', 'openstack cluster profile list')
-    fields = ['id', 'name', 'type', 'created_at']
-    queries = {
-        'limit': args.limit,
-        'marker': args.marker,
-        'sort': args.sort,
-        'global_project': args.global_project,
-    }
-    if args.filters:
-        queries.update(utils.format_parameters(args.filters))
-
-    sortby_index = None if args.sort else 1
-
-    profiles = service.profiles(**queries)
-    formatters = {}
-    if args.global_project:
-        fields.append('project_id')
-    if not args.full_id:
-        formatters = {
-            'id': lambda x: x.id[:8],
-        }
-        if 'project_id' in fields:
-            formatters['project_id'] = lambda x: x.project_id[:8]
-
-    utils.print_list(profiles, fields, formatters=formatters,
-                     sortby_index=sortby_index)
-
-
-def _show_profile(service, profile_id):
-    try:
-        profile = service.get_profile(profile_id)
-    except sdk_exc.ResourceNotFound:
-        raise exc.CommandError(_('Profile not found: %s') % profile_id)
-
-    formatters = {
-        'metadata': utils.json_formatter,
-    }
-
-    formatters['spec'] = utils.nested_dict_formatter(
-        ['type', 'version', 'properties'],
-        ['property', 'value'])
-
-    utils.print_dict(profile.to_dict(), formatters=formatters)
-
-
-@utils.arg('-s', '--spec-file', metavar='<SPEC FILE>', required=True,
-           help=_('The spec file used to create the profile.'))
-@utils.arg('-M', '--metadata', metavar='<"KEY1=VALUE1;KEY2=VALUE2...">',
-           help=_('Metadata values to be attached to the profile. '
-                  'This can be specified multiple times, or once with '
-                  'key-value pairs separated by a semicolon.'),
-           action='append')
-@utils.arg('name', metavar='<PROFILE_NAME>',
-           help=_('Name of the profile to create.'))
-def do_profile_create(service, args):
-    """Create a profile."""
-    show_deprecated('senlin profile-create',
-                    'openstack cluster profile create')
-    spec = utils.get_spec_content(args.spec_file)
-    type_name = spec.get('type', None)
-    type_version = spec.get('version', None)
-    properties = spec.get('properties', None)
-    if type_name is None:
-        raise exc.CommandError(_("Missing 'type' key in spec file."))
-    if type_version is None:
-        raise exc.CommandError(_("Missing 'version' key in spec file."))
-    if properties is None:
-        raise exc.CommandError(_("Missing 'properties' key in spec file."))
-
-    if type_name == 'os.heat.stack':
-        stack_properties = utils.process_stack_spec(properties)
-        spec['properties'] = stack_properties
-
-    params = {
-        'name': args.name,
-        'spec': spec,
-        'metadata': utils.format_parameters(args.metadata),
-    }
-
-    profile = service.create_profile(**params)
-    _show_profile(service, profile.id)
-
-
-@utils.arg('id', metavar='<PROFILE>',
-           help=_('Name or ID of profile to show.'))
-def do_profile_show(service, args):
-    """Show the profile details."""
-    show_deprecated('senlin profile-show', 'openstack cluster profile show')
-    _show_profile(service, args.id)
-
-
-@utils.arg('-n', '--name', metavar='<NAME>',
-           help=_('The new name for the profile.'))
-@utils.arg('-M', '--metadata', metavar='<"KEY1=VALUE1;KEY2=VALUE2...">',
-           help=_("Metadata values to be attached to the profile. "
-                  "This can be specified multiple times, or once with "
-                  "key-value pairs separated by a semicolon. Use '{}' "
-                  "can clean metadata "),
-           action='append')
-@utils.arg('id', metavar='<PROFILE_ID>',
-           help=_('Name or ID of the profile to update.'))
-def do_profile_update(service, args):
-    """Update a profile."""
-    show_deprecated('senlin profile-update',
-                    'openstack cluster profile update')
-    params = {
-        'name': args.name,
-    }
-    if args.metadata:
-        params['metadata'] = utils.format_parameters(args.metadata)
-
-    # Find the profile first, we need its id
-    try:
-        profile = service.get_profile(args.id)
-    except sdk_exc.ResourceNotFound:
-        raise exc.CommandError(_('Profile not found: %s') % args.id)
-    service.update_profile(profile, **params)
-    _show_profile(service, profile.id)
-
-
-@utils.arg('id', metavar='<PROFILE>', nargs='+',
-           help=_('Name or ID of profile(s) to delete.'))
-def do_profile_delete(service, args):
-    """Delete profile(s)."""
-    show_deprecated('senlin profile-delete',
-                    'openstack cluster profile delete')
-    failure_count = 0
-
-    for pid in args.id:
-        try:
-            service.delete_profile(pid, False)
-        except Exception as ex:
-            failure_count += 1
-            print(ex)
-    if failure_count > 0:
-        msg = _('Failed to delete some of the specified profile(s).')
-        raise exc.CommandError(msg)
-    print('Profile deleted: %s' % args.id)
-
-
-@utils.arg('-s', '--spec-file', metavar='<SPEC FILE>', required=True,
-           help=_('The spec file of the profile to be validated.'))
-def do_profile_validate(service, args):
-    """Validate a profile."""
-    show_deprecated('senlin profile-validate',
-                    'openstack cluster profile validate')
-    spec = utils.get_spec_content(args.spec_file)
-    type_name = spec.get('type', None)
-    type_version = spec.get('version', None)
-    properties = spec.get('properties', None)
-    if type_name is None:
-        raise exc.CommandError(_("Missing 'type' key in spec file."))
-    if type_version is None:
-        raise exc.CommandError(_("Missing 'version' key in spec file."))
-    if properties is None:
-        raise exc.CommandError(_("Missing 'properties' key in spec file."))
-
-    if type_name == 'os.heat.stack':
-        stack_properties = utils.process_stack_spec(properties)
-        spec['properties'] = stack_properties
-
-    params = {
-        'spec': spec,
-    }
-
-    profile = service.validate_profile(**params)
-
-    formatters = {
-        'metadata': utils.json_formatter,
-    }
-
-    formatters['spec'] = utils.nested_dict_formatter(
-        ['type', 'version', 'properties'],
-        ['property', 'value'])
-
-    utils.print_dict(profile.to_dict(), formatters=formatters)
-
-
-# POLICY TYPES
-
-
-def do_policy_type_list(service, args):
-    """List the available policy types."""
-    show_deprecated('senlin policy-type-list',
-                    'openstack cluster policy type list')
-
-    class _PolicyType(object):
-
-        def __init__(self, name, version, status):
-            self.name = name
-            self.version = version
-            self.support_status = status
-
-    fields = ['name', 'version', 'support_status']
-    types = service.policy_types()
-
-    results = []
-    for t in types:
-        for v in t.support_status.keys():
-            ss = '\n'.join([' since '.join((item['status'], item['since']))
-                           for item in t.support_status[v]])
-            results.append(_PolicyType(t.name, v, ss))
-
-    utils.print_list(results, fields, sortby_index=0)
-
-
-@utils.arg('type_name', metavar='<TYPE_NAME>',
-           help=_('Policy type to retrieve.'))
-@utils.arg('-F', '--format', metavar='<FORMAT>',
-           choices=utils.supported_formats.keys(),
-           help=_("The template output format, one of: %s.")
-                 % ', '.join(utils.supported_formats.keys()))
-def do_policy_type_show(service, args):
-    """Get the details about a policy type."""
-    show_deprecated('senlin policy-type-show',
-                    'openstack cluster policy type show')
-    try:
-        res = service.get_policy_type(args.type_name)
-    except sdk_exc.ResourceNotFound:
-        raise exc.CommandError(_('Policy type not found: %s') % args.type_name)
-
-    pt = res.to_dict()
-    if args.format:
-        print(utils.format_output(pt, format=args.format))
-    else:
-        print(utils.format_output(pt))
-
-
-# POLICIES
-
-@utils.arg('-f', '--filters', metavar='<"KEY1=VALUE1;KEY2=VALUE2...">',
-           help=_('Filter parameters to apply on returned policies. '
-                  'This can be specified multiple times, or once with '
-                  'parameters separated by a semicolon.'),
-           action='append')
-@utils.arg('-l', '--limit', metavar='<LIMIT>',
-           help=_('Limit the number of policies returned.'))
-@utils.arg('-m', '--marker', metavar='<ID>',
-           help=_('Only return policies that appear after the given ID.'))
-@utils.arg('-o', '--sort', metavar='<KEY:DIR>',
-           help=_('Sorting option which is a string containing a list of keys '
-                  'separated by commas. Each key can be optionally appended '
-                  'by a sort direction (:asc or :desc)'))
-@utils.arg('-g', '--global-project', default=False, action="store_true",
-           help=_('Indicate that the list should include policies from'
-                  ' all projects. This option is subject to access policy '
-                  'checking. Default is False.'))
-@utils.arg('-F', '--full-id', default=False, action="store_true",
-           help=_('Print full IDs in list.'))
-def do_policy_list(service, args=None):
-    """List policies that meet the criteria."""
-    show_deprecated('senlin policy-list', 'openstack cluster policy list')
-    fields = ['id', 'name', 'type', 'created_at']
-    queries = {
-        'limit': args.limit,
-        'marker': args.marker,
-        'sort': args.sort,
-        'global_project': args.global_project,
-    }
-    if args.filters:
-        queries.update(utils.format_parameters(args.filters))
-
-    sortby_index = None if args.sort else 1
-    policies = service.policies(**queries)
-    formatters = {}
-    if args.global_project:
-        fields.append('project_id')
-    if not args.full_id:
-        formatters = {
-            'id': lambda x: x.id[:8]
-        }
-        if 'project_id' in fields:
-            formatters['project_id'] = lambda x: x.project_id[:8]
-
-    utils.print_list(policies, fields, formatters=formatters,
-                     sortby_index=sortby_index)
-
-
-def _show_policy(service, policy_id):
-    try:
-        policy = service.get_policy(policy_id)
-    except sdk_exc.ResourceNotFound:
-        raise exc.CommandError(_('Policy not found: %s') % policy_id)
-
-    formatters = {
-        'metadata': utils.json_formatter,
-        'spec': utils.json_formatter,
-    }
-    utils.print_dict(policy.to_dict(), formatters=formatters)
-
-
-@utils.arg('-s', '--spec-file', metavar='<SPEC_FILE>', required=True,
-           help=_('The spec file used to create the policy.'))
-@utils.arg('name', metavar='<NAME>',
-           help=_('Name of the policy to create.'))
-def do_policy_create(service, args):
-    """Create a policy."""
-    show_deprecated('senlin policy-create', 'openstack cluster policy create')
-    spec = utils.get_spec_content(args.spec_file)
-    attrs = {
-        'name': args.name,
-        'spec': spec,
-    }
-
-    policy = service.create_policy(**attrs)
-    _show_policy(service, policy.id)
-
-
-@utils.arg('id', metavar='<POLICY>',
-           help=_('Name or ID of the policy to be shown.'))
-def do_policy_show(service, args):
-    """Show the policy details."""
-    show_deprecated('senlin policy-show', 'openstack cluster policy show')
-    _show_policy(service, policy_id=args.id)
-
-
-@utils.arg('-n', '--name', metavar='<NAME>',
-           help=_('New name of the policy to be updated.'))
-@utils.arg('id', metavar='<POLICY>',
-           help=_('Name of the policy to be updated.'))
-def do_policy_update(service, args):
-    """Update a policy."""
-    show_deprecated('senlin policy-update', 'openstack cluster policy update')
-    params = {
-        'name': args.name,
-    }
-
-    policy = service.get_policy(args.id)
-    if policy is not None:
-        service.update_policy(policy, **params)
-        _show_policy(service, policy_id=policy.id)
-
-
-@utils.arg('id', metavar='<POLICY>', nargs='+',
-           help=_('Name or ID of policy(s) to delete.'))
-def do_policy_delete(service, args):
-    """Delete policy(s)."""
-    show_deprecated('senlin policy-delete', 'openstack cluster policy delete')
-    failure_count = 0
-
-    for pid in args.id:
-        try:
-            service.delete_policy(pid, False)
-        except Exception as ex:
-            failure_count += 1
-            print(ex)
-    if failure_count > 0:
-        msg = _('Failed to delete some of the specified policy(s).')
-        raise exc.CommandError(msg)
-    print('Policy deleted: %s' % args.id)
-
-
-@utils.arg('-s', '--spec-file', metavar='<SPEC_FILE>', required=True,
-           help=_('The spec file of the policy to be validated.'))
-def do_policy_validate(service, args):
-    """Validate a policy spec."""
-    show_deprecated('senlin policy-validate',
-                    'openstack cluster policy validate')
-    spec = utils.get_spec_content(args.spec_file)
-    attrs = {
-        'spec': spec,
-    }
-
-    policy = service.validate_policy(**attrs)
-    formatters = {
-        'metadata': utils.json_formatter,
-        'spec': utils.json_formatter,
-    }
-    utils.print_dict(policy.to_dict(), formatters=formatters)
-
-# CLUSTERS
-
-
-@utils.arg('-f', '--filters', metavar='<"KEY1=VALUE1;KEY2=VALUE2...">',
-           help=_('Filter parameters to apply on returned clusters. '
-                  'This can be specified multiple times, or once with '
-                  'parameters separated by a semicolon.'),
-           action='append')
-@utils.arg('-o', '--sort', metavar='<KEY:DIR>',
-           help=_('Sorting option which is a string containing a list of keys '
-                  'separated by commas. Each key can be optionally appended '
-                  'by a sort direction (:asc or :desc)'))
-@utils.arg('-l', '--limit', metavar='<LIMIT>',
-           help=_('Limit the number of clusters returned.'))
-@utils.arg('-m', '--marker', metavar='<ID>',
-           help=_('Only return clusters that appear after the given cluster '
-                  'ID.'))
-@utils.arg('-g', '--global-project', default=False, action="store_true",
-           help=_('Indicate that the cluster list should include clusters from'
-                  ' all projects. This option is subject to access policy '
-                  'checking. Default is False.'))
-@utils.arg('-F', '--full-id', default=False, action="store_true",
-           help=_('Print full IDs in list.'))
-def do_cluster_list(service, args=None):
-    """List the user's clusters."""
-    show_deprecated('senlin cluster-list', 'openstack cluster list')
-    fields = ['id', 'name', 'status', 'created_at', 'updated_at']
-    queries = {
-        'limit': args.limit,
-        'marker': args.marker,
-        'sort': args.sort,
-        'global_project': args.global_project,
-    }
-    if args.filters:
-        queries.update(utils.format_parameters(args.filters))
-
-    sortby_index = None if args.sort else 3
-
-    clusters = service.clusters(**queries)
-    formatters = {}
-    if args.global_project:
-        fields.append('project_id')
-    if not args.full_id:
-        formatters = {
-            'id': lambda x: x.id[:8]
-        }
-        if 'project_id' in fields:
-            formatters['project_id'] = lambda x: x.project_id[:8]
-
-    utils.print_list(clusters, fields, formatters=formatters,
-                     sortby_index=sortby_index)
-
-
-def _show_cluster(service, cluster_id):
-    try:
-        cluster = service.get_cluster(cluster_id)
-    except sdk_exc.ResourceNotFound:
-        raise exc.CommandError(_('Cluster not found: %s') % cluster_id)
-
-    formatters = {
-        'config': utils.json_formatter,
-        'metadata': utils.json_formatter,
-        'node_ids': utils.list_formatter,
-    }
-    cluster_attrs = cluster.to_dict()
-    cluster_attrs.pop('is_profile_only')
-    utils.print_dict(cluster_attrs, formatters=formatters)
-
-
-@utils.arg('-p', '--profile', metavar='<PROFILE>', required=True,
-           help=_('Default profile Id or name used for this cluster.'))
-@utils.arg('-n', '--min-size', metavar='<MIN-SIZE>', default=0,
-           help=_('Min size of the cluster. Default to 0.'))
-@utils.arg('-m', '--max-size', metavar='<MAX-SIZE>', default=-1,
-           help=_('Max size of the cluster. Default to -1, means unlimited.'))
-@utils.arg('-c', '--desired-capacity', metavar='<DESIRED-CAPACITY>', default=0,
-           help=_('Desired capacity of the cluster. Default to min_size if '
-                  'min_size is specified else 0.'))
-@utils.arg('-t', '--timeout', metavar='<TIMEOUT>', type=int,
-           help=_('Cluster creation timeout in seconds.'))
-@utils.arg('-C', '--config', metavar='<"key1=value1;key2=value2...">',
-           help=_('Configuration of the cluster. Default to {}. '
-                  'This can be specified multiple times, or once with '
-                  'key-value pairs separated by a semicolon.'),
-           action='append')
-@utils.arg('-M', '--metadata', metavar='<"KEY1=VALUE1;KEY2=VALUE2...">',
-           help=_('Metadata values to be attached to the cluster. '
-                  'This can be specified multiple times, or once with '
-                  'key-value pairs separated by a semicolon.'),
-           action='append')
-@utils.arg('name', metavar='<CLUSTER_NAME>',
-           help=_('Name of the cluster to create.'))
-def do_cluster_create(service, args):
-    """Create the cluster."""
-    show_deprecated('senlin cluster-create', 'openstack cluster create')
-    if args.min_size and not args.desired_capacity:
-        args.desired_capacity = args.min_size
-    attrs = {
-        'config': utils.format_parameters(args.config),
-        'name': args.name,
-        'profile_id': args.profile,
-        'min_size': args.min_size,
-        'max_size': args.max_size,
-        'desired_capacity': args.desired_capacity,
-        'metadata': utils.format_parameters(args.metadata),
-        'timeout': args.timeout
-    }
-
-    cluster = service.create_cluster(**attrs)
-    _show_cluster(service, cluster.id)
-
-
-@utils.arg('-p', '--path', metavar='<PATH>', required=True,
-           help=_('A Json path string specifying the attribute to collect.'))
-@utils.arg('-L', '--list', default=False, action="store_true",
-           help=_('Print a full list that contains both node ids and '
-                  'attribute values instead of values only. Default is '
-                  'False.'))
-@utils.arg('-F', '--full-id', default=False, action="store_true",
-           help=_('Print full IDs in list.'))
-@utils.arg('id', metavar='<CLUSTER>',
-           help=_('Name or ID of cluster(s) to operate on.'))
-def do_cluster_collect(service, args):
-    """Collect attributes across a cluster."""
-    show_deprecated('senlin cluster-collect', 'openstack cluster collect')
-
-    attrs = service.collect_cluster_attrs(args.id, args.path)
-    if args.list:
-        fields = ['node_id', 'attr_value']
-        formatters = {
-            'attr_value': lambda x: utils.json_formatter(x.attr_value)
-        }
-        if not args.full_id:
-            formatters['node_id'] = lambda x: x.node_id[:8]
-        utils.print_list(attrs, fields, formatters=formatters)
-    else:
-        for attr in attrs:
-            print(attr.attr_value)
-
-
-@utils.arg('id', metavar='<CLUSTER>', nargs='+',
-           help=_('Name or ID of cluster(s) to delete.'))
-@utils.arg('-f', '--force-delete', default=False, action="store_true",
-           help=_('Force to delete cluster(s).'))
-def do_cluster_delete(service, args):
-    """Delete the cluster(s)."""
-    show_deprecated('senlin cluster-delete', 'openstack cluster delete')
-
-    result = {}
-    for cid in args.id:
-        try:
-            cluster = service.delete_cluster(cid, args.force_delete, False)
-            result[cid] = ('OK', cluster.location.split('/')[-1])
-        except Exception as ex:
-            result[cid] = ('ERROR', six.text_type(ex))
-
-    for rid, res in result.items():
-        utils.print_action_result(rid, res)
-
-
-def _run_script(node_id, addr, net, addr_type, port, user, ipv6, identity_file,
-                script, options, output=None):
-    version = 6 if ipv6 else 4
-
-    # Select the network to use.
-    if net:
-        addresses = addr.get(net)
-        if not addresses:
-            output['status'] = _('FAILED')
-            output['reason'] = _("Node '%(node)s' is not attached to network "
-                                 "'%(net)s'.") % {'node': node_id, 'net': net}
-            return
-    else:
-        # network not specified
-        if len(addr) > 1:
-            output['status'] = _('FAILED')
-            output['reason'] = _("Node '%(node)s' is attached to more than "
-                                 "one network. Please pick the network to "
-                                 "use.") % {'node': node_id}
-            return
-        elif not addr:
-            output['status'] = _('FAILED')
-            output['reason'] = _("Node '%(node)s' is not attached to any "
-                                 "network.") % {'node': node_id}
-            return
-        else:
-            addresses = list(addr.values())[0]
-
-    # Select the address in the selected network.
-    # If the extension is not present, we assume the address to be floating.
-    matching_addresses = []
-    for a in addresses:
-        a_type = a.get('OS-EXT-IPS:type', 'floating')
-        a_version = a.get('version')
-        if (a_version == version and a_type == addr_type):
-            matching_addresses.append(a.get('addr'))
-
-    if not matching_addresses:
-        output['status'] = _('FAILED')
-        output['reason'] = _("No address that would match network '%(net)s' "
-                             "and type '%(type)s' of IPv%(ver)s has been "
-                             "found for node '%(node)s'."
-                             ) % {'net': net, 'type': addr_type,
-                                  'ver': version, 'node': node_id}
-        return
-
-    if len(matching_addresses) > 1:
-        output['status'] = _('FAILED')
-        output['reason'] = _("More than one IPv%(ver)s %(type)s address "
-                             "found.") % {'ver': version, 'type': addr_type}
-        return
-
-    ip_address = str(matching_addresses[0])
-    identity = '-i %s' % identity_file if identity_file else ''
-
-    cmd = [
-        'ssh',
-        '-%d' % version,
-        '-p%d' % port,
-        identity,
-        options,
-        '%s@%s' % (user, ip_address),
-        '%s' % script
-    ]
-    logger.debug("%s" % cmd)
-    proc = subprocess.Popen(cmd, stdout=subprocess.PIPE)
-    (stdout, stderr) = proc.communicate()
-    while proc.returncode is None:
-        time.sleep(1)
-    if proc.returncode == 0:
-        output['status'] = _('SUCCEEDED (0)')
-        output['output'] = stdout
-        if stderr:
-            output['error'] = stderr
-    else:
-        output['status'] = _('FAILED (%d)') % proc.returncode
-        output['output'] = stdout
-        if stderr:
-            output['error'] = stderr
-
-
-@utils.arg("-p", "--port", metavar="<PORT>", type=int, default=22,
-           help=_("Optional flag to indicate the port to use (Default=22)."))
-@utils.arg("-t", "--address-type", type=str, default="floating",
-           help=_("Optional flag to indicate which IP type to use. Possible "
-                  "values includes 'fixed' and 'floating' (the Default)."))
-@utils.arg("-n", "--network", metavar='<NETWORK>', default='',
-           help=_('Network to use for the ssh.'))
-@utils.arg("-6", "--ipv6", action="store_true", default=False,
-           help=_("Optional flag to indicate whether to use an IPv6 address "
-                  "attached to a server. (Defaults to IPv4 address)"))
-@utils.arg("-u", "--user", metavar="<USER>", default="root",
-           help=_("Login to use."))
-@utils.arg("-i", "--identity-file",
-           help=_("Private key file, same as the '-i' option to the ssh "
-                  "command."))
-@utils.arg("-O", "--ssh-options", default="",
-           help=_("Extra options to pass to ssh. see: man ssh."))
-@utils.arg("-s", "--script", metavar="<FILE>", required=True,
-           help=_("Script file to run."))
-@utils.arg("id", metavar="<CLUSTER>",
-           help=_('Name or ID of the cluster.'))
-def do_cluster_run(service, args):
-    """Run shell scripts on all nodes of a cluster."""
-    show_deprecated('senlin cluster-run', 'openstack cluster run')
-
-    if '@' in args.id:
-        user, cluster = args.id.split('@', 1)
-        args.user = user
-        args.cluster = cluster
-
-    try:
-        attributes = service.collect_cluster_attrs(args.id, 'details')
-    except sdk_exc.ResourceNotFound:
-        raise exc.CommandError(_("Cluster not found: %s") % args.id)
-
-    script = None
-    try:
-        f = open(args.script, 'r')
-        script = f.read()
-    except Exception:
-        raise exc.CommandError(_("Cound not open script file: %s") %
-                               args.script)
-
-    tasks = dict()
-    for attr in attributes:
-        node_id = attr.node_id
-        addr = attr.attr_value['addresses']
-
-        output = dict()
-        th = threading.Thread(
-            target=_run_script,
-            args=(node_id, addr, args.network, args.address_type, args.port,
-                  args.user, args.ipv6, args.identity_file,
-                  script, args.ssh_options),
-            kwargs={'output': output})
-        th.start()
-        tasks[th] = (node_id, output)
-
-    for t in tasks:
-        t.join()
-
-    for t in tasks:
-        node_id, result = tasks[t]
-        print("node: %s" % node_id)
-        print("status: %s" % result.get('status'))
-        if "reason" in result:
-            print("reason: %s" % result.get('reason'))
-        if "output" in result:
-            print("output:\n%s" % result.get('output'))
-        if "error" in result:
-            print("error:\n%s" % result.get('error'))
-
-
-@utils.arg('-p', '--profile', metavar='<PROFILE>',
-           help=_('ID or name of new profile to use.'))
-@utils.arg('-P', '--profile-only', metavar='<BOOLEAN>', default=False,
-           help=_("Whether the cluster should be updated profile only. "
-                  "If false, it will be applied to all existing nodes. "
-                  "If true, any newly created nodes will use the new profile, "
-                  "but existing nodes will not be changed. Default is False."))
-@utils.arg('-t', '--timeout', metavar='<TIMEOUT>',
-           help=_('New timeout (in seconds) value for the cluster.'))
-@utils.arg('-C', '--config', metavar='<"key1=value1;key2=value2...">',
-           help=_('Configuration of the cluster. Default to {}. '
-                  'This can be specified multiple times, or once with '
-                  'key-value pairs separated by a semicolon.'),
-           action='append')
-@utils.arg('-M', '--metadata', metavar='<"KEY1=VALUE1;KEY2=VALUE2...">',
-           help=_("Metadata values to be attached to the cluster. "
-                  "This can be specified multiple times, or once with "
-                  "key-value pairs separated by a semicolon. Use '{}' "
-                  "can clean metadata "),
-           action='append')
-@utils.arg('-n', '--name', metavar='<NAME>',
-           help=_('New name for the cluster to update.'))
-@utils.arg('id', metavar='<CLUSTER>',
-           help=_('Name or ID of cluster to be updated.'))
-def do_cluster_update(service, args):
-    """Update the cluster."""
-    show_deprecated('senlin cluster-update', 'openstack cluster update')
-    cluster = service.get_cluster(args.id)
-    attrs = {
-        'name': args.name,
-        'profile_id': args.profile,
-        'profile_only': strutils.bool_from_string(
-            args.profile_only, strict=True
-        ),
-        'metadata': utils.format_parameters(args.metadata),
-        'timeout': args.timeout,
-    }
-
-    service.update_cluster(cluster, **attrs)
-    _show_cluster(service, cluster.id)
-
-
-@utils.arg('id', metavar='<CLUSTER>',
-           help=_('Name or ID of cluster to show.'))
-def do_cluster_show(service, args):
-    """Show details of the cluster."""
-    show_deprecated('senlin cluster-show', 'openstack cluster show')
-    _show_cluster(service, args.id)
-
-
-@utils.arg('-f', '--filters', metavar='<"KEY1=VALUE1;KEY2=VALUE2...">',
-           help=_('Filter parameters to apply on returned nodes. '
-                  'This can be specified multiple times, or once with '
-                  'parameters separated by a semicolon.'),
-           action='append')
-@utils.arg('-l', '--limit', metavar='<LIMIT>',
-           help=_('Limit the number of nodes returned.'))
-@utils.arg('-m', '--marker', metavar='<ID>',
-           help=_('Only return nodes that appear after the given node ID.'))
-@utils.arg('-F', '--full-id', default=False, action="store_true",
-           help=_('Print full IDs in list.'))
-@utils.arg('id', metavar='<CLUSTER>',
-           help=_('Name or ID of cluster to nodes from.'))
-def do_cluster_node_list(service, args):
-    """List nodes from cluster."""
-    show_deprecated('senlin cluster-node-list',
-                    'openstack cluster members list')
-    queries = {
-        'cluster_id': args.id,
-        'limit': args.limit,
-        'marker': args.marker,
-    }
-    if args.filters:
-        queries.update(utils.format_parameters(args.filters))
-
-    nodes = service.nodes(**queries)
-    if not args.full_id:
-        formatters = {
-            'id': lambda x: x.id[:8],
-            'physical_id': lambda x: x.physical_id[:8] if x.physical_id else ''
-        }
-    else:
-        formatters = {}
-
-    fields = ['id', 'name', 'index', 'status', 'physical_id', 'created_at']
-    utils.print_list(nodes, fields, formatters=formatters, sortby_index=5)
-
-
-@utils.arg('-n', '--nodes', metavar='<NODES>', required=True,
-           help=_('ID of nodes to be added; multiple nodes can be separated '
-                  'with ","'))
-@utils.arg('id', metavar='<CLUSTER>',
-           help=_('Name or ID of cluster to operate on.'))
-def do_cluster_node_add(service, args):
-    """Add specified nodes to cluster."""
-    show_deprecated('senlin cluster-node-add',
-                    'openstack cluster node members add')
-    node_ids = args.nodes.split(',')
-    resp = service.cluster_add_nodes(args.id, node_ids)
-    print('Request accepted by action: %s' % resp['action'])
-
-
-@utils.arg('-n', '--nodes', metavar='<NODES>', required=True,
-           help=_('ID of nodes to be deleted; multiple nodes can be separated '
-                  'with ",".'))
-@utils.arg('-d', '--destroy-after-deletion', metavar='<BOOLEAN>',
-           required=False, default=False,
-           help=_('Whether nodes should be destroyed after deleted. '
-                  'Default is False.'))
-@utils.arg('id', metavar='<CLUSTER>',
-           help=_('Name or ID of cluster to operate on.'))
-def do_cluster_node_del(service, args):
-    """Delete specified nodes from cluster."""
-    show_deprecated('senlin cluster-node-del',
-                    'openstack cluster node members del')
-    node_ids = args.nodes.split(',')
-    destroy = args.destroy_after_deletion
-    destroy = strutils.bool_from_string(destroy, strict=True)
-    kwargs = {"destroy_after_deletion": destroy}
-    resp = service.cluster_del_nodes(args.id, node_ids, **kwargs)
-    print('Request accepted by action: %s' % resp['action'])
-
-
-@utils.arg('-n', '--nodes', metavar='<OLD_NODE1=NEW_NODE1>', required=True,
-           help=_("OLD_NODE is the name or ID of a node to be replaced, "
-                  "NEW_NODE is the name or ID of a node as replacement. "
-                  "This can be specified multiple times, or once with "
-                  "node-pairs separated by a comma ','."),
-           action='append')
-@utils.arg('id', metavar='<CLUSTER>',
-           help=_('Name or ID of cluster to operate on.'))
-def do_cluster_node_replace(service, args):
-    """Replace the nodes in cluster with specified nodes."""
-    show_deprecated('senlin cluster-node-replace',
-                    'openstack cluster node members replace')
-    nodepairs = {}
-    for nodepair in args.nodes:
-        key = nodepair.split('=')[0]
-        value = nodepair.split('=')[1]
-        nodepairs[key] = value
-    resp = service.cluster_replace_nodes(args.id, nodepairs)
-    print('Request accepted by action: %s' % resp['action'])
-
-
-@utils.arg('-c', '--capacity', metavar='<CAPACITY>', type=int,
-           help=_('The desired number of nodes of the cluster.'))
-@utils.arg('-a', '--adjustment', metavar='<ADJUSTMENT>', type=int,
-           help=_('A positive integer meaning the number of nodes to add, '
-                  'or a negative integer indicating the number of nodes to '
-                  'remove.'))
-@utils.arg('-p', '--percentage', metavar='<PERCENTAGE>', type=float,
-           help=_('A value that is interpreted as the percentage of size '
-                  'adjustment. This value can be positive or negative.'))
-@utils.arg('-t', '--min-step', metavar='<MIN_STEP>', type=int,
-           help=_('An integer specifying the number of nodes for adjustment '
-                  'when <PERCENTAGE> is specified.'))
-@utils.arg('-s', '--strict', action='store_true', default=False,
-           help=_('A boolean specifying whether the resize should be '
-                  'performed on a best-effort basis when the new capacity '
-                  'may go beyond size constraints.'))
-@utils.arg('-n', '--min-size', metavar='MIN', type=int,
-           help=_('New lower bound of cluster size.'))
-@utils.arg('-m', '--max-size', metavar='MAX', type=int,
-           help=_('New upper bound of cluster size. A value of -1 indicates '
-                  'no upper limit on cluster size.'))
-@utils.arg('id', metavar='<CLUSTER>',
-           help=_('Name or ID of cluster to operate on.'))
-def do_cluster_resize(service, args):
-    """Resize a cluster."""
-    # validate parameters
-    # NOTE: this will be much simpler if cliutils supports exclusive groups
-    show_deprecated('senlin cluster-resize', 'openstack cluster resize')
-    action_args = {}
-
-    capacity = args.capacity
-    adjustment = args.adjustment
-    percentage = args.percentage
-    min_size = args.min_size
-    max_size = args.max_size
-    min_step = args.min_step
-
-    if sum(v is not None for v in (capacity, adjustment, percentage, min_size,
-                                   max_size)) == 0:
-        raise exc.CommandError(_("At least one parameter of 'capacity', "
-                                 "'adjustment', 'percentage', 'min_size', "
-                                 " and 'max_size' should be specified."))
-
-    if sum(v is not None for v in (capacity, adjustment, percentage)) > 1:
-        raise exc.CommandError(_("Only one of 'capacity', 'adjustment' and "
-                                 "'percentage' can be specified."))
-
-    action_args['adjustment_type'] = None
-    action_args['number'] = None
-
-    if capacity is not None:
-        if capacity < 0:
-            raise exc.CommandError(_('Cluster capacity must be larger than '
-                                     'or equal to zero.'))
-        action_args['adjustment_type'] = 'EXACT_CAPACITY'
-        action_args['number'] = capacity
-
-    if adjustment is not None:
-        if adjustment == 0:
-            raise exc.CommandError(_('Adjustment cannot be zero.'))
-        action_args['adjustment_type'] = 'CHANGE_IN_CAPACITY'
-        action_args['number'] = adjustment
-
-    if percentage is not None:
-        if (percentage == 0 or percentage == 0.0):
-            raise exc.CommandError(_('Percentage cannot be zero.'))
-        action_args['adjustment_type'] = 'CHANGE_IN_PERCENTAGE'
-        action_args['number'] = percentage
-
-    if min_step is not None:
-        if percentage is None:
-            raise exc.CommandError(_('Min step is only used with percentage.'))
-
-    if min_size is not None:
-        if min_size < 0:
-            raise exc.CommandError(_('Min size cannot be less than zero.'))
-        if max_size is not None and max_size >= 0 and min_size > max_size:
-            raise exc.CommandError(_('Min size cannot be larger than '
-                                     'max size.'))
-        if capacity is not None and min_size > capacity:
-            raise exc.CommandError(_('Min size cannot be larger than the '
-                                     'specified capacity'))
-
-    if max_size is not None:
-        if capacity is not None and max_size > 0 and max_size < capacity:
-            raise exc.CommandError(_('Max size cannot be less than the '
-                                     'specified capacity.'))
-        # do a normalization
-        if max_size < 0:
-            max_size = -1
-
-    action_args['min_size'] = min_size
-    action_args['max_size'] = max_size
-    action_args['min_step'] = min_step
-    action_args['strict'] = args.strict
-
-    resp = service.cluster_resize(args.id, **action_args)
-    print('Request accepted by action: %s' % resp['action'])
-
-
-@utils.arg('-c', '--count', metavar='<COUNT>',
-           help=_('Number of nodes to be added to the specified cluster.'))
-@utils.arg('id', metavar='<CLUSTER>',
-           help=_('Name or ID of cluster to operate on.'))
-def do_cluster_scale_out(service, args):
-    """Scale out a cluster by the specified number of nodes."""
-    show_deprecated('senlin cluster-scale-out', 'openstack cluster expand')
-    resp = service.cluster_scale_out(args.id, args.count)
-    print('Request accepted by action %s' % resp['action'])
-
-
-@utils.arg('-c', '--count', metavar='<COUNT>',
-           help=_('Number of nodes to be deleted from the specified cluster.'))
-@utils.arg('id', metavar='<CLUSTER>',
-           help=_('Name or ID of cluster to operate on.'))
-def do_cluster_scale_in(service, args):
-    """Scale in a cluster by the specified number of nodes."""
-    show_deprecated('senlin cluster-scale-in', 'openstack cluster shrink')
-    resp = service.cluster_scale_in(args.id, args.count)
-    print('Request accepted by action %s' % resp['action'])
-
-
-@utils.arg('-f', '--filters', metavar='<"KEY1=VALUE1;KEY2=VALUE2...">',
-           help=_('Filter parameters to apply on returned results. '
-                  'This can be specified multiple times, or once with '
-                  'parameters separated by a semicolon.'),
-           action='append')
-@utils.arg('-o', '--sort', metavar='<SORT_STRING>',
-           help=_('Sorting option which is a string containing a list of keys '
-                  'separated by commas. Each key can be optionally appended '
-                  'by a sort direction (:asc or :desc)'))
-@utils.arg('-F', '--full-id', default=False, action="store_true",
-           help=_('Print full IDs in list.'))
-@utils.arg('id', metavar='<CLUSTER>',
-           help=_('Name or ID of cluster to query on.'))
-def do_cluster_policy_list(service, args):
-    """List policies from cluster."""
-    show_deprecated('senlin cluster-policy-list',
-                    'openstack cluster policy binding list')
-    fields = ['policy_id', 'policy_name', 'policy_type', 'is_enabled']
-
-    cluster = service.get_cluster(args.id)
-    queries = {
-        'sort': args.sort,
-    }
-
-    if args.filters:
-        queries.update(utils.format_parameters(args.filters))
-
-    sortby_index = None if args.sort else 3
-    policies = service.cluster_policies(cluster.id, **queries)
-    formatters = {}
-    if not args.full_id:
-        formatters = {
-            'policy_id': lambda x: x.policy_id[:8]
-        }
-
-    utils.print_list(policies, fields, formatters=formatters,
-                     sortby_index=sortby_index)
-
-
-@utils.arg('-p', '--policy', metavar='<POLICY>', required=True,
-           help=_('ID or name of the policy to query on.'))
-@utils.arg('id', metavar='<CLUSTER>',
-           help=_('ID or name of the cluster to query on.'))
-def do_cluster_policy_show(service, args):
-    """Show a specific policy that is bound to the specified cluster."""
-    show_deprecated('senlin cluster-policy-show',
-                    'openstack cluster policy binding show')
-    binding = service.get_cluster_policy(args.policy, args.id)
-    utils.print_dict(binding.to_dict())
-
-
-@utils.arg('-p', '--policy', metavar='<POLICY>', required=True,
-           help=_('ID or name of policy to be attached.'))
-@utils.arg('-e', '--enabled', metavar='<BOOLEAN>', default=True,
-           help=_('Whether the policy should be enabled once attached. '
-                  'Default to enabled.'))
-@utils.arg('id', metavar='<NAME or ID>',
-           help=_('Name or ID of cluster to operate on.'))
-def do_cluster_policy_attach(service, args):
-    """Attach policy to cluster."""
-    show_deprecated('senlin cluster-policy-attach',
-                    'openstack cluster policy attach')
-    kwargs = {
-        'enabled': strutils.bool_from_string(args.enabled, strict=True),
-    }
-
-    resp = service.cluster_attach_policy(args.id, args.policy, **kwargs)
-    print('Request accepted by action: %s' % resp['action'])
-
-
-@utils.arg('-p', '--policy', metavar='<POLICY>', required=True,
-           help=_('ID or name of policy to be detached.'))
-@utils.arg('id', metavar='<NAME or ID>',
-           help=_('Name or ID of cluster to operate on.'))
-def do_cluster_policy_detach(service, args):
-    """Detach policy from cluster."""
-    show_deprecated('senlin cluster-policy-detach',
-                    'openstack cluster policy detach')
-    resp = service.cluster_detach_policy(args.id, args.policy)
-    print('Request accepted by action %s' % resp['action'])
-
-
-@utils.arg('-p', '--policy', metavar='<POLICY>', required=True,
-           help=_('ID or name of policy to be updated.'))
-@utils.arg('-e', '--enabled', metavar='<BOOLEAN>',
-           help=_('Whether the policy should be enabled.'))
-@utils.arg('id', metavar='<NAME or ID>',
-           help=_('Name or ID of cluster to operate on.'))
-def do_cluster_policy_update(service, args):
-    """Update a policy's properties on a cluster."""
-    show_deprecated('senlin cluster-policy-update',
-                    'openstack cluster policy binding update')
-    kwargs = {
-        'enabled': strutils.bool_from_string(args.enabled, strict=True),
-    }
-
-    resp = service.cluster_update_policy(args.id, args.policy, **kwargs)
-    print('Request accepted by action: %s' % resp['action'])
-
-
-@utils.arg('id', metavar='<CLUSTER>', nargs='+',
-           help=_('ID or name of cluster(s) to operate on.'))
-def do_cluster_check(service, args):
-    """Check the cluster(s)."""
-    show_deprecated('senlin cluster-check', 'openstack cluster check')
-    for cid in args.id:
-        resp = service.check_cluster(cid)
-        print('Cluster check request on cluster %(cid)s is accepted by '
-              'action %(action)s.' % {'cid': cid, 'action': resp['action']})
-
-
-@utils.arg('-c', '--check', metavar='<BOOLEAN>', default=False,
-           help=_("Whether the cluster should check it's nodes status before "
-                  "doing cluster recover. Default is false"))
-@utils.arg('id', metavar='<CLUSTER>', nargs='+',
-           help=_('ID or name of cluster(s) to operate on.'))
-def do_cluster_recover(service, args):
-    """Recover the cluster(s)."""
-    show_deprecated('senlin cluster-recover', 'openstack cluster recover')
-
-    params = {
-        'check': strutils.bool_from_string(args.check, strict=True)
-    }
-
-    for cid in args.id:
-        resp = service.recover_cluster(cid, **params)
-        print('Cluster recover request on cluster %(cid)s is accepted by '
-              'action %(action)s.' % {'cid': cid, 'action': resp['action']})
-
-
-@utils.arg('-p', '--params', metavar='<"KEY1=VALUE1;KEY2=VALUE2...">',
-           help=_("Parameter name and values for the operation specified. "
-                  "This can be specified multiple times, or once with "
-                  "key-value pairs separated by a semicolon."),
-           action='append')
-@utils.arg('-o', '--operation', metavar='<OPERATION>',
-           help=_("Name of an operation to be executed on the cluster."))
-@utils.arg('id', metavar='<CLUSTER>',
-           help=_('ID or name of a cluster.'))
-def do_cluster_op(service, args):
-    """Run an operation on a cluster."""
-    show_deprecated('senlin cluster-op', 'openstack cluster op')
-    params = utils.format_parameters(args.params)
-
-    try:
-        service.perform_operation_on_cluster(args.id, args.operation,
-                                             **params)
-    except exc.HTTPNotFound:
-        raise exc.CommandError(_('Cluster "%s" is not found') % args.id)
-    print('Request accepted')
-
-
-# NODES
-
-
-@utils.arg('-c', '--cluster', metavar='<CLUSTER>',
-           help=_('ID or name of cluster from which nodes are to be listed.'))
-@utils.arg('-f', '--filters', metavar='<"KEY1=VALUE1;KEY2=VALUE2...">',
-           help=_('Filter parameters to apply on returned nodes. '
-                  'This can be specified multiple times, or once with '
-                  'parameters separated by a semicolon.'),
-           action='append')
-@utils.arg('-o', '--sort', metavar='<KEY:DIR>',
-           help=_('Sorting option which is a string containing a list of keys '
-                  'separated by commas. Each key can be optionally appended '
-                  'by a sort direction (:asc or :desc)'))
-@utils.arg('-l', '--limit', metavar='<LIMIT>',
-           help=_('Limit the number of nodes returned.'))
-@utils.arg('-m', '--marker', metavar='<ID>',
-           help=_('Only return nodes that appear after the given node ID.'))
-@utils.arg('-g', '--global-project', default=False, action="store_true",
-           help=_('Indicate that this node list should include nodes from '
-                  'all projects. This option is subject to access policy '
-                  'checking. Default is False.'))
-@utils.arg('-F', '--full-id', default=False, action="store_true",
-           help=_('Print full IDs in list.'))
-def do_node_list(service, args):
-    """Show list of nodes."""
-    show_deprecated('senlin node-list', 'openstack cluster node list')
-
-    fields = ['id', 'name', 'index', 'status', 'cluster_id', 'physical_id',
-              'profile_name', 'created_at', 'updated_at']
-    queries = {
-        'cluster_id': args.cluster,
-        'sort': args.sort,
-        'limit': args.limit,
-        'marker': args.marker,
-        'global_project': args.global_project,
-    }
-
-    if args.filters:
-        queries.update(utils.format_parameters(args.filters))
-
-    sortby_index = None if args.sort else 6
-
-    nodes = service.nodes(**queries)
-
-    if args.global_project:
-        fields.append('project_id')
-    if not args.full_id:
-        formatters = {
-            'id': lambda x: x.id[:8],
-            'cluster_id': lambda x: x.cluster_id[:8] if x.cluster_id else '',
-            'physical_id': lambda x: x.physical_id[:8] if x.physical_id else ''
-        }
-        if 'project_id' in fields:
-            formatters['project_id'] = lambda x: x.project_id[:8]
-    else:
-        formatters = {}
-
-    utils.print_list(nodes, fields, formatters=formatters,
-                     sortby_index=sortby_index)
-
-
-def _show_node(service, node_id, show_details=False):
-    """Show detailed info about the specified node."""
-    try:
-        node = service.get_node(node_id, details=show_details)
-    except sdk_exc.ResourceNotFound:
-        raise exc.CommandError(_('Node not found: %s') % node_id)
-
-    formatters = {
-        'metadata': utils.json_formatter,
-        'data': utils.json_formatter,
-        'dependents': utils.json_formatter,
-    }
-    data = node.to_dict()
-    if show_details and data['details']:
-        formatters['details'] = utils.nested_dict_formatter(
-            list(data['details'].keys()), ['property', 'value'])
-
-    utils.print_dict(data, formatters=formatters)
-
-
-@utils.arg('-p', '--profile', metavar='<PROFILE>', required=True,
-           help=_('Profile Id or name used for this node.'))
-@utils.arg('-c', '--cluster', metavar='<CLUSTER>',
-           help=_('Cluster Id for this node.'))
-@utils.arg('-r', '--role', metavar='<ROLE>',
-           help=_('Role for this node in the specific cluster.'))
-@utils.arg('-M', '--metadata', metavar='<"KEY1=VALUE1;KEY2=VALUE2...">',
-           help=_('Metadata values to be attached to the node. '
-                  'This can be specified multiple times, or once with '
-                  'key-value pairs separated by a semicolon.'),
-           action='append')
-@utils.arg('name', metavar='<NODE_NAME>',
-           help=_('Name of the node to create.'))
-def do_node_create(service, args):
-    """Create the node."""
-    show_deprecated('senlin node-create', 'openstack cluster node create')
-    attrs = {
-        'name': args.name,
-        'cluster_id': args.cluster,
-        'profile_id': args.profile,
-        'role': args.role,
-        'metadata': utils.format_parameters(args.metadata),
-    }
-
-    node = service.create_node(**attrs)
-    _show_node(service, node.id)
-
-
-@utils.arg('-i', '--identity', metavar='<IDENTITY>', required=True,
-           help=_('Physical resource id.'))
-@utils.arg('-t', '--type', metavar='<TYPE>', required=True,
-           help=_('The name of the profile type.'))
-@utils.arg('-r', '--role', metavar='<ROLE>',
-           help=_('Role for this node in the specific cluster.'))
-@utils.arg('-M', '--metadata', metavar='<"KEY1=VALUE1;KEY2=VALUE2...">',
-           help=_('Metadata values to be attached to the node. '
-                  'This can be specified multiple times, or once with '
-                  'key-value pairs separated by a semicolon.'),
-           action='append')
-@utils.arg('-n', '--name', metavar='<NAME>',
-           help=_('The name for the node.'))
-@utils.arg('-o', '--overrides', metavar='<JSON">',
-           help=_('JSON formatted specification for overriding this node '
-                  'properties.'))
-@utils.arg('-p', '--preview', default=False,
-           help=_('Whether preview the node adopt request. If set, '
-                  'only previewing this node and do not adopt.'),
-           action='store_true')
-@utils.arg('-s', '--snapshot', default=False,
-           help=_('Whether a shapshot of the existing physical object should '
-                  'be created before the object is adopted as a node.'),
-           action='store_true')
-def do_node_adopt(service, args):
-    """Adopt (or preview) a node."""
-    show_deprecated('senlin node-adopt', 'openstack cluster node adopt')
-    if args.preview:
-        _do_node_adopt_preview(service, args)
-    else:
-        _do_node_adopt(service, args)
-
-
-def _do_node_adopt_preview(service, args):
-    attrs = {
-        'identity': args.identity,
-        'overrides': utils.format_json_parameter(args.overrides),
-        'snapshot': args.snapshot,
-        'type': args.type
-    }
-
-    node = service.adopt_node(True, **attrs)
-
-    formatters = {}
-    formatters['node_preview'] = utils.nested_dict_formatter(
-        ['type', 'version', 'properties'],
-        ['property', 'value'])
-    utils.print_dict(node['node_profile'], formatters=formatters)
-
-
-def _do_node_adopt(service, args):
-    attrs = {
-        'identity': args.identity,
-        'name': args.name,
-        'role': args.role,
-        'metadata': utils.format_parameters(args.metadata),
-        'overrides': utils.format_json_parameter(args.overrides),
-        'snapshot': args.snapshot,
-        'type': args.type
-    }
-
-    node = service.adopt_node(**attrs)
-    _show_node(service, node.id)
-
-
-@utils.arg('-D', '--details', default=False, action="store_true",
-           help=_('Include physical object details.'))
-@utils.arg('id', metavar='<NODE>',
-           help=_('Name or ID of the node to show the details for.'))
-def do_node_show(service, args):
-    """Show detailed info about the specified node."""
-    show_deprecated('senlin node-show', 'openstack cluster node show')
-    _show_node(service, args.id, args.details)
-
-
-@utils.arg('id', metavar='<NODE>', nargs='+',
-           help=_('Name or ID of node(s) to delete.'))
-@utils.arg('-f', '--force-delete', default=False, action="store_true",
-           help=_('Force to delete the node(s).'))
-def do_node_delete(service, args):
-    """Delete the node(s)."""
-    show_deprecated('senlin node-delete', 'openstack cluster node delete')
-
-    result = {}
-    for nid in args.id:
-        try:
-            node = service.delete_node(nid, args.force_delete, False)
-            result[nid] = ('OK', node.location.split('/')[-1])
-        except Exception as ex:
-            result[nid] = ('ERROR', six.text_type(ex))
-
-    for rid, res in result.items():
-        utils.print_action_result(rid, res)
-
-
-@utils.arg('-n', '--name', metavar='<NAME>',
-           help=_('New name for the node.'))
-@utils.arg('-p', '--profile', metavar='<PROFILE ID>',
-           help=_('ID or name of new profile to use.'))
-@utils.arg('-r', '--role', metavar='<ROLE>',
-           help=_('Role for this node in the specific cluster.'))
-@utils.arg('-M', '--metadata', metavar='<"KEY1=VALUE1;KEY2=VALUE2...">',
-           help=_("Metadata values to be attached to the node. "
-                  "This can be specified multiple times, or once with "
-                  "key-value pairs separated by a semicolon. Use '{}' "
-                  "can clean metadata "),
-           action='append')
-@utils.arg('id', metavar='<NODE>',
-           help=_('Name or ID of node to update.'))
-def do_node_update(service, args):
-    """Update the node."""
-    show_deprecated('senlin node-update', 'openstack cluster node update')
-    # Find the node first, we need its UUID
-    try:
-        node = service.get_node(args.id)
-    except sdk_exc.ResourceNotFound:
-        raise exc.CommandError(_('Node not found: %s') % args.id)
-
-    attrs = {
-        'name': args.name,
-        'role': args.role,
-        'profile_id': args.profile,
-        'metadata': utils.format_parameters(args.metadata),
-    }
-
-    service.update_node(node, **attrs)
-    _show_node(service, node.id)
-
-
-@utils.arg('id', metavar='<NODE>', nargs='+',
-           help=_('ID or name of node(s) to check.'))
-def do_node_check(service, args):
-    """Check the node(s)."""
-    show_deprecated('senlin node-check', 'openstack cluster node check')
-    failure_count = 0
-
-    for nid in args.id:
-        try:
-            service.check_node(nid)
-        except exc.HTTPNotFound:
-            failure_count += 1
-            print('Node id "%s" not found' % nid)
-    if failure_count > 0:
-        msg = _('Failed to check some of the specified nodes.')
-        raise exc.CommandError(msg)
-    print('Request accepted')
-
-
-@utils.arg('-c', '--check', metavar='<BOOLEAN>', default=False,
-           help=_("Whether the node(s) should check physical resource status "
-                  "before doing node recover.Default is false"))
-@utils.arg('id', metavar='<NODE>', nargs='+',
-           help=_('ID or name of node(s) to recover.'))
-def do_node_recover(service, args):
-    """Recover the node(s)."""
-    show_deprecated('senlin node-recover', 'openstack cluster node recover')
-    failure_count = 0
-
-    params = {
-        'check': strutils.bool_from_string(args.check, strict=True)
-    }
-
-    for nid in args.id:
-        try:
-            service.recover_node(nid, **params)
-        except exc.HTTPNotFound:
-            failure_count += 1
-            print('Node id "%s" not found' % nid)
-    if failure_count > 0:
-        msg = _('Failed to recover some of the specified nodes.')
-        raise exc.CommandError(msg)
-    print('Request accepted')
-
-
-@utils.arg('-p', '--params', metavar='<"KEY1=VALUE1;KEY2=VALUE2...">',
-           help=_("Parameter name and values for the operation specified. "
-                  "This can be specified multiple times, or once with "
-                  "key-value pairs separated by a semicolon."),
-           action='append')
-@utils.arg('-o', '--operation', metavar='<OPERATION>',
-           help=_("Name of an operation to be executed on the node"))
-@utils.arg('id', metavar='<NODE>',
-           help=_('ID or name of a node.'))
-def do_node_op(service, args):
-    """Run an operation on a node."""
-    show_deprecated('senlin node-op', 'openstack cluster node op')
-    if args.params:
-        params = utils.format_parameters(args.params)
-    else:
-        params = {}
-
-    try:
-        service.perform_operation_on_node(args.id, args.operation,
-                                          **params)
-    except exc.HTTPNotFound:
-        raise exc.CommandError(_('Node "%s" is not found') % args.id)
-    print('Request accepted')
-
-
-# RECEIVERS
-
-
-@utils.arg('-f', '--filters', metavar='<"KEY1=VALUE1;KEY2=VALUE2...">',
-           help=_('Filter parameters to apply on returned receivers. '
-                  'This can be specified multiple times, or once with '
-                  'parameters separated by a semicolon.'),
-           action='append')
-@utils.arg('-l', '--limit', metavar='<LIMIT>',
-           help=_('Limit the number of receivers returned.'))
-@utils.arg('-m', '--marker', metavar='<ID>',
-           help=_('Only return receivers that appear after the given ID.'))
-@utils.arg('-o', '--sort', metavar='<KEY:DIR>',
-           help=_('Sorting option which is a string containing a list of keys '
-                  'separated by commas. Each key can be optionally appended '
-                  'by a sort direction (:asc or :desc)'))
-@utils.arg('-g', '--global-project', default=False, action="store_true",
-           help=_('Indicate that the list should include receivers from'
-                  ' all projects. This option is subject to access policy '
-                  'checking. Default is False.'))
-@utils.arg('-F', '--full-id', default=False, action="store_true",
-           help=_('Print full IDs in list.'))
-def do_receiver_list(service, args):
-    """List receivers that meet the criteria."""
-    show_deprecated('senlin receiver-list', 'openstack cluster receiver list')
-    fields = ['id', 'name', 'type', 'cluster_id', 'action', 'created_at']
-    queries = {
-        'limit': args.limit,
-        'marker': args.marker,
-        'sort': args.sort,
-        'global_project': args.global_project,
-    }
-
-    if args.filters:
-        queries.update(utils.format_parameters(args.filters))
-
-    sortby_index = None if args.sort else 0
-
-    receivers = service.receivers(**queries)
-    formatters = {}
-    if args.global_project:
-        fields.append('project_id')
-        fields.append('user_id')
-    if not args.full_id:
-        formatters = {
-            'id': lambda x: x.id[:8],
-            'cluster_id': lambda x: x.cluster_id[:8] if x.cluster_id else '-',
-        }
-        if 'project_id' in fields:
-            formatters['project_id'] = lambda x: x.project_id[:8]
-            formatters['user_id'] = lambda x: x.user_id[:8]
-
-    utils.print_list(receivers, fields, formatters=formatters,
-                     sortby_index=sortby_index)
-
-
-def _show_receiver(service, receiver_id):
-    try:
-        receiver = service.get_receiver(receiver_id)
-    except sdk_exc.ResourceNotFound:
-        raise exc.CommandError(_('Receiver not found: %s') % receiver_id)
-
-    formatters = {
-        'actor': utils.json_formatter,
-        'params': utils.json_formatter,
-        'channel': utils.json_formatter,
-    }
-
-    utils.print_dict(receiver.to_dict(), formatters=formatters)
-
-
-@utils.arg('id', metavar='<RECEIVER>',
-           help=_('Name or ID of the receiver to show.'))
-def do_receiver_show(service, args):
-    """Show the receiver details."""
-    show_deprecated('senlin receiver-show', 'openstack cluster receiver show')
-    _show_receiver(service, receiver_id=args.id)
-
-
-@utils.arg('-t', '--type', metavar='<TYPE>', default='webhook',
-           help=_('Type of the receiver to create. Receiver type can be '
-                  '"webhook" or "message". Default to "webhook".'))
-@utils.arg('-c', '--cluster', metavar='<CLUSTER>',
-           help=_('Targeted cluster for this receiver. Required if receiver '
-                  'type is webhook.'))
-@utils.arg('-a', '--action', metavar='<ACTION>',
-           help=_('Name or ID of the targeted action to be triggered. '
-                  'Required if receiver type is webhook.'))
-@utils.arg('-P', '--params', metavar='<"KEY1=VALUE1;KEY2=VALUE2...">',
-           help=_('A dictionary of parameters that will be passed to target '
-                  'action when the receiver is triggered.'),
-           action='append')
-@utils.arg('name', metavar='<NAME>',
-           help=_('Name of the receiver to create.'))
-def do_receiver_create(service, args):
-    """Create a receiver."""
-    show_deprecated('senlin receiver-create',
-                    'openstack cluster receiver create')
-
-    if args.type == 'webhook':
-        if (not args.cluster or not args.action):
-            msg = _('cluster and action parameters are required to create '
-                    'webhook type of receiver.')
-            raise exc.CommandError(msg)
-
-    params = {
-        'name': args.name,
-        'type': args.type,
-        'cluster_id': args.cluster,
-        'action': args.action,
-        'params': utils.format_parameters(args.params)
-    }
-
-    receiver = service.create_receiver(**params)
-    _show_receiver(service, receiver.id)
-
-
-@utils.arg('-n', '--name', metavar='<NAME>',
-           help=_('The new name for the receiver.'))
-@utils.arg('-a', '--action', metavar='<ACTION>',
-           help=_('Name or ID of the targeted action to be triggered. '
-                  'Required if receiver type is webhook.'))
-@utils.arg('-P', '--params', metavar='<"KEY1=VALUE1;KEY2=VALUE2...">',
-           help=_('A dictionary of parameters that will be passed to target '
-                  'action when the receiver is triggered.'),
-           action='append')
-@utils.arg('id', metavar='<receiver>',
-           help=_('Name or ID of receiver to update.'))
-def do_receiver_update(service, args):
-    """Update a receiver."""
-    show_deprecated('senlin receiver-update',
-                    'openstack cluster receiver update')
-    params = {
-        'name': args.name,
-        'action': args.action,
-        'params': utils.format_parameters(args.params)
-    }
-
-    # Find the receiver first, we need its id
-    try:
-        receiver = service.get_receiver(args.id)
-    except sdk_exc.ResourceNotFound:
-        raise exc.CommandError(_('Receiver not found: %s') % args.id)
-    service.update_receiver(receiver, **params)
-    _show_receiver(service, receiver.id)
-
-
-@utils.arg('id', metavar='<RECEIVER>', nargs='+',
-           help=_('Name or ID of receiver(s) to delete.'))
-def do_receiver_delete(service, args):
-    """Delete receiver(s)."""
-    show_deprecated('senlin receiver-delete',
-                    'openstack cluster receiver delete')
-    failure_count = 0
-
-    for wid in args.id:
-        try:
-            service.delete_receiver(wid, False)
-        except Exception as ex:
-            failure_count += 1
-            print(ex)
-    if failure_count > 0:
-        msg = _('Failed to delete some of the specified receiver(s).')
-        raise exc.CommandError(msg)
-    print('Receivers deleted: %s' % args.id)
-
-
-# EVENTS
-
-
-@utils.arg('-f', '--filters', metavar='<"KEY1=VALUE1;KEY2=VALUE2...">',
-           help=_('Filter parameters to apply on returned events. '
-                  'This can be specified multiple times, or once with '
-                  'parameters separated by a semicolon.'),
-           action='append')
-@utils.arg('-l', '--limit', metavar='<LIMIT>',
-           help=_('Limit the number of events returned.'))
-@utils.arg('-m', '--marker', metavar='<ID>',
-           help=_('Only return events that appear after the given event ID.'))
-@utils.arg('-o', '--sort', metavar='<KEY:DIR>',
-           help=_('Sorting option which is a string containing a list of keys '
-                  'separated by commas. Each key can be optionally appended '
-                  'by a sort direction (:asc or :desc)'))
-@utils.arg('-g', '--global-project', default=False, action="store_true",
-           help=_('Whether events from all projects should be listed. '
-                  ' Default to False. Setting this to True may demand '
-                  'for an admin privilege.'))
-@utils.arg('-F', '--full-id', default=False, action="store_true",
-           help=_('Print full IDs in list.'))
-def do_event_list(service, args):
-    """List events."""
-    show_deprecated('senlin event-list', 'openstack cluster event list')
-    fields = ['id', 'generated_at', 'obj_type', 'obj_id', 'obj_name', 'action',
-              'status', 'level', 'cluster_id']
-    field_labels = ['id', 'timestamp', 'obj_type', 'obj_id', 'obj_name',
-                    'action', 'status', 'level', 'cluster_id']
-
-    queries = {
-        'sort': args.sort,
-        'limit': args.limit,
-        'marker': args.marker,
-        'global_project': args.global_project,
-    }
-
-    if args.filters:
-        queries.update(utils.format_parameters(args.filters))
-
-    sortby_index = None if args.sort else 0
-
-    formatters = {}
-    if args.global_project:
-        fields.append('project_id')
-        field_labels.append('project_id')
-    if not args.full_id:
-        formatters['id'] = lambda x: x.id[:8]
-        formatters['obj_id'] = lambda x: x.obj_id[:8] if x.obj_id else ''
-        formatters['cluster_id'] = (lambda x: x.cluster_id[:8]
-                                    if x.cluster_id else '')
-        if 'project_id' in fields:
-            formatters['project_id'] = lambda x: x.project_id[:8]
-
-    events = service.events(**queries)
-    utils.print_list(events, fields, formatters=formatters,
-                     sortby_index=sortby_index, field_labels=field_labels)
-
-
-@utils.arg('id', metavar='<EVENT>',
-           help=_('ID of event to display details for.'))
-def do_event_show(service, args):
-    """Describe the event."""
-    show_deprecated('senlin event-show', 'openstack cluster event show')
-    try:
-        event = service.get_event(args.id)
-    except sdk_exc.ResourceNotFound:
-        raise exc.CommandError(_("Event not found: %s") % args.id)
-
-    utils.print_dict(event.to_dict())
-
-
-# ACTIONS
-
-
-@utils.arg('-f', '--filters', metavar='<"KEY1=VALUE1;KEY2=VALUE2...">',
-           help=_('Filter parameters to apply on returned actions. '
-                  'This can be specified multiple times, or once with '
-                  'parameters separated by a semicolon.'),
-           action='append')
-@utils.arg('-o', '--sort', metavar='<KEY:DIR>',
-           help=_('Sorting option which is a string containing a list of keys '
-                  'separated by commas. Each key can be optionally appended '
-                  'by a sort direction (:asc or :desc)'))
-@utils.arg('-l', '--limit', metavar='<LIMIT>',
-           help=_('Limit the number of actions returned.'))
-@utils.arg('-m', '--marker', metavar='<ID>',
-           help=_('Only return actions that appear after the given node ID.'))
-@utils.arg('-g', '--global-project', default=False, action="store_true",
-           help=_('Whether actions from all projects should be listed. '
-                  ' Default to False. Setting this to True may demand '
-                  'for an admin privilege.'))
-@utils.arg('-F', '--full-id', default=False, action="store_true",
-           help=_('Print full IDs in list.'))
-def do_action_list(service, args):
-    """List actions."""
-    show_deprecated('senlin action-list', 'openstack cluster action list')
-    fields = ['id', 'name', 'action', 'status', 'target_id', 'depends_on',
-              'depended_by', 'created_at']
-
-    queries = {
-        'sort': args.sort,
-        'limit': args.limit,
-        'marker': args.marker,
-        'global_project': args.global_project,
-    }
-
-    if args.filters:
-        queries.update(utils.format_parameters(args.filters))
-
-    sortby_index = None if args.sort else 0
-
-    actions = service.actions(**queries)
-
-    formatters = {}
-    s = None
-    if not args.full_id:
-        s = 8
-        formatters['id'] = lambda x: x.id[:s]
-        formatters['target_id'] = lambda x: x.target_id[:s]
-
-    formatters['depends_on'] = lambda x: '\n'.join(a[:s] for a in x.depends_on)
-    formatters['depended_by'] = lambda x: '\n'.join(a[:s] for a in x.
-                                                    depended_by)
-
-    utils.print_list(actions, fields, formatters=formatters,
-                     sortby_index=sortby_index)
-
-
-@utils.arg('id', metavar='<ACTION>',
-           help=_('Name or ID of the action to show the details for.'))
-def do_action_show(service, args):
-    """Show detailed info about the specified action."""
-    show_deprecated('senlin action-show', 'openstack cluster action show')
-    try:
-        action = service.get_action(args.id)
-    except sdk_exc.ResourceNotFound:
-        raise exc.CommandError(_('Action not found: %s') % args.id)
-
-    formatters = {
-        'inputs': utils.json_formatter,
-        'outputs': utils.json_formatter,
-        'metadata': utils.json_formatter,
-        'data': utils.json_formatter,
-        'depends_on': utils.list_formatter,
-        'depended_by': utils.list_formatter,
-    }
-
-    utils.print_dict(action.to_dict(), formatters=formatters)
-
-
-def do_service_list(service, args=None):
-    """Show a list of all running services."""
-    show_deprecated('senlin service-list',
-                    'openstack cluster service list')
-    fields = ['binary', 'host', 'status', 'state', 'updated_at',
-              'disabled_reason']
-    queries = {}
-    result = service.services(**queries)
-
-    formatters = {}
-    utils.print_list(result, fields, formatters=formatters)
diff --git a/setup.cfg b/setup.cfg
index 5fae003e..56fbbaf5 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -23,9 +23,6 @@ packages =
     senlinclient
 
 [entry_points]
-console_scripts =
-    senlin = senlinclient.shell:main
-
 openstack.cli.extension =
     clustering = senlinclient.plugin
 
diff --git a/tools/senlin.bash_completion b/tools/senlin.bash_completion
deleted file mode 100644
index a82bad82..00000000
--- a/tools/senlin.bash_completion
+++ /dev/null
@@ -1,27 +0,0 @@
-# bash completion for openstack senlin 
-
-_senlin_opts="" # lazy init
-_senlin_flags="" # lazy init
-_senlin_opts_exp="" # lazy init
-_senlin()
-{
-    local cur prev kbc
-    COMPREPLY=()
-    cur="${COMP_WORDS[COMP_CWORD]}"
-    prev="${COMP_WORDS[COMP_CWORD-1]}"
-
-    if [ "x$_senlin_opts" == "x" ] ; then
-        kbc="`senlin bash-completion | sed -e "s/ -h / /"`"
-        _senlin_opts="`echo "$kbc" | sed -e "s/--[a-z0-9_-]*//g" -e "s/[ ][ ]*/ /g"`"
-        _senlin_flags="`echo " $kbc" | sed -e "s/ [^-][^-][a-z0-9_-]*//g" -e "s/[ ][ ]*/ /g"`"
-        _senlin_opts_exp="`echo $_senlin_opts | sed -e "s/[ ]/|/g"`"
-    fi
-
-    if [[ " ${COMP_WORDS[@]} " =~ " "($_senlin_opts_exp)" " && "$prev" != "help" ]] ; then
-        COMPREPLY=($(compgen -W "${_senlin_flags}" -- ${cur}))
-    else
-        COMPREPLY=($(compgen -W "${_senlin_opts}" -- ${cur}))
-    fi
-    return 0
-}
-complete -o default -F _senlin senlin