diff --git a/senlinclient/tests/functional/base.py b/senlinclient/tests/functional/base.py
index ed6dfe29..8b13bf5d 100644
--- a/senlinclient/tests/functional/base.py
+++ b/senlinclient/tests/functional/base.py
@@ -12,10 +12,12 @@
 
 import os
 import six
+import time
 
 from oslo_utils import uuidutils
 from tempest.lib.cli import base
 from tempest.lib.cli import output_parser
+from tempest.lib import exceptions as tempest_lib_exc
 
 
 class OpenStackClientTestBase(base.ClientTestBase):
@@ -63,8 +65,56 @@ class OpenStackClientTestBase(base.ClientTestBase):
         return os.path.join(os.path.dirname(os.path.realpath(__file__)),
                             'policies/%s' % policy_name)
 
-    def policy_create(self, name, profile):
-        pf = self._get_policy_path(profile)
+    def wait_for_status(self, name, status, check_type, timeout=60,
+                        poll_interval=5):
+        """Wait until name reaches given status.
+
+        :param name: node or cluster name
+        :param status: expected status of node or cluster
+        :param timeout: timeout in seconds
+        :param poll_interval: poll interval in seconds
+        """
+        if check_type == 'node':
+            cmd = ('cluster node show %s' % name)
+        elif check_type == 'cluster':
+            cmd = ('cluster show %s' % name)
+        time.sleep(poll_interval)
+        start_time = time.time()
+        while time.time() - start_time < timeout:
+            check_status = self.openstack(cmd)
+            result = self.show_to_dict(check_status)
+            if result['status'] == status:
+                break
+            time.sleep(poll_interval)
+        else:
+            message = ("%s %s did not reach status %s after %d s"
+                       % (check_type, name, status, timeout))
+            raise tempest_lib_exc.TimeoutException(message)
+
+    def wait_for_delete(self, name, check_type, timeout=60,
+                        poll_interval=5):
+        """Wait until delete finish"""
+        if check_type == 'node':
+            cmd = ('cluster node show %s' % name)
+        if check_type == 'cluster':
+            cmd = ('cluster show %s' % name)
+        time.sleep(poll_interval)
+        start_time = time.time()
+        while time.time() - start_time < timeout:
+            try:
+                self.openstack(cmd)
+            except tempest_lib_exc.CommandFailed as ex:
+                if "No Node found" or "No Cluster found" in ex.stderr:
+                    break
+            time.sleep(poll_interval)
+        else:
+
+            message = ("failed in deleting %s %s after %d seconds"
+                       % (check_type, name, timeout))
+            raise tempest_lib_exc.TimeoutException(message)
+
+    def policy_create(self, name, policy):
+        pf = self._get_policy_path(policy)
         cmd = ('cluster policy create --spec-file %s %s'
                % (pf, name))
         policy_raw = self.openstack(cmd)
@@ -75,8 +125,8 @@ class OpenStackClientTestBase(base.ClientTestBase):
         cmd = ('cluster policy delete %s --force' % name_or_id)
         self.openstack(cmd)
 
-    def profile_create(self, name, policy):
-        pf = self._get_profile_path(policy)
+    def profile_create(self, name, profile='cirros_basic.yaml'):
+        pf = self._get_profile_path(profile)
         cmd = ('cluster profile create --spec-file %s %s'
                % (pf, name))
         profile_raw = self.openstack(cmd)
@@ -86,3 +136,16 @@ class OpenStackClientTestBase(base.ClientTestBase):
     def profile_delete(self, name_or_id):
         cmd = ('cluster profile delete %s --force' % name_or_id)
         self.openstack(cmd)
+
+    def node_create(self, profile, name):
+        cmd = ('cluster node create --profile %s %s'
+               % (profile, name))
+        node_raw = self.openstack(cmd)
+        result = self.show_to_dict(node_raw)
+        self.wait_for_status(name, 'ACTIVE', 'node', 120)
+        return result
+
+    def node_delete(self, name_or_id):
+        cmd = ('cluster node delete %s --force' % name_or_id)
+        self.openstack(cmd)
+        self.wait_for_delete(name_or_id, 'node', 120)
diff --git a/senlinclient/tests/functional/profiles/cirros_basic.yaml b/senlinclient/tests/functional/profiles/cirros_basic.yaml
index ad122a7a..e4825c96 100644
--- a/senlinclient/tests/functional/profiles/cirros_basic.yaml
+++ b/senlinclient/tests/functional/profiles/cirros_basic.yaml
@@ -1,7 +1,6 @@
 type: os.nova.server
 version: 1.0
 properties:
-  name: cirros_server
   flavor: 1
   image: "cirros-0.3.5-x86_64-disk.img"
   networks:
diff --git a/senlinclient/tests/functional/test_nodes.py b/senlinclient/tests/functional/test_nodes.py
index f0025159..5e407b37 100644
--- a/senlinclient/tests/functional/test_nodes.py
+++ b/senlinclient/tests/functional/test_nodes.py
@@ -23,3 +23,79 @@ class NodeTest(base.OpenStackClientTestBase):
                                            'cluster_id', 'physical_id',
                                            'profile_name', 'created_at',
                                            'updated_at'])
+
+    def test_node_create(self):
+        name = self.name_generate()
+        pf = self.profile_create(name)
+        node = self.node_create(pf['id'], name)
+        self.assertEqual(node['name'], name)
+        self.node_delete(node['id'])
+        self.addCleanup(self.profile_delete, pf['id'])
+
+    def test_node_update(self):
+        old_name = self.name_generate()
+        pf = self.profile_create(old_name)
+        n1 = self.node_create(pf['id'], old_name)
+        new_name = self.name_generate()
+        pf_new = self.profile_create(new_name)
+        role = 'master'
+        cmd = ('cluster node update --name %s --role %s --profile %s %s'
+               % (new_name, role, pf_new['id'], n1['id']))
+        self.openstack(cmd)
+        self.wait_for_status(n1['id'], 'ACTIVE', 'node', 120)
+        raw_node = self.openstack('cluster node show %s' % n1['id'])
+        node_data = self.show_to_dict(raw_node)
+        self.assertEqual(node_data['name'], new_name)
+        self.assertNotEqual(node_data['name'], old_name)
+        self.assertEqual(node_data['role'], role)
+        self.assertEqual(node_data['profile_id'], pf_new['id'])
+        self.node_delete(new_name)
+        self.addCleanup(self.profile_delete, pf['id'])
+        self.addCleanup(self.profile_delete, pf_new['id'])
+
+    def test_node_detail(self):
+        name = self.name_generate()
+        pf = self.profile_create(name)
+        node = self.node_create(pf['id'], name)
+        cmd = ('cluster node show --details %s' % name)
+        raw_node = self.openstack(cmd)
+        node_data = self.show_to_dict(raw_node)
+        self.assertIn('details', node_data)
+        self.assertIsNotNone(node_data['details'])
+        self.node_delete(node['id'])
+        self.addCleanup(self.profile_delete, pf['id'])
+
+    # NOTE(Qiming): Since functional tests only focus on the client/server
+    # interaction without invovling other OpenStack services, it is not
+    # possible to mock a node failure and then test if the check logic works.
+    # Such tests would be left to integration tests instead.
+    def test_node_check(self):
+        name = self.name_generate()
+        pf = self.profile_create(name)
+        node = self.node_create(pf['id'], name)
+        cmd = ('cluster node check %s' % node['id'])
+        self.openstack(cmd)
+        check_raw = self.openstack('cluster node show %s' % name)
+        check_data = self.show_to_dict(check_raw)
+        self.assertIn('Check', check_data['status_reason'])
+        node_status = ['ACTIVE', 'ERROR']
+        self.assertIn(check_data['status'], node_status)
+        self.node_delete(node['id'])
+        self.addCleanup(self.profile_delete, pf['id'])
+
+    # NOTE(Qiming): A end-to-end test of the node recover operation needs to
+    # be done with other OpenStack services involved, thus out of scope for
+    # functional tests. Such tests would be left to integration tests instead.
+    def test_node_recover(self):
+        name = self.name_generate()
+        pf = self.profile_create(name)
+        node = self.node_create(pf['id'], name)
+        cmd = ('cluster node recover --check true %s' % node['id'])
+        self.openstack(cmd)
+        self.wait_for_status(name, 'ACTIVE', 'node', 120)
+        recover_raw = self.openstack('cluster node show %s' % name)
+        recover_data = self.show_to_dict(recover_raw)
+        self.assertIn('Recover', recover_data['status_reason'])
+        self.assertEqual('ACTIVE', recover_data['status'])
+        self.node_delete(node['id'])
+        self.addCleanup(self.profile_delete, pf['id'])
diff --git a/senlinclient/tests/functional/test_policy_types.py b/senlinclient/tests/functional/test_policy_types.py
index e09f4760..ab239008 100644
--- a/senlinclient/tests/functional/test_policy_types.py
+++ b/senlinclient/tests/functional/test_policy_types.py
@@ -19,8 +19,10 @@ class PolicyTypeTest(base.OpenStackClientTestBase):
     def test_policy_type_list(self):
         result = self.openstack('cluster policy type list')
         policy_type = self.parser.listing(result)
-        self.assertTableStruct(policy_type, ['name', 'version',
-                                             'support_status'])
+        columns = ['name', 'version']
+        if any('support_status' in i for i in policy_type):
+            columns.append('support_status')
+        self.assertTableStruct(policy_type, columns)
 
     def test_policy_type_show(self):
         params = ['senlin.policy.affinity-1.0',
diff --git a/senlinclient/tests/functional/test_profile_types.py b/senlinclient/tests/functional/test_profile_types.py
index 0044dfb3..8d973133 100644
--- a/senlinclient/tests/functional/test_profile_types.py
+++ b/senlinclient/tests/functional/test_profile_types.py
@@ -22,11 +22,13 @@ class ProfileTypeTest(base.OpenStackClientTestBase):
     def test_profile_type_list(self):
         result = self.openstack('cluster profile type list')
         profile_type = self.parser.listing(result)
-        self.assertTableStruct(profile_type, ['name', 'version',
-                                              'support_status'])
+        columns = ['name', 'version']
+        if any('support_status' in i for i in profile_type):
+            columns.append('support_status')
+        self.assertTableStruct(profile_type, columns)
 
     def test_profile_list_debug(self):
-        self.openstack('cluster profile list', flags='--debug')
+        self.openstack('cluster profile type list', flags='--debug')
 
     def test_profile_type_show(self):
         params = ['container.dockerinc.docker-1.0',