Repository: pywbem/pywbemtools

Issue:
Refactor pywbemcli and contextobj classes
Refactor these classes to:
Part 1

1. Eliminate the use of some of the properties in ContextObj such as conn and wbemserver since they are already used in PywbemServer. Specifically this means putting wbem_server and conn in PywbemServer and removing them from ContextObj. Note that this means changing all references to these two attributes which will cause changes to almost all command action methods (i.e. those that use conn or wbemserver) as art of the server connection

2.PywbemServer objects have a connect/disconnect functionality (although the methods are named create_connection() and reset()), which we could use to properly set up and tear down connections. They way we use this functionality currently is not very straight forward, for example "connection select" creates a new context object and globally patches that in, or commands in interactive mode make a deep copy of PywbemServer and modify it, but do not disconnect properly.

Part2

Refactor the pywbemcli code that rebuilds the PywbemServer object in interactive mode. (This will be a separate pr)





Discussion:
andy-maier: When testing it, I found only one issue, when the connection is specified with -n, -s, or -m in interactive mode:

```
$ echo "-n pegasus server brand" | pywbemcli -v
Connection repository with 5 connection definitions and connections file "/Users/maiera/.pywbemcli_connections.yaml" (exists)
Current connection: "None"
Enter 'help' for help, <CTRL-D> or ':q' to exit pywbemcli or <CTRL-r> to search history, 
Error: No  current server for command "server brand" that requires a WBEM server. Specify a server with the "--server", "--mock-server", or"--name" general option, by setting the corresponding environment variables, or in interactive mode use "connection select" to define a target server
```

The same command in command mode works:
```
$ pywbemcli -v -n pegasus server brand
Connection repository with 5 connection definitions and connections file "/Users/maiera/.pywbemcli_connections.yaml" (exists)
-Debug: PywbemServer connect to https://localhost:5989
OpenPegasus
Debug: PywbemServer disconnect from https://localhost:5989
```

The same command in interactive mode when the connection is specified on the command line also works fine:
```
$ echo "server brand" | pywbemcli -v -n pegasus
Connection repository with 5 connection definitions and connections file "/Users/maiera/.pywbemcli_connections.yaml" (exists)
Enter 'help' for help, <CTRL-D> or ':q' to exit pywbemcli or <CTRL-r> to search history, 
-Debug: PywbemServer connect to https://localhost:5989
OpenPegasus
Debug: PywbemServer disconnect from https://localhost:5989
```
andy-maier: In addition, the test coverage goes down by 1.4%. This can be seen once the Pylint error is fixed, or in the coverage report of PR #899 : https://coveralls.io/builds/37676122
coveralls: 
[![Coverage Status](https://coveralls.io/builds/37691831/badge)](https://coveralls.io/builds/37691831)

Coverage increased (+0.2%) to 92.944% when pulling **f2aa375b2dc51f8722fa8f4706dc30cf16267ae9 on ks/#912-refactor-class-part2** into **6c9afa372397d493c03a816318124e071617ad7b on master**.

KSchopmeyer: Note this corrects the issue Andy found where the 3 forms of  a simple request delivered different results and adds test specifically for that.
andy-maier: Rebased the PR on the updated PR #915.
andy-maier: It has gotten better, but is not yet good:

Ok:
```
$ pywbemcli -v -n pegasus server brand
Connection repository with 5 connection definitions and connections file "/Users/maiera/.pywbemcli_connections.yaml" (exists)
-Debug: PywbemServer connect to https://localhost:5989
OpenPegasus
Debug: PywbemServer disconnect from https://localhost:5989
```

Ok:
```
$ echo "server brand" | pywbemcli -v -n pegasus
Connection repository with 5 connection definitions and connections file "/Users/maiera/.pywbemcli_connections.yaml" (exists)
Enter 'help' for help, <CTRL-D> or ':q' to exit pywbemcli or <CTRL-r> to search history, 
-Debug: PywbemServer connect to https://localhost:5989
OpenPegasus
Debug: PywbemServer disconnect from https://localhost:5989
```

Disconnect missing:
```
$ echo "-n pegasus server brand" | pywbemcli -v
Connection repository with 5 connection definitions and connections file "/Users/maiera/.pywbemcli_connections.yaml" (exists)
Current connection: "None"
Enter 'help' for help, <CTRL-D> or ':q' to exit pywbemcli or <CTRL-r> to search history, 
-Debug: PywbemServer connect to https://localhost:5989
OpenPegasus
```


Patch:
diff --git a/pywbemtools/pywbemcli/_cmd_connection.py b/pywbemtools/pywbemcli/_cmd_connection.py
index cf3ca45..472e981 100644
--- a/pywbemtools/pywbemcli/_cmd_connection.py
+++ b/pywbemtools/pywbemcli/_cmd_connection.py
@@ -681,17 +681,18 @@ def cmd_connection_select(context, name, options):
                          context.verbose,
                          context.pdb,
                          context.warn,
-                         context.connections_repo)
+                         context.connections_repo,
+                         context.interactive_mode,
+                         False)
 
     # Update the root context making this context the basis for future
     # commands in the current interactive session
     ContextObj.update_root_click_context(new_ctx)
 
-    # close any existing connection.
-    # TODO/KS: Validate that the first test is required.
-    if context.pywbem_server_exists():
-        if context.is_connected:
-            context.pywbem_server.disconnect()
+    # Close any existing connection.
+    if context.is_connected():
+        context.pywbem_server.disconnect()
+
     context.spinner_stop()
     if options['default']:
         connections_repo.default_connection_name = name
diff --git a/pywbemtools/pywbemcli/_cmd_statistics.py b/pywbemtools/pywbemcli/_cmd_statistics.py
index 8b29736..0a02a1c 100644
--- a/pywbemtools/pywbemcli/_cmd_statistics.py
+++ b/pywbemtools/pywbemcli/_cmd_statistics.py
@@ -215,23 +215,6 @@ OBJMGR_STAT_PROPERTY_NAME = "GatherStatisticalData"
 CIMOM_STATISTICAL_DATA_CLASS = "CIM_CIMOMStatisticalData"
 
 
-def test_conn_exists(context):
-    """
-    Test if server defined.
-
-    Returns if server defined
-
-    Exception:
-       click.Exception if no current server.
-    """
-    # TODO/KS: Is this necessary since we should be protecting with classes
-    #          or does the pywbem_server access provide same protection??
-    conn = context.pywbem_server.conn
-    if not conn:
-        raise click.ClickException(
-            'No server defined in current context.')
-
-
 def get_objmgr_inst(context):
     """
     Return the state of statistics gathering on the server.
@@ -342,7 +325,6 @@ def cmd_statistics_server_on(context):
     Attempt to enable the statistics gathering on the current server if there
     is a current server defined.
     """
-    test_conn_exists(context)
     set_server_statistics(context, True)
 
 
@@ -351,7 +333,6 @@ def cmd_statistics_server_off(context):
     Attempt to disable the statistics gathering on the current server if there
     is a current server defined.
     """
-    test_conn_exists(context)
     set_server_statistics(context, False)
 
 
@@ -370,10 +351,8 @@ def cmd_statistics_status(context):
     """
 
     # validate output format with default of table
-    output_format = validate_output_format(context.output_format, ['TABLE',
-                                                                   'TEXT'])
-
-    test_conn_exists(context)
+    output_format = validate_output_format(context.output_format,
+                                           ['TABLE', 'TEXT'])
 
     # Display server status as on/off
     try:
@@ -410,7 +389,6 @@ def cmd_statistics_reset(context):
     It does reset the statistics kept in the pywbem client for server
     response time.
     """
-    test_conn_exists(context)
 
     conn = context.pywbem_server.conn
     conn.statistics.reset()
@@ -424,11 +402,10 @@ def cmd_statistics_show(context):
     If the statistics are enabled for the client and the connection exits,
     display the statistics
     """
-    test_conn_exists(context)
+
     conn = context.pywbem_server.conn
 
     context.spinner_stop()
-
     click.echo(context.format_statistics(conn.statistics, context))
 
 
@@ -576,9 +553,6 @@ def cmd_statistics_server_show(context):
             #       Since we want to be able to relate that to the server time
             #       in the client statistics, we calculate the server time
             #       as CIMOM time plus provider time.
-            # TODO: Clarify for OpenPegasus whether CimomElapsedTime is just
-            #       CIMOM time as described, or is already the total server
-            #       time.
             avg_server_time_ms = avg_cimom_time_ms + avg_provider_time_ms
 
             avg_request_size = avg_value(inst['RequestSize'],
diff --git a/pywbemtools/pywbemcli/_context_obj.py b/pywbemtools/pywbemcli/_context_obj.py
index 98162a0..e23e95c 100644
--- a/pywbemtools/pywbemcli/_context_obj.py
+++ b/pywbemtools/pywbemcli/_context_obj.py
@@ -31,7 +31,8 @@ import click_spinner
 from ._common import format_table, validate_output_format
 
 
-class ContextObj(object):  # pylint: disable=useless-object-inheritance
+class ContextObj(object):
+    # pylint: disable=useless-object-inheritance, too-many-instance-attributes
     """
         Manage the pywbemcli context that is carried within the Click
         context object in the obj parameter. This is the object that
@@ -44,7 +45,52 @@ class ContextObj(object):  # pylint: disable=useless-object-inheritance
     # pylint: disable=unused-argument
     def __init__(self, pywbem_server, output_format, use_pull,
                  pull_max_cnt, timestats, log, verbose, pdb,
-                 warn, connections_repo):
+                 warn, connections_repo, interactive_mode,
+                 close_interactive_server):
+        """
+        Parameters:
+
+          pywbem_server :class:`PywbemServer`):
+            PywbemServer instance to be used for connection
+
+          output_format (:term:`string or None):
+            String representing the type of output from the --output-format
+            general option or None if the default is to be used.
+
+          use_pull (:class:`py:bool` or None):
+            If boolean defines whether pull operations are to be used.  If
+            None, the pywbem client will decide.           `
+
+          pull_max_cnt(:term:`integer`):
+            Positive integer represenging the max size of each pull
+            operation response
+
+          timestats (:class:`py:bool`):
+            See timestats general option
+
+          log (:term:`string or None):
+            String defining the characteristics of log output for each
+            WBEM operation or None if logging is disabled.
+
+          verbose (:class:`py:bool`):
+            See verbose general option
+
+          pdb (:class:`py:bool`):
+            See pdb general option
+
+          warn (:class:`py:bool`):
+            See warn general option
+
+          connections_repo:
+
+          interactive_mode (:class:`py:bool`):
+            If True, pywbemcli is in interactive mode.
+
+          close_interactive_server (:class:`py:bool`):
+            Flag that defines interactive command with a server definition
+            that must be disconnected after the command
+
+        """
 
         self._pywbem_server = pywbem_server
         self._output_format = output_format
@@ -58,6 +104,8 @@ class ContextObj(object):  # pylint: disable=useless-object-inheritance
         # maintained through interactive session.
         self._warn = warn
         self._connections_repo = connections_repo
+        self.interactive_mode = interactive_mode
+        self._close_interactive_server = close_interactive_server
 
         self._spinner_enabled = None  # Deferred init in getter
         self._spinner_obj = click_spinner.Spinner()
@@ -65,10 +113,11 @@ class ContextObj(object):  # pylint: disable=useless-object-inheritance
     def __repr__(self):
         return 'ContextObj(at {:08x}, pywbem_server={!r}, outputformat={}, ' \
                'use_pull={}, pull_max_cnt={}, timestats={}, verbose={}, ' \
-               'spinner_enabled={}' \
+               'spinner_enabled={}, interactive_mode={}' \
                .format(id(self), self._pywbem_server, self.output_format,
                        self.use_pull, self.pull_max_cnt, self.timestats,
-                       self.verbose, self.spinner_enabled)
+                       self.verbose, self.spinner_enabled,
+                       self._interactive_mode)
 
     @property
     def output_format(self):
@@ -92,6 +141,21 @@ class ContextObj(object):  # pylint: disable=useless-object-inheritance
         # pylint: disable=attribute-defined-outside-init
         self._timestats = value
 
+    @property
+    def interactive_mode(self):
+        """
+        :class:`py:bool`: 'True' if in interactive mode and 'False' if
+        in the command mode.
+        """
+        return self._interactive_mode
+
+    @interactive_mode.setter
+    def interactive_mode(self, mode):
+        """Setter method; for a description see the getter method."""
+        assert isinstance(mode, bool)
+        # pylint: disable=attribute-defined-outside-init
+        self._interactive_mode = mode
+
     @property
     def use_pull(self):
         """
@@ -138,16 +202,16 @@ class ContextObj(object):  # pylint: disable=useless-object-inheritance
         """
         return self._connections_repo
 
-    @property
     def is_connected(self):
         """
-        bool: Indicates whether currently contected to a WBEM server.
+        bool: Indicates whether currently connected to a WBEM server. Where
+        connected implies both the existence of the PywbemServer instance
+        and that the connection to a server has been made.
 
         That is the case as soon as commands have been executed that
         communicate with the server.
         """
-        return self._pywbem_server and \
-            self._pywbem_server.wbem_server is not None
+        return self._pywbem_server and self._pywbem_server.connected
 
     @property
     def pywbem_server(self):
@@ -287,11 +351,17 @@ class ContextObj(object):  # pylint: disable=useless-object-inheritance
                 self.spinner_stop()
 
             # Issue statistics if requested and if the command used a conn.
-            if self.timestats and self.is_connected:
+            if self.timestats and self.is_connected():
                 context = click.get_current_context()
                 click.echo(self.format_statistics(
                     self.pywbem_server.conn.statistics, context.obj))
 
+            # Close any existing connection if in command mode or if the
+            # close_interactive_server flag is set
+            if not self.interactive_mode or self._close_interactive_server:
+                if self.is_connected():
+                    self.pywbem_server.disconnect()
+
     def format_statistics(self, statistics, context):
         # pylint: disable=no-self-use
         """
diff --git a/pywbemtools/pywbemcli/_pywbem_server.py b/pywbemtools/pywbemcli/_pywbem_server.py
index 722db5c..56582bb 100644
--- a/pywbemtools/pywbemcli/_pywbem_server.py
+++ b/pywbemtools/pywbemcli/_pywbem_server.py
@@ -33,11 +33,6 @@ from .config import DEFAULT_URL_SCHEME, DEFAULT_CONNECTION_TIMEOUT, \
     DEFAULT_NAMESPACE, MAX_TIMEOUT
 from ._pywbemcli_operations import PYWBEMCLIConnection, PYWBEMCLIFakedConnection
 
-# TODO(PR900): Activate the PR900 flags when continuing the work on PR #900;
-#              Remove the PR900 flags when done.
-PR900_REQUIRE_CORRECT_CONNECTION_STATUS = False
-PR900_DISCONNECT_CALLS_CLOSE = False
-
 WBEM_SERVER_OBJ = None
 
 PYWBEMCLI_LOG = 'pywbemcli.log'
@@ -443,6 +438,14 @@ class PywbemServer(object):
                 verbose=ctx.obj.verbose)
         return self._wbem_server
 
+    @property
+    def connected(self):
+        """
+        Current connected state. Returns True if connected and
+        False if not connected.
+        """
+        return bool(self._wbem_server)
+
     def password_prompt(self, ctx):
         """
         Request password from console.
@@ -519,20 +522,18 @@ class PywbemServer(object):
 
         Must be connected to the server when calling this method.
         """
-        # print("Debug: PywbemServer disconnect")
-
-        # TODO(PR900): Remove the PR900 flag test and else path once done.
-        if PR900_REQUIRE_CORRECT_CONNECTION_STATUS:
-            assert self._wbem_server is not None  # Must be connected
-        else:
-            # Old code to be removed once done
-            if self._wbem_server is None:
-                return
+        assert self._wbem_server is not None  # Must be connected
 
-        # TODO(PR900): Remove the PR900 flag test and else path once done.
-        if PR900_DISCONNECT_CALLS_CLOSE:
-            self._wbem_server.conn.close()
+        ctx = click.get_current_context()
+        if ctx.obj:
+            if ctx.obj.verbose:
+                if self._mock_server:
+                    server_txt = "mock environment {}".format(self._mock_server)
+                else:
+                    server_txt = "WBEM server {}".format(self._server)
+                click.echo("Disconnecting from {}".format(server_txt))
 
+        self._wbem_server.conn.close()
         self._wbem_server = None
 
     def connect(self, log=None, use_pull=None, verbose=None):
@@ -551,15 +552,17 @@ class PywbemServer(object):
           ValueError: Other issues with server-related attributes of this
             object that are considered programming errors.
         """
-        # print("Debug: PywbemServer connect")
 
-        # TODO(PR900): Remove the PR900 flag test and else path once done.
-        if PR900_REQUIRE_CORRECT_CONNECTION_STATUS:
-            assert self._wbem_server is None  # Must be disconnected
-        else:
-            # Old code to be removed once done
-            if self._wbem_server is not None:
-                return
+        assert self._wbem_server is None  # Must be disconnected
+
+        ctx = click.get_current_context()
+        if ctx.obj:
+            if ctx.obj.verbose:
+                if self._mock_server:
+                    server_txt = "mock environment {}".format(self._mock_server)
+                else:
+                    server_txt = "WBEM server {}".format(self._server)
+                click.echo("Connecting to {}".format(server_txt))
 
         if self._mock_server:
             conn = PYWBEMCLIFakedConnection(
diff --git a/pywbemtools/pywbemcli/pywbemcli.py b/pywbemtools/pywbemcli/pywbemcli.py
index e265b15..fd72900 100644
--- a/pywbemtools/pywbemcli/pywbemcli.py
+++ b/pywbemtools/pywbemcli/pywbemcli.py
@@ -82,14 +82,24 @@ def _validate_connection_name(connections_repo, connection_name):
     """
     Validate that connection name exists in the connection_repo and that
     the connection name can be retrieved.
+
+    Returns:
+        PywbemServer object that defines connection if found in connections_file
+
+    Raises:
+        click.ClickException - connection not found
+        ConnectionsFileError - Connections file invalid
     """
     try:
-        _ = connections_repo[connection_name]
+        pywbem_server = connections_repo[connection_name]
+        return pywbem_server
+
     except KeyError:
         raise click.ClickException(
             'Connection definition "{}" not found in connections '
             'file "{}"'.
             format(connection_name, connections_repo.connections_file))
+
     except ConnectionsFileError as cfe:
         click.echo('Fatal error: {0}: {1}'.
                    format(cfe.__class__.__name__, cfe),
@@ -589,6 +599,7 @@ def cli(ctx, server, connection_name, default_namespace, user, password,
 
         https://pywbemtools.readthedocs.io/en/stable/
     """
+
     resolved_mock_server = []
 
     # Options not allowed when -name is used and when
@@ -637,6 +648,8 @@ def cli(ctx, server, connection_name, default_namespace, user, password,
     # Process mock_server option
     if mock_server:
         resolved_mock_server = _resolve_mock_server(mock_server)
+    else:
+        resolved_mock_server = None
 
     # Validate that only one of server, mock_server, and connection name exists
     # We this manually because click does not have way to do mutual exclusion
@@ -651,6 +664,11 @@ def cli(ctx, server, connection_name, default_namespace, user, password,
 
     resolved_timeout = timeout or DEFAULT_CONNECTION_TIMEOUT
 
+    # Set flag that will be used to indicate server change in
+    # in interactive mode so the server can be disconnected after
+    # use.
+    close_interactive_server = False
+
     # Command mode (ctx is None) or initial input in interactive mode.
     # Apply the documented option defaults to create a pywbem_server instance
     # and a ContextObj instance
@@ -696,18 +714,18 @@ def cli(ctx, server, connection_name, default_namespace, user, password,
         # Apply the general options from the command line options
         # or from the context object to modify the PywbemServer object and
         # for a new ContextObj for the command
+        modified_server = False
 
-        # If --name option, get server from connection
+        # If --name option, get pywbem_server object from connection
         if connection_name:
-            # validate_connections_file(connections_repo). In interactive mode
-            # raise ClickException but do not abort unless connections
-            # file does not exist or is bad.
             _validate_connections_file(connections_repo)
-            _validate_connection_name(connections_repo, connection_name)
+            pywbem_server = _validate_connection_name(connections_repo,
+                                                      connection_name)
+            modified_server = True
+            close_interactive_server = True
 
         # If other parameters, modify the existing connection and reset it.
-        # Issue 912, part 3, TODO: refactor this code to avoid deepcopy when not
-        # needed.
+        # TODO: refactor this code to avoid deepcopy when not needed.
         else:
             if pywbem_server is None:
                 # Copy to keep the original clean from any changes
@@ -717,81 +735,66 @@ def cli(ctx, server, connection_name, default_namespace, user, password,
                 if ctx.obj.pywbem_server_exists():
                     pywbem_server = deepcopy(ctx.obj.pywbem_server)
 
-            # Modify the currently defined pywbem_server if it is defined
-            # with any general options including resetting options and if
-            # there are any modifications to pywbem_server, set that modified
-            # server into the current context object.
-            # In cases where input parameter of "" is allowed to
-            # reset option values, the option is tested for "is not None"
-            # to include empty string in test.
-            if pywbem_server:
-                modified_server = False
-                if server:
-                    if pywbem_server.mock_server:
-                        pywbem_server.mock_server = []
-                    pywbem_server.server = server
-                    modified_server = True
-                if mock_server:
-                    if pywbem_server.server:
-                        pywbem_server.server = None
-                    pywbem_server.mock_server = resolved_mock_server
-                    modified_server = True
-                if user is not None:
-                    pywbem_server.user = _set_default_if_empty_str(user)
-                    modified_server = True
-                if password is not None:
-                    pywbem_server.password = _set_default_if_empty_str(password)
-                    modified_server = True
-                if verify is not None:
-                    pywbem_server.verify = resolved_verify
-                    modified_server = True
-                if ca_certs:
-                    pywbem_server.ca_certs = ca_certs
-                    modified_server = True
-                if certfile is not None:
-                    pywbem_server.certfile = _set_default_if_empty_str(certfile)
-                    modified_server = True
-                if keyfile is not None:
-                    pywbem_server.keyfile = _set_default_if_empty_str(keyfile)
-                    modified_server = True
-                if timeout:
-                    pywbem_server.timeout = resolved_timeout
-                    modified_server = True
-                if use_pull:
-                    pywbem_server.use_pull = resolved_use_pull
-                    modified_server = True
-                if default_namespace is not None:
-                    pywbem_server.default_namespace = \
-                        _set_default_if_empty_str(default_namespace,
-                                                  DEFAULT_NAMESPACE)
-                    modified_server = True
-
-                # If modified, disconnect existing connection
-                if modified_server:
+        # Modify the currently defined pywbem_server if it is defined
+        # with any general options including resetting options and if
+        # there are any modifications to pywbem_server, set that modified
+        # server into the current context object.
+        # In cases where input parameter of "" is allowed to
+        # reset option values, the option is tested for "is not None"
+        # to include empty string in test.
+
+        if pywbem_server:
+            if server:
+                if pywbem_server.mock_server:
+                    pywbem_server.mock_server = []
+                pywbem_server.server = server
+                modified_server = True
+                close_interactive_server = True
+            if mock_server:
+                if pywbem_server.server:
+                    pywbem_server.server = None
+                pywbem_server.mock_server = resolved_mock_server
+                modified_server = True
+            if user is not None:
+                pywbem_server.user = _set_default_if_empty_str(user)
+                modified_server = True
+            if password is not None:
+                pywbem_server.password = _set_default_if_empty_str(password)
+                modified_server = True
+            if verify is not None:
+                pywbem_server.verify = resolved_verify
+                modified_server = True
+            if ca_certs:
+                pywbem_server.ca_certs = ca_certs
+                modified_server = True
+            if certfile is not None:
+                pywbem_server.certfile = _set_default_if_empty_str(certfile)
+                modified_server = True
+            if keyfile is not None:
+                pywbem_server.keyfile = _set_default_if_empty_str(keyfile)
+                modified_server = True
+            if timeout:
+                pywbem_server.timeout = resolved_timeout
+                modified_server = True
+            if use_pull:
+                pywbem_server.use_pull = resolved_use_pull
+                modified_server = True
+            if default_namespace is not None:
+                pywbem_server.default_namespace = \
+                    _set_default_if_empty_str(default_namespace,
+                                              DEFAULT_NAMESPACE)
+                modified_server = True
+
+            # If modified, disconnect existing connection
+            # This MUST BE two if statements to work.
+            if modified_server:
+                if pywbem_server.connected:
                     pywbem_server.disconnect()
-                else:
-                    pywbem_server = ctx.obj.pywbem_server
-            elif connection_name:
-                # TODO/KS: Explore source of all of the following. Do they need
-                # to come from cli or can they come from previous object?
-                # They all need to come from either the object itself
-                # or cli parameters. Issue #912 part3
-                pywbem_server = _create_server_instance(
-                    server, connection_name,
-                    resolved_default_namespace,
-                    user, password,
-                    timeout,
-                    resolved_use_pull,
-                    resolved_pull_max_cnt,
-                    resolved_verify,
-                    certfile, keyfile,
-                    resolved_ca_certs,
-                    resolved_mock_server,
-                    connections_repo,
-                    conditional_options,
-                    verbose)
             else:
-                pywbem_server = None
+                pywbem_server = ctx.obj.pywbem_server
+
+        else:
+            pywbem_server = None
 
         # The following variables are maintained only in the context_obj and
         # not attached to any particular connection. If the cli argument is
@@ -848,14 +851,19 @@ def cli(ctx, server, connection_name, default_namespace, user, password,
     # command line.
     # Set pull_max_cnt to either resolved or default here so we keep default
     # in persistent file but set working value in context.
+
+    interactive_mode = ctx.obj.interactive_mode if ctx.obj else False
     ctx.obj = ContextObj(pywbem_server, output_format,
                          resolved_use_pull,
                          resolved_pull_max_cnt,
                          timestats,
                          log, verbose, pdb,
                          warn,
-                         connections_repo)
-    # Env.var PYWBEMCLI_DIAGNOSTICS turns on disgnostic prints for developer
+                         connections_repo,
+                         interactive_mode,
+                         close_interactive_server)
+
+    # Env.var PYWBEMCLI_DIAGNOSTICS turns on diagnostic prints for developer
     # use and is therefore not documented.
     if os.getenv('PYWBEMCLI_DIAGNOSTICS'):
         display_click_context(ctx, msg="DIAGNOSTICS-NEWCTX: Initial context:",
@@ -869,10 +877,17 @@ def cli(ctx, server, connection_name, default_namespace, user, password,
             format(_python_nm[0], _python_nm[1]),
             DeprecationWarning)
 
-    # Invoke command if one exists or start the repl mode
+    # If no invoked_subcommand, there is no command to execute this flag
+    # causes us to start interactive mode
     if ctx.invoked_subcommand is None:
+        ctx.obj.interactive_mode = True
         ctx.invoke(repl)
 
+        # Exit interactive mode and exit Pywbemcli. Disconnect any connected
+        # server.
+        if ctx.obj.is_connected():
+            ctx.obj.pywbem_server.disconnect()
+
 
 @cli.command('help', options_metavar=GENERAL_OPTS_TXT)
 @add_options(help_option)


Test patch:
diff --git a/tests/unit/test_common.py b/tests/unit/test_common.py
index effd48c..b15969c 100755
--- a/tests/unit/test_common.py
+++ b/tests/unit/test_common.py
@@ -848,7 +848,7 @@ def test_pick_one_from_list(testcase, options, choices, exp_rtn):
     # test option with only one choice bypasses user request
     if not choices:
         context = ContextObj(None, None, None, None, None, None, None, None,
-                             None, None)
+                             None, None, False, False)
 
         # The code to be tested
         act_rtn = pick_one_from_list(context, options, title)
@@ -867,7 +867,7 @@ def test_pick_one_from_list(testcase, options, choices, exp_rtn):
             with patch(mock_echo_func):
                 # Fake context object
                 context = ContextObj(None, None, None, None, None, None, None,
-                                     None, None, None)
+                                     None, None, None, False, False)
 
                 # The code to be tested
                 act_rtn = pick_one_from_list(context, options, title)
diff --git a/tests/unit/test_context_obj.py b/tests/unit/test_context_obj.py
index b17117d..2e0111f 100755
--- a/tests/unit/test_context_obj.py
+++ b/tests/unit/test_context_obj.py
@@ -44,6 +44,8 @@ def test_ContextObj_init():
     pdb = False
     warn = False
     connections_repo = None
+    interactive_mode = True
+    close_interactive_server = True
 
     ctxobj = ContextObj(
         pywbem_server=svr,
@@ -55,6 +57,9 @@ def test_ContextObj_init():
         verbose=verbose,
         pdb=pdb,
         warn=warn,
-        connections_repo=connections_repo)
+        connections_repo=connections_repo,
+        interactive_mode=interactive_mode,
+        close_interactive_server=close_interactive_server)
 
     assert ctxobj.pywbem_server == svr
+    assert ctxobj.interactive_mode is True
diff --git a/tests/unit/test_general_options.py b/tests/unit/test_general_options.py
index a146e7b..e29f667 100644
--- a/tests/unit/test_general_options.py
+++ b/tests/unit/test_general_options.py
@@ -33,6 +33,9 @@ from .cli_test_extensions import CLITestsBase, PYWBEM_0, PYWBEM_1
 from .common_options_help_lines import CMD_OPTION_HELP_HELP_LINE
 
 TEST_DIR = os.path.dirname(__file__)
+CONNECTION_REPO_TEST_FILE_PATH = os.path.join(TEST_DIR,
+                                              'tst_general_options.yaml')
+CONN_REPO_FP_STR = str(CONNECTION_REPO_TEST_FILE_PATH)
 
 
 def GET_TEST_PATH_STR(filename):  # pylint: disable=invalid-name
@@ -52,6 +55,8 @@ PYTHON_MOCK_FILE_PATH = os.path.join(TEST_DIR, 'simple_python_mock_script.py')
 BAD_MOF_FILE_PATH = os.path.join(TEST_DIR, 'mof_with_error.mof')
 BAD_PY_FILE_PATH = os.path.join(TEST_DIR, 'py_with_error.py')
 
+MOCK_SERVER_MODEL = os.path.join(TEST_DIR, 'testmock', 'wbemserver_mock.py')
+
 GENERAL_HELP_LINES = [
     """
     Usage: pywbemcli [GENERAL-OPTIONS] COMMAND [ARGS] [COMMAND-OPTIONS]
@@ -1287,7 +1292,6 @@ TEST_CASES = [
       'test': 'innows'},
      None, FAIL],
 
-
     ['Verify operation that makes request to WBEM server fails with no server.',
      {'args': ['enumerate'],
       'cmdgrp': 'class', },
@@ -1298,7 +1302,7 @@ TEST_CASES = [
 
     #
     #  Verify operation that tries interactive general options for name
-    #  and server/mock server fails
+    #  and server/mock server fails.
     #
 
     ['Test conflicting server defintions interactive mode.',
@@ -1307,6 +1311,57 @@ TEST_CASES = [
       'rc': 0,
       'test': 'innows'},
      None, OK],
+
+    #
+    # Test of 3 ways to execute same simple command. Creates a mock
+    # and tests combination of single comman. Uses -v to test for
+    # Connect and disconnect
+
+    ['Verify new config file with defined data is created and saved.',
+     {'general': ['-v', '-C', CONN_REPO_FP_STR],
+      'stdin': ['connection save blah',
+                'connection show']},
+     {'stdout': ['default-namespace root/cimv2'],
+      'test': 'innows'},
+     SIMPLE_MOCK_MODEL_FILE, OK],
+
+    ['Verify simple pywbem command against file works and verbose connect and '
+     'disconnect work.',
+     {'general': ['-n', 'blah', '-v', '-C', CONN_REPO_FP_STR],
+      'args': ['enumerate', '--di', '--no', '--subclass-of', 'CIM_Foo_sub'],
+      'cmdgrp': 'class'},
+     {'stdout': ["Connecting to mock environment",
+                 'CIM_Foo_sub_sub',
+                 "Disconnecting from mock environment"],
+      'test': 'innows'},
+     None, OK],
+
+    ['Verify simple pywbem command with stdin on same connection name.',
+     {'general': ['-n', 'blah', '-v', '-C', CONN_REPO_FP_STR],
+      'stdin': ['class enumerate --di --no --subclass-of CIM_Foo_sub']},
+     {'stdout': ["Connecting to mock environment",
+                 'CIM_Foo_sub_sub',
+                 "Disconnecting from mock environment"],
+      'test': 'innows'},
+     None, OK],
+
+    ['Verify cmd with -n option in stdin.',
+     {'general': ['-n', 'blah', '-v', '-C', CONN_REPO_FP_STR],
+      'stdin': ['-n blah -v class enumerate --di --no '
+                '--subclass-of CIM_Foo_sub']},
+     {'stdout': ["Connecting to mock environment",
+                 'CIM_Foo_sub_sub',
+                 "Disconnecting from mock environment"],
+      'test': 'innows'},
+     None, OK],
+
+    ['Verify interactive create mock with bad file name does not fail.',
+     {'general': ['-v', '-C', CONN_REPO_FP_STR],
+      'stdin': ['connection delete blah']},
+     {'stdout': ['Deleted connection "blah"'],
+      'test': 'innows', },
+     None, OK],
+
 ]
 
 # TODO add test for pull operations with pull ops max size variations