From: Szilard Cserey Date: Wed, 18 Mar 2015 14:49:24 +0000 (+0100) Subject: Automatic Deployment X-Git-Tag: arno.2015.1.0~88^2 X-Git-Url: https://gerrit.opnfv.org/gerrit/gitweb?a=commitdiff_plain;h=1e066de62f2b4bcc833ce62a16efdcbf71d3dd9b;p=genesis.git Automatic Deployment - Deployment Hardware Adapter - Deployment Environment Adapter - Fuel environment cleanup - Fuel environment configuration JIRA: [BGS-2] Create Fuel deployment script Change-Id: Ie8aa6c0817a73c507cb4345bde1e2c904bb5b105 Signed-off-by: Szilard Cserey --- diff --git a/fuel/ci/deploy.sh b/fuel/ci/deploy.sh old mode 100644 new mode 100755 index e69de29..d11c65a --- 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