############################################################################## # Copyright (c) 2017 ZTE Corporation and others. # # All rights reserved. This program and the accompanying materials # are made available under the terms of the Apache License, Version 2.0 # which accompanies this distribution, and is available at # http://www.apache.org/licenses/LICENSE-2.0 ############################################################################## import commands import itertools import os import shutil import time from config.schemas import ( MIN_NODE_DISK_SIZE, MIN_CEPH_DISK_SIZE ) from daisy_server import ( DaisyServer ) from libvirt_utils import ( create_virtual_disk, create_vm, reboot_vm, delete_vm_and_disk, create_virtual_network, delete_virtual_network, get_vm_mac_addresses ) from utils import ( WORKSPACE, LD, LI, LW, err_exit, run_shell, path_join, ipmi_reboot_node, ) CREATE_QCOW2_PATH = path_join(WORKSPACE, 'tools') VIRT_NET_TEMPLATE_PATH = path_join(WORKSPACE, 'templates/virtual_environment/networks') VMDEPLOY_DAISY_SERVER_NET = path_join(VIRT_NET_TEMPLATE_PATH, 'daisy.xml') VMDEPLOY_TARGET_NODE_NET = path_join(VIRT_NET_TEMPLATE_PATH, 'external.xml') VMDEPLOY_TARGET_KEEPALIVED_NET = path_join(VIRT_NET_TEMPLATE_PATH, 'keepalived.xml') VMDEPLOY_DAISY_SERVER_VM = path_join(WORKSPACE, 'templates/virtual_environment/vms/daisy.xml') BMDEPLOY_DAISY_SERVER_VM = path_join(WORKSPACE, 'templates/physical_environment/vms/daisy.xml') ALL_IN_ONE_TEMPLATE = path_join(WORKSPACE, 'templates/virtual_environment/vms/all_in_one.xml') CONTROLLER_TEMPLATE = path_join(WORKSPACE, 'templates/virtual_environment/vms/controller.xml') COMPUTE_TEMPLATE = path_join(WORKSPACE, 'templates/virtual_environment/vms/computer.xml') class DaisyEnvironment(object): def __new__(cls, deploy_struct, net_struct, adapter, pxe_bridge, daisy_server_info, work_dir, storage_dir, scenario): if adapter == 'libvirt': return VirtualEnvironment(deploy_struct, net_struct, adapter, pxe_bridge, daisy_server_info, work_dir, storage_dir, scenario) else: return BareMetalEnvironment(deploy_struct, net_struct, adapter, pxe_bridge, daisy_server_info, work_dir, storage_dir, scenario) class DaisyEnvironmentBase(object): def __init__(self, deploy_struct, net_struct, adapter, pxe_bridge, daisy_server_info, work_dir, storage_dir, scenario): self.deploy_struct = deploy_struct self.net_struct = net_struct self.adapter = adapter self.pxe_bridge = pxe_bridge self.work_dir = work_dir self.storage_dir = storage_dir self.daisy_server_info = daisy_server_info self.server = None self.scenario = scenario LI('Daisy Environment Initialized') def delete_daisy_server(self): delete_vm_and_disk(self.daisy_server_info['name']) def create_daisy_server_image(self): LI('Begin to create Daisy Server image') script = path_join(CREATE_QCOW2_PATH, 'daisy-img-modify.sh') sub_script = path_join(CREATE_QCOW2_PATH, 'centos-img-modify.sh') cmd = '{script} -c {sub_script} -a {address} -g {gateway} -s {disk_size}'.format( script=script, sub_script=sub_script, address=self.daisy_server_info['address'], gateway=self.daisy_server_info['gateway'], disk_size=self.daisy_server_info['disk_size']) LI('Command is: ') LI(' %s' % cmd) # status, output = commands.getstatusoutput(cmd) status = run_shell(cmd) if status: err_exit('Failed to create Daisy Server image') if os.access(self.daisy_server_info['image'], os.R_OK): os.remove(self.daisy_server_info['image']) image = path_join(self.work_dir, 'daisy/centos7.qcow2') shutil.move(image, self.daisy_server_info['image']) LI('Daisy Server image is created %s' % self.daisy_server_info['image']) def connect_daisy_server(self, remote_dir, bin_file, deploy_file_name, net_file_name): self.server = DaisyServer(self.daisy_server_info['name'], self.daisy_server_info['address'], self.daisy_server_info['password'], remote_dir, bin_file, self.adapter, self.scenario, deploy_file_name, net_file_name) self.server.connect() def install_daisy(self): self.server.install_daisy() class BareMetalEnvironment(DaisyEnvironmentBase): def delete_old_environment(self, skip_daisy=False): if skip_daisy: LI('Skip deletion of old daisy server VM') else: LW('Begin to delete old environment !') self.delete_daisy_server() LW('Old environment cleanup finished !') def create_daisy_server(self): self.create_daisy_server_image() self.create_daisy_server_vm() def create_daisy_server_vm(self): # TODO: refactor the structure of deploy.yml, add VM template param of Daisy Server if 'template' in self.deploy_struct: # get VM name of Daisy Server from the template template = self.deploy_struct['template'] else: template = BMDEPLOY_DAISY_SERVER_VM create_vm(template, name=self.daisy_server_info['name'], disks=[self.daisy_server_info['image']], physical_bridge=self.pxe_bridge) def reboot_nodes(self, boot_dev=None): for node in self.deploy_struct['hosts']: if 'ipmi_ip' not in node \ or 'ipmi_user' not in node \ or 'ipmi_pass' not in node: err_exit('Missing ipmi information') ipmi_reboot_node(node['ipmi_ip'], node['ipmi_user'], node['ipmi_pass'], boot_source=boot_dev) def deploy(self, deploy_file, net_file, skip_preparation=False): if not skip_preparation: self.server.prepare_configurations(deploy_file, net_file) self.server.prepare_cluster() self.reboot_nodes(boot_dev='pxe') self.server.prepare_host_and_pxe() LI('The hosts number is %d' % len(self.deploy_struct['hosts'])) self.server.check_os_installation(len(self.deploy_struct['hosts'])) time.sleep(10) self.server.check_openstack_installation(len(self.deploy_struct['hosts'])) self.server.post_deploy() class VirtualEnvironment(DaisyEnvironmentBase): def __init__(self, deploy_struct, net_struct, adapter, pxe_bridge, daisy_server_info, work_dir, storage_dir, scenario): super(VirtualEnvironment, self).__init__(deploy_struct, net_struct, adapter, pxe_bridge, daisy_server_info, work_dir, storage_dir, scenario) self.check_configuration() self._daisy_server_net = None self._daisy_os_net = None self._daisy_keepalived_net = None def check_configuration(self): self.check_nodes_template() def check_nodes_template(self): for node in self.deploy_struct['hosts']: template = node.get('template', None) if not template or os.access(template, os.R_OK): continue elif os.access(path_join(WORKSPACE, template), os.R_OK): template_new = path_join(WORKSPACE, template) LD('Template of VM node %s is %s' % (node.get('name', ''), template_new)) node['template'] = template_new else: err_exit('The template of vm node %s does not exist.' % node.get('name')) def create_daisy_server_network(self): net_name = create_virtual_network(VMDEPLOY_DAISY_SERVER_NET) if net_name != self.pxe_bridge: delete_virtual_network(VMDEPLOY_DAISY_SERVER_NET) err_exit('Network name %s is wrong, pxe bridge is %s' % (net_name, self.pxe_bridge)) self._daisy_server_net = net_name def create_daisy_server_vm(self): # TODO: refactor the structure of deploy.yml, add VM template param of Daisy Server if 'template' in self.deploy_struct: # get VM name of Daisy Server from the template template = self.deploy_struct['template'] else: template = VMDEPLOY_DAISY_SERVER_VM create_vm(template, name=self.daisy_server_info['name'], disks=[self.daisy_server_info['image']]) def create_daisy_server(self): self.create_daisy_server_image() self.create_daisy_server_network() self.create_daisy_server_vm() def create_virtual_node(self, node): name = node['name'] roles = node['roles'] disks = self.deploy_struct.get('disks', {}) controller_size = disks.get('controller', MIN_NODE_DISK_SIZE) compute_size = disks.get('compute', MIN_NODE_DISK_SIZE) LI('Begin to create virtual node %s, roles %s' % (name, roles)) if 'CONTROLLER_LB' in roles: size = controller_size if 'COMPUTER' in roles: size = compute_size if compute_size > controller_size else controller_size template = ALL_IN_ONE_TEMPLATE else: template = CONTROLLER_TEMPLATE else: size = compute_size template = COMPUTE_TEMPLATE if 'template' in node: template = node['template'] disk_file = path_join(self.storage_dir, name + '.qcow2') create_virtual_disk(disk_file, size) disks = [disk_file] ceph_disk_name = self.deploy_struct.get('ceph_disk_name', '') # if ceph_disk_name and ceph_disk_name != '/dev/sda' and 'CONTROLLER_LB' in roles: if ceph_disk_name and ceph_disk_name != '/dev/sda': ceph_size = self.deploy_struct.get('disks', {}).get('ceph', MIN_CEPH_DISK_SIZE) ceph_disk_file = path_join(self.storage_dir, name + '_data.qcow2') create_virtual_disk(ceph_disk_file, ceph_size) disks.append(ceph_disk_file) return create_vm(template, name, disks) def create_nodes(self): # TODO: support virtNetTemplatePath in deploy.yml # and multi interfaces, not only all-in-one net_name = create_virtual_network(VMDEPLOY_TARGET_NODE_NET) self._daisy_os_net = net_name net_name = create_virtual_network(VMDEPLOY_TARGET_KEEPALIVED_NET) self._daisy_keepalived_net = net_name for node in self.deploy_struct['hosts']: domain = self.create_virtual_node(node) node['mac_addresses'] = get_vm_mac_addresses(domain) time.sleep(20) def reboot_nodes(self, boot_devs=None): for node in self.deploy_struct['hosts']: reboot_vm(node['name'], boot_devs=boot_devs) def delete_nodes(self): for host in self.deploy_struct['hosts']: delete_vm_and_disk(host['name']) def delete_networks(self, skip_daisy=False): if 'virtNetTemplatePath' in self.deploy_struct: path = self.deploy_struct['virtNetTemplatePath'] else: path = VIRT_NET_TEMPLATE_PATH if not os.path.isdir(path): LW('Cannot find the virtual network template path %s' % path) return for f in os.listdir(path): if not (skip_daisy and f == 'daisy.xml'): f = path_join(path, f) if os.path.isfile(f): delete_virtual_network(f) def delete_old_environment(self, skip_daisy=False): LW('Begin to delete old environment !') self.delete_nodes() if skip_daisy: LI('Skip deletion of old daisy server VM and network') else: self.delete_daisy_server() self.delete_networks(skip_daisy=skip_daisy) LW('Old environment cleanup finished !') def deploy(self, deploy_file, net_file, skip_preparation=False): if not skip_preparation: self.server.prepare_configurations(deploy_file, net_file) self.server.prepare_cluster() self.create_nodes() self.server.copy_new_deploy_config(self.deploy_struct) self.server.prepare_host_and_pxe() LI('Begin Daisy virtual-deploy os and openstack') self.reboot_nodes() LI('Sleep 20s to wait the VM(s) startup') time.sleep(20) self.server.install_virtual_nodes() LI('The hosts number is %d' % len(self.deploy_struct['hosts'])) self.server.check_os_installation(len(self.deploy_struct['hosts'])) time.sleep(10) self.reboot_nodes(boot_devs=['hd']) self.server.check_openstack_installation(len(self.deploy_struct['hosts'])) self.server.post_deploy() self._post_deploy() def _post_deploy(self): LI('Disable iptables rules of daisy networks on deploy server') networks = [self._daisy_server_net, self._daisy_os_net] options = ['-o', '-i'] for (network, option) in list(itertools.product(networks, options)): cmd = 'iptables -D FORWARD {option} {network} -j REJECT --reject-with icmp-port-unreachable'.format( option=option, network=network) status, output = commands.getstatusoutput(cmd) if status: LW('iptables command failed: %s' % output) else: LI('iptables command success: %s' % cmd)