This is an automated email from the ASF dual-hosted git repository.

kturner pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/fluo-muchos.git


The following commit(s) were added to refs/heads/master by this push:
     new 0b23a93  Refactor config (#288)
0b23a93 is described below

commit 0b23a9399f372dd3f17f8cab1f39ed5d57a8eb1e
Author: Min Pae <[email protected]>
AuthorDate: Wed Nov 6 06:05:37 2019 -0800

    Refactor config (#288)
    
    The following commits were squashed :
    
    * refactor config.py to separate target specific code
    
    - split config.py to DeployConfig base class and Ec2DeployConfig,
      ExistingDeployConfig, and AzureDeployConfig subclasses
    - add @abstractmethod annotations for common interfaces to DeployConfig
      base class
    - move target specific code to respective subclasses
    
    * move config tests to target specific subdirectory
    
    * add config decorators and move ec2 specific config
    
    - add python decorators to annotate config items to allow generic
      handling of configs
    - moved some ec2 specific configs to Ec2DeployConfig
    
    * add config value validation decorator
    
    * rename host_vars and play_vars to ansible_*
    
    * use config/base.ansible_*_vars to render vars
    
    * add hadoop 3.2.1 checksum and remove azure_vars
    
    - integrate default value handling for host_var and play_var in to base
      config class accessor for respective var dictionaries
    - remove use of 'azure_vars' and consolidate as host_vars
    
    * remove fstype and force_format from base, existing, and azure config
    
    * remove checks for fstype and force_format from existing and azure tests
    
    * addressing review comments
    
    - made config.shutdown_delay_minutes not abstract so existing and azure
      cluster types wouldn't have to implement it
    - changed _ansible_vars dictionary values a list of structs rather than
      tuples so that each field has an understandable name
    - removed instance_tags from base config
---
 lib/muchos/config/__init__.py            |  37 +++
 lib/muchos/config/azure.py               | 107 ++++++++
 lib/muchos/{config.py => config/base.py} | 416 ++++++++++++-------------------
 lib/muchos/config/decorators.py          | 111 +++++++++
 lib/muchos/config/ec2.py                 | 184 ++++++++++++++
 lib/muchos/config/existing.py            |  50 ++++
 lib/muchos/config/validators.py          |  58 +++++
 lib/muchos/existing.py                   |  42 +---
 lib/tests/azure/__init__.py              |  17 ++
 lib/tests/azure/test_config.py           |  84 +++++++
 lib/tests/ec2/__init__.py                |  17 ++
 lib/tests/{ => ec2}/test_config.py       |  91 +------
 lib/tests/existing/__init__.py           |  17 ++
 lib/tests/existing/test_config.py        |  31 +++
 14 files changed, 879 insertions(+), 383 deletions(-)

diff --git a/lib/muchos/config/__init__.py b/lib/muchos/config/__init__.py
new file mode 100644
index 0000000..13294f1
--- /dev/null
+++ b/lib/muchos/config/__init__.py
@@ -0,0 +1,37 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+from muchos.config.base import BaseConfig, SERVICES, OPTIONAL_SERVICES
+from muchos.config.existing import ExistingDeployConfig
+from muchos.config.ec2 import Ec2DeployConfig
+from muchos.config.azure import AzureDeployConfig
+
+from configparser import ConfigParser
+
+def DeployConfig(deploy_path, config_path, hosts_path, checksums_path, 
templates_path, cluster_name):
+    c = ConfigParser()
+    c.read(config_path)
+    cluster_type = c.get('general', 'cluster_type')
+
+    if cluster_type == 'existing':
+        return ExistingDeployConfig(deploy_path, config_path, hosts_path, 
checksums_path, templates_path, cluster_name)
+
+    if cluster_type == 'ec2':
+        return Ec2DeployConfig(deploy_path, config_path, hosts_path, 
checksums_path, templates_path, cluster_name)
+
+    if cluster_type == 'azure':
+        return AzureDeployConfig(deploy_path, config_path, hosts_path, 
checksums_path, templates_path, cluster_name)
\ No newline at end of file
diff --git a/lib/muchos/config/azure.py b/lib/muchos/config/azure.py
new file mode 100644
index 0000000..fe93b55
--- /dev/null
+++ b/lib/muchos/config/azure.py
@@ -0,0 +1,107 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+from muchos.config import BaseConfig
+from muchos.config.decorators import *
+from muchos.config.validators import *
+from sys import exit
+from muchos.util import get_ephemeral_devices, get_arch
+import os
+import json
+import glob
+
+
+class AzureDeployConfig(BaseConfig):
+    def __init__(self, deploy_path, config_path, hosts_path, checksums_path, 
templates_path, cluster_name):
+        super(AzureDeployConfig, self).__init__(deploy_path, config_path, 
hosts_path, checksums_path, templates_path, cluster_name)
+
+    def verify_config(self, action):
+        self._verify_config(action)
+
+        proxy = self.get('general', 'proxy_hostname')
+        cluster_type = self.get('general', 'cluster_type')
+        if cluster_type not in ['azure']:
+            if not proxy:
+                exit("ERROR - proxy.hostname must be set in muchos.props")
+
+            if proxy not in self.node_d:
+                exit("ERROR - The proxy (set by property proxy_hostname={0}) 
cannot be found in 'nodes' section of "
+                     "muchos.props".format(proxy))
+
+    def verify_launch(self):
+        pass
+
+    def node_type_map(self):
+        return {}
+
+    def mount_root(self):
+        return self.get('azure', 'mount_root')
+
+    def data_dirs_common(self, nodeType):
+        data_dirs = []
+
+        num_disks = int(self.get("azure", "numdisks"))
+        range_var = num_disks + 1
+        for diskNum in range(1, range_var):
+            data_dirs.append(self.get("azure", "mount_root") +
+                                str(diskNum))
+
+        return data_dirs
+
+    def metrics_drive_ids(self):
+        drive_ids = []
+        range_var = int(self.get("azure", "numdisks")) + 1
+        for i in range(1, range_var):
+            drive_ids.append(self.get("azure", "metrics_drive_root") +
+                                str(i))
+        return drive_ids
+
+    @ansible_host_var
+    @default(None)
+    def azure_fileshare_mount(self):
+        return self.get('azure', 'azure_fileshare_mount')
+
+    @ansible_host_var
+    @default(None)
+    def azure_fileshare(self):
+        return self.get('azure', 'azure_fileshare')
+    
+    @ansible_host_var
+    @default(None)
+    def azure_fileshare_username(self):
+        return self.get('azure', 'azure_fileshare_username')
+
+    @ansible_host_var
+    @default(None)
+    def azure_fileshare_password(self):
+        return self.get('azure', 'azure_fileshare_password')
+
+    @ansible_host_var(name='az_omsIntegrationNeeded')
+    @default(False)
+    @is_valid(is_in([True, False]))
+    def omsIntegrationNeeded(self):
+        return self.getboolean('azure', 'az_omsIntegrationNeeded')
+
+    @ansible_host_var(name='az_logs_id')
+    @default(None)
+    def logs_id(self):
+        return self.get('azure', 'az_logs_id')
+
+    @ansible_host_var(name='az_logs_key')
+    @default(None)
+    def logs_key(self):
+        return self.get('azure', 'az_logs_key')
\ No newline at end of file
diff --git a/lib/muchos/config.py b/lib/muchos/config/base.py
similarity index 61%
rename from lib/muchos/config.py
rename to lib/muchos/config/base.py
index c62c282..9c6696a 100644
--- a/lib/muchos/config.py
+++ b/lib/muchos/config/base.py
@@ -15,9 +15,14 @@
 # limitations under the License.
 #
 
+from abc import ABCMeta, abstractmethod
+
+from collections import ChainMap
 from configparser import ConfigParser
 from sys import exit
-from .util import get_ephemeral_devices, get_arch
+from muchos.config.decorators import *
+from muchos.config.validators import *
+from muchos.util import get_ephemeral_devices, get_arch
 import os
 import json
 import glob
@@ -27,39 +32,137 @@ SERVICES = ['zookeeper', 'namenode', 'resourcemanager', 
'accumulomaster', 'mesos
 
 OPTIONAL_SERVICES = ['fluo', 'fluo_yarn', 'metrics', 'mesosmaster', 'spark', 
'client', 'swarmmanager', 'journalnode', 'zkfc']
 
+_HOST_VAR_DEFAULTS = {
+  'accumulo_home': '"{{ install_dir }}/accumulo-{{ accumulo_version }}"',
+  'accumulo_instance': None,
+  'accumulo_major_version': '"{{ accumulo_version.split(\'.\')[0] }}"',
+  'accumulo_password': None,
+  'accumulo_tarball': 'accumulo-{{ accumulo_version }}-bin.tar.gz',
+  'accumulo_version': None,
+  'cluster_type': None,
+  'cluster_group': None,
+  'cluster_user': None,
+  'default_data_dirs': None,
+  'download_software': None,
+  'fluo_home': '"{{ install_dir }}/fluo-{{ fluo_version }}"',
+  'fluo_tarball': 'fluo-{{ fluo_version }}-bin.tar.gz',
+  'fluo_version': None,
+  'fluo_yarn_home': '"{{ install_dir }}/fluo-yarn-{{ fluo_yarn_version }}"',
+  'fluo_yarn_tarball': 'fluo-yarn-{{ fluo_yarn_version }}-bin.tar.gz',
+  'fluo_yarn_version': None,
+  'hadoop_home': '"{{ install_dir }}/hadoop-{{ hadoop_version }}"',
+  'hadoop_tarball': 'hadoop-{{ hadoop_version }}.tar.gz',
+  'hadoop_version': None,
+  'hadoop_major_version': '"{{ hadoop_version.split(\'.\')[0] }}"',
+  'hdfs_root': "{% if hdfs_ha %}hdfs://{{ nameservice_id }}{% else %}hdfs://{{ 
groups[\'namenode\'][0] }}:8020{% endif %}",
+  'hdfs_ha': None,
+  'nameservice_id': None,
+  'install_dir': None,
+  'install_hub': None,
+  'java_home': '"/usr/lib/jvm/java"',
+  'java_package': '"java-1.8.0-openjdk-devel"',
+  'journal_quorum': "{% for host in groups['journalnode'] %}{{ host }}:8485{% 
if not loop.last %};{% endif %}{% endfor %}",
+  'maven_home': '"{{ install_dir }}/apache-maven-{{ maven_version }}"',
+  'maven_tarball': 'apache-maven-{{ maven_version }}-bin.tar.gz',
+  'maven_version': '3.6.1',
+  'spark_home': '"{{ install_dir }}/spark-{{ spark_version 
}}-bin-without-hadoop"',
+  'spark_tarball': 'spark-{{ spark_version }}-bin-without-hadoop.tgz',
+  'spark_version': None,
+  'tarballs_dir': '"{{ user_home }}/tarballs"',
+  'user_home': None,
+  'worker_data_dirs': None,
+  'zookeeper_connect': "{% for host in groups['zookeepers'] %}{{ host 
}}:2181{% if not loop.last %},{% endif %}{% endfor %}",
+  'zookeeper_client_port': '"2181"',
+  'zookeeper_home': '"{{ install_dir }}/zookeeper-{{ zookeeper_version }}"',
+  'zookeeper_tarball': 'zookeeper-{{ zookeeper_version }}.tar.gz',
+  'zookeeper_version': None
+}
+
+_PLAY_VAR_DEFAULTS = {
+  'accumulo_dcache_size': None,
+  'accumulo_icache_size': None,
+  'accumulo_imap_size': None,
+  'accumulo_sha256': None,
+  'accumulo_tserv_mem': None,
+  'fluo_sha256': None,
+  'fluo_worker_instances_multiplier': None,
+  'fluo_worker_mem_mb': None,
+  'fluo_worker_threads': None,
+  'fluo_yarn_sha256': None,
+  'hadoop_sha256': None,
+  'hub_version': '2.2.3',
+  'hub_home': '"{{ install_dir }}/hub-linux-amd64-{{ hub_version }}"',
+  'hub_tarball': 'hub-linux-amd64-{{ hub_version }}.tgz',
+  'hub_sha256': 
'54c35a459a4241b7ae4c28bcfea0ceef849dd2f8a9dd2b82ba2ba964a743e6bc',
+  'maven_sha256': 
'2528c35a99c30f8940cc599ba15d34359d58bec57af58c1075519b8cd33b69e7',
+  'metrics_drive_ids': None,
+  'mount_root': None,
+  'node_type_map': None,
+  'spark_sha256': None,
+  'shutdown_delay_minutes': None,
+  'twill_reserve_mem_mb': None,
+  'yarn_nm_mem_mb': None,
+  'zookeeper_sha256': None
+}
+
+_EXTRA_VAR_DEFAULTS = {}
 
-class DeployConfig(ConfigParser):
 
+class BaseConfig(ConfigParser, metaclass=ABCMeta):
     def __init__(self, deploy_path, config_path, hosts_path, checksums_path, 
templates_path, cluster_name):
-        ConfigParser.__init__(self)
+        super(BaseConfig, self).__init__()
         self.optionxform = str
         self.deploy_path = deploy_path
         self.read(config_path)
         self.hosts_path = hosts_path
         self.cluster_name = cluster_name
-        self.sg_name = cluster_name + '-group'
-        self.ephemeral_root = 'ephemeral'
         self.cluster_type = self.get('general', 'cluster_type')
-        self.metrics_drive_root = 'media-' + self.ephemeral_root
         self.node_d = None
         self.hosts = None
         self.checksums_path = checksums_path
         self.checksums_d = None
-        self.init_nodes()
-        self.cluster_template_d = None
-        self.init_template(templates_path)
-
+        self._init_nodes()
+
+    def ansible_host_vars(self):
+        return dict(
+            ChainMap(self._ansible_vars_from_decorators('host'),
+                     getattr(self, 'HOST_VAR_DEFAULTS', {}),
+                     _HOST_VAR_DEFAULTS))
+
+    def ansible_play_vars(self):
+        software_checksums = {
+            '{}_sha256'.format(k): self.checksum(k) for
+            k in ['accumulo', 'fluo', 'fluo_yarn', 'hadoop', 'spark', 
'zookeeper']
+        }
+        return dict(
+            ChainMap(self._ansible_vars_from_decorators('play'),
+                     software_checksums,
+                     getattr(self, 'PLAY_VAR_DEFAULTS', {}),
+                     _PLAY_VAR_DEFAULTS))
+
+    def ansible_extra_vars(self):
+        return dict(
+            ChainMap(self._ansible_vars_from_decorators('extra'),
+                     getattr(self, 'EXTRA_VAR_DEFAULTS', {}),
+                     _EXTRA_VAR_DEFAULTS))
+
+    def _ansible_vars_from_decorators(self, var_type):
+        # only render play_vars for base and cluster specific config
+        f = ["{}deployconfig".format(self.get_cluster_type()), "baseconfig"]
+        return {
+            v.var_name: getattr(self, v.property_name)() for v in
+                        # filter out any classes that are not baseconfig or
+                        # the cluster specific config
+                        filter(lambda t: t.class_name.lower() in f,
+                        # get all ansible vars of var_type
+                        get_ansible_vars(var_type))}
+
+    @abstractmethod
     def verify_config(self, action):
-        proxy = self.get('general', 'proxy_hostname')
-        cluster_type = self.get('general', 'cluster_type')
-        if cluster_type not in ['azure']:
-            if not proxy:
-                exit("ERROR - proxy.hostname must be set in muchos.props")
-
-            if proxy not in self.node_d:
-                exit("ERROR - The proxy (set by property proxy_hostname={0}) 
cannot be found in 'nodes' section of "
-                     "muchos.props".format(proxy))
+        raise NotImplementedError()
 
+    # helper for verify_config to call for common checks
+    def _verify_config(self, action):
         if action in ['launch', 'setup']:
             for service in SERVICES:
                 if service not in OPTIONAL_SERVICES:
@@ -71,11 +174,11 @@ class DeployConfig(ConfigParser):
             if self.java_product_version() >= 11 and 
StrictVersion(self.version('accumulo')) <= StrictVersion("1.9.3"):
                 exit("ERROR - Java 11 is not supported with Accumulo version 
'{0}'".format(self.version('accumulo')))
 
+    @abstractmethod
     def verify_launch(self):
-        self.verify_instance_type(self.get('ec2', 'default_instance_type'))
-        self.verify_instance_type(self.get('ec2', 'worker_instance_type'))
+        raise NotImplementedError()
 
-    def init_nodes(self):
+    def _init_nodes(self):
         self.node_d = {}
         for (hostname, value) in self.items('nodes'):
             if hostname in self.node_d:
@@ -88,27 +191,12 @@ class DeployConfig(ConfigParser):
                     exit('Unknown service "%s" declared for node %s' % 
(service, hostname))
             self.node_d[hostname] = service_list
 
-    def default_ephemeral_devices(self):
-        return get_ephemeral_devices(self.get('ec2', 'default_instance_type'))
-
-    def worker_ephemeral_devices(self):
-        return get_ephemeral_devices(self.get('ec2', 'worker_instance_type'))
-
-    def max_ephemeral(self):
-        return max((len(self.default_ephemeral_devices()), 
len(self.worker_ephemeral_devices())))
-
+    @abstractmethod
+    @ansible_play_var
     def node_type_map(self):
-        if self.cluster_template_d:
-            return self.cluster_template_d['devices']
-
-        node_types = {}
-        if self.get_cluster_type() == 'ec2':
-            node_list = [('default', self.default_ephemeral_devices()), 
('worker', self.worker_ephemeral_devices())]
-
-            for (ntype, devices) in node_list:
-                node_types[ntype] = {'mounts': self.mounts(len(devices)), 
'devices': devices}
-        return node_types
+        raise NotImplementedError()
 
+    @is_valid(is_in(['default', 'worker']))
     def node_type(self, hostname):
         if 'worker' in self.node_d[hostname]:
             return 'worker'
@@ -123,77 +211,36 @@ class DeployConfig(ConfigParser):
             mounts.append(self.mount_root() + str(i))
         return mounts
 
+    @abstractmethod
+    @ansible_play_var
     def mount_root(self):
-        if self.get_cluster_type() == 'ec2':
-            return '/media/' + self.ephemeral_root
-        elif self.get_cluster_type() == 'existing':
-            return self.get('existing', 'mount_root')
-        elif self.get_cluster_type() == 'azure':
-            return self.get('azure', 'mount_root')
-
-    def fstype(self):
-        retval = None
-        if self.get_cluster_type() == 'ec2':
-            retval = self.get('ec2', 'fstype')
-            if not retval:
-                return 'ext3'
-        return retval
-
-    def force_format(self):
-        retval = 'no'
-        if self.get_cluster_type() == 'ec2':
-            retval = self.get('ec2', 'force_format')
-            if not retval:
-                return 'no'
-        return retval
+        raise NotImplementedError()
 
+    @abstractmethod
     def data_dirs_common(self, nodeType):
-        data_dirs = []
-
-        if self.get_cluster_type() == 'ec2':
-            data_dirs = self.node_type_map()[nodeType]['mounts']
-        elif self.get_cluster_type() == 'existing':
-            data_dirs = self.get('existing', 'data_dirs').split(",")
-        elif self.get_cluster_type() == 'azure':
-            num_disks = int(self.get("azure", "numdisks"))
-            range_var = num_disks + 1
-            for diskNum in range(1, range_var):
-                data_dirs.append(self.get("azure", "mount_root") +
-                                 str(diskNum))
-
-        return data_dirs
+        raise NotImplementedError()
 
+    @ansible_host_var
     def worker_data_dirs(self):
         return self.data_dirs_common("worker")
 
+    @ansible_host_var
     def default_data_dirs(self):
         return self.data_dirs_common("default")
 
+    @abstractmethod
+    @ansible_play_var
     def metrics_drive_ids(self):
-        if self.get_cluster_type() == 'ec2':
-            drive_ids = []
-            for i in range(0, self.max_ephemeral()):
-                drive_ids.append(self.metrics_drive_root + str(i))
-            return drive_ids
-        elif self.get_cluster_type() == 'existing':
-            return self.get("existing", "metrics_drive_ids").split(",")
-        elif self.get_cluster_type() == 'azure':
-            drive_ids = []
-            range_var = int(self.get("azure", "numdisks")) + 1
-            for i in range(1, range_var):
-                drive_ids.append(self.get("azure", "metrics_drive_root") +
-                                 str(i))
-            return drive_ids
+        raise NotImplementedError()
 
+    @ansible_play_var
     def shutdown_delay_minutes(self):
-        retval = '0'
-        if self.get_cluster_type() == 'ec2':
-            retval = self.get("ec2", "shutdown_delay_minutes")
-        return retval
+        return '0'
 
     def version(self, software_id):
         return self.get('general', software_id + '_version')
 
+    @ansible_host_var
     def java_product_version(self):
         java_version_map = {
                 "java-1.8.0-openjdk": 8,
@@ -238,22 +285,6 @@ class DeployConfig(ConfigParser):
             exit('ERROR - Failed to find checksums for %s %s in %s' % 
(software, version, self.checksums_path))
         return self.checksums_d[key]
 
-    def verify_instance_type(self, instance_type):
-        if not self.cluster_template_d:
-            if get_arch(instance_type) == 'pvm':
-                exit("ERROR - Configuration contains instance type '{0}' that 
uses pvm architecture."
-                     "Only hvm architecture is 
supported!".format(instance_type))
-
-    def instance_tags(self):
-        retd = {}
-        if self.has_option('ec2', 'instance_tags'):
-            value = self.get('ec2', 'instance_tags')
-            if value:
-                for kv in value.split(','):
-                    (key, val) = kv.split(':')
-                    retd[key] = val
-        return retd
-
     def nodes(self):
         return self.node_d
 
@@ -266,6 +297,7 @@ class DeployConfig(ConfigParser):
                 return True
         return False
 
+    # test method, might want to make private or just move to test module
     def get_host_services(self):
         retval = []
         for (hostname, service_list) in list(self.node_d.items()):
@@ -273,6 +305,7 @@ class DeployConfig(ConfigParser):
         retval.sort()
         return retval
 
+    # test method, might want to make private or just move to test module
     def get_service_private_ips(self, service):
         retval = []
         for (hostname, service_list) in list(self.node_d.items()):
@@ -289,6 +322,7 @@ class DeployConfig(ConfigParser):
         retval.sort()
         return retval
 
+    # test method, might want to make private or just move to test module
     def get_non_proxy(self):
         retval = []
         proxy_ip = self.get_private_ip(self.get('general', 'proxy_hostname'))
@@ -383,153 +417,13 @@ class DeployConfig(ConfigParser):
                     print(self.get(section, key))
                     return
         exit("Property '{0}' was not found".format(key))
-
-    def init_template(self, templates_path):
-        if self.has_option('ec2', 'cluster_template'):
-            template_id = self.get('ec2', 'cluster_template')
-            template_path = os.path.join(templates_path, template_id)
-            if os.path.exists(template_path):
-                self.cluster_template_d = {'id': template_id}
-                self.load_template_ec2_requests(template_path)
-                self.load_template_device_map(template_path)
-            self.validate_template()
-
-    def load_template_ec2_requests(self, template_dir):
-        for json_path in glob.glob(os.path.join(template_dir, '*.json')):
-            service = os.path.basename(json_path).rsplit('.', 1)[0]
-            if service not in SERVICES:
-                exit("ERROR - Template '{0}' has unrecognized option '{1}'. 
Must be one of {2}".format(
-                    self.cluster_template_d['id'], service, str(SERVICES)))
-            with open(json_path, 'r') as json_file:
-                # load as string, so we can use string.Template to inject 
config values
-                self.cluster_template_d[service] = json_file.read()
-
-    def load_template_device_map(self, template_dir):
-        device_map_path = os.path.join(template_dir, 'devices')
-        if not os.path.isfile(device_map_path):
-            exit("ERROR - template '{0}' is missing 'devices' 
config".format(self.cluster_template_d['id']))
-        with open(device_map_path, 'r') as json_file:
-            self.cluster_template_d['devices'] = json.load(json_file)
-
-    def validate_template(self):
-        if not self.cluster_template_d:
-            exit("ERROR - Template '{0}' is not 
defined!".format(self.get('ec2', 'cluster_template')))
-
-        if 'worker' not in self.cluster_template_d:
-            exit("ERROR - '{0}' template config is invalid. No 'worker' launch 
request is defined".format(
-                self.cluster_template_d['id']))
-
-        if 'worker' not in self.cluster_template_d['devices']:
-            exit("ERROR - '{0}' template is invalid. The devices file must 
have a 'worker' device map".format(
-                self.cluster_template_d['id']))
-
-        if 'default' not in self.cluster_template_d['devices']:
-            exit("ERROR - '{0}' template is invalid. The devices file must 
have a 'default' device map".format(
-                self.cluster_template_d['id']))
-
-        # Validate the selected launch template for each host
-
-        worker_count = 0
-        for hostname in self.node_d:
-            # first service listed denotes the selected template
-            selected_ec2_request = self.node_d[hostname][0]
-            if 'worker' == selected_ec2_request:
-                worker_count = worker_count + 1
-            else:
-                if 'worker' in self.node_d[hostname]:
-                    exit("ERROR - '{0}' node config is invalid. The 'worker' 
service should be listed first".format(
-                        hostname))
-            if selected_ec2_request not in self.cluster_template_d:
-                if len(self.node_d[hostname]) > 1:
-                    print('Hint: In template mode, the first service listed 
for a host denotes its EC2 template')
-                exit("ERROR - '{0}' node config is invalid. No EC2 template 
defined for the '{1}' service".format(
-                    hostname, selected_ec2_request))
-
-        if worker_count == 0:
-            exit("ERROR - No worker instances are defined for template 
'{0}'".format(self.cluster_template_d['id']))
-
-
-HOST_VAR_DEFAULTS = {
-  'accumulo_home': '"{{ install_dir }}/accumulo-{{ accumulo_version }}"',
-  'accumulo_instance': None,
-  'accumulo_major_version': '"{{ accumulo_version.split(\'.\')[0] }}"',
-  'accumulo_password': None,
-  'accumulo_tarball': 'accumulo-{{ accumulo_version }}-bin.tar.gz',
-  'accumulo_version': None,
-  'cluster_type': None,
-  'cluster_group': None,
-  'cluster_user': None,
-  'default_data_dirs': None,
-  'download_software': None,
-  'fluo_home': '"{{ install_dir }}/fluo-{{ fluo_version }}"',
-  'fluo_tarball': 'fluo-{{ fluo_version }}-bin.tar.gz',
-  'fluo_version': None,
-  'fluo_yarn_home': '"{{ install_dir }}/fluo-yarn-{{ fluo_yarn_version }}"',
-  'fluo_yarn_tarball': 'fluo-yarn-{{ fluo_yarn_version }}-bin.tar.gz',
-  'fluo_yarn_version': None,
-  'hadoop_home': '"{{ install_dir }}/hadoop-{{ hadoop_version }}"',
-  'hadoop_tarball': 'hadoop-{{ hadoop_version }}.tar.gz',
-  'hadoop_version': None,
-  'hadoop_major_version': '"{{ hadoop_version.split(\'.\')[0] }}"',
-  'hdfs_root': "{% if hdfs_ha %}hdfs://{{ nameservice_id }}{% else %}hdfs://{{ 
groups[\'namenode\'][0] }}:8020{% endif %}",
-  'hdfs_ha': None,
-  'nameservice_id': None,
-  'install_dir': None,
-  'install_hub': None,
-  'java_home': '"/usr/lib/jvm/java"',
-  'java_package': '"java-1.8.0-openjdk-devel"',
-  'journal_quorum': "{% for host in groups['journalnode'] %}{{ host }}:8485{% 
if not loop.last %};{% endif %}{% endfor %}",
-  'maven_home': '"{{ install_dir }}/apache-maven-{{ maven_version }}"',
-  'maven_tarball': 'apache-maven-{{ maven_version }}-bin.tar.gz',
-  'maven_version': '3.6.1',
-  'spark_home': '"{{ install_dir }}/spark-{{ spark_version 
}}-bin-without-hadoop"',
-  'spark_tarball': 'spark-{{ spark_version }}-bin-without-hadoop.tgz',
-  'spark_version': None,
-  'tarballs_dir': '"{{ user_home }}/tarballs"',
-  'user_home': None,
-  'worker_data_dirs': None,
-  'zookeeper_connect': "{% for host in groups['zookeepers'] %}{{ host 
}}:2181{% if not loop.last %},{% endif %}{% endfor %}",
-  'zookeeper_client_port': '"2181"',
-  'zookeeper_home': '"{{ install_dir }}/zookeeper-{{ zookeeper_version }}"',
-  'zookeeper_tarball': 'zookeeper-{{ zookeeper_version }}.tar.gz',
-  'zookeeper_version': None
-}
-
-PLAY_VAR_DEFAULTS = {
-  'accumulo_dcache_size': None,
-  'accumulo_icache_size': None,
-  'accumulo_imap_size': None,
-  'accumulo_sha256': None,
-  'accumulo_tserv_mem': None,
-  'fluo_sha256': None,
-  'fluo_worker_instances_multiplier': None,
-  'fluo_worker_mem_mb': None,
-  'fluo_worker_threads': None,
-  'fluo_yarn_sha256': None,
-  'force_format': None,
-  'fstype': None,
-  'hadoop_sha256': None,
-  'hub_version': '2.2.3',
-  'hub_home': '"{{ install_dir }}/hub-linux-amd64-{{ hub_version }}"',
-  'hub_tarball': 'hub-linux-amd64-{{ hub_version }}.tgz',
-  'hub_sha256': 
'54c35a459a4241b7ae4c28bcfea0ceef849dd2f8a9dd2b82ba2ba964a743e6bc',
-  'maven_sha256': 
'2528c35a99c30f8940cc599ba15d34359d58bec57af58c1075519b8cd33b69e7',
-  'metrics_drive_ids': None,
-  'mount_root': None,
-  'node_type_map': None,
-  'spark_sha256': None,
-  'shutdown_delay_minutes': None,
-  'twill_reserve_mem_mb': None,
-  'yarn_nm_mem_mb': None,
-  'zookeeper_sha256': None
-}
-
-AZURE_VAR_DEFAULTS = {
-  'azure_fileshare_mount': None,
-  'azure_fileshare': None,
-  'azure_fileshare_username': None,
-  'azure_fileshare_password': None,
-  'az_omsIntegrationNeeded': None,
-  'az_logs_id': None,
-  'az_logs_key': None
-}
+    
+    def resolve_value(self, config_name, default=None):
+        # listed low to high priority
+        section_priority = ['general', 'ansible-vars', self.get('performance', 
'profile')]
+        all_values = [self.get(section, config_name, fallback=None) for 
section in section_priority]
+        try:
+            *_, val = filter(None, all_values)
+        except ValueError:
+            return default
+        return val
\ No newline at end of file
diff --git a/lib/muchos/config/decorators.py b/lib/muchos/config/decorators.py
new file mode 100644
index 0000000..57151b8
--- /dev/null
+++ b/lib/muchos/config/decorators.py
@@ -0,0 +1,111 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+from collections.abc import Iterable
+from functools import wraps
+
+
+# struct to hold information about ansible vars defined via decorators.
+# var_name indicates the desired variable name
+# class_name indicates the class name where the variable was defined
+# property_name indicates the class property/function where the variable was
+#               defined
+class _ansible_var(object):
+    def __init__(self, var_name, class_name, property_name):
+        self.var_name = var_name
+        self.class_name = class_name
+        self.property_name = property_name
+
+# each entry of _ansible_vars will contain a list of _ansible_var instances
+_ansible_vars = dict(
+    host=[],
+    play=[],
+    extra=[]
+)
+
+def get_ansible_vars(var_type):
+    return _ansible_vars.get(var_type)
+
+# ansible hosts inventory variables
+def ansible_host_var(name=None):
+    return ansible_var_decorator('host', name)
+
+# ansible group/all variables
+def ansible_play_var(name=None):
+    return ansible_var_decorator('play', name)
+
+# ansible extra variables
+def ansible_extra_var(name=None):
+    return ansible_var_decorator('extra', name)
+
+def ansible_var_decorator(var_type, name):
+    def _decorator(func):
+        ansible_var = _ansible_var(
+            var_name=name if isinstance(name, str) else func.__name__,
+            class_name=func.__qualname__.split('.')[0],
+            property_name=func.__name__)
+        _ansible_vars[var_type].append(ansible_var)
+        return func
+
+    if callable(name):
+        return _decorator(name)
+    return _decorator
+
+def default(val):
+    def _default(func):
+        @wraps(func)
+        def wrapper(*args, **kwargs):
+            try:
+                res = func(*args, **kwargs)
+            except:
+                return val
+            else:
+                if res in [None, 0, ''] or len(res) == 0:
+                    return val
+                return res
+        return wrapper
+    return _default
+
+def required(func):
+    @wraps(func)
+    def wrapper(*args, **kwargs):
+        res = func(*args, **kwargs)
+        if res in [None, 0, ''] or len(res) == 0:
+            raise ConfigMissingError(func.__name__)
+        return res
+    return wrapper
+
+def is_valid(validators):
+    if not isinstance(validators, Iterable):
+        validators = [validators]
+    def _validate(func):
+        @wraps(func)
+        def wrapper(*args, **kwargs):
+            res = func(*args, **kwargs)
+            failed_checks = list(filter(lambda f: f(res) is not True, 
validators))
+            if len(failed_checks) > 0:
+                raise Exception("{}={} checked validation {}".format(
+                    func.__name__, res,
+                    [str(v) for v in failed_checks]))
+            return res
+        return wrapper
+    return _validate
+
+class ConfigMissingError(Exception):
+    def __init__(self, name):
+        super(ConfigMissingError, self).__init__("{} is missing from the 
configuration".format(name))
+
diff --git a/lib/muchos/config/ec2.py b/lib/muchos/config/ec2.py
new file mode 100644
index 0000000..7eaa4ab
--- /dev/null
+++ b/lib/muchos/config/ec2.py
@@ -0,0 +1,184 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+from muchos.config import SERVICES, OPTIONAL_SERVICES
+from muchos.config.base import BaseConfig
+from sys import exit
+from muchos.config.decorators import *
+from muchos.config.validators import *
+from muchos.util import get_ephemeral_devices, get_arch
+import os
+import json
+import glob
+
+
+class Ec2DeployConfig(BaseConfig):
+
+    def __init__(self, deploy_path, config_path, hosts_path, checksums_path, 
templates_path, cluster_name):
+        super(Ec2DeployConfig, self).__init__(deploy_path, config_path, 
hosts_path, checksums_path, templates_path, cluster_name)
+        self.sg_name = cluster_name + '-group'
+        self.ephemeral_root = 'ephemeral'
+        self.cluster_template_d = None
+        self.metrics_drive_root = 'media-' + self.ephemeral_root
+        self.init_template(templates_path)
+
+    def verify_config(self, action):
+        self._verify_config(action)
+
+    def verify_launch(self):
+        self.verify_instance_type(self.get('ec2', 'default_instance_type'))
+        self.verify_instance_type(self.get('ec2', 'worker_instance_type'))
+
+    def init_nodes(self):
+        self.node_d = {}
+        for (hostname, value) in self.items('nodes'):
+            if hostname in self.node_d:
+                exit('Hostname {0} already exists twice in 
nodes'.format(hostname))
+            service_list = []
+            for service in value.split(','):
+                if service in SERVICES:
+                    service_list.append(service)
+                else:
+                    exit('Unknown service "%s" declared for node %s' % 
(service, hostname))
+            self.node_d[hostname] = service_list
+
+    def default_ephemeral_devices(self):
+        return get_ephemeral_devices(self.get('ec2', 'default_instance_type'))
+
+    def worker_ephemeral_devices(self):
+        return get_ephemeral_devices(self.get('ec2', 'worker_instance_type'))
+
+    def max_ephemeral(self):
+        return max((len(self.default_ephemeral_devices()), 
len(self.worker_ephemeral_devices())))
+
+    def node_type_map(self):
+        if self.cluster_template_d:
+            return self.cluster_template_d['devices']
+
+        node_types = {}
+
+        node_list = [('default', self.default_ephemeral_devices()), ('worker', 
self.worker_ephemeral_devices())]
+
+        for (ntype, devices) in node_list:
+            node_types[ntype] = {'mounts': self.mounts(len(devices)), 
'devices': devices}
+
+        return node_types
+
+    def mount_root(self):
+        return '/media/' + self.ephemeral_root
+
+    @ansible_play_var
+    @default('ext3')
+    def fstype(self):
+        return self.get('ec2', 'fstype')
+
+    @ansible_play_var
+    @default('no')
+    def force_format(self):
+        return self.get('ec2', 'force_format')
+
+    def data_dirs_common(self, nodeType):
+        return self.node_type_map()[nodeType]['mounts']
+
+    def metrics_drive_ids(self):
+        drive_ids = []
+        for i in range(0, self.max_ephemeral()):
+            drive_ids.append(self.metrics_drive_root + str(i))
+        return drive_ids
+
+    def shutdown_delay_minutes(self):
+        return self.get("ec2", "shutdown_delay_minutes")
+
+    def verify_instance_type(self, instance_type):
+        if not self.cluster_template_d:
+            if get_arch(instance_type) == 'pvm':
+                exit("ERROR - Configuration contains instance type '{0}' that 
uses pvm architecture."
+                     "Only hvm architecture is 
supported!".format(instance_type))
+
+    def instance_tags(self):
+        retd = {}
+        if self.has_option('ec2', 'instance_tags'):
+            value = self.get('ec2', 'instance_tags')
+            if value:
+                for kv in value.split(','):
+                    (key, val) = kv.split(':')
+                    retd[key] = val
+        return retd
+
+    def init_template(self, templates_path):
+        if self.has_option('ec2', 'cluster_template'):
+            template_id = self.get('ec2', 'cluster_template')
+            template_path = os.path.join(templates_path, template_id)
+            if os.path.exists(template_path):
+                self.cluster_template_d = {'id': template_id}
+                self.load_template_ec2_requests(template_path)
+                self.load_template_device_map(template_path)
+            self.validate_template()
+
+    def load_template_ec2_requests(self, template_dir):
+        for json_path in glob.glob(os.path.join(template_dir, '*.json')):
+            service = os.path.basename(json_path).rsplit('.', 1)[0]
+            if service not in SERVICES:
+                exit("ERROR - Template '{0}' has unrecognized option '{1}'. 
Must be one of {2}".format(
+                    self.cluster_template_d['id'], service, str(SERVICES)))
+            with open(json_path, 'r') as json_file:
+                # load as string, so we can use string.Template to inject 
config values
+                self.cluster_template_d[service] = json_file.read()
+
+    def load_template_device_map(self, template_dir):
+        device_map_path = os.path.join(template_dir, 'devices')
+        if not os.path.isfile(device_map_path):
+            exit("ERROR - template '{0}' is missing 'devices' 
config".format(self.cluster_template_d['id']))
+        with open(device_map_path, 'r') as json_file:
+            self.cluster_template_d['devices'] = json.load(json_file)
+
+    def validate_template(self):
+        if not self.cluster_template_d:
+            exit("ERROR - Template '{0}' is not 
defined!".format(self.get('ec2', 'cluster_template')))
+
+        if 'worker' not in self.cluster_template_d:
+            exit("ERROR - '{0}' template config is invalid. No 'worker' launch 
request is defined".format(
+                self.cluster_template_d['id']))
+
+        if 'worker' not in self.cluster_template_d['devices']:
+            exit("ERROR - '{0}' template is invalid. The devices file must 
have a 'worker' device map".format(
+                self.cluster_template_d['id']))
+
+        if 'default' not in self.cluster_template_d['devices']:
+            exit("ERROR - '{0}' template is invalid. The devices file must 
have a 'default' device map".format(
+                self.cluster_template_d['id']))
+
+        # Validate the selected launch template for each host
+
+        worker_count = 0
+        for hostname in self.node_d:
+            # first service listed denotes the selected template
+            selected_ec2_request = self.node_d[hostname][0]
+            if 'worker' == selected_ec2_request:
+                worker_count = worker_count + 1
+            else:
+                if 'worker' in self.node_d[hostname]:
+                    exit("ERROR - '{0}' node config is invalid. The 'worker' 
service should be listed first".format(
+                        hostname))
+            if selected_ec2_request not in self.cluster_template_d:
+                if len(self.node_d[hostname]) > 1:
+                    print('Hint: In template mode, the first service listed 
for a host denotes its EC2 template')
+                exit("ERROR - '{0}' node config is invalid. No EC2 template 
defined for the '{1}' service".format(
+                    hostname, selected_ec2_request))
+
+        if worker_count == 0:
+            exit("ERROR - No worker instances are defined for template 
'{0}'".format(self.cluster_template_d['id']))
diff --git a/lib/muchos/config/existing.py b/lib/muchos/config/existing.py
new file mode 100644
index 0000000..e418b06
--- /dev/null
+++ b/lib/muchos/config/existing.py
@@ -0,0 +1,50 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+from muchos.config import BaseConfig
+from sys import exit
+from muchos.config.decorators import *
+from muchos.config.validators import *
+from muchos.util import get_ephemeral_devices, get_arch
+import os
+import json
+import glob
+
+
+class ExistingDeployConfig(BaseConfig):
+
+    def __init__(self, deploy_path, config_path, hosts_path, checksums_path, 
templates_path, cluster_name):
+        super(ExistingDeployConfig, self).__init__(deploy_path, config_path, 
hosts_path, checksums_path, templates_path, cluster_name)
+
+    def verify_config(self, action):
+        self._verify_config(action)
+
+    def verify_launch(self):
+        pass
+
+    def node_type_map(self):
+        node_types = {}
+        return node_types
+
+    def mount_root(self):
+        return self.get('existing', 'mount_root')
+
+    def data_dirs_common(self, nodeType):
+        return self.get('existing', 'data_dirs').split(",")
+
+    def metrics_drive_ids(self):
+        return self.get("existing", "metrics_drive_ids").split(",")
diff --git a/lib/muchos/config/validators.py b/lib/muchos/config/validators.py
new file mode 100644
index 0000000..1e8d49b
--- /dev/null
+++ b/lib/muchos/config/validators.py
@@ -0,0 +1,58 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+class _validator(object):
+    def __init__(self, f, msg):
+        self.f = f
+        self.msg = msg
+
+    def __call__(self, n):
+        return self.f(n)
+
+    def __str__(self):
+        return self.msg
+
+
+def greater_than(val):
+    return _validator(
+        lambda n: n > val,
+        "must be greater than {}".format(val))
+
+def less_than(val):
+    return _validator(
+        lambda n: n < val,
+        "must be less than {}".format(val))
+
+def equals(val):
+    return _validator(
+        lambda n: n == val,
+        "must equal {}".format(val))
+
+def contains(val):
+    return _validator(
+        lambda n: val in n,
+        "must contain {}".format(val))
+
+def is_in(val):
+    return _validator(
+        lambda n: n in val,
+        "must be in {}".format(val))
+
+def is_type(t):
+    return _validator(
+        lambda n: isinstance(n, t),
+        "must be of type {}".format(t))
\ No newline at end of file
diff --git a/lib/muchos/existing.py b/lib/muchos/existing.py
index f1b3128..523b2a7 100644
--- a/lib/muchos/existing.py
+++ b/lib/muchos/existing.py
@@ -23,9 +23,6 @@ from os.path import isfile, join
 from sys import exit
 from os import listdir
 
-from .config import HOST_VAR_DEFAULTS, PLAY_VAR_DEFAULTS, AZURE_VAR_DEFAULTS
-
-
 class ExistingCluster:
 
     def __init__(self, config):
@@ -38,36 +35,13 @@ class ExistingCluster:
         config = self.config
         print('Syncing ansible directory on {0} cluster proxy 
node'.format(config.cluster_name))
 
-        host_vars = HOST_VAR_DEFAULTS
-        play_vars = PLAY_VAR_DEFAULTS
-
-        azure_vars = AZURE_VAR_DEFAULTS
-
-        for section in ("general", "ansible-vars", config.get('performance', 
'profile'), "azure"):
-            for (name, value) in config.items(section):
-                if name not in ('proxy_hostname', 'proxy_socks_port'):
-                    if name in host_vars:
-                        host_vars[name] = value
-                    if name in play_vars:
-                        play_vars[name] = value
-                    if name in azure_vars:
-                        azure_vars[name] = value
-
-        play_vars['accumulo_sha256'] = config.checksum('accumulo')
-        play_vars['fluo_sha256'] = config.checksum('fluo')
-        play_vars['fluo_yarn_sha256'] = config.checksum('fluo_yarn')
-        play_vars['hadoop_sha256'] = config.checksum('hadoop')
-        play_vars['spark_sha256'] = config.checksum('spark')
-        play_vars['zookeeper_sha256'] = config.checksum('zookeeper')
-        play_vars["shutdown_delay_minutes"] = config.shutdown_delay_minutes()
-        play_vars["metrics_drive_ids"] = config.metrics_drive_ids()
-        play_vars["mount_root"] = config.mount_root()
-        play_vars["node_type_map"] = config.node_type_map()
-        play_vars["fstype"] = config.fstype()
-        play_vars["force_format"] = config.force_format()
-        host_vars['worker_data_dirs'] = str(config.worker_data_dirs())
-        host_vars['default_data_dirs'] = str(config.default_data_dirs())
-        host_vars['java_product_version'] = str(config.java_product_version())
+        host_vars = config.ansible_host_vars()
+        play_vars = config.ansible_play_vars()
+
+        for k,v in host_vars.items():
+            host_vars[k] = self.config.resolve_value(k, default=v)
+        for k,v in play_vars.items():
+            play_vars[k] = self.config.resolve_value(k, default=v)
 
         with open(join(config.deploy_path, "ansible/site.yml"), 'w') as 
site_file:
             print("- import_playbook: common.yml", file=site_file)
@@ -148,8 +122,6 @@ class ExistingCluster:
             print("\n[all:vars]", file=hosts_file)
             for (name, value) in sorted(host_vars.items()):
                 print("{0} = {1}".format(name, value), file=hosts_file)
-            for (name, value) in sorted(azure_vars.items()):
-                print("{0} = {1}".format(name, value), file=hosts_file)
 
         with open(join(config.deploy_path, "ansible/group_vars/all"), 'w') as 
play_vars_file:
             for (name, value) in sorted(play_vars.items()):
diff --git a/lib/tests/azure/__init__.py b/lib/tests/azure/__init__.py
new file mode 100644
index 0000000..555eccf
--- /dev/null
+++ b/lib/tests/azure/__init__.py
@@ -0,0 +1,17 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
diff --git a/lib/tests/azure/test_config.py b/lib/tests/azure/test_config.py
new file mode 100644
index 0000000..e9dcfd2
--- /dev/null
+++ b/lib/tests/azure/test_config.py
@@ -0,0 +1,84 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+from muchos.config import AzureDeployConfig
+
+
+def test_azure_cluster():
+    c = AzureDeployConfig("muchos", '../conf/muchos.props.example', 
'../conf/hosts/example/example_cluster',
+                     '../conf/checksums', '../conf/templates', 'mycluster')
+
+    # since we are sharing a single muchos.props.example file, we need
+    # to stub the cluster type to be azure (as the file itself has a default 
of ec2)
+
+    c.cluster_type = 'azure'
+
+    assert c.checksum_ver('accumulo', '1.9.0') == 
'f68a6145029a9ea843b0305c90a7f5f0334d8a8ceeea94734267ec36421fe7fe'
+    assert c.checksum('accumulo') == 
'df172111698c7a73aa031de09bd5589263a6b824482fbb9b4f0440a16602ed47'
+    assert c.get('azure', 'vm_sku') == 'Standard_D8s_v3'
+    assert c.get('azure', 'managed_disk_type') == 'Standard_LRS'
+    assert c.user_home() == '/home/centos'
+    assert c.mount_root() == '/var/data'
+    assert c.worker_data_dirs() == ['/var/data1', '/var/data2', '/var/data3']
+    assert c.default_data_dirs() == ['/var/data1', '/var/data2', '/var/data3']
+    assert c.metrics_drive_ids() == ['var-data1', 'var-data2', 'var-data3']
+    assert c.shutdown_delay_minutes() == '0'
+    assert c.mounts(2) == ['/var/data0', '/var/data1']
+    assert c.node_type('worker1') == 'worker'
+    assert c.node_type('leader1') == 'default'
+    assert c.has_option('azure', 'resource_group')
+    assert c.has_option('azure', 'vnet')
+    assert c.has_option('azure', 'vnet_cidr')
+    assert c.has_option('azure', 'subnet')
+    assert c.has_option('azure', 'subnet_cidr')
+    assert c.has_option('azure', 'numnodes')
+    assert c.has_option('azure', 'location')
+    assert len(c.nodes()) == 6
+    assert c.get_node('leader1') == ['namenode', 'resourcemanager', 
'accumulomaster', 'zookeeper']
+    assert c.get_node('leader2') == ['metrics']
+    assert c.get_node('worker1') == ['worker', 'swarmmanager']
+    assert c.get_node('worker2') == ['worker']
+    assert c.get_node('worker3') == ['worker']
+    assert c.has_service('accumulomaster')
+    assert not c.has_service('fluo')
+    assert c.get_service_hostnames('worker') == ['worker1', 'worker2', 
'worker3', 'worker4']
+    assert c.get_service_hostnames('zookeeper') == ['leader1']
+    assert c.get_hosts() == {'leader2': ('10.0.0.1', None), 'leader1': 
('10.0.0.0', '23.0.0.0'),
+                             'worker1': ('10.0.0.2', None), 'worker3': 
('10.0.0.4', None),
+                             'worker2': ('10.0.0.3', None), 'worker4': 
('10.0.0.5', None)}
+    assert c.get_public_ip('leader1') == '23.0.0.0'
+    assert c.get_private_ip('leader1') == '10.0.0.0'
+    assert c.cluster_name == 'mycluster'
+    assert c.get_cluster_type() == 'azure'
+    assert c.version("accumulo").startswith('2.')
+    assert c.version("fluo").startswith('1.')
+    assert c.version("hadoop").startswith('3.')
+    assert c.version("zookeeper").startswith('3.')
+    assert c.get_service_private_ips("worker") == ['10.0.0.2', '10.0.0.3', 
'10.0.0.4', '10.0.0.5']
+    assert c.get('general', 'proxy_hostname') == "leader1"
+    assert c.proxy_public_ip() == "23.0.0.0"
+    assert c.proxy_private_ip() == "10.0.0.0"
+    assert c.get('general', 'cluster_user') == "centos"
+    assert c.get('general', 'cluster_group') == "centos"
+    assert c.get_non_proxy() == [('10.0.0.1', 'leader2'), ('10.0.0.2', 
'worker1'), ('10.0.0.3', 'worker2'),
+                                 ('10.0.0.4', 'worker3'), ('10.0.0.5', 
'worker4')]
+    assert c.get_host_services() == [('leader1', 'namenode resourcemanager 
accumulomaster zookeeper'),
+                                     ('leader2', 'metrics'),
+                                     ('worker1', 'worker swarmmanager'),
+                                     ('worker2', 'worker'),
+                                     ('worker3', 'worker'),
+                                     ('worker4', 'worker')]
diff --git a/lib/tests/ec2/__init__.py b/lib/tests/ec2/__init__.py
new file mode 100644
index 0000000..555eccf
--- /dev/null
+++ b/lib/tests/ec2/__init__.py
@@ -0,0 +1,17 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
diff --git a/lib/tests/test_config.py b/lib/tests/ec2/test_config.py
similarity index 53%
rename from lib/tests/test_config.py
rename to lib/tests/ec2/test_config.py
index f6501ff..fe06fe5 100644
--- a/lib/tests/test_config.py
+++ b/lib/tests/ec2/test_config.py
@@ -15,11 +15,11 @@
 # limitations under the License.
 #
 
-from muchos.config import DeployConfig
+from muchos.config import Ec2DeployConfig
 
 
 def test_ec2_cluster():
-    c = DeployConfig("muchos", '../conf/muchos.props.example', 
'../conf/hosts/example/example_cluster',
+    c = Ec2DeployConfig("muchos", '../conf/muchos.props.example', 
'../conf/hosts/example/example_cluster',
                      '../conf/checksums', '../conf/templates', 'mycluster')
     assert c.checksum_ver('accumulo', '1.9.0') == 
'f68a6145029a9ea843b0305c90a7f5f0334d8a8ceeea94734267ec36421fe7fe'
     assert c.checksum('accumulo') == 
'df172111698c7a73aa031de09bd5589263a6b824482fbb9b4f0440a16602ed47'
@@ -81,91 +81,8 @@ def test_ec2_cluster():
                                      ('worker4', 'worker')]
 
 
-def test_azure_cluster():
-    c = DeployConfig("muchos", '../conf/muchos.props.example', 
'../conf/hosts/example/example_cluster',
-                     '../conf/checksums', '../conf/templates', 'mycluster')
-
-    # since we are sharing a single muchos.props.example file, we need
-    # to stub the cluster type to be azure (as the file itself has a default 
of ec2)
-
-    c.cluster_type = 'azure'
-
-    assert c.checksum_ver('accumulo', '1.9.0') == 
'f68a6145029a9ea843b0305c90a7f5f0334d8a8ceeea94734267ec36421fe7fe'
-    assert c.checksum('accumulo') == 
'df172111698c7a73aa031de09bd5589263a6b824482fbb9b4f0440a16602ed47'
-    assert c.get('azure', 'vm_sku') == 'Standard_D8s_v3'
-    assert c.get('azure', 'managed_disk_type') == 'Standard_LRS'
-    assert c.user_home() == '/home/centos'
-    assert c.mount_root() == '/var/data'
-    assert c.force_format() == 'no'
-    assert c.worker_data_dirs() == ['/var/data1', '/var/data2', '/var/data3']
-    assert c.default_data_dirs() == ['/var/data1', '/var/data2', '/var/data3']
-    assert c.metrics_drive_ids() == ['var-data1', 'var-data2', 'var-data3']
-    assert c.shutdown_delay_minutes() == '0'
-    assert c.mounts(2) == ['/var/data0', '/var/data1']
-    assert c.node_type('worker1') == 'worker'
-    assert c.node_type('leader1') == 'default'
-    assert c.has_option('azure', 'resource_group')
-    assert c.has_option('azure', 'vnet')
-    assert c.has_option('azure', 'vnet_cidr')
-    assert c.has_option('azure', 'subnet')
-    assert c.has_option('azure', 'subnet_cidr')
-    assert c.has_option('azure', 'numnodes')
-    assert c.has_option('azure', 'location')
-    assert c.instance_tags() == {}
-    assert len(c.nodes()) == 6
-    assert c.get_node('leader1') == ['namenode', 'resourcemanager', 
'accumulomaster', 'zookeeper']
-    assert c.get_node('leader2') == ['metrics']
-    assert c.get_node('worker1') == ['worker', 'swarmmanager']
-    assert c.get_node('worker2') == ['worker']
-    assert c.get_node('worker3') == ['worker']
-    assert c.has_service('accumulomaster')
-    assert not c.has_service('fluo')
-    assert c.get_service_hostnames('worker') == ['worker1', 'worker2', 
'worker3', 'worker4']
-    assert c.get_service_hostnames('zookeeper') == ['leader1']
-    assert c.get_hosts() == {'leader2': ('10.0.0.1', None), 'leader1': 
('10.0.0.0', '23.0.0.0'),
-                             'worker1': ('10.0.0.2', None), 'worker3': 
('10.0.0.4', None),
-                             'worker2': ('10.0.0.3', None), 'worker4': 
('10.0.0.5', None)}
-    assert c.get_public_ip('leader1') == '23.0.0.0'
-    assert c.get_private_ip('leader1') == '10.0.0.0'
-    assert c.cluster_name == 'mycluster'
-    assert c.get_cluster_type() == 'azure'
-    assert c.version("accumulo").startswith('2.')
-    assert c.version("fluo").startswith('1.')
-    assert c.version("hadoop").startswith('3.')
-    assert c.version("zookeeper").startswith('3.')
-    assert c.get_service_private_ips("worker") == ['10.0.0.2', '10.0.0.3', 
'10.0.0.4', '10.0.0.5']
-    assert c.get('general', 'proxy_hostname') == "leader1"
-    assert c.proxy_public_ip() == "23.0.0.0"
-    assert c.proxy_private_ip() == "10.0.0.0"
-    assert c.get('general', 'cluster_user') == "centos"
-    assert c.get('general', 'cluster_group') == "centos"
-    assert c.get_non_proxy() == [('10.0.0.1', 'leader2'), ('10.0.0.2', 
'worker1'), ('10.0.0.3', 'worker2'),
-                                 ('10.0.0.4', 'worker3'), ('10.0.0.5', 
'worker4')]
-    assert c.get_host_services() == [('leader1', 'namenode resourcemanager 
accumulomaster zookeeper'),
-                                     ('leader2', 'metrics'),
-                                     ('worker1', 'worker swarmmanager'),
-                                     ('worker2', 'worker'),
-                                     ('worker3', 'worker'),
-                                     ('worker4', 'worker')]
-
-
-def test_existing_cluster():
-    c = DeployConfig("muchos", '../conf/muchos.props.example', 
'../conf/hosts/example/example_cluster',
-                     '../conf/checksums', '../conf/templates', 'mycluster')
-    c.cluster_type = 'existing'
-    assert c.get_cluster_type() == 'existing'
-    assert c.node_type_map() == {}
-    assert c.mount_root() == '/var/data'
-    assert c.fstype() is None
-    assert c.force_format() == 'no'
-    assert c.worker_data_dirs() == ['/var/data1', '/var/data2', '/var/data3']
-    assert c.default_data_dirs() == ['/var/data1', '/var/data2', '/var/data3']
-    assert c.metrics_drive_ids() == ['var-data1', 'var-data2', 'var-data3']
-    assert c.shutdown_delay_minutes() == '0'
-
-
 def test_case_sensitive():
-    c = DeployConfig("muchos", '../conf/muchos.props.example', 
'../conf/hosts/example/example_cluster',
+    c = Ec2DeployConfig("muchos", '../conf/muchos.props.example', 
'../conf/hosts/example/example_cluster',
                      '../conf/checksums', '../conf/templates', 'mycluster')
     assert c.has_option('ec2', 'default_instance_type') == True
     assert c.has_option('ec2', 'Default_instance_type') == False
@@ -175,7 +92,7 @@ def test_case_sensitive():
 
 
 def test_ec2_cluster_template():
-    c = DeployConfig("muchos", '../conf/muchos.props.example', 
'../conf/hosts/example/example_cluster',
+    c = Ec2DeployConfig("muchos", '../conf/muchos.props.example', 
'../conf/hosts/example/example_cluster',
                      '../conf/checksums', '../conf/templates', 'mycluster')
 
     c.set('ec2', 'cluster_template', 'example')
diff --git a/lib/tests/existing/__init__.py b/lib/tests/existing/__init__.py
new file mode 100644
index 0000000..555eccf
--- /dev/null
+++ b/lib/tests/existing/__init__.py
@@ -0,0 +1,17 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
diff --git a/lib/tests/existing/test_config.py 
b/lib/tests/existing/test_config.py
new file mode 100644
index 0000000..ef9a29a
--- /dev/null
+++ b/lib/tests/existing/test_config.py
@@ -0,0 +1,31 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+from muchos.config import ExistingDeployConfig
+
+
+def test_existing_cluster():
+    c = ExistingDeployConfig("muchos", '../conf/muchos.props.example', 
'../conf/hosts/example/example_cluster',
+                     '../conf/checksums', '../conf/templates', 'mycluster')
+    c.cluster_type = 'existing'
+    assert c.get_cluster_type() == 'existing'
+    assert c.node_type_map() == {}
+    assert c.mount_root() == '/var/data'
+    assert c.worker_data_dirs() == ['/var/data1', '/var/data2', '/var/data3']
+    assert c.default_data_dirs() == ['/var/data1', '/var/data2', '/var/data3']
+    assert c.metrics_drive_ids() == ['var-data1', 'var-data2', 'var-data3']
+    assert c.shutdown_delay_minutes() == '0'
\ No newline at end of file

Reply via email to