summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-x[-rw-r--r--]fuel/ci/deploy.sh3
-rw-r--r--fuel/deploy/dea.py52
-rw-r--r--fuel/deploy/dea.yaml26
-rw-r--r--fuel/deploy/deploy.py202
-rw-r--r--fuel/deploy/dha.py49
5 files changed, 332 insertions, 0 deletions
diff --git a/fuel/ci/deploy.sh b/fuel/ci/deploy.sh
index e69de29..d11c65a 100644..100755
--- a/fuel/ci/deploy.sh
+++ b/fuel/ci/deploy.sh
@@ -0,0 +1,3 @@
+# To be able to deploy on a certain metal environment there needs to be a Deployment Environment Adaptor executable"
+# properly added to $PATH such that deploy.sh can call it by $dea [options] as indicated by ./deploy -h.
+
diff --git a/fuel/deploy/dea.py b/fuel/deploy/dea.py
new file mode 100644
index 0000000..0ab215d
--- /dev/null
+++ b/fuel/deploy/dea.py
@@ -0,0 +1,52 @@
+import yaml
+
+class DeploymentEnvironmentAdapter(object):
+ def __init__(self):
+ self.dea_struct = None
+ self.blade_ids = {}
+ self.blades = {}
+ self.shelf_ids = []
+
+ def parse_yaml(self, yaml_path):
+ with open(yaml_path) as yaml_file:
+ self.dea_struct = yaml.load(yaml_file)
+ self.collect_shelf_and_blade_info()
+
+ def get_no_of_blades(self):
+ no_of_blades = 0
+ for shelf in self.dea_struct['shelf']:
+ no_of_blades += len(shelf['blade'])
+ return no_of_blades
+
+ def get_server_type(self):
+ return self.dea_struct['server_type']
+
+ def get_environment_name(self):
+ return self.dea_struct['name']
+
+ def get_shelf_ids(self):
+ return self.shelf_ids
+
+ def get_blade_ids(self, shelf_id):
+ return self.blade_ids[shelf_id]
+
+ def collect_shelf_and_blade_info(self):
+ self.blade_ids = {}
+ self.blades = {}
+ self.shelf_ids = []
+ for shelf in self.dea_struct['shelf']:
+ self.shelf_ids.append(shelf['id'])
+ blade_ids = self.blade_ids[shelf['id']] = []
+ blades = self.blades[shelf['id']] = {}
+ for blade in shelf['blade']:
+ blade_ids.append(blade['id'])
+ blades[blade['id']] = blade
+
+ def is_controller(self, shelf_id, blade_id):
+ blade = self.blades[shelf_id][blade_id]
+ return (True if 'role' in blade and blade['role'] == 'controller'
+ else False)
+
+ def is_compute_host(self, shelf_id, blade_id):
+ blade = self.blades[shelf_id][blade_id]
+ return True if 'role' not in blade else False \ No newline at end of file
diff --git a/fuel/deploy/dea.yaml b/fuel/deploy/dea.yaml
new file mode 100644
index 0000000..5ade83f
--- /dev/null
+++ b/fuel/deploy/dea.yaml
@@ -0,0 +1,26 @@
+---
+name: ENV-1
+server_type: esxi
+shelf:
+ - id: 1
+ blade:
+ - id: 1
+ role: controller
+ - id: 2
+ - id: 3
+ role: controller
+ - id: 4
+ - id: 5
+ - id: 6
+networking:
+ switch_type: esxi
+ switch_mgmt_ip: 192.168.0.1/24
+ vlan:
+ - name: traffic
+ tag: 100
+ - name: storage
+ tag: 102
+ - name: control
+ tag: 101
+ - name: management
+... \ No newline at end of file
diff --git a/fuel/deploy/deploy.py b/fuel/deploy/deploy.py
new file mode 100644
index 0000000..4df4f36
--- /dev/null
+++ b/fuel/deploy/deploy.py
@@ -0,0 +1,202 @@
+import subprocess
+import sys
+import time
+import os
+from dha import DeploymentHardwareAdapter
+from dea import DeploymentEnvironmentAdapter
+
+SUPPORTED_RELEASE = 'Juno on CentOS 6.5'
+N = {'id': 0, 'status': 1, 'name': 2, 'cluster': 3, 'ip': 4, 'mac': 5,
+ 'roles': 6, 'pending_roles': 7, 'online': 8}
+E = {'id': 0, 'status': 1, 'name': 2, 'mode': 3, 'release_id': 4,
+ 'changes': 5, 'pending_release_id': 6}
+R = {'id': 0, 'name': 1, 'state': 2, 'operating_system': 3, 'version': 4}
+RO = {'name': 0, 'conflicts': 1}
+
+def exec_cmd(cmd):
+ process = subprocess.Popen(cmd,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT,
+ shell=True)
+ return process.communicate()[0]
+
+def parse(printout):
+ parsed_list = []
+ lines = printout.splitlines()
+ for l in lines[2:]:
+ parsed = [e.strip() for e in l.split('|')]
+ parsed_list.append(parsed)
+ return parsed_list
+
+def err(error_message):
+ sys.stderr.write(error_message)
+ sys.exit(1)
+
+
+
+class Deploy(object):
+
+ def __init__(self):
+ self.supported_release = None
+
+ def get_id_list(self, list):
+ return [l[0] for l in list]
+
+ def cleanup_fuel_environments(self, env_list):
+ WAIT_LOOP = 10
+ SLEEP_TIME = 2
+ id_list = self.get_id_list(env_list)
+ for id in id_list:
+ exec_cmd('fuel env --env %s --delete' % id)
+ for i in range(WAIT_LOOP):
+ if id in self.get_id_list(parse(exec_cmd('fuel env list'))):
+ time.sleep(SLEEP_TIME)
+ else:
+ continue
+
+ def cleanup_fuel_nodes(self, node_list):
+ for node in node_list:
+ if node[N['status']] == 'discover':
+ exec_cmd('fuel node --node-id %s --delete-from-db'
+ % node[N['id']])
+ exec_cmd('dockerctl shell cobbler cobbler system remove '
+ '--name node-%s' % node[N['id']])
+
+ def check_previous_installation(self):
+ env_list = parse(exec_cmd('fuel env list'))
+ if env_list:
+ self.cleanup_fuel_environments(env_list)
+ node_list = parse(exec_cmd('fuel node list'))
+ if node_list:
+ self.cleanup_fuel_nodes(node_list)
+
+ def check_supported_release(self):
+ release_list= parse(exec_cmd('fuel release -l'))
+ for release in release_list:
+ if release[R['name']] == SUPPORTED_RELEASE:
+ self.supported_release = release
+ break
+ if not self.supported_release:
+ err("This Fuel doesn't contain the following "
+ "release: %s\n" % SUPPORTED_RELEASE)
+
+ def check_role_definitions(self):
+ role_list= parse(exec_cmd('fuel role --release %s'
+ % self.supported_release[R['id']]))
+ roles = [role[RO['name']] for role in role_list]
+ if 'compute' not in roles:
+ err("Role compute does not exist in release %"
+ % self.supported_release[R['name']])
+ if 'controller' not in roles:
+ err("Role controller does not exist in release %"
+ % self.supported_release[R['name']])
+
+ def check_prerequisites(self):
+ self.check_supported_release()
+ self.check_role_definitions()
+ self.check_previous_installation()
+
+ def count_discovered_nodes(self, node_list):
+ discovered_nodes = 0
+ for node in node_list:
+ if node[N['status']] == 'discover':
+ discovered_nodes += 1
+ return discovered_nodes
+
+ def wait_for_discovered_blades(self, no_of_blades):
+ WAIT_LOOP = 10
+ SLEEP_TIME = 2
+ all_discovered = False
+ node_list = parse(exec_cmd('fuel node list'))
+ for i in range(WAIT_LOOP):
+ if (self.count_discovered_nodes(node_list) < no_of_blades):
+ time.sleep(SLEEP_TIME)
+ node_list = parse(exec_cmd('fuel node list'))
+ else:
+ all_discovered = True
+ break
+ if not all_discovered:
+ err("There are %s blades defined, but not all of "
+ "them have been discovered\n" % no_of_blades)
+
+ def assign_cluster_node_ids(self, dha, dea, controllers, compute_hosts):
+ node_list= parse(exec_cmd('fuel node list'))
+ for shelf_id in dea.get_shelf_ids():
+ for blade_id in dea.get_blade_ids(shelf_id):
+ blade_mac_list = dha.get_blade_mac_addresses(
+ shelf_id, blade_id)
+
+ found = False
+ for node in node_list:
+ if (node[N['mac']] in blade_mac_list and
+ node[N['status']] == 'discover'):
+ found = True
+ break
+ if found:
+ if dea.is_controller(shelf_id, blade_id):
+ controllers.append(node[N['id']])
+ if dea.is_compute_host(shelf_id, blade_id):
+ compute_hosts.append(node[N['id']])
+ else:
+ err("Could not find the Node ID for blade "
+ "with MACs %s or blade is not in "
+ "discover status\n" % blade_mac_list)
+
+ def env_exists(self, env_name):
+ env_list = parse(exec_cmd('fuel env --list'))
+ for env in env_list:
+ if env[E['name']] == env_name and env[E['status']] == 'new':
+ return True
+ return False
+
+ def configure_environment(self, dea):
+ env_name = dea.get_environment_name()
+ exec_cmd('fuel env -c --name %s --release %s --mode ha --net neutron '
+ '--nst vlan' % (env_name, self.supported_release[R['id']]))
+
+ if not self.env_exists(env_name):
+ err("Failed to create environment %s" % env_name)
+
+
+
+def main():
+
+ yaml_path = exec_cmd('pwd').strip() + '/dea.yaml'
+ deploy = Deploy()
+
+ dea = DeploymentEnvironmentAdapter()
+
+ if not os.path.isfile(yaml_path):
+ sys.stderr.write("ERROR: File %s not found\n" % yaml_path)
+ sys.exit(1)
+
+ dea.parse_yaml(yaml_path)
+
+ dha = DeploymentHardwareAdapter(dea.get_server_type())
+
+ deploy.check_prerequisites()
+
+ dha.power_off_blades()
+
+ dha.configure_networking()
+
+ dha.reset_to_factory_defaults()
+
+ dha.set_boot_order()
+
+ dha.power_on_blades()
+
+ dha.get_blade_mac_addresses()
+
+ deploy.wait_for_discovered_blades(dea.get_no_of_blades())
+
+ controllers = []
+ compute_hosts = []
+ deploy.assign_cluster_node_ids(dha, dea, controllers, compute_hosts)
+
+ deploy.configure_environment(dea)
+
+
+
+if __name__ == '__main__':
+ main() \ No newline at end of file
diff --git a/fuel/deploy/dha.py b/fuel/deploy/dha.py
new file mode 100644
index 0000000..f78686b
--- /dev/null
+++ b/fuel/deploy/dha.py
@@ -0,0 +1,49 @@
+
+class DeploymentHardwareAdapter(object):
+ def __new__(cls, server_type):
+ if cls is DeploymentHardwareAdapter:
+ if server_type == 'esxi': return EsxiAdapter()
+ if server_type == 'hp': return HpAdapter()
+ if server_type == 'dell': return DellAdapter()
+ if server_type == 'libvirt': return LibvirtAdapter()
+ return super(DeploymentHardwareAdapter, cls).__new__(cls)
+
+
+class HardwareAdapter(object):
+
+ def power_off_blades(self):
+ raise NotImplementedError
+
+ def power_on_blades(self):
+ raise NotImplementedError
+
+ def power_cycle_blade(self):
+ raise NotImplementedError
+
+ def set_boot_order(self):
+ raise NotImplementedError
+
+ def reset_to_factory_defaults(self):
+ raise NotImplementedError
+
+ def configure_networking(self):
+ raise NotImplementedError
+
+ def get_blade_mac_addresses(self, shelf_id, blade_id):
+ raise NotImplementedError
+
+ def get_blade_hardware_info(self, shelf_id, blade_id):
+ raise NotImplementedError
+
+
+class EsxiAdapter(HardwareAdapter):
+ pass
+
+class LibvirtAdapter(HardwareAdapter):
+ pass
+
+class HpAdapter(HardwareAdapter):
+ pass
+
+class DellAdapter(HardwareAdapter):
+ pass