diff --git a/novaclient/__init__.py b/novaclient/__init__.py
index bfa75532f..43e5f2567 100644
--- a/novaclient/__init__.py
+++ b/novaclient/__init__.py
@@ -14,5 +14,10 @@
 
 import pbr.version
 
+from novaclient import api_versions
+
 
 __version__ = pbr.version.VersionInfo('python-novaclient').version_string()
+
+API_MIN_VERSION = api_versions.APIVersion("2.1")
+API_MAX_VERSION = api_versions.APIVersion("2.1")
diff --git a/novaclient/api_versions.py b/novaclient/api_versions.py
index e50edea2a..ba4a56db8 100644
--- a/novaclient/api_versions.py
+++ b/novaclient/api_versions.py
@@ -19,6 +19,7 @@ import re
 
 from oslo_utils import strutils
 
+import novaclient
 from novaclient import exceptions
 from novaclient.i18n import _, _LW
 from novaclient import utils
@@ -229,6 +230,75 @@ def get_api_version(version_string):
     return api_version
 
 
+def _get_server_version_range(client):
+    version = client.versions.get_current()
+
+    if not hasattr(version, 'version') or not version.version:
+        return APIVersion(), APIVersion()
+
+    return APIVersion(version.min_version), APIVersion(version.version)
+
+
+def discover_version(client, requested_version):
+    """Returns latest version supported by both API and client.
+
+    :param client: client object
+    :returns: APIVersion
+    """
+
+    server_start_version, server_end_version = _get_server_version_range(
+        client)
+
+    if (not requested_version.is_latest() and
+            requested_version != APIVersion('2.0')):
+        if server_start_version.is_null() and server_end_version.is_null():
+            raise exceptions.UnsupportedVersion(
+                _("Server doesn't support microversions"))
+        if not requested_version.matches(server_start_version,
+                                         server_end_version):
+            raise exceptions.UnsupportedVersion(
+                _("The specified version isn't supported by server. The valid "
+                  "version range is '%(min)s' to '%(max)s'") % {
+                    "min": server_start_version.get_string(),
+                    "max": server_end_version.get_string()})
+        return requested_version
+
+    if requested_version == APIVersion('2.0'):
+        if (server_start_version == APIVersion('2.1') or
+                (server_start_version.is_null() and
+                 server_end_version.is_null())):
+            return APIVersion('2.0')
+        else:
+            raise exceptions.UnsupportedVersion(
+                _("The server isn't backward compatible with Nova V2 REST "
+                  "API"))
+
+    if server_start_version.is_null() and server_end_version.is_null():
+        return APIVersion('2.0')
+    elif novaclient.API_MIN_VERSION > server_end_version:
+        raise exceptions.UnsupportedVersion(
+            _("Server version is too old. The client valid version range is "
+              "'%(client_min)s' to '%(client_max)s'. The server valid version "
+              "range is '%(server_min)s' to '%(server_max)s'.") % {
+                  'client_min': novaclient.API_MIN_VERSION.get_string(),
+                  'client_max': novaclient.API_MAX_VERSION.get_string(),
+                  'server_min': server_start_version.get_string(),
+                  'server_max': server_end_version.get_string()})
+    elif novaclient.API_MAX_VERSION < server_start_version:
+        raise exceptions.UnsupportedVersion(
+            _("Server version is too new. The client valid version range is "
+              "'%(client_min)s' to '%(client_max)s'. The server valid version "
+              "range is '%(server_min)s' to '%(server_max)s'.") % {
+                  'client_min': novaclient.API_MIN_VERSION.get_string(),
+                  'client_max': novaclient.API_MAX_VERSION.get_string(),
+                  'server_min': server_start_version.get_string(),
+                  'server_max': server_end_version.get_string()})
+    elif novaclient.API_MAX_VERSION <= server_end_version:
+        return novaclient.API_MAX_VERSION
+    elif server_end_version < novaclient.API_MAX_VERSION:
+        return server_end_version
+
+
 def update_headers(headers, api_version):
     """Set 'X-OpenStack-Nova-API-Version' header if api_version is not null"""
 
diff --git a/novaclient/shell.py b/novaclient/shell.py
index acda8bf04..42fcbdcda 100644
--- a/novaclient/shell.py
+++ b/novaclient/shell.py
@@ -403,8 +403,8 @@ class OpenStackComputeShell(object):
             metavar='<compute-api-ver>',
             default=cliutils.env('OS_COMPUTE_API_VERSION',
                                  default=DEFAULT_OS_COMPUTE_API_VERSION),
-            help=_('Accepts X, X.Y (where X is major and Y is minor part), '
-                   'defaults to env[OS_COMPUTE_API_VERSION].'))
+            help=_('Accepts X, X.Y (where X is major and Y is minor part) or '
+                   '"X.latest", defaults to env[OS_COMPUTE_API_VERSION].'))
         parser.add_argument(
             '--os_compute_api_version',
             help=argparse.SUPPRESS)
@@ -547,18 +547,6 @@ class OpenStackComputeShell(object):
     def main(self, argv):
         # Parse args once to find version and debug settings
         parser = self.get_base_parser()
-        (options, args) = parser.parse_known_args(argv)
-        self.setup_debugging(options.debug)
-
-        # Discover available auth plugins
-        novaclient.auth_plugin.discover_auth_systems()
-
-        api_version = api_versions.get_api_version(
-            options.os_compute_api_version)
-
-        # build available subcommands based on version
-        self.extensions = self._discover_extensions(api_version)
-        self._run_extension_hooks('__pre_parse_args__')
 
         # NOTE(dtroyer): Hackery to handle --endpoint_type due to argparse
         #                thinking usage-list --end is ambiguous; but it
@@ -568,24 +556,17 @@ class OpenStackComputeShell(object):
             spot = argv.index('--endpoint_type')
             argv[spot] = '--endpoint-type'
 
-        subcommand_parser = self.get_subcommand_parser(
-            api_version, do_help=("help" in args))
-        self.parser = subcommand_parser
+        (args, args_list) = parser.parse_known_args(argv)
 
-        if options.help or not argv:
-            subcommand_parser.print_help()
-            return 0
+        self.setup_debugging(args.debug)
+        self.extensions = []
+        do_help = ('help' in argv) or not argv
 
-        args = subcommand_parser.parse_args(argv)
-        self._run_extension_hooks('__post_parse_args__', args)
+        # Discover available auth plugins
+        novaclient.auth_plugin.discover_auth_systems()
 
-        # Short-circuit and deal with help 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
+        api_version = api_versions.get_api_version(
+            args.os_compute_api_version)
 
         os_username = args.os_username
         os_user_id = args.os_user_id
@@ -631,13 +612,13 @@ class OpenStackComputeShell(object):
             endpoint_type += 'URL'
 
         if not service_type:
-            service_type = (cliutils.get_service_type(args.func) or
-                            DEFAULT_NOVA_SERVICE_TYPE)
+            # Note(alex_xu): We need discover version first, so if there isn't
+            # service type specified, we use default nova service type.
+            service_type = DEFAULT_NOVA_SERVICE_TYPE
 
         # If we have an auth token but no management_url, we must auth anyway.
         # Expired tokens are handled by client.py:_cs_request
-        must_auth = not (cliutils.isunauthenticated(args.func)
-                         or (auth_token and management_url))
+        must_auth = not (auth_token and management_url)
 
         # Do not use Keystone session for cases with no session support. The
         # presence of auth_plugin means os_auth_system is present and is not
@@ -648,7 +629,7 @@ class OpenStackComputeShell(object):
 
         # FIXME(usrleon): Here should be restrict for project id same as
         # for os_username or os_password but for compatibility it is not.
-        if must_auth:
+        if must_auth and not do_help:
             if auth_plugin:
                 auth_plugin.parse_opts(args)
 
@@ -702,8 +683,8 @@ class OpenStackComputeShell(object):
                         project_domain_id=args.os_project_domain_id,
                         project_domain_name=args.os_project_domain_name)
 
-        if not any([args.os_tenant_id, args.os_tenant_name,
-                    args.os_project_id, args.os_project_name]):
+        if not do_help and not any([args.os_tenant_id, args.os_tenant_name,
+                                    args.os_project_id, args.os_project_name]):
             raise exc.CommandError(_("You must provide a project name or"
                                      " project id via --os-project-name,"
                                      " --os-project-id, env[OS_PROJECT_ID]"
@@ -711,11 +692,77 @@ class OpenStackComputeShell(object):
                                      " use os-project and os-tenant"
                                      " interchangeably."))
 
-        if not os_auth_url:
+        if not os_auth_url and not do_help:
             raise exc.CommandError(
                 _("You must provide an auth url "
                   "via either --os-auth-url or env[OS_AUTH_URL]"))
 
+        # This client is just used to discover api version. Version API needn't
+        # microversion, so we just pass version 2 at here.
+        self.cs = client.Client(
+            api_versions.APIVersion("2.0"),
+            os_username, os_password, os_tenant_name,
+            tenant_id=os_tenant_id, user_id=os_user_id,
+            auth_url=os_auth_url, insecure=insecure,
+            region_name=os_region_name, endpoint_type=endpoint_type,
+            extensions=self.extensions, service_type=service_type,
+            service_name=service_name, auth_system=os_auth_system,
+            auth_plugin=auth_plugin, auth_token=auth_token,
+            volume_service_name=volume_service_name,
+            timings=args.timings, bypass_url=bypass_url,
+            os_cache=os_cache, http_log_debug=args.debug,
+            cacert=cacert, timeout=timeout,
+            session=keystone_session, auth=keystone_auth)
+
+        if not do_help:
+            if not api_version.is_latest():
+                if api_version > api_versions.APIVersion("2.0"):
+                    if not api_version.matches(novaclient.API_MIN_VERSION,
+                                               novaclient.API_MAX_VERSION):
+                        raise exc.CommandError(
+                            _("The specified version isn't supported by "
+                              "client. The valid version range is '%(min)s' "
+                              "to '%(max)s'") % {
+                                "min": novaclient.API_MIN_VERSION.get_string(),
+                                "max": novaclient.API_MAX_VERSION.get_string()}
+                        )
+            api_version = api_versions.discover_version(self.cs, api_version)
+
+        # build available subcommands based on version
+        self.extensions = self._discover_extensions(api_version)
+        self._run_extension_hooks('__pre_parse_args__')
+
+        subcommand_parser = self.get_subcommand_parser(
+            api_version, do_help=do_help)
+        self.parser = subcommand_parser
+
+        if args.help or not argv:
+            subcommand_parser.print_help()
+            return 0
+
+        args = subcommand_parser.parse_args(argv)
+        self._run_extension_hooks('__post_parse_args__', args)
+
+        # Short-circuit and deal with help 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
+
+        if not args.service_type:
+            service_type = (cliutils.get_service_type(args.func) or
+                            DEFAULT_NOVA_SERVICE_TYPE)
+
+        if cliutils.isunauthenticated(args.func):
+            # NOTE(alex_xu): We need authentication for discover microversion.
+            # But the subcommands may needn't it. If the subcommand needn't,
+            # we clear the session arguements.
+            keystone_session = None
+            keystone_auth = None
+
+        # Recreate client object with discovered version.
         self.cs = client.Client(
             api_version,
             os_username, os_password, os_tenant_name,
@@ -727,7 +774,7 @@ class OpenStackComputeShell(object):
             auth_plugin=auth_plugin, auth_token=auth_token,
             volume_service_name=volume_service_name,
             timings=args.timings, bypass_url=bypass_url,
-            os_cache=os_cache, http_log_debug=options.debug,
+            os_cache=os_cache, http_log_debug=args.debug,
             cacert=cacert, timeout=timeout,
             session=keystone_session, auth=keystone_auth)
 
diff --git a/novaclient/tests/unit/test_api_versions.py b/novaclient/tests/unit/test_api_versions.py
index 2db3a8859..e3c204de4 100644
--- a/novaclient/tests/unit/test_api_versions.py
+++ b/novaclient/tests/unit/test_api_versions.py
@@ -1,4 +1,4 @@
-# Copyright 2015 Mirantis
+# Copyright 2016 Mirantis
 # All Rights Reserved.
 #
 #    Licensed under the Apache License, Version 2.0 (the "License"); you may
@@ -15,9 +15,11 @@
 
 import mock
 
+import novaclient
 from novaclient import api_versions
 from novaclient import exceptions
 from novaclient.tests.unit import utils
+from novaclient.v2 import versions
 
 
 class APIVersionTestCase(utils.TestCase):
@@ -247,3 +249,88 @@ class WrapsTestCase(utils.TestCase):
         some_func(obj, *some_args, **some_kwargs)
 
         checker.assert_called_once_with(*((obj,) + some_args), **some_kwargs)
+
+
+class DiscoverVersionTestCase(utils.TestCase):
+    def setUp(self):
+        super(DiscoverVersionTestCase, self).setUp()
+        self.orig_max = novaclient.API_MAX_VERSION
+        self.orig_min = novaclient.API_MIN_VERSION
+        self.addCleanup(self._clear_fake_version)
+
+    def _clear_fake_version(self):
+        novaclient.API_MAX_VERSION = self.orig_max
+        novaclient.API_MIN_VERSION = self.orig_min
+
+    def test_server_is_too_new(self):
+        fake_client = mock.MagicMock()
+        fake_client.versions.get_current.return_value = mock.MagicMock(
+            version="2.7", min_version="2.4")
+        novaclient.API_MAX_VERSION = api_versions.APIVersion("2.3")
+        novaclient.API_MIN_VERSION = api_versions.APIVersion("2.1")
+        self.assertRaises(exceptions.UnsupportedVersion,
+                          api_versions.discover_version, fake_client,
+                          api_versions.APIVersion('2.latest'))
+
+    def test_server_is_too_old(self):
+        fake_client = mock.MagicMock()
+        fake_client.versions.get_current.return_value = mock.MagicMock(
+            version="2.7", min_version="2.4")
+        novaclient.API_MAX_VERSION = api_versions.APIVersion("2.10")
+        novaclient.API_MIN_VERSION = api_versions.APIVersion("2.9")
+
+        self.assertRaises(exceptions.UnsupportedVersion,
+                          api_versions.discover_version, fake_client,
+                          api_versions.APIVersion('2.latest'))
+
+    def test_server_end_version_is_the_latest_one(self):
+        fake_client = mock.MagicMock()
+        fake_client.versions.get_current.return_value = mock.MagicMock(
+            version="2.7", min_version="2.4")
+        novaclient.API_MAX_VERSION = api_versions.APIVersion("2.11")
+        novaclient.API_MIN_VERSION = api_versions.APIVersion("2.1")
+
+        self.assertEqual(
+            "2.7",
+            api_versions.discover_version(
+                fake_client,
+                api_versions.APIVersion('2.latest')).get_string())
+
+    def test_client_end_version_is_the_latest_one(self):
+        fake_client = mock.MagicMock()
+        fake_client.versions.get_current.return_value = mock.MagicMock(
+            version="2.16", min_version="2.4")
+        novaclient.API_MAX_VERSION = api_versions.APIVersion("2.11")
+        novaclient.API_MIN_VERSION = api_versions.APIVersion("2.1")
+
+        self.assertEqual(
+            "2.11",
+            api_versions.discover_version(
+                fake_client,
+                api_versions.APIVersion('2.latest')).get_string())
+
+    def test_server_without_microversion(self):
+        fake_client = mock.MagicMock()
+        fake_client.versions.get_current.return_value = mock.MagicMock(
+            version='', min_version='')
+        novaclient.API_MAX_VERSION = api_versions.APIVersion("2.11")
+        novaclient.API_MIN_VERSION = api_versions.APIVersion("2.1")
+
+        self.assertEqual(
+            "2.0",
+            api_versions.discover_version(
+                fake_client,
+                api_versions.APIVersion('2.latest')).get_string())
+
+    def test_server_without_microversion_and_no_version_field(self):
+        fake_client = mock.MagicMock()
+        fake_client.versions.get_current.return_value = versions.Version(
+            None, {})
+        novaclient.API_MAX_VERSION = api_versions.APIVersion("2.11")
+        novaclient.API_MIN_VERSION = api_versions.APIVersion("2.1")
+
+        self.assertEqual(
+            "2.0",
+            api_versions.discover_version(
+                fake_client,
+                api_versions.APIVersion('2.latest')).get_string())
diff --git a/novaclient/tests/unit/test_shell.py b/novaclient/tests/unit/test_shell.py
index 1caa56cbd..2209f6996 100644
--- a/novaclient/tests/unit/test_shell.py
+++ b/novaclient/tests/unit/test_shell.py
@@ -104,6 +104,19 @@ class ShellTest(utils.TestCase):
         self.nc_util = mock.patch(
             'novaclient.openstack.common.cliutils.isunauthenticated').start()
         self.nc_util.return_value = False
+        self.mock_server_version_range = mock.patch(
+            'novaclient.api_versions._get_server_version_range').start()
+        self.mock_server_version_range.return_value = (
+            novaclient.API_MIN_VERSION,
+            novaclient.API_MIN_VERSION)
+        self.orig_max_ver = novaclient.API_MAX_VERSION
+        self.orig_min_ver = novaclient.API_MIN_VERSION
+        self.addCleanup(self._clear_fake_version)
+        self.addCleanup(mock.patch.stopall)
+
+    def _clear_fake_version(self):
+        novaclient.API_MAX_VERSION = self.orig_max_ver
+        novaclient.API_MIN_VERSION = self.orig_min_ver
 
     def shell(self, argstr, exitcodes=(0,)):
         orig = sys.stdout
@@ -168,6 +181,7 @@ class ShellTest(utils.TestCase):
                             matchers.MatchesRegex(r, re.DOTALL | re.MULTILINE))
 
     def test_help_no_options(self):
+        self.make_env()
         required = [
             '.*?^usage: ',
             '.*?^\s+root-password\s+Change the admin password',
@@ -179,6 +193,7 @@ class ShellTest(utils.TestCase):
                             matchers.MatchesRegex(r, re.DOTALL | re.MULTILINE))
 
     def test_bash_completion(self):
+        self.make_env()
         stdout, stderr = self.shell('bash-completion')
         # just check we have some output
         required = [
@@ -403,6 +418,79 @@ class ShellTest(utils.TestCase):
         keyring_saver = mock_client_instance.client.keyring_saver
         self.assertIsInstance(keyring_saver, novaclient.shell.SecretsHelper)
 
+    @mock.patch('novaclient.client.Client')
+    def test_microversion_with_latest(self, mock_client):
+        self.make_env()
+        novaclient.API_MAX_VERSION = api_versions.APIVersion('2.3')
+        self.mock_server_version_range.return_value = (
+            api_versions.APIVersion("2.1"), api_versions.APIVersion("2.3"))
+        self.shell('--os-compute-api-version 2.latest list')
+        client_args = mock_client.call_args_list[1][0]
+        self.assertEqual(api_versions.APIVersion("2.3"), client_args[0])
+
+    @mock.patch('novaclient.client.Client')
+    def test_microversion_with_specified_version(self, mock_client):
+        self.make_env()
+        self.mock_server_version_range.return_value = (
+            api_versions.APIVersion("2.10"), api_versions.APIVersion("2.100"))
+        novaclient.API_MAX_VERSION = api_versions.APIVersion("2.100")
+        novaclient.API_MIN_VERSION = api_versions.APIVersion("2.90")
+        self.shell('--os-compute-api-version 2.99 list')
+        client_args = mock_client.call_args_list[1][0]
+        self.assertEqual(api_versions.APIVersion("2.99"), client_args[0])
+
+    @mock.patch('novaclient.client.Client')
+    def test_microversion_with_specified_version_out_of_range(self,
+                                                              mock_client):
+        novaclient.API_MAX_VERSION = api_versions.APIVersion("2.100")
+        novaclient.API_MIN_VERSION = api_versions.APIVersion("2.90")
+        self.assertRaises(exceptions.CommandError,
+                          self.shell, '--os-compute-api-version 2.199 list')
+
+    @mock.patch('novaclient.client.Client')
+    def test_microversion_with_v2_and_v2_1_server(self, mock_client):
+        self.make_env()
+        self.mock_server_version_range.return_value = (
+            api_versions.APIVersion('2.1'), api_versions.APIVersion('2.3'))
+        novaclient.API_MAX_VERSION = api_versions.APIVersion("2.100")
+        novaclient.API_MIN_VERSION = api_versions.APIVersion("2.1")
+        self.shell('--os-compute-api-version 2 list')
+        client_args = mock_client.call_args_list[1][0]
+        self.assertEqual(api_versions.APIVersion("2.0"), client_args[0])
+
+    @mock.patch('novaclient.client.Client')
+    def test_microversion_with_v2_and_v2_server(self, mock_client):
+        self.make_env()
+        self.mock_server_version_range.return_value = (
+            api_versions.APIVersion(), api_versions.APIVersion())
+        novaclient.API_MAX_VERSION = api_versions.APIVersion("2.100")
+        novaclient.API_MIN_VERSION = api_versions.APIVersion("2.1")
+        self.shell('--os-compute-api-version 2 list')
+        client_args = mock_client.call_args_list[1][0]
+        self.assertEqual(api_versions.APIVersion("2.0"), client_args[0])
+
+    @mock.patch('novaclient.client.Client')
+    def test_microversion_with_v2_without_server_compatible(self, mock_client):
+        self.make_env()
+        self.mock_server_version_range.return_value = (
+            api_versions.APIVersion('2.2'), api_versions.APIVersion('2.3'))
+        novaclient.API_MAX_VERSION = api_versions.APIVersion("2.100")
+        novaclient.API_MIN_VERSION = api_versions.APIVersion("2.1")
+        self.assertRaises(
+            exceptions.UnsupportedVersion,
+            self.shell, '--os-compute-api-version 2 list')
+
+    def test_microversion_with_specific_version_without_microversions(self):
+        self.make_env()
+        self.mock_server_version_range.return_value = (
+            api_versions.APIVersion(), api_versions.APIVersion())
+        novaclient.API_MAX_VERSION = api_versions.APIVersion("2.100")
+        novaclient.API_MIN_VERSION = api_versions.APIVersion("2.1")
+        self.assertRaises(
+            exceptions.UnsupportedVersion,
+            self.shell,
+            '--os-compute-api-version 2.3 list')
+
 
 class TestLoadVersionedActions(utils.TestCase):
 
diff --git a/novaclient/tests/unit/v2/fakes.py b/novaclient/tests/unit/v2/fakes.py
index f468e3e48..9433f6799 100644
--- a/novaclient/tests/unit/v2/fakes.py
+++ b/novaclient/tests/unit/v2/fakes.py
@@ -2260,7 +2260,14 @@ class FakeSessionMockClient(base_client.SessionClient, FakeHTTPClient):
         self.callstack = []
         self.auth = mock.Mock()
         self.session = mock.Mock()
+        self.session.get_endpoint.return_value = FakeHTTPClient.get_endpoint(
+            self)
         self.service_type = 'service_type'
+        self.service_name = None
+        self.endpoint_override = None
+        self.interface = None
+        self.region_name = None
+        self.version = None
 
         self.auth.get_auth_ref.return_value.project_id = 'tenant_id'