Repository: AgnostiqHQ/covalent
Pull request #1249 (2022-09-16 04:29:31)

1215 split config into client and server
<!--
⚠️ Your pull request title should be short detailed and understandable for all.
⚠️ Increment the version number in the /VERSION file. If this is a bugfix, increment the patch version (the rightmost number), for example 0.18.2 becomes 0.18.3. If it's a new feature, increment the minor version (the middle number), for example 0.18.2 becomes 0.19.0.
⚠️ Add a note to /CHANGELOG.md with your version number and the date, summarizing the changes.
⚠️ If your pull request fixes an open issue, please link to the issue.
⚠️ Rebase the latest changes from the develop branch. Assuming origin points to https://github.com/AgnostiqHQ/covalent, you should run git rebase origin/develop.
-->

- [ ] I have added the tests to cover my changes.
- [ ] I have updated the documentation and CHANGELOG accordingly.
- [ ] I have read the CONTRIBUTING document.


codecov[bot]: # [Codecov](https://codecov.io/gh/AgnostiqHQ/covalent/pull/1249?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=AgnostiqHQ) Report
> Merging [#1249](https://codecov.io/gh/AgnostiqHQ/covalent/pull/1249?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=AgnostiqHQ) (39e4aa2) into [develop](https://codecov.io/gh/AgnostiqHQ/covalent/commit/b1a14b7d0bcd27bf8f59e13914bba9353200aac1?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=AgnostiqHQ) (b1a14b7) will **decrease** coverage by `4.38%`.
> The diff coverage is `93.73%`.

<details><summary>Additional details and impacted files</summary>


[![Impacted file tree graph](https://codecov.io/gh/AgnostiqHQ/covalent/pull/1249/graphs/tree.svg?width=650&height=150&src=pr&token=YGHCB3DE4P&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=AgnostiqHQ)](https://codecov.io/gh/AgnostiqHQ/covalent/pull/1249?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=AgnostiqHQ)

```diff
@@             Coverage Diff             @@
##           develop    #1249      +/-   ##
===========================================
- Coverage    86.45%   82.07%   -4.39%     
===========================================
  Files           59       62       +3     
  Lines         3816     3815       -1     
===========================================
- Hits          3299     3131     -168     
- Misses         517      684     +167     
```

| Flag | Coverage Δ | |
|---|---|---|
| Dispatcher | `78.10% <92.00%> (+2.94%)` | :arrow_up: |
| Functional_Tests | `?` | |
| SDK | `83.34% <94.45%> (+3.81%)` | :arrow_up: |

</details>

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4c052d5a..a7032087 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -25,6 +25,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 - `setup.py` modified to install client side requirements only, if `COVALENT_SDK_ONLY` environment variable is present and `True`.
 - Updated `requirements.txt` and `tests/requirements.txt`
 - Updated `nbconvert` by dependabot
+- Split the `ConfigManager` into `Client` and `Server` components
+- Update the `set/get/update` config methods to distinguish between the client and server parts
 
 ### Docs
 
diff --git a/covalent/_data_store/datastore.py b/covalent/_data_store/datastore.py
index 70772b12..31c1ba10 100644
--- a/covalent/_data_store/datastore.py
+++ b/covalent/_data_store/datastore.py
@@ -31,7 +31,7 @@ from alembic.script import ScriptDirectory
 from sqlalchemy import create_engine
 from sqlalchemy.orm import Session, sessionmaker
 
-from .._shared_files.config import get_config
+from .._shared_files.config import CMType, get_config
 from . import models
 
 
@@ -45,7 +45,7 @@ class DataStore:
         if db_URL:
             self.db_URL = db_URL
         else:
-            self.db_URL = "sqlite+pysqlite:///" + get_config("dispatcher.db_path")
+            self.db_URL = "sqlite+pysqlite:///" + get_config(CMType.SERVER, "service.db_path")
 
         self.engine = create_engine(self.db_URL, **kwargs)
         self.Session = sessionmaker(self.engine)
diff --git a/covalent/_dispatcher_plugins/local.py b/covalent/_dispatcher_plugins/local.py
index 91bff06d..92432aff 100644
--- a/covalent/_dispatcher_plugins/local.py
+++ b/covalent/_dispatcher_plugins/local.py
@@ -27,7 +27,7 @@ import requests
 from .._results_manager import wait
 from .._results_manager.result import Result
 from .._results_manager.results_manager import get_result
-from .._shared_files.config import get_config
+from .._shared_files.config import CMType, get_config
 from .._workflow.lattice import Lattice
 from .base import BaseDispatcher
 
@@ -41,9 +41,9 @@ class LocalDispatcher(BaseDispatcher):
     @staticmethod
     def dispatch(
         orig_lattice: Lattice,
-        dispatcher_addr: str = get_config("dispatcher.address")
+        dispatcher_addr: str = get_config(CMType.CLIENT, "server.address")
         + ":"
-        + str(get_config("dispatcher.port")),
+        + str(get_config(CMType.CLIENT, "server.port")),
     ) -> Callable:
         """
         Wrapping the dispatching functionality to allow input passing
@@ -92,9 +92,9 @@ class LocalDispatcher(BaseDispatcher):
     @staticmethod
     def dispatch_sync(
         lattice: Lattice,
-        dispatcher_addr: str = get_config("dispatcher.address")
+        dispatcher_addr: str = get_config(CMType.CLIENT, "server.address")
         + ":"
-        + str(get_config("dispatcher.port")),
+        + str(get_config(CMType.CLIENT, "server.port")),
     ) -> Callable:
         """
         Wrapping the synchronous dispatching functionality to allow input
diff --git a/covalent/_results_manager/results_manager.py b/covalent/_results_manager/results_manager.py
index b3cb98de..b4f48bb1 100644
--- a/covalent/_results_manager/results_manager.py
+++ b/covalent/_results_manager/results_manager.py
@@ -33,7 +33,7 @@ from .. import _workflow as ct
 from .._data_store.datastore import DataStore
 from .._data_store.models import Lattice
 from .._shared_files import logger
-from .._shared_files.config import get_config
+from .._shared_files.config import CMType, get_config
 from .._shared_files.util_classes import Status
 from .._workflow.transport import TransportableObject
 from .result import Result
@@ -184,7 +184,9 @@ def result_from(lattice_record: Lattice) -> Result:
 def _get_result_from_dispatcher(
     dispatch_id: str,
     wait: bool = False,
-    dispatcher: str = get_config("dispatcher.address") + ":" + str(get_config("dispatcher.port")),
+    dispatcher: str = get_config(CMType.CLIENT, "server.address")
+    + ":"
+    + str(get_config(CMType.CLIENT, "server.port")),
     status_only: bool = False,
 ) -> Dict:
 
@@ -223,7 +225,7 @@ def _get_result_from_dispatcher(
 
 def _delete_result(
     dispatch_id: str,
-    results_dir: str = get_config("dispatcher.results_dir"),
+    results_dir: str = get_config(CMType.SERVER, "service.results_dir"),
     remove_parent_directory: bool = False,
 ) -> None:
     """
@@ -303,7 +305,9 @@ def sync(
 
 def cancel(
     dispatch_id: str,
-    dispatcher: str = get_config("dispatcher.address") + ":" + str(get_config("dispatcher.port")),
+    dispatcher: str = get_config(CMType.CLIENT, "server.address")
+    + ":"
+    + str(get_config(CMType.CLIENT, "server.port")),
 ) -> str:
     """
     Cancel a running dispatch.
diff --git a/covalent/_results_manager/utils.py b/covalent/_results_manager/utils.py
index 6ae3d9ed..e52f7e5f 100644
--- a/covalent/_results_manager/utils.py
+++ b/covalent/_results_manager/utils.py
@@ -22,7 +22,7 @@
 
 import requests
 
-from .._shared_files.config import get_config
+from .._shared_files.config import CMType, get_config
 
 
 def convert_to_lattice_function_call(
@@ -54,7 +54,9 @@ def convert_to_lattice_function_call(
 
 
 def _db_path(
-    dispatcher: str = get_config("dispatcher.address") + ":" + str(get_config("dispatcher.port")),
+    dispatcher: str = get_config(CMType.CLIENT, "server.address")
+    + ":"
+    + str(get_config(CMType.CLIENT, "server.port")),
 ) -> str:
     url = "http://" + dispatcher + "/api/db-path"
     r = requests.get(url)
diff --git a/covalent/_shared_files/config.py b/covalent/_shared_files/config.py
index 89eb82e9..1ef048b5 100644
--- a/covalent/_shared_files/config.py
+++ b/covalent/_shared_files/config.py
@@ -18,10 +18,12 @@
 #
 # Relief from the License may be granted by purchasing a commercial license.
 
+import copy
 import fcntl
 import os
 import shutil
 from dataclasses import asdict
+from enum import Enum
 from functools import reduce
 from operator import getitem
 from pathlib import Path
@@ -32,6 +34,11 @@ import toml
 """Configuration manager."""
 
 
+class CMType(Enum):
+    CLIENT = 1
+    SERVER = 2
+
+
 class ConfigManager:
     """
     Configuration manager class object.
@@ -39,34 +46,19 @@ class ConfigManager:
     This class is used to manage an in-memory copy of a TOML configuration file.
     """
 
-    def __init__(self) -> None:
-
-        from .defaults import DefaultConfig
-
-        DEFAULT_CONFIG = asdict(DefaultConfig())
+    def __init__(self, config_dict, config_file) -> None:
 
-        self.config_file = DEFAULT_CONFIG["sdk"]["config_file"]
-
-        self.generate_default_config()
-
-        try:
-            with open(self.config_file, "r") as f:
-                pass
+        self.config_file = config_file
 
+        self.generate_default_config(config_dict)
+        if os.path.exists(self.config_file):
+            # Update config with user configuration file:
             self.update_config()
-        except FileNotFoundError:
+        else:
             Path(self.config_file).parent.mkdir(parents=True, exist_ok=True)
             self.write_config()
 
-        Path(self.get("sdk.log_dir")).mkdir(parents=True, exist_ok=True)
-        Path(self.get("sdk.executor_dir")).mkdir(parents=True, exist_ok=True)
-        Path(self.get("dispatcher.cache_dir")).mkdir(parents=True, exist_ok=True)
-        Path(self.get("dispatcher.results_dir")).mkdir(parents=True, exist_ok=True)
-        Path(self.get("dispatcher.log_dir")).mkdir(parents=True, exist_ok=True)
-        Path(self.get("user_interface.log_dir")).mkdir(parents=True, exist_ok=True)
-        Path(self.get("dispatcher.db_path")).parent.mkdir(parents=True, exist_ok=True)
-
-    def generate_default_config(self) -> None:
+    def generate_default_config(self, config_dict) -> None:
         """
         Load the default configuration into memory.
 
@@ -77,9 +69,7 @@ class ConfigManager:
             None
         """
 
-        from .defaults import DefaultConfig
-
-        self.config_data = asdict(DefaultConfig())
+        self.config_data = copy.deepcopy(config_dict)  # asdict(DefaultConfig())
 
     def update_config(
         self, new_entries: Optional[Dict] = None, override_existing: bool = True
@@ -204,7 +194,40 @@ class ConfigManager:
             data[keys[-1]] = value
 
 
-def set_config(new_config: Union[Dict, str], new_value: Any = None) -> None:
+class ClientConfigManager(ConfigManager):
+    def __init__(self):
+        from .defaults import DefaultClientConfig, get_default_config_path
+
+        DEFAULT_CONFIG = asdict(DefaultClientConfig())
+
+        super().__init__(DEFAULT_CONFIG, get_default_config_path(CMType.CLIENT))
+
+        Path(self.get("sdk.log_dir")).mkdir(parents=True, exist_ok=True)
+        Path(self.get("sdk.executor_dir")).mkdir(parents=True, exist_ok=True)
+
+
+class ServerConfigManager(ConfigManager):
+    def __init__(self):
+        from .defaults import DefaultServerConfig, get_default_config_path
+
+        DEFAULT_CONFIG = asdict(DefaultServerConfig())
+
+        super().__init__(DEFAULT_CONFIG, get_default_config_path(CMType.SERVER))
+
+        Path(self.get("service.cache_dir")).mkdir(parents=True, exist_ok=True)
+        Path(self.get("service.results_dir")).mkdir(parents=True, exist_ok=True)
+        Path(self.get("service.log_dir")).mkdir(parents=True, exist_ok=True)
+        Path(self.get("service.db_path")).parent.mkdir(parents=True, exist_ok=True)
+
+
+def get_default_config_manager(type: CMType):
+    """
+    Return the default config manager depending on the type
+    """
+    return ClientConfigManager() if type == CMType.CLIENT else ServerConfigManager()
+
+
+def set_config(type: CMType, new_config: Union[Dict, str], new_value: Any = None) -> None:
     """
     Update the configuration.
 
@@ -218,7 +241,7 @@ def set_config(new_config: Union[Dict, str], new_value: Any = None) -> None:
     Returns:
         None
     """
-    cm = ConfigManager()
+    cm = get_default_config_manager(type)
 
     if isinstance(new_config, str):
         cm.set(new_config, new_value)
@@ -228,7 +251,7 @@ def set_config(new_config: Union[Dict, str], new_value: Any = None) -> None:
     cm.write_config()
 
 
-def get_config(entries: Union[str, List] = None) -> Union[Dict, Union[str, int]]:
+def get_config(type: CMType, entries: Union[str, List] = None) -> Union[Dict, Union[str, int]]:
     """
     Return a configuration setting.
 
@@ -245,7 +268,7 @@ def get_config(entries: Union[str, List] = None) -> Union[Dict, Union[str, int]]
     """
 
     entries = entries or []
-    cm = ConfigManager()
+    cm = get_default_config_manager(type)
 
     if isinstance(entries, List) and len(entries) == 0:
         # If no arguments are passed, return the full configuration as a dict
@@ -262,7 +285,7 @@ def get_config(entries: Union[str, List] = None) -> Union[Dict, Union[str, int]]
         return dict(zip(entries, values))
 
 
-def reload_config() -> None:
+def reload_config(type: CMType) -> None:
     """
     Reload the configuration from the TOML file.
 
@@ -272,11 +295,13 @@ def reload_config() -> None:
     Returns:
         None
     """
-    cm = ConfigManager()
+    cm = get_default_config_manager(type)
     cm.read_config()
 
 
-def update_config(new_entries: Optional[Dict] = None, override_existing: bool = True) -> None:
+def update_config(
+    type: CMType, new_entries: Optional[Dict] = None, override_existing: bool = True
+) -> None:
     """
     Read the configuration from the TOML file and append to default
         (or existing) configuration. Optionally, update configuration
@@ -290,5 +315,5 @@ def update_config(new_entries: Optional[Dict] = None, override_existing: bool =
     Returns:
         None
     """
-    cm = ConfigManager()
+    cm = get_default_config_manager(type)
     cm.update_config(new_entries, override_existing)
diff --git a/covalent/_shared_files/defaults.py b/covalent/_shared_files/defaults.py
index 8ec1188f..1d54a9d4 100644
--- a/covalent/_shared_files/defaults.py
+++ b/covalent/_shared_files/defaults.py
@@ -27,6 +27,8 @@ from typing import Dict, List
 
 import dask.system
 
+from .config import CMType
+
 prefix_separator = ":"
 
 parameter_prefix = f"{prefix_separator}parameter{prefix_separator}"
@@ -65,20 +67,46 @@ def get_default_sdk_config():
     }
 
 
-def get_default_dispatcher_config():
+def get_default_server_config():
+    """
+    Return the default server config for a client
+    """
     return {
-        "address": "localhost",
+        "address": os.environ.get("COVALENT_DISPATCHER_ADDR")
+        if os.environ.get("COVALENT_DISPATCHER_ADDR")
+        else "localhost",
         "port": int(os.environ.get("COVALENT_SVC_PORT"))
         if os.environ.get("COVALENT_SVC_PORT")
         else 48008,
-        "cache_dir": os.environ.get("COVALENT_CACHE_DIR")
-        or ((os.environ.get("XDG_CACHE_HOME") or (os.environ["HOME"] + "/.cache")) + "/covalent"),
+    }
+
+
+def get_default_service_config():
+    """
+    Return the default service configuration for the server (a.k.a dispatcher)
+    """
+    return {
+        "address": "0.0.0.0" if os.environ.get("COVALENT_SERVER_IFACE_ANY") else "localhost",
+        "port": int(os.environ.get("COVALENT_SVC_PORT"))
+        if os.environ.get("COVALENT_SVC_PORT")
+        else 48008,
+        "dev_port": int(os.environ.get("COVALENT_DEV_PORT"))
+        if os.environ.get("COVALENT_DEV_PORT")
+        else 49009,
         "results_dir": os.environ.get("COVALENT_RESULTS_DIR", "results"),
+        "cache_dir": os.environ.get("COVALENT_CACHE_DIR")
+        or (
+            (os.environ.get("XDG_CACHE_HOME") or (os.environ.get("HOME") + "/.cache"))
+            + "/covalent"
+        ),
         "log_dir": os.environ.get("COVALENT_LOGDIR")
-        or ((os.environ.get("XDG_CACHE_HOME") or (os.environ["HOME"] + "/.cache")) + "/covalent"),
+        or (
+            (os.environ.get("XDG_CACHE_HOME") or (os.environ.get("HOME") + "/.cache"))
+            + "/covalent"
+        ),
         "db_path": os.environ.get("COVALENT_DATABASE")
         or (
-            (os.environ.get("XDG_DATA_HOME") or (os.environ["HOME"] + "/.local/share"))
+            (os.environ.get("XDG_DATA_HOME") or (os.environ.get("HOME") + "/.local/share"))
             + "/covalent/dispatcher_db.sqlite"
         ),
     }
@@ -101,24 +129,12 @@ def get_default_workflow_data_config():
         "storage_type": "local",
         "base_dir": os.environ.get("COVALENT_DATA_DIR")
         or (
-            (os.environ.get("XDG_DATA_HOME") or (os.environ["HOME"] + "/.local/share"))
+            (os.environ.get("XDG_DATA_HOME") or (os.environ.get("HOME") + "/.local/share"))
             + "/covalent/workflow_data"
         ),
     }
 
 
-def get_default_ui_config():
-    return {
-        "address": "localhost",
-        "port": int(os.environ.get("COVALENT_SVC_PORT"))
-        if os.environ.get("COVALENT_SVC_PORT")
-        else 48008,
-        "dev_port": 49009,
-        "log_dir": os.environ.get("COVALENT_LOGDIR")
-        or ((os.environ.get("XDG_CACHE_HOME") or (os.environ["HOME"] + "/.cache")) + "/covalent"),
-    }
-
-
 def get_default_executor() -> dict:
     """
     Gets the executor based on whether Dask service is running.
@@ -128,17 +144,31 @@ def get_default_executor() -> dict:
     """
     from .config import get_config
 
-    return "local" if get_config("sdk.no_cluster") else "dask"
+    return "local" if get_config(CMType.CLIENT, "sdk.no_cluster") else "dask"
+
+
+def get_default_config_path(type: CMType) -> str:
+    base = (
+        os.environ.get("COVALENT_CONFIG_DIR")
+        or os.environ.get("XDG_CONFIG_DIR")
+        or (os.environ.get("HOME") + "/.config")
+    )
+    filename = "/covalent/covalent.conf" if type == CMType.CLIENT else "/covalent/covalentd.conf"
+    return base + filename
 
 
 # Default configuration settings
 @dataclass
-class DefaultConfig:
+class DefaultClientConfig:
     sdk: Dict = field(default_factory=get_default_sdk_config)
-    dispatcher: Dict = field(default_factory=get_default_dispatcher_config)
+    server: Dict = field(default_factory=get_default_server_config)
+
+
+@dataclass
+class DefaultServerConfig:
+    service: Dict = field(default_factory=get_default_service_config)
     dask: Dict = field(default_factory=get_default_dask_config)
     workflow_data: Dict = field(default_factory=get_default_workflow_data_config)
-    user_interface: Dict = field(default_factory=get_default_ui_config)
 
 
 @dataclass
diff --git a/covalent/_shared_files/logger.py b/covalent/_shared_files/logger.py
index 4634bc4e..a550a033 100644
--- a/covalent/_shared_files/logger.py
+++ b/covalent/_shared_files/logger.py
@@ -25,13 +25,13 @@ import os
 import sys
 import time
 
-from .config import get_config
+from .config import CMType, get_config
 
 logging.Formatter.converter = time.gmtime
 
 app_log = logging.getLogger(__name__)
 
-log_level = get_config("sdk.log_level").upper()
+log_level = get_config(CMType.CLIENT, "sdk.log_level").upper()
 app_log.setLevel(log_level)
 
 # Set the format
@@ -54,13 +54,13 @@ log_stack_info = os.environ.get("LOGSTACK", "TRUE").upper() == "TRUE"
 log_debug_info = os.environ.get("LOGDEBUG", "FALSE").upper() == "TRUE"
 
 # Also log to file
-log_to_file = get_config("sdk.enable_logging").upper() == "TRUE"
+log_to_file = get_config(CMType.CLIENT, "sdk.enable_logging").upper() == "TRUE"
 
 if log_to_file:
     import datetime
     import logging.handlers
 
-    log_dir = get_config("sdk.log_dir")
+    log_dir = get_config(CMType.CLIENT, "sdk.log_dir")
     os.makedirs(log_dir, exist_ok=True)
 
     timestamp = datetime.datetime.utcnow().strftime("%Y-%m-%d_%H.%M.%S_UTC")
diff --git a/covalent/_workflow/lattice.py b/covalent/_workflow/lattice.py
index 120b907d..a73954f6 100644
--- a/covalent/_workflow/lattice.py
+++ b/covalent/_workflow/lattice.py
@@ -33,7 +33,7 @@ from typing import TYPE_CHECKING, Any, Callable, List, Optional, Union
 
 from .._data_store import DataStoreSession
 from .._shared_files import logger
-from .._shared_files.config import get_config
+from .._shared_files.config import CMType, get_config
 from .._shared_files.context_managers import active_lattice_manager
 from .._shared_files.defaults import DefaultMetadataValues
 from .._shared_files.utils import get_named_params, get_serialized_function_str
@@ -311,7 +311,7 @@ def lattice(
     executor: Optional[
         Union[List[Union[str, "BaseExecutor"]], Union[str, "BaseExecutor"]]
     ] = DEFAULT_METADATA_VALUES["executor"],
-    results_dir: Optional[str] = get_config("dispatcher.results_dir"),
+    results_dir: Optional[str] = get_config(CMType.SERVER, "service.results_dir"),
     workflow_executor: Optional[
         Union[List[Union[str, "BaseExecutor"]], Union[str, "BaseExecutor"]]
     ] = DEFAULT_METADATA_VALUES["workflow_executor"],
diff --git a/covalent/executor/__init__.py b/covalent/executor/__init__.py
index f836844f..df8c9f00 100644
--- a/covalent/executor/__init__.py
+++ b/covalent/executor/__init__.py
@@ -31,7 +31,7 @@ from typing import Any, Dict, List, Union
 import pkg_resources
 
 from .._shared_files import logger
-from .._shared_files.config import get_config, update_config
+from .._shared_files.config import CMType, get_config, update_config
 from .base import BaseExecutor, wrapper_fn
 
 app_log = logger.app_log
@@ -79,7 +79,7 @@ class _ExecutorManager:
         self._load_executors(pkg_plugins_path)
 
         # Look for executor plugins in a user-defined path:
-        user_plugins_path = get_config("sdk.executor_dir")
+        user_plugins_path = get_config(CMType.CLIENT, "sdk.executor_dir")
         self._load_executors(user_plugins_path)
 
         # Look for pip-installed plugins:
@@ -106,8 +106,8 @@ class _ExecutorManager:
 
         elif isinstance(name, str):
             if name in self.executor_plugins_map:
-                update_config()
-                default_options = get_config(f"executors.{name}")
+                update_config(CMType.CLIENT)
+                default_options = get_config(CMType.CLIENT, f"executors.{name}")
                 return self.executor_plugins_map[name](**default_options)
             else:
                 message = f"No executor found by name: {name}."
@@ -171,7 +171,7 @@ class _ExecutorManager:
                 default_params = {
                     "executors": {short_name: getattr(the_module, "_EXECUTOR_PLUGIN_DEFAULTS")},
                 }
-                update_config(default_params, override_existing=False)
+                update_config(CMType.CLIENT, default_params, override_existing=False)
 
         else:
             # The requested plugin (the_module.module_name) was not found in the module.
diff --git a/covalent/executor/executor_plugins/dask.py b/covalent/executor/executor_plugins/dask.py
index 7265a9e7..d37f33e9 100644
--- a/covalent/executor/executor_plugins/dask.py
+++ b/covalent/executor/executor_plugins/dask.py
@@ -36,7 +36,7 @@ from dask.distributed import Client
 from covalent._shared_files import logger
 
 # Relative imports are not allowed in executor plugins
-from covalent._shared_files.config import get_config
+from covalent._shared_files.config import CMType, get_config
 from covalent._shared_files.utils import _address_client_mapper
 from covalent.executor.base import AsyncBaseExecutor
 
@@ -84,7 +84,7 @@ class DaskExecutor(AsyncBaseExecutor):
 
         if not scheduler_address:
             try:
-                scheduler_address = get_config("dask.scheduler_address")
+                scheduler_address = get_config(CMType.SERVER, "dask.scheduler_address")
             except KeyError as ex:
                 app_log.debug(
                     "No dask scheduler address found in config. Address must be set manually."
diff --git a/covalent_dispatcher/_cli/service.py b/covalent_dispatcher/_cli/service.py
index 016f97ef..bb17ff77 100644
--- a/covalent_dispatcher/_cli/service.py
+++ b/covalent_dispatcher/_cli/service.py
@@ -37,13 +37,20 @@ from distributed.comm import unparse_address
 from distributed.core import connect, rpc
 
 from covalent._data_store.datastore import DataStore
-from covalent._shared_files.config import ConfigManager, get_config, set_config
+from covalent._shared_files.config import (
+    ClientConfigManager,
+    CMType,
+    ServerConfigManager,
+    get_config,
+    set_config,
+)
 from covalent.utils.migrate import migrate_pickled_result_object
 
-cm = ConfigManager()
+ccm = ClientConfigManager()
+scm = ServerConfigManager()
 
-UI_PIDFILE = get_config("dispatcher.cache_dir") + "/ui.pid"
-UI_LOGFILE = get_config("user_interface.log_dir") + "/covalent_ui.log"
+UI_PIDFILE = get_config(CMType.SERVER, "service.cache_dir") + "/ui.pid"
+UI_LOGFILE = get_config(CMType.SERVER, "service.log_dir") + "/covalent_ui.log"
 UI_SRVDIR = f"{os.path.dirname(os.path.abspath(__file__))}/../../covalent_ui"
 
 MIGRATION_WARNING_MSG = "There have been changes applied to the database."
@@ -168,7 +175,7 @@ def _graceful_start(
     """
     pid = _read_pid(pidfile)
     if psutil.pid_exists(pid):
-        port = get_config("user_interface.port")
+        port = get_config(CMType.SERVER, "service.port")
         click.echo(f"Covalent server is already running at http://localhost:{port}.")
         return port
 
@@ -239,7 +246,7 @@ def _graceful_shutdown(pidfile: str) -> None:
 @click.option(
     "-p",
     "--port",
-    default=get_config("user_interface.port"),
+    default=get_config(CMType.SERVER, "service.port"),
     show_default=True,
     help="Server port number.",
 )
@@ -301,7 +308,7 @@ def start(
     Start the Covalent server.
     """
     if develop:
-        set_config({"sdk.log_level": "debug"})
+        set_config(CMType.CLIENT, {"sdk.log_level": "debug"})
 
     db = DataStore.factory()
     if db.is_migration_pending and not ignore_migrations:
@@ -309,24 +316,22 @@ def start(
         click.echo(MIGRATION_COMMAND_MSG)
         return ctx.exit(1)
 
-    set_config("user_interface.port", port)
-    set_config("dispatcher.port", port)
+    set_config(CMType.SERVER, "service.port", port)
 
     if not no_cluster:
         if mem_per_worker:
-            set_config("dask.mem_per_worker", mem_per_worker)
+            set_config(CMType.SERVER, "dask.mem_per_worker", mem_per_worker)
         if threads_per_worker:
-            set_config("dask.threads_per_worker", threads_per_worker)
+            set_config(CMType.SERVER, "dask.threads_per_worker", threads_per_worker)
         if workers:
-            set_config("dask.num_workers", workers)
+            set_config(CMType.SERVER, "dask.num_workers", workers)
 
-        set_config("sdk.no_cluster", "false")
+        set_config(CMType.CLIENT, "sdk.no_cluster", "false")
     else:
-        set_config("sdk.no_cluster", "true")
+        set_config(CMType.CLIENT, "sdk.no_cluster", "true")
 
     port = _graceful_start(UI_SRVDIR, UI_PIDFILE, UI_LOGFILE, port, no_cluster, develop)
-    set_config("user_interface.port", port)
-    set_config("dispatcher.port", port)
+    set_config(CMType.SERVER, "service.port", port)
 
     # Wait until the server actually starts listening on the port
     server_listening = False
@@ -363,12 +368,12 @@ def restart(ctx, port: bool, develop: bool) -> None:
     """
 
     configuration = {
-        "port": port or get_config("user_interface.port"),
-        "develop": develop or (get_config("sdk.log_level") == "debug"),
-        "no_cluster": get_config("user_interface.port"),
-        "mem_per_worker": get_config("dask.mem_per_worker"),
-        "threads_per_worker": get_config("dask.threads_per_worker"),
-        "workers": set_config("dask.num_workers"),
+        "port": port or get_config(CMType.SERVER, "service.port"),
+        "develop": develop or (get_config(CMType.CLIENT, "sdk.log_level") == "debug"),
+        "no_cluster": get_config(CMType.SERVER, "service.port"),
+        "mem_per_worker": get_config(CMType.SERVER, "dask.mem_per_worker"),
+        "threads_per_worker": get_config(CMType.SERVER, "dask.threads_per_worker"),
+        "workers": set_config(CMType.SERVER, "dask.num_workers"),
     }
 
     ctx.invoke(stop)
@@ -383,7 +388,7 @@ def status() -> None:
 
     pid = _read_pid(UI_PIDFILE)
     if _read_pid(UI_PIDFILE) != -1 and psutil.pid_exists(pid):
-        ui_port = get_config("user_interface.port")
+        ui_port = get_config(CMType.SERVER, "service.port")
         click.echo(f"Covalent server is running at http://localhost:{ui_port}.")
     else:
         _rm_pid_file(UI_PIDFILE)
@@ -406,20 +411,20 @@ def purge(hard: bool, yes: bool, hell_yeah: bool) -> None:
     Purge Covalent from this system. This command is for developers.
     """
 
-    removal_list = {
-        get_config("sdk.log_dir"),
-        get_config("dispatcher.cache_dir"),
-        get_config("dispatcher.log_dir"),
-        get_config("user_interface.log_dir"),
-        os.path.dirname(cm.config_file),
-    }
+    removal_list = [
+        get_config(CMType.CLIENT, "sdk.log_dir"),
+        get_config(CMType.SERVER, "service.cache_dir"),
+        get_config(CMType.SERVER, "service.log_dir"),
+        os.path.dirname(ccm.config_file),
+        os.path.dirname(scm.config_file),
+    ]
 
     if hell_yeah:
         hard = True
         yes = True
 
     if hard:
-        removal_list.add(get_config("dispatcher.db_path"))
+        removal_list.append(get_config(CMType.SERVER, "service.db_path"))
 
     if not yes:
 
@@ -572,8 +577,8 @@ def cluster(
     # addr of the admin server for the Dask cluster process
     # started with covalent
     loop = asyncio.get_event_loop()
-    admin_host = get_config("dask.admin_host")
-    admin_port = get_config("dask.admin_port")
+    admin_host = get_config(CMType.SERVER, "dask.admin_host")
+    admin_port = get_config(CMType.SERVER, "dask.admin_port")
     admin_server_addr = unparse_address("tcp", f"{admin_host}:{admin_port}")
 
     if status:
@@ -634,5 +639,7 @@ def cluster(
 @click.command()
 def config() -> None:
     """Print Covalent's configuration to stdout"""
-    cm.read_config()
-    click.echo(json.dumps(cm.config_data, sort_keys=True, indent=4))
+    ccm.read_config()
+    click.echo(json.dumps(ccm.config_data, sort_keys=True, indent=4))
+    scm.read_config()
+    click.echo(json.dumps(scm.config_data, sort_keys=True, indent=4))
diff --git a/covalent_dispatcher/_db/dispatchdb.py b/covalent_dispatcher/_db/dispatchdb.py
index f1adad98..3c3008cb 100644
--- a/covalent_dispatcher/_db/dispatchdb.py
+++ b/covalent_dispatcher/_db/dispatchdb.py
@@ -29,7 +29,7 @@ import simplejson
 import covalent.executor as covalent_executor
 from covalent._data_store import DataStore
 from covalent._shared_files import logger
-from covalent._shared_files.config import get_config
+from covalent._shared_files.config import CMType, get_config
 from covalent._shared_files.util_classes import Status
 
 app_log = logger.app_log
@@ -169,7 +169,7 @@ class DispatchDB:
         if dbpath:
             self._dbpath = dbpath
         else:
-            self._dbpath = get_config("user_interface.dispatch_db")
+            self._dbpath = get_config(CMType.SERVER, "service.dispatch_db")
 
     def __enter__(self):
         return self
diff --git a/covalent_dispatcher/_service/app_dask.py b/covalent_dispatcher/_service/app_dask.py
index 613b2bce..6886db3d 100644
--- a/covalent_dispatcher/_service/app_dask.py
+++ b/covalent_dispatcher/_service/app_dask.py
@@ -11,7 +11,7 @@ from dask.distributed import LocalCluster
 from distributed.core import Server, rpc
 
 from covalent._shared_files import logger
-from covalent._shared_files.config import get_config, update_config
+from covalent._shared_files.config import CMType, get_config, update_config
 from covalent._shared_files.utils import get_random_available_port
 
 app_log = logger.app_log
@@ -169,17 +169,17 @@ class DaskCluster(Process):
         self.admin_port = get_random_available_port()
         # Read the configuration options from the main config file
         try:
-            self.num_workers = get_config("dask.num_workers")
+            self.num_workers = get_config(CMType.SERVER, "dask.num_workers")
         except KeyError:
             self.logger.warning("Num workers not specified, using default = 1")
 
         try:
-            self.mem_per_worker = get_config("dask.mem_per_worker")
+            self.mem_per_worker = get_config(CMType.SERVER, "dask.mem_per_worker")
         except KeyError:
             self.logger.warning("Memory limit per worker not provided, using default = 'auto'")
 
         try:
-            self.threads_per_worker = get_config("dask.threads_per_worker")
+            self.threads_per_worker = get_config(CMType.SERVER, "dask.threads_per_worker")
         except KeyError:
             self.logger.warning("Threads per worker not provided, using default = 1")
 
@@ -201,6 +201,7 @@ class DaskCluster(Process):
 
         try:
             update_config(
+                CMType.SERVER,
                 {
                     "dask": {
                         "scheduler_address": scheduler_address,
@@ -210,7 +211,7 @@ class DaskCluster(Process):
                         "admin_host": self.admin_host,
                         "admin_port": self.admin_port,
                     }
-                }
+                },
             )
 
             admin = DaskAdminWorker(self.cluster, self.admin_host, self.admin_port, self.logger)
diff --git a/covalent_ui/api/main.py b/covalent_ui/api/main.py
index 0b2ae7f6..919bbac6 100644
--- a/covalent_ui/api/main.py
+++ b/covalent_ui/api/main.py
@@ -28,15 +28,15 @@ from fastapi.middleware.cors import CORSMiddleware
 from fastapi.responses import JSONResponse
 
 from covalent._shared_files import logger
-from covalent._shared_files.config import get_config
+from covalent._shared_files.config import CMType, get_config
 from covalent_ui.api.v1.routes import routes
 
 # Config
 
 WEBHOOK_PATH = "/api/webhook"
-address = get_config("user_interface.address")
-port = str(get_config("user_interface.dev_port"))
-socket_port = str(get_config("user_interface.port"))
+address = get_config(CMType.SERVER, "service.address")
+port = str(get_config(CMType.SERVER, "service.dev_port"))
+socket_port = str(get_config(CMType.SERVER, "service.port"))
 origins = [f"http://{address}:{port}"]
 socket_origins = [f"http://{address}:{port}", f"http://{address}:{socket_port}"]
 
diff --git a/covalent_ui/app.py b/covalent_ui/app.py
index baca3d91..19eca70a 100644
--- a/covalent_ui/app.py
+++ b/covalent_ui/app.py
@@ -27,7 +27,7 @@ from fastapi.templating import Jinja2Templates
 
 from covalent._data_store.datastore import DataStore
 from covalent._shared_files import logger
-from covalent._shared_files.config import get_config
+from covalent._shared_files.config import CMType, get_config
 from covalent_dispatcher._service.app_dask import DaskCluster
 from covalent_ui.api.main import app as fastapi_app
 from covalent_ui.api.main import sio
@@ -84,7 +84,7 @@ if __name__ == "__main__":
     if args.port:
         port = int(args.port)
     else:
-        port = int(get_config("dispatcher.port"))
+        port = int(get_config(CMType.SERVER, "service.port"))
 
     host = "localhost" if not COVALENT_SERVER_IFACE_ANY else "0.0.0.0"
 
diff --git a/covalent_ui/result_webhook.py b/covalent_ui/result_webhook.py
index a6448c4f..8bfb49e8 100644
--- a/covalent_ui/result_webhook.py
+++ b/covalent_ui/result_webhook.py
@@ -26,10 +26,10 @@ import requests
 import covalent_ui.app as ui_server
 from covalent._results_manager import Result
 from covalent._shared_files import logger
-from covalent._shared_files.config import get_config
+from covalent._shared_files.config import CMType, get_config
 from covalent_dispatcher._db.dispatchdb import encode_dict, extract_graph, extract_metadata
 
-DEFAULT_PORT = get_config("user_interface.port")
+DEFAULT_PORT = get_config(CMType.SERVER, "service.port")
 
 app_log = logger.app_log
 
diff --git a/doc/source/how_to/config/customization.ipynb b/doc/source/how_to/config/customization.ipynb
index 7781f205..626e4fd3 100644
--- a/doc/source/how_to/config/customization.ipynb
+++ b/doc/source/how_to/config/customization.ipynb
@@ -251,7 +251,7 @@
  ],
  "metadata": {
   "kernelspec": {
-   "display_name": "Python 3 (ipykernel)",
+   "display_name": "Python 3.8.13 ('cova-dev')",
    "language": "python",
    "name": "python3"
   },
@@ -265,7 +265,11 @@
    "name": "python",
    "nbconvert_exporter": "python",
    "pygments_lexer": "ipython3",
-   "version": "3.8.12"
+   "version": "3.8.13"
+  },
+  "vscode": {
+   "interpreter": {
+   }
   }
  },
  "nbformat": 4,


Test patch:
diff --git a/tests/covalent_dispatcher_tests/_cli/service_test.py b/tests/covalent_dispatcher_tests/_cli/service_test.py
index f0b219ee..d9a820bc 100644
--- a/tests/covalent_dispatcher_tests/_cli/service_test.py
+++ b/tests/covalent_dispatcher_tests/_cli/service_test.py
@@ -21,13 +21,14 @@
 """Tests for Covalent command line interface (CLI) Tool."""
 
 import tempfile
-from unittest.mock import Mock
+from unittest.mock import MagicMock, Mock
 
 import mock
 import pytest
 from click.testing import CliRunner
 
 from covalent._data_store.datastore import DataStore
+from covalent._shared_files.config import CMType
 from covalent_dispatcher._cli.service import (
     MIGRATION_COMMAND_MSG,
     MIGRATION_WARNING_MSG,
@@ -222,7 +223,7 @@ def test_start(mocker, monkeypatch, is_migration_pending, ignore_migrations):
 
     if ignore_migrations or not is_migration_pending:
         graceful_start_mock.assert_called_once()
-        assert set_config_mock.call_count == 6
+        assert set_config_mock.call_count == 4
         # set_config_mock.assert_called_once()
     else:
         assert MIGRATION_COMMAND_MSG in res.output
@@ -300,22 +301,18 @@ def test_is_server_running(mocker):
 def test_purge_proceed(hard, mocker):
     """Test the 'covalent purge' CLI command."""
 
-    from covalent_dispatcher._cli.service import UI_PIDFILE, cm
+    from covalent_dispatcher._cli.service import UI_PIDFILE
 
     runner = CliRunner()
 
     dir_list = [
-        mock.call(dirs)
-        for dirs in [
-            "sdk.log_dir",
-            "dispatcher.cache_dir",
-            "dispatcher.log_dir",
-            "user_interface.log_dir",
-        ]
+        mock.call(CMType.CLIENT, "sdk.log_dir"),
+        mock.call(CMType.SERVER, "service.cache_dir"),
+        mock.call(CMType.SERVER, "service.log_dir"),
     ]
 
-    def get_config_side_effect(conf_name):
-        return "file" if conf_name == "dispatcher.db_path" else "dir"
+    def get_config_side_effect(*args):
+        return "file" if args[0] == CMType.SERVER and args[1] == "service.db_path" else "dir"
 
     get_config_mock = mocker.patch(
         "covalent_dispatcher._cli.service.get_config", side_effect=get_config_side_effect
@@ -338,15 +335,22 @@ def test_purge_proceed(hard, mocker):
     if hard:
         result = runner.invoke(purge, args="--hard", input="y")
 
-        dir_list.append(mock.call("dispatcher.db_path"))
-        os_path_isdir_mock.assert_has_calls([mock.call("file"), mock.call("file")], any_order=True)
+        dir_list.append(mock.call(CMType.SERVER, "service.db_path"))
+        os_path_isdir_mock.assert_has_calls(
+            [mock.call("dir"), mock.call("dir"), mock.call("dir"), mock.call("file")],
+            any_order=True,
+        )
         os_remove_mock.assert_called_with("file")
+        assert get_config_mock.call_count == 4
     else:
         result = runner.invoke(purge, input="y")
 
-    get_config_mock.assert_has_calls(dir_list, any_order=True)
-    os_path_dirname_mock.assert_called_with(cm.config_file)
-    os_path_isdir_mock.assert_has_calls([mock.call("dir"), mock.call("dir")], any_order=True)
+        assert get_config_mock.call_count == 3
+        get_config_mock.assert_has_calls(dir_list, any_order=True)
+        assert os_path_dirname_mock.call_count == 2
+        os_path_isdir_mock.assert_has_calls(
+            [mock.call("dir"), mock.call("dir"), mock.call("dir")], any_order=True
+        )
 
     graceful_shutdown_mock.assert_called_with(UI_PIDFILE)
 
@@ -358,23 +362,16 @@ def test_purge_proceed(hard, mocker):
 @pytest.mark.parametrize("hard", [False, True])
 def test_purge_abort(hard, mocker):
     """Test the 'covalent purge' CLI command."""
-
-    from covalent_dispatcher._cli.service import cm
-
     runner = CliRunner()
 
     dir_list = [
-        mock.call(dirs)
-        for dirs in [
-            "sdk.log_dir",
-            "dispatcher.cache_dir",
-            "dispatcher.log_dir",
-            "user_interface.log_dir",
-        ]
+        mock.call(CMType.CLIENT, "sdk.log_dir"),
+        mock.call(CMType.SERVER, "service.cache_dir"),
+        mock.call(CMType.SERVER, "service.log_dir"),
     ]
 
-    def get_config_side_effect(conf_name):
-        return "file" if conf_name == "dispatcher.db_path" else "dir"
+    def get_config_side_effect(*args):
+        return "file" if args[0] == CMType.SERVER and args[1] == "service.db_path" else "dir"
 
     get_config_mock = mocker.patch(
         "covalent_dispatcher._cli.service.get_config", side_effect=get_config_side_effect
@@ -392,13 +389,14 @@ def test_purge_abort(hard, mocker):
 
     if hard:
         result = runner.invoke(purge, input="n", args="--hard")
-        dir_list.append(mock.call("dispatcher.db_path"))
+        dir_list.append(mock.call(CMType.SERVER, "service.db_path"))
         os_path_isdir_mock.assert_has_calls([mock.call("file")])
     else:
         result = runner.invoke(purge, input="n")
 
     get_config_mock.assert_has_calls(dir_list, any_order=True)
-    os_path_dirname_mock.assert_called_with(cm.config_file)
+    assert os_path_dirname_mock.call_count == 2
+    # os_path_dirname_mock.assert_called_with(cm.config_file)
     os_path_isdir_mock.assert_has_calls([mock.call("dir")])
 
     assert "Aborted!\n" in result.output
@@ -431,9 +429,9 @@ def test_logs(exists, mocker):
 
 def test_config(mocker):
     """Test covalent config cli"""
-    from covalent._shared_files.config import ConfigManager as cm
 
-    cfg_read_config_mock = mocker.patch("covalent_dispatcher._cli.service.cm.read_config")
+    cfg_read_config_mock = mocker.patch("covalent_dispatcher._cli.service.ccm.read_config")
+    scm_cfg_read_config_mock = mocker.patch("covalent_dispatcher._cli.service.scm.read_config")
     json_dumps_mock = mocker.patch("covalent_dispatcher._cli.service.json.dumps")
     click_echo_mock = mocker.patch("covalent_dispatcher._cli.service.click.echo")
 
@@ -441,8 +439,9 @@ def test_config(mocker):
     runner.invoke(config)
 
     cfg_read_config_mock.assert_called_once()
-    json_dumps_mock.assert_called_once()
-    click_echo_mock.assert_called_once()
+    scm_cfg_read_config_mock.assert_called_once()
+    assert json_dumps_mock.call_count == 2
+    assert click_echo_mock.call_count == 2
 
 
 @pytest.mark.parametrize("workers", [1, 2, 3, 4])
@@ -721,7 +720,7 @@ def test_start_config_mem_per_worker(mocker, monkeypatch):
 
     res = runner.invoke(start, cli_args)
 
-    assert set_config_mock.call_count == 7
+    assert set_config_mock.call_count == 5
     graceful_start_mock.assert_called_once()
 
 
@@ -747,7 +746,7 @@ def test_start_config_threads_per_worker(mocker, monkeypatch):
 
     res = runner.invoke(start, cli_args)
 
-    assert set_config_mock.call_count == 7
+    assert set_config_mock.call_count == 5
     graceful_start_mock.assert_called_once()
 
 
@@ -773,7 +772,7 @@ def test_start_config_num_workers(mocker, monkeypatch):
 
     res = runner.invoke(start, cli_args)
 
-    assert set_config_mock.call_count == 7
+    assert set_config_mock.call_count == 5
     graceful_start_mock.assert_called_once()
 
 
@@ -799,7 +798,7 @@ def test_start_all_dask_config(mocker, monkeypatch):
 
     res = runner.invoke(start, cli_args)
 
-    assert set_config_mock.call_count == 9
+    assert set_config_mock.call_count == 7
     graceful_start_mock.assert_called_once()
 
 
@@ -825,7 +824,7 @@ def test_start_dask_config_options_workers_and_mem_per_worker(mocker, monkeypatc
 
     res = runner.invoke(start, cli_args)
 
-    assert set_config_mock.call_count == 8
+    assert set_config_mock.call_count == 6
     graceful_start_mock.assert_called_once()
 
 
@@ -851,7 +850,7 @@ def test_start_dask_config_options_workers_and_threads_per_worker(mocker, monkey
 
     res = runner.invoke(start, cli_args)
 
-    assert set_config_mock.call_count == 8
+    assert set_config_mock.call_count == 6
     graceful_start_mock.assert_called_once()
 
 
@@ -877,5 +876,93 @@ def test_start_dask_config_options_mem_per_workers_and_threads_per_worker(mocker
 
     res = runner.invoke(start, cli_args)
 
-    assert set_config_mock.call_count == 8
+    assert set_config_mock.call_count == 6
+    graceful_start_mock.assert_called_once()
+
+
+def test_sdk_no_cluster(mocker, monkeypatch):
+    runner = CliRunner()
+    port_val = 42
+
+    graceful_start_mock = mocker.patch(
+        "covalent_dispatcher._cli.service._graceful_start", return_value=port_val
+    )
+    db_mock = Mock()
+    mocker.patch.object(DataStore, "factory", lambda: db_mock)
+    monkeypatch.setattr("covalent_dispatcher._cli.service.UI_SRVDIR", "mock")
+    set_config_mock = mocker.patch("covalent_dispatcher._cli.service.set_config")
+    monkeypatch.setattr("covalent_dispatcher._cli.service.UI_SRVDIR", "mock")
+    monkeypatch.setattr("covalent_dispatcher._cli.service.UI_PIDFILE", "mock")
+    monkeypatch.setattr("covalent_dispatcher._cli.service.UI_LOGFILE", "mock")
+
+    db_mock.is_migration_pending = False
+
+    cli_args = f"--port {port_val} -d --no-cluster"
+
+    res = runner.invoke(start, cli_args)
+
     graceful_start_mock.assert_called_once()
+    set_config_mock.assert_has_calls([mock.call(CMType.CLIENT, "sdk.no_cluster", "true")])
+
+
+def test_purge_hidden_option(mocker):
+    """Test the 'covalent purge' CLI command."""
+
+    from covalent_dispatcher._cli.service import UI_PIDFILE
+
+    runner = CliRunner()
+
+    dir_list = [
+        mock.call(CMType.CLIENT, "sdk.log_dir"),
+        mock.call(CMType.SERVER, "service.cache_dir"),
+        mock.call(CMType.SERVER, "service.log_dir"),
+        mock.call(CMType.SERVER, "service.db_path"),
+    ]
+
+    def get_config_side_effect(*args):
+        return "file" if args[0] == CMType.SERVER and args[1] == "service.db_path" else "dir"
+
+    get_config_mock = mocker.patch(
+        "covalent_dispatcher._cli.service.get_config", side_effect=get_config_side_effect
+    )
+    os_path_dirname_mock = mocker.patch(
+        "covalent_dispatcher._cli.service.os.path.dirname", return_value="dir"
+    )
+
+    def isdir_side_effect(path):
+        return path == "dir"
+
+    os_path_isdir_mock = mocker.patch(
+        "covalent_dispatcher._cli.service.os.path.isdir", side_effect=isdir_side_effect
+    )
+
+    graceful_shutdown_mock = mocker.patch("covalent_dispatcher._cli.service._graceful_shutdown")
+    shutil_rmtree_mock = mocker.patch("covalent_dispatcher._cli.service.shutil.rmtree")
+    os_remove_mock = mocker.patch("covalent_dispatcher._cli.service.os.remove")
+
+    result = runner.invoke(purge, args="--hell-yeah")
+
+    os_path_isdir_mock.assert_has_calls(
+        [mock.call("dir"), mock.call("dir"), mock.call("dir"), mock.call("file")],
+        any_order=True,
+    )
+    os_remove_mock.assert_called_with("file")
+    assert get_config_mock.call_count == 4
+
+    graceful_shutdown_mock.assert_called_with(UI_PIDFILE)
+
+    shutil_rmtree_mock.assert_has_calls([mock.call("dir", ignore_errors=True)])
+
+    assert "Covalent server files have been purged.\n" in result.output
+
+
+def test_terminate_child_processes(mocker):
+    from covalent_dispatcher._cli.service import _terminate_child_processes
+
+    psutil_process_mock = mocker.patch(
+        "covalent_dispatcher._cli.service.psutil.Process", return_value=MagicMock()
+    )
+
+    _terminate_child_processes(1)
+
+    psutil_process_mock.assert_called_with(1)
diff --git a/tests/covalent_dispatcher_tests/_service/app_dask_test.py b/tests/covalent_dispatcher_tests/_service/app_dask_test.py
new file mode 100644
index 00000000..9380bc95
--- /dev/null
+++ b/tests/covalent_dispatcher_tests/_service/app_dask_test.py
@@ -0,0 +1,15 @@
+from mock import MagicMock
+
+
+def test_dask_cluster_init(mocker):
+    process_mock = mocker.patch("covalent_dispatcher._service.app_dask.Process.__init__")
+    get_config_mock = mocker.patch(
+        "covalent_dispatcher._service.app_dask.get_config", side_effect=KeyError("")
+    )
+
+    from covalent_dispatcher._service.app_dask import DaskCluster
+
+    cluster = DaskCluster("test", MagicMock())
+
+    process_mock.assert_called_once()
+    assert get_config_mock.call_count == 3
diff --git a/tests/covalent_dispatcher_tests/_service/app_test.py b/tests/covalent_dispatcher_tests/_service/app_test.py
index 2056a035..40551e4f 100644
--- a/tests/covalent_dispatcher_tests/_service/app_test.py
+++ b/tests/covalent_dispatcher_tests/_service/app_test.py
@@ -153,3 +153,13 @@ def test_get_result_dispatch_id_not_found(mocker, test_db_file, client, tmp_path
     mocker.patch("covalent_dispatcher._service.app.Lattice", MockLattice)
     response = client.get(f"/api/result/{DISPATCH_ID}")
     assert response.status_code == 404
+
+
+def test_db_path_get_config(mocker):
+    dbpath = "/Users/root/covalent/results.db"
+
+    get_config_mock = mocker.patch("covalent_dispatcher._db.dispatchdb.get_config")
+
+    dispatch_db = DispatchDB()
+
+    get_config_mock.assert_called_once()
diff --git a/tests/covalent_tests/data_store/data_store_test.py b/tests/covalent_tests/data_store/data_store_test.py
index 69aaf1bc..71b6b2cc 100644
--- a/tests/covalent_tests/data_store/data_store_test.py
+++ b/tests/covalent_tests/data_store/data_store_test.py
@@ -23,11 +23,11 @@ Unit tests for DataStore object
 """
 
 from covalent._data_store.datastore import DataStore
-from covalent._shared_files.config import get_config
+from covalent._shared_files.config import CMType, get_config
 
 
 def test_datastore_init():
     """Test data store initialization method."""
 
     ds = DataStore(db_URL=None)
-    assert ds.db_URL == "sqlite+pysqlite:///" + get_config("dispatcher.db_path")
+    assert ds.db_URL == "sqlite+pysqlite:///" + get_config(CMType.SERVER, "service.db_path")
diff --git a/tests/covalent_tests/executor/init_test.py b/tests/covalent_tests/executor/init_test.py
index b752cba3..3e74e5fd 100644
--- a/tests/covalent_tests/executor/init_test.py
+++ b/tests/covalent_tests/executor/init_test.py
@@ -24,6 +24,7 @@ from unittest.mock import MagicMock
 
 import pytest
 
+from covalent._shared_files.config import CMType
 from covalent.executor import BaseExecutor, _executor_manager, _ExecutorManager
 
 
@@ -32,7 +33,7 @@ def test_get_executor_local(mocker):
 
     update_config_mock = mocker.patch("covalent.executor.update_config")
     _executor_manager.get_executor(name="local")
-    update_config_mock.assert_called_once_with()
+    update_config_mock.assert_called_once_with(CMType.CLIENT)
 
 
 def test_executor_manager_init(mocker):
@@ -67,7 +68,7 @@ def test_executor_manager_generate_plugins_list(mocker):
 
     em.generate_plugins_list()
     os_path_join_mock.assert_called_once_with("covalent", "executor_plugins")
-    get_config_mock.assert_called_once_with("sdk.executor_dir")
+    get_config_mock.assert_called_once_with(CMType.CLIENT, "sdk.executor_dir")
 
     assert load_executors_mock.mock_calls == [
         mocker.call("pkg_plugins_path"),
@@ -99,8 +100,8 @@ def test_get_executor(mocker):
 
     em.executor_plugins_map = {"mock_name": plugin_mock}
     resp = em.get_executor(name="mock_name")
-    update_config_mock.assert_called_once_with()
-    get_config_mock.assert_called_once_with("executors.mock_name")
+    update_config_mock.assert_called_once_with(CMType.CLIENT)
+    get_config_mock.assert_called_once_with(CMType.CLIENT, "executors.mock_name")
     assert resp == "plugin map func called"
 
     # Case 3 - name is str and not in executor_plugin_map
diff --git a/tests/covalent_tests/results_manager_tests/results_manager_test.py b/tests/covalent_tests/results_manager_tests/results_manager_test.py
index e422a16b..be8706d6 100644
--- a/tests/covalent_tests/results_manager_tests/results_manager_test.py
+++ b/tests/covalent_tests/results_manager_tests/results_manager_test.py
@@ -35,7 +35,7 @@ from covalent._results_manager.results_manager import (
     result_from,
     sync,
 )
-from covalent._shared_files.config import get_config
+from covalent._shared_files.config import CMType, get_config
 
 DISPATCH_ID = "91c3ee18-5f2d-44ee-ac2a-39b79cf56646"
 
@@ -97,7 +97,11 @@ def test_get_result_from_dispatcher(mocker):
     mock_response.append(Mock(status=200, msg=HTTPMessage()))
     getconn_mock.return_value.getresponse.side_effect = mock_response
     dispatch_id = "9d1b308b-4763-4990-ae7f-6a6e36d35893"
-    dispatcher = get_config("dispatcher.address") + ":" + str(get_config("dispatcher.port"))
+    dispatcher = (
+        get_config(CMType.CLIENT, "server.address")
+        + ":"
+        + str(get_config(CMType.CLIENT, "server.port"))
+    )
     _get_result_from_dispatcher(
         dispatch_id, wait=wait.LONG, dispatcher=dispatcher, status_only=False
     )
diff --git a/tests/covalent_tests/shared_files/config_test.py b/tests/covalent_tests/shared_files/config_test.py
index f1b8bdcc..ee7f0953 100644
--- a/tests/covalent_tests/shared_files/config_test.py
+++ b/tests/covalent_tests/shared_files/config_test.py
@@ -23,10 +23,18 @@ from dataclasses import asdict
 
 import pytest
 
-from covalent._shared_files.config import ConfigManager, get_config, reload_config, set_config
-from covalent._shared_files.defaults import DefaultConfig
+from covalent._shared_files.config import (
+    ClientConfigManager,
+    ServerConfigManager,
+    get_config,
+    reload_config,
+    set_config,
+)
+from covalent._shared_files.defaults import CMType, DefaultClientConfig, DefaultServerConfig
 
-DEFAULT_CONFIG = asdict(DefaultConfig())
+DEFAULT_CONFIG = {}
+DEFAULT_CLIENT_CONFIG = asdict(DefaultClientConfig())
+DEFAULT_SERVER_CONFIG = asdict(DefaultServerConfig())
 
 
 @pytest.mark.parametrize(
@@ -37,12 +45,29 @@ DEFAULT_CONFIG = asdict(DefaultConfig())
         ("HOME", ".config/covalent/covalent.conf"),
     ],
 )
-def test_config_manager_init_directory_setting(monkeypatch, dir_env, conf_dir):
+def test_client_config_manager_init_directory_setting(monkeypatch, dir_env, conf_dir):
+    """Test the init method for the config manager."""
+
+    with tempfile.TemporaryDirectory() as tmp_dir:
+        monkeypatch.setenv(dir_env, tmp_dir)
+        cm = ClientConfigManager()
+        assert cm.config_file == f"{tmp_dir}/{conf_dir}"
+
+
+@pytest.mark.parametrize(
+    "dir_env, conf_dir",
+    [
+        ("COVALENT_CONFIG_DIR", "covalent/covalentd.conf"),
+        ("XDG_CONFIG_DIR", "covalent/covalentd.conf"),
+        ("HOME", ".config/covalent/covalentd.conf"),
+    ],
+)
+def test_server_config_manager_init_directory_setting(monkeypatch, dir_env, conf_dir):
     """Test the init method for the config manager."""
 
     with tempfile.TemporaryDirectory() as tmp_dir:
         monkeypatch.setenv(dir_env, tmp_dir)
-        cm = ConfigManager()
+        cm = ServerConfigManager()
         assert cm.config_file == f"{tmp_dir}/{conf_dir}"
 
 
@@ -50,38 +75,76 @@ def test_config_manager_init_directory_setting(monkeypatch, dir_env, conf_dir):
     "path_exists, write_config_called, update_config_called",
     [(False, True, False), (True, False, True)],
 )
-def test_config_manager_init_write_update_config(
+def test_client_config_manager_init_write_update_config(
     mocker, monkeypatch, path_exists, write_config_called, update_config_called
 ):
-    """Test the init method for the config manager."""
+    """Test the init method for the client config manager."""
+
+    config_keys = ["sdk.log_dir", "sdk.executor_dir"]
+
+    with tempfile.TemporaryDirectory() as tmp_dir:
+        monkeypatch.setenv("COVALENT_CONFIG_DIR", tmp_dir)
+        update_config_mock = mocker.patch(
+            "covalent._shared_files.config.ClientConfigManager.update_config"
+        )
+        write_config_mock = mocker.patch(
+            "covalent._shared_files.config.ClientConfigManager.write_config"
+        )
+        get_mock = mocker.patch(
+            "covalent._shared_files.config.ClientConfigManager.get", side_effect=config_keys
+        )
+        path_mock = mocker.patch("covalent._shared_files.config.Path.__init__", return_value=None)
+        mocker.patch("os.path.exists", return_value=path_exists)
+
+        cm = ClientConfigManager()
+        assert hasattr(cm, "config_data")
+        assert write_config_mock.called is write_config_called
+        assert update_config_mock.called is update_config_called
+
+    get_mock_calls = get_mock.mock_calls
+    path_mock_calls = path_mock.mock_calls
+
+    for key in config_keys:
+        assert mocker.call(key) in get_mock_calls and path_mock_calls
+
+
+@pytest.mark.parametrize(
+    "path_exists, write_config_called, update_config_called",
+    [(False, True, False), (True, False, True)],
+)
+def test_server_config_manager_init_write_update_config(
+    mocker, monkeypatch, path_exists, write_config_called, update_config_called
+):
+    """Test the init method for the server config manager."""
 
     config_keys = [
-        "sdk.log_dir",
-        "sdk.executor_dir",
-        "dispatcher.cache_dir",
-        "dispatcher.results_dir",
-        "dispatcher.log_dir",
-        "user_interface.log_dir",
-        "dispatcher.db_path",
+        "service.cache_dir",
+        "service.results_dir",
+        "service.log_dir",
+        "service.db_path",
     ]
 
     with tempfile.TemporaryDirectory() as tmp_dir:
         monkeypatch.setenv("COVALENT_CONFIG_DIR", tmp_dir)
         update_config_mock = mocker.patch(
-            "covalent._shared_files.config.ConfigManager.update_config"
+            "covalent._shared_files.config.ServerConfigManager.update_config"
         )
         write_config_mock = mocker.patch(
-            "covalent._shared_files.config.ConfigManager.write_config"
+            "covalent._shared_files.config.ServerConfigManager.write_config"
         )
         get_mock = mocker.patch(
-            "covalent._shared_files.config.ConfigManager.get", side_effect=config_keys
+            "covalent._shared_files.config.ServerConfigManager.get", side_effect=config_keys
         )
         path_mock = mocker.patch("covalent._shared_files.config.Path.__init__", return_value=None)
         side_effect = None if path_exists else FileNotFoundError
         open_mock = mocker.patch("covalent._shared_files.config.open", side_effect=side_effect)
+        path_exists_mock = mocker.patch(
+            "covalent._shared_files.config.os.path.exists", return_value=path_exists
+        )
 
-        cm = ConfigManager()
+        cm = ServerConfigManager()
         assert hasattr(cm, "config_data")
+        path_exists_mock.assert_called_once()
         assert write_config_mock.called is write_config_called
         assert update_config_mock.called is update_config_called
 
@@ -92,45 +155,105 @@ def test_config_manager_init_write_update_config(
         assert mocker.call(key) in get_mock_calls and path_mock_calls
 
 
-def test_set_config_str_key(mocker):
+def test_set_client_config_str_key(mocker):
+    """Test the set_config method when the input is a string."""
+
+    cm_set_mock = mocker.patch("covalent._shared_files.config.ClientConfigManager.set")
+    cm_write_config = mocker.patch(
+        "covalent._shared_files.config.ClientConfigManager.write_config"
+    )
+
+    set_config(CMType.CLIENT, "mock_section.mock_variable", "mock_value")
+
+    cm_set_mock.assert_called_once_with("mock_section.mock_variable", "mock_value")
+    cm_write_config.call_count == 2
+
+
+def test_set_server_config_str_key(mocker):
     """Test the set_config method when the input is a string."""
 
-    cm_set_mock = mocker.patch("covalent._shared_files.config.ConfigManager.set")
-    cm_write_config = mocker.patch("covalent._shared_files.config.ConfigManager.write_config")
+    cm_set_mock = mocker.patch("covalent._shared_files.config.ServerConfigManager.set")
+    cm_write_config = mocker.patch(
+        "covalent._shared_files.config.ServerConfigManager.write_config"
+    )
 
-    set_config("mock_section.mock_variable", "mock_value")
+    set_config(CMType.SERVER, "mock_section.mock_variable", "mock_value")
 
     cm_set_mock.assert_called_once_with("mock_section.mock_variable", "mock_value")
     cm_write_config.call_count == 2
 
 
-def test_set_config_dict_key(mocker):
+def test_set_client_config_dict_key(mocker):
     """Test the set_config method when the input is a dictionary."""
 
-    cm_set_mock = mocker.patch("covalent._shared_files.config.ConfigManager.set")
-    cm_write_config = mocker.patch("covalent._shared_files.config.ConfigManager.write_config")
+    cm_set_mock = mocker.patch("covalent._shared_files.config.ClientConfigManager.set")
+    cm_write_config = mocker.patch(
+        "covalent._shared_files.config.ClientConfigManager.write_config"
+    )
 
-    set_config({"mock_section.mock_variable": "mock_value"})
+    set_config(CMType.CLIENT, {"mock_section.mock_variable": "mock_value"})
 
     cm_set_mock.assert_called_once_with("mock_section.mock_variable", "mock_value")
     cm_write_config.call_count == 2
 
 
-def test_generate_default_config(mocker):
-    """Tests that the default configuration was loaded."""
+def test_set_server_config_dict_key(mocker):
+    """Test the set_config method when the input is a dictionary."""
+
+    cm_set_mock = mocker.patch("covalent._shared_files.config.ServerConfigManager.set")
+    cm_write_config = mocker.patch(
+        "covalent._shared_files.config.ServerConfigManager.write_config"
+    )
+
+    set_config(CMType.SERVER, {"mock_section.mock_variable": "mock_value"})
+
+    cm_set_mock.assert_called_once_with("mock_section.mock_variable", "mock_value")
+    cm_write_config.call_count == 2
+
+
+def test_generate_default_client_config(mocker):
+    """Tests that the default client configuration was loaded."""
+
+    cm = ClientConfigManager()
+    cm_deepcopy_mock = mocker.patch(
+        "covalent._shared_files.config.copy.deepcopy", return_value=DEFAULT_CLIENT_CONFIG
+    )
+
+    cm.generate_default_config(DEFAULT_CLIENT_CONFIG)
+    cm_deepcopy_mock.assert_called_once_with(DEFAULT_CLIENT_CONFIG)
+    assert cm.config_data == DEFAULT_CLIENT_CONFIG
+
+
+def test_generate_default_server_config(mocker):
+    """Tests that the default server configuration was loaded."""
+
+    cm = ServerConfigManager()
+    cm_deepcopy_mock = mocker.patch(
+        "covalent._shared_files.config.copy.deepcopy", return_value=DEFAULT_SERVER_CONFIG
+    )
+
+    cm.generate_default_config(DEFAULT_SERVER_CONFIG)
+    cm_deepcopy_mock.assert_called_once_with(DEFAULT_SERVER_CONFIG)
+    assert cm.config_data == DEFAULT_SERVER_CONFIG
+
 
-    cm = ConfigManager()
-    cm_deepcopy_mock = mocker.patch("covalent._shared_files.config.copy.deepcopy", return_value={})
+def test_read_client_config(mocker):
+    """Test the read_config method for the client config manager."""
 
-    cm.generate_default_config()
-    cm_deepcopy_mock.assert_called_once_with(DEFAULT_CONFIG)
-    assert cm.config_data == DEFAULT_CONFIG
+    cm = ClientConfigManager()
+    test_data = {"test": "test"}
+    toml_load_mock = mocker.patch(
+        "covalent._shared_files.config.toml.load", return_value=test_data
+    )
+    cm.read_config()
+    toml_load_mock.assert_called_with(cm.config_file)
+    assert cm.config_data == test_data
 
 
-def test_read_config(mocker):
-    """Test the read_config method for the config manager."""
+def test_read_server_config(mocker):
+    """Test the read_config method for the server config manager."""
 
-    cm = ConfigManager()
+    cm = ServerConfigManager()
     test_data = {"test": "test"}
     toml_load_mock = mocker.patch(
         "covalent._shared_files.config.toml.load", return_value=test_data
@@ -140,35 +263,73 @@ def test_read_config(mocker):
     assert cm.config_data == test_data
 
 
-def test_get():
-    """Test the get method for the config manager."""
+def test_client_get():
+    """Test the get method for the client config manager."""
+
+    cm = ClientConfigManager()
+
+    assert cm.get("server.port") == cm.config_data["server"]["port"]
+
+
+def test_server_get():
+    """Test the get method for the server config manager."""
+
+    cm = ServerConfigManager()
+
+    assert cm.get("service.port") == cm.config_data["service"]["port"]
+
+
+def test_generate_default_client_config():
+    """Test that the default client configuration was loaded."""
+
+    cm = ClientConfigManager()
+    cm.generate_default_config(DEFAULT_CLIENT_CONFIG)
+    assert cm.config_data == DEFAULT_CLIENT_CONFIG
+    assert cm.config_data is not DEFAULT_SERVER_CONFIG
 
-    cm = ConfigManager()
 
-    assert cm.get("dispatcher.port") == cm.config_data["dispatcher"]["port"]
+def test_generate_default_server_config():
+    """Test that the default server configuration was loaded."""
 
+    cm = ServerConfigManager()
+    cm.generate_default_config(DEFAULT_SERVER_CONFIG)
+    assert cm.config_data == DEFAULT_SERVER_CONFIG
+    assert cm.config_data is not DEFAULT_CLIENT_CONFIG
 
-def test_generate_default_config():
-    """Test that the default configuration was loaded."""
 
-    cm = ConfigManager()
-    cm.generate_default_config()
-    assert cm.config_data == DEFAULT_CONFIG
-    assert cm.config_data is not DEFAULT_CONFIG
+def test_reload_client_config(mocker):
+    """Test the client reload_config method."""
+
+    cm_read_config = mocker.patch("covalent._shared_files.config.ClientConfigManager.read_config")
+    reload_config(CMType.CLIENT)
+    cm_read_config.assert_called_once_with()
 
 
-def test_reload_config(mocker):
-    """Test the reload_config method."""
+def test_reload_server_config(mocker):
+    """Test the server reload_config method."""
 
-    cm_read_config = mocker.patch("covalent._shared_files.config.ConfigManager.read_config")
-    reload_config()
+    cm_read_config = mocker.patch("covalent._shared_files.config.ServerConfigManager.read_config")
+    reload_config(CMType.SERVER)
     cm_read_config.assert_called_once_with()
 
 
-def test_purge_config(mocker):
-    """Test the purge_config method for config manager."""
+def test_purge_client_config(mocker):
+    """Test the purge_config method for client config manager."""
+
+    cm = ClientConfigManager()
+    os_dir_mock = mocker.patch(
+        "covalent._shared_files.config.os.path.dirname", return_value="mock_dir"
+    )
+    rmtree_mock = mocker.patch("covalent._shared_files.config.shutil.rmtree")
+    cm.purge_config()
+    os_dir_mock.assert_called_once_with(cm.config_file)
+    rmtree_mock.assert_called_once_with("mock_dir", ignore_errors=True)
+
+
+def test_purge_server_config(mocker):
+    """Test the purge_config method for server config manager."""
 
-    cm = ConfigManager()
+    cm = ServerConfigManager()
     os_dir_mock = mocker.patch(
         "covalent._shared_files.config.os.path.dirname", return_value="mock_dir"
     )
@@ -178,36 +339,76 @@ def test_purge_config(mocker):
     rmtree_mock.assert_called_once_with("mock_dir", ignore_errors=True)
 
 
-def test_get_config():
-    """Test config retrieval function."""
+def test_client_get_config():
+    """Test client config retrieval function."""
 
-    from covalent._shared_files.config import ConfigManager
+    from covalent._shared_files.config import ClientConfigManager
 
-    cm = ConfigManager()
+    cm = ClientConfigManager()
 
     # Case 1 - Empty list
-    assert get_config(entries=[]) == cm.config_data
+    assert get_config(CMType.CLIENT, entries=[]) == cm.config_data
 
     # Case 2 - List with one item
-    assert get_config(entries=["dispatcher.port"]) == cm.config_data["dispatcher"]["port"]
+    assert get_config(CMType.CLIENT, entries=["server.port"]) == cm.config_data["server"]["port"]
 
     # Case 3 - String
-    assert get_config(entries="dispatcher.port") == cm.config_data["dispatcher"]["port"]
+    assert get_config(CMType.CLIENT, entries="server.port") == cm.config_data["server"]["port"]
 
     # Case 4 - List with > 1 items
 
-    test_list = ["dispatcher.address", "dispatcher.port"]
+    test_list = ["server.address", "server.port"]
 
-    assert get_config(entries=test_list) == {
-        "dispatcher.address": cm.config_data["dispatcher"]["address"],
-        "dispatcher.port": cm.config_data["dispatcher"]["port"],
+    assert get_config(CMType.CLIENT, entries=test_list) == {
+        "server.address": cm.config_data["server"]["address"],
+        "server.port": cm.config_data["server"]["port"],
     }
 
 
-def test_write_config(mocker):
-    """Test the write_config method for config manager."""
+def test_server_get_config():
+    """Test client config retrieval function."""
+
+    from covalent._shared_files.config import ServerConfigManager
+
+    cm = ServerConfigManager()
+
+    # Case 1 - Empty list
+    assert get_config(CMType.SERVER, entries=[]) == cm.config_data
+
+    # Case 2 - List with one item
+    assert get_config(CMType.SERVER, entries=["service.port"]) == cm.config_data["service"]["port"]
 
-    cm = ConfigManager()
+    # Case 3 - String
+    assert get_config(CMType.SERVER, entries="service.port") == cm.config_data["service"]["port"]
+
+    # Case 4 - List with > 1 items
+
+    test_list = ["service.address", "service.port"]
+
+    assert get_config(CMType.SERVER, entries=test_list) == {
+        "service.address": cm.config_data["service"]["address"],
+        "service.port": cm.config_data["service"]["port"],
+    }
+
+
+def test_write_client_config(mocker):
+    """Test the write_config method for client config manager."""
+
+    cm = ClientConfigManager()
+    toml_dump_mock = mocker.patch("covalent._shared_files.config.toml.dump")
+    fnctl_lock_mock = mocker.patch("covalent._shared_files.config.fcntl.lockf")
+    open_mock = mocker.patch("covalent._shared_files.config.open")
+    mock_file = open_mock.return_value.__enter__.return_value
+    cm.write_config()
+    fnctl_lock_mock.assert_called_once()
+    toml_dump_mock.assert_called_once_with(cm.config_data, mock_file)
+    open_mock.assert_called_once_with(cm.config_file, "w")
+
+
+def test_write_server_config(mocker):
+    """Test the write_config method for server config manager."""
+
+    cm = ServerConfigManager()
     toml_dump_mock = mocker.patch("covalent._shared_files.config.toml.dump")
     open_mock = mocker.patch("covalent._shared_files.config.open")
     lock_mock = mocker.patch("fcntl.lockf")
@@ -218,10 +419,30 @@ def test_write_config(mocker):
     lock_mock.assert_called_once()
 
 
-def test_config_manager_set(mocker):
-    """Test the set method in config manager."""
+def test_client_config_manager_set(mocker):
+    """Test the set method in client config manager."""
+
+    cm = ClientConfigManager()
+    cm.config_data = {"mock_section": {"mock_dir": "initial_value"}}
+    cm.set("mock_section.mock_dir", "final_value")
+    assert cm.config_data == {"mock_section": {"mock_dir": "final_value"}}
+
+    cm.set("mock_section.new_mock_dir", "mock_value")
+    assert cm.config_data == {
+        "mock_section": {"mock_dir": "final_value", "new_mock_dir": "mock_value"}
+    }
+
+    cm.set("new_mock_section.new_mock_dir", "mock_value")
+    assert cm.config_data == {
+        "mock_section": {"mock_dir": "final_value", "new_mock_dir": "mock_value"},
+        "new_mock_section": {"new_mock_dir": {"new_mock_dir": "mock_value"}},
+    }
+
+
+def test_server_config_manager_set(mocker):
+    """Test the set method in server config manager."""
 
-    cm = ConfigManager()
+    cm = ServerConfigManager()
     cm.config_data = {"mock_section": {"mock_dir": "initial_value"}}
     cm.set("mock_section.mock_dir", "final_value")
     assert cm.config_data == {"mock_section": {"mock_dir": "final_value"}}