From 945dca48c0b1fd93fec43513afb6680a8d828e33 Mon Sep 17 00:00:00 2001 From: Szilard Cserey Date: Mon, 4 May 2015 12:11:29 +0200 Subject: Autodeploy inspired on Prototype #2 - setup libvirt environment - setup vfuel environment - patch iso - install Fuel Master - deploy cloud - hardware adapter for libvirt - hardware adapter for ipmi - hardware adapter for hp - README: AutoDeploy instructions JIRA: [BGS-2] Create Fuel deployment script Change-Id: I862d824829baaae7d21115776d13355d575a47c8 Signed-off-by: Szilard Cserey --- fuel/deploy/cloud/configure_environment.py | 73 ++++++++++ fuel/deploy/cloud/configure_network.py | 61 +++++++++ fuel/deploy/cloud/configure_nodes.py | 104 +++++++++++++++ fuel/deploy/cloud/configure_settings.py | 47 +++++++ fuel/deploy/cloud/deploy.py | 206 +++++++++++++++++++++++++++++ fuel/deploy/cloud/deployment.py | 113 ++++++++++++++++ 6 files changed, 604 insertions(+) create mode 100644 fuel/deploy/cloud/configure_environment.py create mode 100644 fuel/deploy/cloud/configure_network.py create mode 100644 fuel/deploy/cloud/configure_nodes.py create mode 100644 fuel/deploy/cloud/configure_settings.py create mode 100644 fuel/deploy/cloud/deploy.py create mode 100644 fuel/deploy/cloud/deployment.py (limited to 'fuel/deploy/cloud') diff --git a/fuel/deploy/cloud/configure_environment.py b/fuel/deploy/cloud/configure_environment.py new file mode 100644 index 0000000..d0037d7 --- /dev/null +++ b/fuel/deploy/cloud/configure_environment.py @@ -0,0 +1,73 @@ +import common +import os +import shutil + +from configure_settings import ConfigureSettings +from configure_network import ConfigureNetwork +from configure_nodes import ConfigureNodes + +N = common.N +E = common.E +R = common.R +RO = common.RO +exec_cmd = common.exec_cmd +parse = common.parse +err = common.err +log = common.log + +class ConfigureEnvironment(object): + + def __init__(self, dea, yaml_config_dir, release_id, node_id_roles_dict): + self.env_id = None + self.dea = dea + self.yaml_config_dir = yaml_config_dir + self.env_name = self.dea.get_property('environment_name') + self.release_id = release_id + self.node_id_roles_dict = node_id_roles_dict + self.required_networks = [] + + 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': + self.env_id = env[E['id']] + return True + return False + + def configure_environment(self): + log('Configure environment') + if os.path.exists(self.yaml_config_dir): + log('Deleting existing config directory %s' % self.yaml_config_dir) + shutil.rmtree(self.yaml_config_dir) + log('Creating new config directory %s' % self.yaml_config_dir) + os.makedirs(self.yaml_config_dir) + + mode = self.dea.get_property('environment_mode') + log('Creating environment %s release %s, mode %s, network-mode neutron' + ', net-segment-type vlan' % (self.env_name, self.release_id, mode)) + exec_cmd('fuel env create --name %s --release %s --mode %s ' + '--network-mode neutron --net-segment-type vlan' + % (self.env_name, self.release_id, mode)) + + if not self.env_exists(self.env_name): + err('Failed to create environment %s' % self.env_name) + self.config_settings() + self.config_network() + self.config_nodes() + + def config_settings(self): + settings = ConfigureSettings(self.yaml_config_dir, self.env_id, + self.dea) + settings.config_settings() + + def config_network(self): + network = ConfigureNetwork(self.yaml_config_dir, self.env_id, self.dea) + network.config_network() + + def config_nodes(self): + nodes = ConfigureNodes(self.yaml_config_dir, self.env_id, + self.node_id_roles_dict, self.dea) + nodes.config_nodes() + + + diff --git a/fuel/deploy/cloud/configure_network.py b/fuel/deploy/cloud/configure_network.py new file mode 100644 index 0000000..295eb90 --- /dev/null +++ b/fuel/deploy/cloud/configure_network.py @@ -0,0 +1,61 @@ +import common +import yaml +import io + +N = common.N +E = common.E +R = common.R +RO = common.RO +exec_cmd = common.exec_cmd +parse = common.parse +err = common.err +check_file_exists = common.check_file_exists +log = common.log + +class ConfigureNetwork(object): + + def __init__(self, yaml_config_dir, env_id, dea): + self.yaml_config_dir = yaml_config_dir + self.env_id = env_id + self.dea = dea + self.required_networks = [] + + def download_network_config(self): + log('Download network config for environment %s' % self.env_id) + exec_cmd('fuel network --env %s --download --dir %s' + % (self.env_id, self.yaml_config_dir)) + + def upload_network_config(self): + log('Upload network config for environment %s' % self.env_id) + exec_cmd('fuel network --env %s --upload --dir %s' + % (self.env_id, self.yaml_config_dir)) + + def config_network(self): + log('Configure network') + self.download_network_config() + self.modify_network_config() + self.upload_network_config() + + def modify_network_config(self): + log('Modify network config for environment %s' % self.env_id) + network_yaml = ('%s/network_%s.yaml' + % (self.yaml_config_dir, self.env_id)) + check_file_exists(network_yaml) + + network_config = self.dea.get_property('network') + + with io.open(network_yaml) as stream: + network = yaml.load(stream) + + net_names = self.dea.get_network_names() + net_id = {} + for net in network['networks']: + if net['name'] in net_names: + net_id[net['name']] = {'id': net['id'], + 'group_id': net['group_id']} + + for network in network_config['networks']: + network.update(net_id[network['name']]) + + with io.open(network_yaml, 'w') as stream: + yaml.dump(network_config, stream, default_flow_style=False) \ No newline at end of file diff --git a/fuel/deploy/cloud/configure_nodes.py b/fuel/deploy/cloud/configure_nodes.py new file mode 100644 index 0000000..4d1315a --- /dev/null +++ b/fuel/deploy/cloud/configure_nodes.py @@ -0,0 +1,104 @@ +import common +import yaml +import io +import glob + +N = common.N +E = common.E +R = common.R +RO = common.RO +exec_cmd = common.exec_cmd +parse = common.parse +err = common.err +check_file_exists = common.check_file_exists +log = common.log + + +class ConfigureNodes(object): + + def __init__(self, yaml_config_dir, env_id, node_id_roles_dict, dea): + self.yaml_config_dir = yaml_config_dir + self.env_id = env_id + self.node_id_roles_dict = node_id_roles_dict + self.dea = dea + + def config_nodes(self): + log('Configure nodes') + for node_id, roles_blade in self.node_id_roles_dict.iteritems(): + exec_cmd('fuel node set --node-id %s --role %s --env %s' + % (node_id, ','.join(roles_blade[0]), self.env_id)) + + self.download_deployment_config() + for node_id, roles_blade in self.node_id_roles_dict.iteritems(): + self.download_interface_config(node_id) + self.modify_node_interface(node_id, roles_blade) + self.modify_node_network_schemes(node_id, roles_blade) + self.upload_interface_config(node_id) + self.upload_deployment_config() + + def modify_node_network_schemes(self, node_id, roles_blade): + log('Modify node network transformations in environment %s' + % self.env_id) + type = self.dea.get_node_property(roles_blade[1], 'transformations') + transformations = self.dea.get_transformations(type) + + for node_file in glob.glob('%s/deployment_%s/*_%s.yaml' + % (self.yaml_config_dir, self.env_id, + node_id)): + with io.open(node_file) as stream: + node = yaml.load(stream) + + node['network_scheme']['transformations'] = transformations + + with io.open(node_file, 'w') as stream: + yaml.dump(node, stream, default_flow_style=False) + + + def download_deployment_config(self): + log('Download deployment config for environment %s' % self.env_id) + exec_cmd('fuel deployment --env %s --default --dir %s' + % (self.env_id, self.yaml_config_dir)) + + def upload_deployment_config(self): + log('Upload deployment config for environment %s' % self.env_id) + exec_cmd('fuel deployment --env %s --upload --dir %s' + % (self.env_id, self.yaml_config_dir)) + + def download_interface_config(self, node_id): + log('Download interface config for node %s' % node_id) + exec_cmd('fuel node --env %s --node %s --network --download ' + '--dir %s' % (self.env_id, node_id, self.yaml_config_dir)) + + def upload_interface_config(self, node_id): + log('Upload interface config for node %s' % node_id) + exec_cmd('fuel node --env %s --node %s --network --upload ' + '--dir %s' % (self.env_id, node_id, self.yaml_config_dir)) + + def modify_node_interface(self, node_id, roles_blade): + log('Modify interface config for node %s' % node_id) + interface_yaml = ('%s/node_%s/interfaces.yaml' + % (self.yaml_config_dir, node_id)) + check_file_exists(interface_yaml) + + with io.open(interface_yaml) as stream: + interfaces = yaml.load(stream) + + net_name_id = {} + for interface in interfaces: + for network in interface['assigned_networks']: + net_name_id[network['name']] = network['id'] + + type = self.dea.get_node_property(roles_blade[1], 'interfaces') + interface_config = self.dea.get_interfaces(type) + + for interface in interfaces: + interface['assigned_networks'] = [] + if interface['name'] in interface_config: + for net_name in interface_config[interface['name']]: + net = {} + net['id'] = net_name_id[net_name] + net['name'] = net_name + interface['assigned_networks'].append(net) + + with io.open(interface_yaml, 'w') as stream: + yaml.dump(interfaces, stream, default_flow_style=False) \ No newline at end of file diff --git a/fuel/deploy/cloud/configure_settings.py b/fuel/deploy/cloud/configure_settings.py new file mode 100644 index 0000000..ac0afdc --- /dev/null +++ b/fuel/deploy/cloud/configure_settings.py @@ -0,0 +1,47 @@ +import common +import yaml +import io + +N = common.N +E = common.E +R = common.R +RO = common.RO +exec_cmd = common.exec_cmd +parse = common.parse +err = common.err +check_file_exists = common.check_file_exists +log = common.log + +class ConfigureSettings(object): + + def __init__(self, yaml_config_dir, env_id, dea): + self.yaml_config_dir = yaml_config_dir + self.env_id = env_id + self.dea = dea + + def download_settings(self): + log('Download settings for environment %s' % self.env_id) + exec_cmd('fuel settings --env %s --download --dir %s' + % (self.env_id, self.yaml_config_dir)) + + def upload_settings(self): + log('Upload settings for environment %s' % self.env_id) + exec_cmd('fuel settings --env %s --upload --dir %s' + % (self.env_id, self.yaml_config_dir)) + + def config_settings(self): + log('Configure settings') + self.download_settings() + self.modify_settings() + self.upload_settings() + + def modify_settings(self): + log('Modify settings for environment %s' % self.env_id) + settings_yaml = ('%s/settings_%s.yaml' + % (self.yaml_config_dir, self.env_id)) + check_file_exists(settings_yaml) + + settings = self.dea.get_property('settings') + + with io.open(settings_yaml, 'w') as stream: + yaml.dump(settings, stream, default_flow_style=False) diff --git a/fuel/deploy/cloud/deploy.py b/fuel/deploy/cloud/deploy.py new file mode 100644 index 0000000..c8714f8 --- /dev/null +++ b/fuel/deploy/cloud/deploy.py @@ -0,0 +1,206 @@ +import time +import yaml +import io +import sys + +import common +from dea import DeploymentEnvironmentAdapter +from configure_environment import ConfigureEnvironment +from deployment import Deployment + +YAML_CONF_DIR = '/var/lib/opnfv' + +N = common.N +E = common.E +R = common.R +RO = common.RO +exec_cmd = common.exec_cmd +parse = common.parse +err = common.err +check_file_exists = common.check_file_exists +log = common.log + +class Deploy(object): + + def __init__(self, dea_file, macs_file): + self.dea = DeploymentEnvironmentAdapter(dea_file) + self.macs_file = macs_file + self.macs_per_blade = {} + self.blades = self.dea.get_node_ids() + self.node_ids_dict = {} + self.node_id_roles_dict = {} + self.supported_release = None + self.env_id = None + self.wanted_release = self.dea.get_wanted_release() + + def cleanup_fuel_environments(self, env_list): + WAIT_LOOP = 60 + SLEEP_TIME = 10 + for env in env_list: + log('Deleting environment %s' % env[E['id']]) + exec_cmd('fuel env --env %s --delete' % env[E['id']]) + all_env_erased = False + for i in range(WAIT_LOOP): + env_list = parse(exec_cmd('fuel env list')) + if env_list: + time.sleep(SLEEP_TIME) + else: + all_env_erased = True + break + if not all_env_erased: + err('Could not erase these environments %s' + % [(env[E['id']], env[E['status']]) for env in env_list]) + + def cleanup_fuel_nodes(self, node_list): + for node in node_list: + if node[N['status']] == 'discover': + log('Deleting node %s' % node[N['id']]) + 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): + log('Check previous installation') + 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): + log('Check supported release: %s' % self.wanted_release) + release_list = parse(exec_cmd('fuel release -l')) + for release in release_list: + if release[R['name']] == self.wanted_release: + self.supported_release = release + break + if not self.supported_release: + err('This Fuel does not contain the following release: %s' + % self.wanted_release) + + def check_prerequisites(self): + log('Check prerequisites') + self.check_supported_release() + self.check_previous_installation() + + def get_mac_addresses(self): + with io.open(self.macs_file, 'r') as stream: + self.macs_per_blade = yaml.load(stream) + + def find_mac_in_dict(self, mac): + for blade, mac_list in self.macs_per_blade.iteritems(): + if mac in mac_list: + return blade + + def all_blades_discovered(self): + for blade, node_id in self.node_ids_dict.iteritems(): + if not node_id: + return False + return True + + def not_discovered_blades_summary(self): + summary = '' + for blade, node_id in self.node_ids_dict.iteritems(): + if not node_id: + summary += '\n[blade %s]' % blade + return summary + + def node_discovery(self, node_list, discovered_macs): + for node in node_list: + if (node[N['status']] == 'discover' and + node[N['online']] == 'True' and + node[N['mac']] not in discovered_macs): + discovered_macs.append(node[N['mac']]) + blade = self.find_mac_in_dict(node[N['mac']]) + if blade: + log('Blade %s discovered as Node %s with MAC %s' + % (blade, node[N['id']], node[N['mac']])) + self.node_ids_dict[blade] = node[N['id']] + + def discovery_waiting_loop(self, discovered_macs): + WAIT_LOOP = 180 + SLEEP_TIME = 10 + all_discovered = False + for i in range(WAIT_LOOP): + node_list = parse(exec_cmd('fuel node list')) + if node_list: + self.node_discovery(node_list, discovered_macs) + if self.all_blades_discovered(): + all_discovered = True + break + else: + time.sleep(SLEEP_TIME) + return all_discovered + + def wait_for_discovered_blades(self): + log('Wait for discovered blades') + discovered_macs = [] + for blade in self.blades: + self.node_ids_dict[blade] = None + all_discovered = self.discovery_waiting_loop(discovered_macs) + if not all_discovered: + err('Not all blades have been discovered: %s' + % self.not_discovered_blades_summary()) + + def assign_roles_to_cluster_node_ids(self): + self.node_id_roles_dict = {} + for blade, node_id in self.node_ids_dict.iteritems(): + role_list = [] + role = self.dea.get_node_role(blade) + if role == 'controller': + role_list.extend(['controller', 'mongo']) + elif role == 'compute': + role_list.extend(['compute']) + self.node_id_roles_dict[node_id] = (role_list, blade) + + def configure_environment(self): + config_env = ConfigureEnvironment(self.dea, YAML_CONF_DIR, + self.supported_release[R['id']], + self.node_id_roles_dict) + config_env.configure_environment() + self.env_id = config_env.env_id + + def deploy_cloud(self): + dep = Deployment(self.dea, YAML_CONF_DIR, self.env_id, + self.node_id_roles_dict) + dep.deploy() + + def deploy(self): + self.get_mac_addresses() + self.check_prerequisites() + self.wait_for_discovered_blades() + self.assign_roles_to_cluster_node_ids() + self.configure_environment() + self.deploy_cloud() + +def usage(): + print ''' + Usage: + python deploy.py + + Example: + python deploy.py dea.yaml macs.yaml + ''' + +def parse_arguments(): + if len(sys.argv) != 3: + log('Incorrect number of arguments') + usage() + sys.exit(1) + dea_file = sys.argv[-2] + macs_file = sys.argv[-1] + check_file_exists(dea_file) + check_file_exists(macs_file) + return dea_file, macs_file + +def main(): + + dea_file, macs_file = parse_arguments() + + deploy = Deploy(dea_file, macs_file) + deploy.deploy() + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/fuel/deploy/cloud/deployment.py b/fuel/deploy/cloud/deployment.py new file mode 100644 index 0000000..cf56c36 --- /dev/null +++ b/fuel/deploy/cloud/deployment.py @@ -0,0 +1,113 @@ +import common +import os +import shutil +import glob +import yaml +import io +import time + +N = common.N +E = common.E +R = common.R +RO = common.RO +exec_cmd = common.exec_cmd +run_proc = common.run_proc +parse = common.parse +err = common.err +log = common.log + + +class Deployment(object): + + def __init__(self, dea, yaml_config_dir, env_id, node_id_roles_dict): + self.dea = dea + self.yaml_config_dir = yaml_config_dir + self.env_id = env_id + self.node_id_roles_dict = node_id_roles_dict + + def download_deployment_info(self): + log('Download deployment info for environment %s' % self.env_id) + deployment_dir = '%s/deployment_%s' \ + % (self.yaml_config_dir, self.env_id) + if os.path.exists(deployment_dir): + shutil.rmtree(deployment_dir) + exec_cmd('fuel --env %s deployment --default --dir %s' + % (self.env_id, self.yaml_config_dir)) + + def upload_deployment_info(self): + log('Upload deployment info for environment %s' % self.env_id) + exec_cmd('fuel --env %s deployment --upload --dir %s' + % (self.env_id, self.yaml_config_dir)) + + def config_opnfv(self): + log('Configure OPNFV settings on environment %s' % self.env_id) + opnfv_compute = self.dea.get_opnfv('compute') + opnfv_controller = self.dea.get_opnfv('controller') + self.download_deployment_info() + for node_file in glob.glob('%s/deployment_%s/*.yaml' + % (self.yaml_config_dir, self.env_id)): + with io.open(node_file) as stream: + node = yaml.load(stream) + if node['role'] == 'compute': + node.update(opnfv_compute) + else: + node.update(opnfv_controller) + with io.open(node_file, 'w') as stream: + yaml.dump(node, stream, default_flow_style=False) + self.upload_deployment_info() + + def run_deploy(self): + WAIT_LOOP = 180 + SLEEP_TIME = 60 + LOG_FILE = 'cloud.log' + + log('Starting deployment of environment %s' % self.env_id) + run_proc('fuel --env %s deploy-changes | strings | tee %s' + % (self.env_id, LOG_FILE)) + + ready = False + for i in range(WAIT_LOOP): + env = parse(exec_cmd('fuel env --env %s' % self.env_id)) + log('Environment status: %s' % env[0][E['status']]) + r, _ = exec_cmd('tail -2 %s | head -1' % LOG_FILE, False) + if r: + log(r) + if env[0][E['status']] == 'operational': + ready = True + break + elif env[0][E['status']] == 'error': + break + else: + time.sleep(SLEEP_TIME) + exec_cmd('rm %s' % LOG_FILE) + + if ready: + log('Environment %s successfully deployed' % self.env_id) + else: + err('Deployment failed, environment %s is not operational' + % self.env_id) + + def verify_node_status(self): + node_list = parse(exec_cmd('fuel node list')) + failed_nodes = [] + for node in node_list: + if node[N['status']] != 'ready': + failed_nodes.append((node[N['id']], node[N['status']])) + + if failed_nodes: + summary = '' + for node, status in failed_nodes: + summary += '[node %s, status %s]\n' % (node, status) + err('Deployment failed: %s' % summary) + + def health_check(self): + log('Now running sanity and smoke health checks') + exec_cmd('fuel health --env %s --check sanity,smoke --force' + % self.env_id) + log('Health checks passed !') + + def deploy(self): + self.config_opnfv() + self.run_deploy() + self.verify_node_status() + self.health_check() \ No newline at end of file -- cgit 1.2.3-korg