Repository: google/turbinia
Pull request #301 (2018-11-11 06:45:33)

Make some config vars optional
This PR does the following:
- Makes `None` a valid value for some optional configuration variables
- Renames some configuration vars to be more consistent
- Makes some of the config var comparisons case insensitive
- Updates the config file and text

aarontp: @ericzinnikas FYI

Fixes #181 
Onager: @aarontp Please resolve conflicts before merge

Patch:
diff --git a/tools/gcf_init/deploy_gcf.py b/tools/gcf_init/deploy_gcf.py
index 8cd3b49..3509422 100755
--- a/tools/gcf_init/deploy_gcf.py
+++ b/tools/gcf_init/deploy_gcf.py
@@ -24,11 +24,11 @@ for cloud_function in function_names:
   cmd = (
       'gcloud --project {0:s} beta functions deploy {1:s} --stage-bucket {2:s} '
       '--region {3:s} --trigger-http'.format(
-          config.PROJECT, cloud_function, config.BUCKET_NAME,
+          config.TURBINIA_PROJECT, cloud_function, config.BUCKET_NAME,
           config.TURBINIA_REGION))
   print(subprocess.check_call(cmd, shell=True))
 
 print('/nCreating Datastore index from {0:s}'.format(index_file))
 cmd = 'gcloud --quiet --project {0:s} datastore create-indexes {1:s}'.format(
-    config.PROJECT, index_file)
+    config.TURBINIA_PROJECT, index_file)
 subprocess.check_call(cmd, shell=True)
diff --git a/turbinia/client.py b/turbinia/client.py
index 8cb1d20..77bd38f 100644
--- a/turbinia/client.py
+++ b/turbinia/client.py
@@ -60,7 +60,7 @@ TASK_MAP = {
 }
 
 config.LoadConfig()
-if config.TASK_MANAGER == 'PSQ':
+if config.TASK_MANAGER.lower() == 'psq':
   import psq
 
   from google.cloud import exceptions
@@ -68,7 +68,7 @@ if config.TASK_MANAGER == 'PSQ':
   from google.cloud import pubsub
 
   from turbinia.lib.google_cloud import GoogleCloudFunction
-elif config.TASK_MANAGER == 'Celery':
+elif config.TASK_MANAGER.lower() == 'celery':
   from turbinia.state_manager import RedisStateManager
 
 log = logging.getLogger('turbinia')
@@ -452,11 +452,11 @@ class TurbiniaPsqWorker(object):
     config.LoadConfig()
     psq_publisher = pubsub.PublisherClient()
     psq_subscriber = pubsub.SubscriberClient()
-    datastore_client = datastore.Client(project=config.PROJECT)
+    datastore_client = datastore.Client(project=config.TURBINIA_PROJECT)
     try:
       self.psq = psq.Queue(
-          psq_publisher, psq_subscriber, config.PROJECT, name=config.PSQ_TOPIC,
-          storage=psq.DatastoreStorage(datastore_client))
+          psq_publisher, psq_subscriber, config.TURBINIA_PROJECT,
+          name=config.PSQ_TOPIC, storage=psq.DatastoreStorage(datastore_client))
     except exceptions.GoogleCloudError as e:
       msg = 'Error creating PSQ Queue: {0:s}'.format(str(e))
       log.error(msg)
diff --git a/turbinia/config/__init__.py b/turbinia/config/__init__.py
index 2a4f7b0..d9f8817 100644
--- a/turbinia/config/__init__.py
+++ b/turbinia/config/__init__.py
@@ -32,9 +32,12 @@ CONFIGPATH = [
     os.path.expanduser('~'), '/etc/turbinia',
     os.path.dirname(os.path.abspath(__file__))
 ]
-# Config vars that we expect to exist in the configuration
-CONFIGVARS = [
+
+# Required config vars
+REQUIRED_VARS = [
     # Turbinia Config
+    'INSTANCE_ID',
+    'STATE_MANAGER',
     'TASK_MANAGER',
     'LOG_FILE',
     'LOCK_FILE',
@@ -46,16 +49,20 @@ CONFIGVARS = [
     'SHARED_FILESYSTEM',
     # TODO(aarontp): Move this to the recipe config when it's available.
     'DEBUG_TASKS',
+]
+
+# Optional config vars.  Some may be mandatory depending on the configuration
+# (e.g. if TASK_MANAGER is set to 'PSQ', then the GCE Config variables are
+# required), but these requirements are not enforced.
+OPTIONAL_VARS = [
     # GCE CONFIG
-    'PROJECT',
-    'ZONE',
+    'TURBINIA_PROJECT',
+    'TURBINIA_ZONE',
     'TURBINIA_REGION',
     'BUCKET_NAME',
     'PSQ_TOPIC',
     'PUBSUB_TOPIC',
     'GCS_OUTPUT_PATH',
-    'STATE_MANAGER',
-    'INSTANCE_ID',
     # REDIS CONFIG
     'REDIS_HOST',
     'REDIS_PORT',
@@ -67,6 +74,7 @@ CONFIGVARS = [
     'KOMBU_CHANNEL',
     'KOMBU_DURABLE',
 ]
+
 # Environment variable to look for path data in
 ENVCONFIGVAR = 'TURBINIA_CONFIG_PATH'
 
@@ -113,12 +121,12 @@ def LoadConfig():
 
 def ValidateAndSetConfig(_config):
   """Makes sure that the config has the vars loaded and set in the module."""
-  # TODO(aarontp): Allow for non-mandatory config options
+  CONFIGVARS = REQUIRED_VARS + OPTIONAL_VARS
   for var in CONFIGVARS:
     if not hasattr(_config, var):
       raise TurbiniaConfigException(
           'No config attribute {0:s}:{1:s}'.format(_config.configSource, var))
-    if getattr(_config, var) is None:
+    if var in REQUIRED_VARS and getattr(_config, var) is None:
       raise TurbiniaConfigException(
           'Config attribute {0:s}:{1:s} is not set'.format(
               _config.configSource, var))
diff --git a/turbinia/config/turbinia_config.py b/turbinia/config/turbinia_config.py
index f4a81a9..ab71db1 100644
--- a/turbinia/config/turbinia_config.py
+++ b/turbinia/config/turbinia_config.py
@@ -16,18 +16,22 @@
 
 from __future__ import unicode_literals
 
-# Turbinia Role as 'server' or 'psqworker'
-ROLE = 'server'
 
-# Which user account Turbinia runs as
-USER = 'turbinia'
+################################################################################
+#                          Base Turbinia configuration
+#
+# All options in this section are required to be set to non-empty values.
+################################################################################
 
-# 'PSQ' is currently the only valid option as
-# a distributed task queue using Google Cloud Pub/Sub
-TASK_MANAGER = 'PSQ'
+# A unique ID per Turbinia instance. Used to keep multiple Turbinia instances
+# separate when running with the same Cloud projects or backend servers.
+INSTANCE_ID = 'turbinia-instance1'
 
-# Turbinia's installation directory
-TURBINIA_DIR = '/opt/turbinia'
+# Which state manager to use. Valid options are 'Datastore' or 'Redis'.
+STATE_MANAGER = 'Datastore'
+
+# Which Task manager to use. Valid options are 'PSQ' and 'Celery'.
+TASK_MANAGER = 'PSQ'
 
 # Default base output directory for worker results and evidence.
 OUTPUT_DIR = '/var/tmp'
@@ -38,9 +42,7 @@ OUTPUT_DIR = '/var/tmp'
 # different from the OUTPUT_DIR.
 TMP_DIR = '/tmp'
 
-# File to log to; set this as None if log file is not desired
-# By default, Turbinia logs are written to a directory (GCS_OUTPUT_DIR)
-# in the GCS mount
+# File to log debugging output to.
 LOG_FILE = '%s/turbinia.log' % OUTPUT_DIR
 
 # Path to a lock file used for the worker tasks.
@@ -59,7 +61,7 @@ MOUNT_DIR_PREFIX = '/mnt/turbinia-mounts'
 # This indicates whether the workers are running in an environment with a shared
 # filesystem.  This should be False for environments with workers running in
 # GCE, and True for environments that have workers on dedicated machines with
-# NFS or a SAN for Evidence objects.
+# NFS or a SAN for storing Evidence objects.
 SHARED_FILESYSTEM = False
 
 # This will set debugging flags for processes executed by Tasks (for
@@ -68,54 +70,53 @@ SHARED_FILESYSTEM = False
 # problems.
 DEBUG_TASKS = False
 
-###############################
-# Google Cloud Platform (GCP) #
-###############################
+
+################################################################################
+#                        Google Cloud Platform (GCP)
+#
+# Options in this section are required if the TASK_MANAGER is set to 'PSQ'.
+################################################################################
 
 # GCP project, region and zone where Turbinia will run.  Note that Turbinia does
 # not currently support multi-zone operation.  Even if you are running Turbinia
 # in Hybrid mode (with the Server and Workers running on local machines), you
 # will still need to provide these three parameters.
-# TODO(aarontp): Refactor these (and *PATH) var names to be consistent
-PROJECT = 'None'
-ZONE = 'None'
-TURBINIA_REGION = 'None'
+TURBINIA_PROJECT = None
+TURBINIA_ZONE = None
+TURBINIA_REGION = None
 
 # GCS bucket that has Turbinia specific scripts and can be used to store logs.
 # This must be globally unique within GCP.
-BUCKET_NAME = 'None'
+BUCKET_NAME = None
 
 # This is the internal PubSub topic that PSQ will use.  This should be different
 # than the PUBSUB_TOPIC variable.  The actual PubSub topic created will be this
 # variable prefixed with 'psq-'.
 PSQ_TOPIC = 'turbinia-psq'
 
-# A unique ID per Turbinia instance. Used to namespace datastore entries.
-INSTANCE_ID = 'turbinia-pubsub'
-
-# Topic Turbinia will listen on for new requests.  This should be different than
-# the PSQ_TOPIC variable.
+# The PubSub topic Turbinia will listen on for new requests.  This should be
+# different than the PSQ_TOPIC variable.
 PUBSUB_TOPIC = INSTANCE_ID
 
-# GCS Path to copy worker results and Evidence output to
-# Otherwise, set this as 'None' if output will be stored locally.
+# GCS Path to copy worker results and Evidence output to.
+# Otherwise, set this as 'None' if output will be stored in shared storage.
 GCS_OUTPUT_PATH = 'gs://%s/output' % BUCKET_NAME
 
-# Which state manager to use
-STATE_MANAGER = 'Datastore'
 
-##########
-# CELERY #
-##########
+################################################################################
+#                           Celery / Redis / Kombu
+#
+# Options in this section are required if TASK_MANAGER is set to 'Celery'
+################################################################################
 
 # Method for communication between nodes
-CELERY_BROKER = 'None'
+CELERY_BROKER = None
 
 # Storage for task results/status
-CELERY_BACKEND = 'None'
+CELERY_BACKEND = None
 
 # Can be the same as CELERY_BROKER
-KOMBU_BROKER = 'None'
+KOMBU_BROKER = None
 
 # Used to namespace communications.
 KOMBU_CHANNEL = '%s-kombu' % INSTANCE_ID
@@ -125,6 +126,6 @@ KOMBU_CHANNEL = '%s-kombu' % INSTANCE_ID
 KOMBU_DURABLE = True
 
 # Use Redis for state management
-REDIS_HOST = 'None'
-REDIS_PORT = 'None'
-REDIS_DB = 'None'
+REDIS_HOST = None
+REDIS_PORT = None
+REDIS_DB = None
diff --git a/turbinia/evidence.py b/turbinia/evidence.py
index 62a8e2d..03bb6ee 100644
--- a/turbinia/evidence.py
+++ b/turbinia/evidence.py
@@ -27,7 +27,7 @@ from turbinia.processors import mount_local
 # pylint: disable=keyword-arg-before-vararg
 
 config.LoadConfig()
-if config.TASK_MANAGER == 'PSQ':
+if config.TASK_MANAGER.lower() == 'psq':
   from turbinia.processors import google_cloud
 
 
diff --git a/turbinia/output_manager.py b/turbinia/output_manager.py
index bd188dd..bbf95f1 100644
--- a/turbinia/output_manager.py
+++ b/turbinia/output_manager.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright 2015 Google Inc.
+# Copyright 2018 Google Inc.
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -27,7 +27,7 @@ from turbinia import config
 from turbinia import TurbiniaException
 
 config.LoadConfig()
-if config.TASK_MANAGER == 'PSQ':
+if config.TASK_MANAGER.lower() == 'psq':
   from google.cloud import storage
 
 log = logging.getLogger('turbinia')
@@ -347,7 +347,7 @@ class GCSOutputWriter(OutputWriter):
     """
     super(GCSOutputWriter, self).__init__(*args, **kwargs)
     config.LoadConfig()
-    self.client = storage.Client(project=config.PROJECT)
+    self.client = storage.Client(project=config.TURBINIA_PROJECT)
 
     self.bucket, self.base_output_dir = self._parse_gcs_path(gcs_path)
 
diff --git a/turbinia/processors/google_cloud.py b/turbinia/processors/google_cloud.py
index 5b8a79d..8a85547 100644
--- a/turbinia/processors/google_cloud.py
+++ b/turbinia/processors/google_cloud.py
@@ -83,8 +83,9 @@ def PreprocessAttachDisk(disk_name):
   config.LoadConfig()
   instance_name = GetLocalInstanceName()
   project = GoogleCloudProject(
-      project_id=config.PROJECT, default_zone=config.ZONE)
-  instance = project.GetInstance(instance_name, zone=config.ZONE)
+      project_id=config.TURBINIA_PROJECT, default_zone=config.TURBINIA_ZONE)
+  instance = project.GetInstance(instance_name, zone=config.TURBINIA_ZONE)
+
   disk = instance.GetDisk(disk_name)
   log.info(
       'Attaching disk {0:s} to instance {1:s}'.format(disk_name, instance_name))
@@ -124,8 +125,8 @@ def PostprocessDetachDisk(disk_name, local_path):
   config.LoadConfig()
   instance_name = GetLocalInstanceName()
   project = GoogleCloudProject(
-      project_id=config.PROJECT, default_zone=config.ZONE)
-  instance = project.GetInstance(instance_name, zone=config.ZONE)
+      project_id=config.TURBINIA_PROJECT, default_zone=config.TURBINIA_ZONE)
+  instance = project.GetInstance(instance_name, zone=config.TURBINIA_ZONE)
   disk = instance.GetDisk(disk_name)
   log.info(
       'Detaching disk {0:s} from instance {1:s}'.format(
diff --git a/turbinia/pubsub.py b/turbinia/pubsub.py
index cac082d..c2f2023 100644
--- a/turbinia/pubsub.py
+++ b/turbinia/pubsub.py
@@ -61,7 +61,8 @@ class TurbiniaPubSub(TurbiniaMessageBase):
     """Set up the pubsub publisher."""
     config.LoadConfig()
     self.publisher = pubsub.PublisherClient()
-    self.topic_path = self.publisher.topic_path(config.PROJECT, self.topic_name)
+    self.topic_path = self.publisher.topic_path(
+        config.TURBINIA_PROJECT, self.topic_name)
     try:
       log.debug('Trying to create pubsub topic {0:s}'.format(self.topic_path))
       self.publisher.create_topic(self.topic_path)
@@ -74,10 +75,10 @@ class TurbiniaPubSub(TurbiniaMessageBase):
     config.LoadConfig()
     self.subscriber = pubsub.SubscriberClient()
     subscription_path = self.subscriber.subscription_path(
-        config.PROJECT, self.topic_name)
+        config.TURBINIA_PROJECT, self.topic_name)
     if not self.topic_path:
       self.topic_path = self.subscriber.topic_path(
-          config.PROJECT, self.topic_name)
+          config.TURBINIA_PROJECT, self.topic_name)
     try:
       log.debug(
           'Trying to create subscription {0:s} on topic {1:s}'.format(
diff --git a/turbinia/state_manager.py b/turbinia/state_manager.py
index b2f1381..1619a53 100644
--- a/turbinia/state_manager.py
+++ b/turbinia/state_manager.py
@@ -34,10 +34,10 @@ from turbinia.workers import TurbiniaTask
 from turbinia.workers import TurbiniaTaskResult
 
 config.LoadConfig()
-if config.TASK_MANAGER == 'PSQ':
+if config.TASK_MANAGER.lower() == 'psq':
   from google.cloud import datastore
   from google.cloud import exceptions
-elif config.TASK_MANAGER == 'Celery':
+elif config.TASK_MANAGER.lower() == 'celery':
   import redis
 
 DATETIME_FORMAT = '%Y-%m-%dT%H:%M:%S'
@@ -53,9 +53,9 @@ def get_state_manager():
   """
   config.LoadConfig()
   # pylint: disable=no-else-return
-  if config.STATE_MANAGER == 'Datastore':
+  if config.STATE_MANAGER.lower() == 'datastore':
     return DatastoreStateManager()
-  elif config.STATE_MANAGER == 'redis':
+  elif config.STATE_MANAGER.lower() == 'redis':
     return RedisStateManager()
   else:
     msg = 'State Manager type "{0:s}" not implemented'.format(
@@ -150,7 +150,7 @@ class DatastoreStateManager(BaseStateManager):
 
   def __init__(self):
     config.LoadConfig()
-    self.client = datastore.Client(project=config.PROJECT)
+    self.client = datastore.Client(project=config.TURBINIA_PROJECT)
 
   def _validate_data(self, data):
     for key, value in iter(data.items()):
diff --git a/turbinia/task_manager.py b/turbinia/task_manager.py
index 958e2e1..8833fb5 100644
--- a/turbinia/task_manager.py
+++ b/turbinia/task_manager.py
@@ -26,7 +26,7 @@ from turbinia import state_manager
 from turbinia.jobs import manager as jobs_manager
 
 config.LoadConfig()
-if config.TASK_MANAGER == 'PSQ':
+if config.TASK_MANAGER.lower() == 'psq':
   import psq
 
   from google.cloud import exceptions
@@ -34,7 +34,7 @@ if config.TASK_MANAGER == 'PSQ':
   from google.cloud import pubsub
 
   from turbinia import pubsub as turbinia_pubsub
-elif config.TASK_MANAGER == 'Celery':
+elif config.TASK_MANAGER.lower() == 'celery':
   from celery import states as celery_states
 
   from turbinia import celery as turbinia_celery
@@ -50,9 +50,9 @@ def get_task_manager():
   """
   config.LoadConfig()
   # pylint: disable=no-else-return
-  if config.TASK_MANAGER == 'PSQ':
+  if config.TASK_MANAGER.lower() == 'psq':
     return PSQTaskManager()
-  elif config.TASK_MANAGER == 'Celery':
+  elif config.TASK_MANAGER.lower() == 'celery':
     return CeleryTaskManager()
   else:
     msg = 'Task Manager type "{0:s}" not implemented'.format(
@@ -358,7 +358,7 @@ class PSQTaskManager(BaseTaskManager):
 
     log.debug(
         'Setting up PSQ Task Manager requirements on project {0:s}'.format(
-            config.PROJECT))
+            config.TURBINIA_PROJECT))
     self.server_pubsub = turbinia_pubsub.TurbiniaPubSub(config.PUBSUB_TOPIC)
     if server:
       self.server_pubsub.setup_subscriber()
@@ -366,11 +366,11 @@ class PSQTaskManager(BaseTaskManager):
       self.server_pubsub.setup_publisher()
     psq_publisher = pubsub.PublisherClient()
     psq_subscriber = pubsub.SubscriberClient()
-    datastore_client = datastore.Client(project=config.PROJECT)
+    datastore_client = datastore.Client(project=config.TURBINIA_PROJECT)
     try:
       self.psq = psq.Queue(
-          psq_publisher, psq_subscriber, config.PROJECT, name=config.PSQ_TOPIC,
-          storage=psq.DatastoreStorage(datastore_client))
+          psq_publisher, psq_subscriber, config.TURBINIA_PROJECT,
+          name=config.PSQ_TOPIC, storage=psq.DatastoreStorage(datastore_client))
     except exceptions.GoogleAPIError as e:
       msg = 'Error creating PSQ Queue: {0:s}'.format(str(e))
       log.error(msg)
diff --git a/turbinia/turbiniactl.py b/turbinia/turbiniactl.py
index b7d84d6..84d3ca1 100755
--- a/turbinia/turbiniactl.py
+++ b/turbinia/turbiniactl.py
@@ -325,7 +325,7 @@ def main():
       if args.user or args.request_id or args.task_id:
         print(
             client.close_tasks(
-                instance=config.INSTANCE_ID, project=config.PROJECT,
+                instance=config.INSTANCE_ID, project=config.TURBINIA_PROJECT,
                 region=region, request_id=args.request_id, task_id=args.task_id,
                 user=args.user, requester=getpass.getuser()))
         sys.exit(0)
@@ -337,8 +337,8 @@ def main():
 
     if args.wait and args.request_id:
       client.wait_for_request(
-          instance=config.INSTANCE_ID, project=config.PROJECT, region=region,
-          request_id=args.request_id, user=args.user,
+          instance=config.INSTANCE_ID, project=config.TURBINIA_PROJECT,
+          region=region, request_id=args.request_id, user=args.user,
           poll_interval=args.poll_interval)
     elif args.wait and not args.request_id:
       log.info(
@@ -347,8 +347,8 @@ def main():
 
     print(
         client.format_task_status(
-            instance=config.INSTANCE_ID, project=config.PROJECT, region=region,
-            days=args.days_history, task_id=args.task_id,
+            instance=config.INSTANCE_ID, project=config.TURBINIA_PROJECT,
+            region=region, days=args.days_history, task_id=args.task_id,
             request_id=args.request_id, user=args.user,
             all_fields=args.all_fields))
   elif args.command == 'listjobs':
@@ -371,12 +371,12 @@ def main():
           'object'.format(evidence_.type))
       sys.exit(1)
 
-  if is_cloud_disk and evidence_.project != config.PROJECT:
+  if is_cloud_disk and evidence_.project != config.TURBINIA_PROJECT:
     msg = (
         'Turbinia project {0:s} is different from evidence project {1:s}. '
         'This processing request will fail unless the Turbinia service '
         'account has permissions to this project.'.format(
-            config.PROJECT, evidence_.project))
+            config.TURBINIA_PROJECT, evidence_.project))
     if args.force_evidence:
       log.warning(msg)
     else:
@@ -414,11 +414,12 @@ def main():
           'Waiting for request {0:s} to complete'.format(request.request_id))
       region = config.TURBINIA_REGION
       client.wait_for_request(
-          instance=config.INSTANCE_ID, project=config.PROJECT, region=region,
-          request_id=request.request_id, poll_interval=args.poll_interval)
+          instance=config.INSTANCE_ID, project=config.TURBINIA_PROJECT,
+          region=region, request_id=request.request_id,
+          poll_interval=args.poll_interval)
       print(
           client.format_task_status(
-              instance=config.INSTANCE_ID, project=config.PROJECT,
+              instance=config.INSTANCE_ID, project=config.TURBINIA_PROJECT,
               region=region, request_id=request.request_id,
               all_fields=args.all_fields))
 


Test patch:
diff --git a/turbinia/config/turbinia_config_test.py b/turbinia/config/turbinia_config_test.py
index 4978dab..1e7f691 100644
--- a/turbinia/config/turbinia_config_test.py
+++ b/turbinia/config/turbinia_config_test.py
@@ -31,10 +31,12 @@ class TestTurbiniaConfig(unittest.TestCase):
     # Remove the loaded attributes because the module is loaded before the
     # tests start by turbinia __init__.
     # pylint: disable=expression-not-assigned
-    [delattr(config, a) for a in config.CONFIGVARS if hasattr(config, a)]
+    config_vars = config.REQUIRED_VARS + config.OPTIONAL_VARS
+    [delattr(config, a) for a in config_vars if hasattr(config, a)]
     cls.CONFIGPATH_SAVE = config.CONFIGPATH
     cls.CONFIGFILES_SAVE = config.CONFIGFILES
-    cls.CONFIGVARS_SAVE = config.CONFIGVARS
+    cls.REQUIRED_VARS_SAVE = config.REQUIRED_VARS
+    cls.OPTIONAL_VARS_SAVE = config.OPTIONAL_VARS
 
   def setUp(self):
     # Record the module attributes so we can remove them after the test to
@@ -45,14 +47,16 @@ class TestTurbiniaConfig(unittest.TestCase):
     config.CONFIG = None
     config.CONFIGPATH = [os.path.dirname(self.config_file)]
     config.CONFIGFILES = [os.path.basename(self.config_file)]
-    config.CONFIGVARS = []
+    config.REQUIRED_VARS = []
+    config.OPTIONAL_VARS = []
 
   @classmethod
   def tearDownClass(cls):
     """Called after tests in the class have been run."""
     config.CONFIGPATH = cls.CONFIGPATH_SAVE
     config.CONFIGFILES = cls.CONFIGFILES_SAVE
-    config.CONFIGVARS = cls.CONFIGVARS_SAVE
+    config.REQUIRED_VARS = cls.REQUIRED_VARS_SAVE
+    config.OPTIONAL_VARS = cls.OPTIONAL_VARS_SAVE
 
   def tearDown(self):
     os.remove(self.config_file)
@@ -72,27 +76,36 @@ class TestTurbiniaConfig(unittest.TestCase):
 
   def testBasicConfig(self):
     """Test out a basic config."""
-    self.WriteConfig('PROJECT = "foo"\nZONE = "bar"\n')
-    config.CONFIGVARS = ['PROJECT', 'ZONE']
-    self.assertFalse(hasattr(config, 'PROJECT'))
+    self.WriteConfig('TURBINIA_PROJECT = "foo"\nTURBINIA_ZONE = "bar"\n')
+    config.REQUIRED_VARS = ['TURBINIA_PROJECT', 'TURBINIA_ZONE']
+    self.assertFalse(hasattr(config, 'TURBINIA_PROJECT'))
     config.LoadConfig()
-    self.assertTrue(hasattr(config, 'PROJECT'))
-    self.assertTrue(hasattr(config, 'ZONE'))
-    self.assertEqual(config.PROJECT, 'foo')
-    self.assertEqual(config.ZONE, 'bar')
+    self.assertTrue(hasattr(config, 'TURBINIA_PROJECT'))
+    self.assertTrue(hasattr(config, 'TURBINIA_ZONE'))
+    self.assertEqual(config.TURBINIA_PROJECT, 'foo')
+    self.assertEqual(config.TURBINIA_ZONE, 'bar')
 
-  def testMissingKeyConfig(self):
-    """Test that config errors out when not all variables exist."""
+  def testMissingRequiredKeyConfig(self):
+    """Test that config errors out when not all required variables exist."""
     self.WriteConfig('EXISTS = "bar"\n')
-    config.CONFIGVARS = ['DOESNOTEXIST', 'EXISTS']
+    config.REQUIRED_VARS = ['DOESNOTEXIST', 'EXISTS']
     self.assertRaises(config.TurbiniaConfigException, config.LoadConfig)
 
-  def testUnsetKeyConfig(self):
-    """Test that config errors out when not all variables are set."""
+  def testUnsetRequiredKeyConfig(self):
+    """Test that config errors out when not all required variables are set."""
     self.WriteConfig('UNSETKEY = None\nSETKEY = "bar"\n')
-    config.CONFIGVARS = ['UNSETKEY', 'SETKEY']
+    config.REQUIRED_VARS = ['UNSETKEY', 'SETKEY']
     self.assertRaises(config.TurbiniaConfigException, config.LoadConfig)
 
+  def testUnsetOptionalKeyConfig(self):
+    """Test that config errors out when not all optional variables are set."""
+    self.WriteConfig('UNSETKEY = None\nSETKEY = "bar"\n')
+    config.REQUIRED_VARS = ['SETKEY']
+    config.OPTIONAL_VARS = ['UNSETKEY']
+    config.LoadConfig()
+    self.assertEqual(config.UNSETKEY, None)
+    self.assertEqual(config.SETKEY, 'bar')
+
   def testMissingConfig(self):
     """Test non-existent config."""
     config.CONFIGPATH = ['DOESNOTEXIST']
@@ -103,11 +116,11 @@ class TestTurbiniaConfig(unittest.TestCase):
     os.environ[config.ENVCONFIGVAR] = config.CONFIGPATH[0]
     config.CONFIGPATH = ['DOESNOTEXIST']
 
-    self.WriteConfig('PROJECT = "foo"\nZONE = "bar"\n')
-    config.CONFIGVARS = ['PROJECT', 'ZONE']
+    self.WriteConfig('TURBINIA_PROJECT = "foo"\nTURBINIA_ZONE = "bar"\n')
+    config.REQUIRED_VARS = ['TURBINIA_PROJECT', 'TURBINIA_ZONE']
     config.LoadConfig()
-    self.assertTrue(hasattr(config, 'PROJECT'))
-    self.assertTrue(hasattr(config, 'ZONE'))
-    self.assertEqual(config.PROJECT, 'foo')
-    self.assertEqual(config.ZONE, 'bar')
+    self.assertTrue(hasattr(config, 'TURBINIA_PROJECT'))
+    self.assertTrue(hasattr(config, 'TURBINIA_ZONE'))
+    self.assertEqual(config.TURBINIA_PROJECT, 'foo')
+    self.assertEqual(config.TURBINIA_ZONE, 'bar')
     os.environ.pop(config.ENVCONFIGVAR)