summaryrefslogtreecommitdiffstats
path: root/apex/overcloud/node.py
blob: 622d1fd1beb81e3f2ee5a2501da2281e1f194331 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
##############################################################################
# Copyright (c) 2018 Tim Rozet (trozet@redhat.com) 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 logging
import os
import shutil
import xml.etree.ElementTree as ET

import distro
import libvirt

from apex.common.exceptions import OvercloudNodeException


class OvercloudNode:
    """
    Overcloud server
    """
    def __init__(self, role, ip, ovs_ctrlrs, ovs_mgrs, name, node_xml,
                 disk_img):
        self.role = role
        self.ip = ip
        self.ovs_ctrlrs = ovs_ctrlrs
        self.ovs_mgrs = ovs_mgrs
        self.name = name
        self.node_xml_file = node_xml
        self.node_xml = None
        self.vm = None
        self.disk_img = None
        if not os.path.isfile(self.node_xml_file):
            raise OvercloudNodeException('XML definition file not found: '
                                         '{}'.format(self.node_xml_file))
        if not os.path.isfile(disk_img):
            raise OvercloudNodeException('Disk image file not found: '
                                         '{}'.format(disk_img))
        self.conn = libvirt.open('qemu:///system')
        if not self.conn:
            raise OvercloudNodeException('Unable to open libvirt connection')

        self.create(src_disk=disk_img)

    def _configure_disk(self, disk):
        # find default storage pool path
        pool = self.conn.storagePoolLookupByName('default')
        if pool is None:
            raise OvercloudNodeException('Cannot find default storage pool')
        pool_xml = pool.XMLDesc()
        logging.debug('Default storage pool xml: {}'.format(pool_xml))
        etree = ET.fromstring(pool_xml)
        try:
            path = etree.find('target').find('path').text
            logging.info('System libvirt default pool path: {}'.format(path))
        except AttributeError as e:
            logging.error('Failure to find libvirt storage path: {}'.format(
                e))
            raise OvercloudNodeException('Cannot find default storage path')
        # copy disk to system path
        self.disk_img = os.path.join(path, os.path.basename(disk))
        logging.info('Copying disk image to: {}. This may take some '
                     'time...'.format(self.disk_img))
        shutil.copyfile(disk, self.disk_img)

    @staticmethod
    def _update_xml(xml, disk_path=None):
        """
        Updates a libvirt XML file for the current architecture and OS of this
        machine
        :param xml: XML string of Libvirt domain definition
        :param disk_path: Optional file path to update for the backing disk
        image
        :return: Updated XML
        """
        logging.debug('Parsing xml')
        try:
            etree = ET.fromstring(xml)
        except ET.ParseError:
            logging.error('Unable to parse node XML: {}'.format(xml))
            raise OvercloudNodeException('Unable to parse node XML')

        try:
            type_element = etree.find('os').find('type')
            if 'machine' in type_element.keys():
                type_element.set('machine', 'pc')
                logging.debug('XML updated with machine "pc"')
        except AttributeError:
            logging.warning('Failure to set XML machine type')

        # qemu-kvm path may differ per system, need to detect it and update xml
        linux_ver = distro.linux_distribution()[0]
        if linux_ver == 'Fedora':
            qemu_path = '/usr/bin/qemu-kvm'
        else:
            qemu_path = '/usr/libexec/qemu-kvm'

        try:
            etree.find('devices').find('emulator').text = qemu_path
            logging.debug('XML updated with emulator location: '
                          '{}'.format(qemu_path))
            xml = ET.tostring(etree).decode('utf-8')
        except AttributeError:
            logging.warning('Failure to update XML qemu path')

        if disk_path:
            try:
                disk_element = etree.find('devices').find('disk').find(
                    'source')
                disk_element.set('file', disk_path)
                logging.debug('XML updated with file path: {}'.format(
                    disk_path))
            except AttributeError:
                logging.error('Failure to parse XML and set disk type')
                raise OvercloudNodeException(
                    'Unable to set new disk path in xml {}'.format(xml))

        return ET.tostring(etree).decode('utf-8')

    def create(self, src_disk):
        # copy disk to pool and get new disk location
        logging.debug('Preparing disk image')
        self._configure_disk(src_disk)
        logging.debug('Parsing node XML from {}'.format(self.node_xml_file))
        with open(self.node_xml_file, 'r') as fh:
            self.node_xml = fh.read()
        # if machine is not pc we need to set, also need to update qemu-kvm and
        # storage location
        self.node_xml = self._update_xml(self.node_xml, self.disk_img)
        logging.info('Creating node {} in libvirt'.format(self.name))
        self.vm = self.conn.defineXML(self.node_xml)

    def start(self):
        """
        Boot node in libvirt
        :return:
        """
        try:
            self.vm.create()
            logging.info('Node {} started'.format(self.name))
        except libvirt.libvirtError as e:
            logging.error('Failed to start domain: {}'.format(self.name))
            raise OvercloudNodeException('Failed to start VM. Reason: '
                                         '{}'.format(e))