summaryrefslogtreecommitdiff
path: root/nova/virt/baremetal/virtual_power_driver.py
blob: 2adc611561ef409384d0b7fb23a0ed2782c4613a (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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
# Copyright 2012 Hewlett-Packard Development Company, L.P.
# All Rights Reserved.
#
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
#    not use this file except in compliance with the License. You may obtain
#    a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
#    License for the specific language governing permissions and limitations
#    under the License.
#
# Virtual power driver

from oslo.config import cfg
from oslo.utils import importutils

from nova import context as nova_context
from nova import exception
from nova.i18n import _
from nova.openstack.common import log as logging
from nova.openstack.common import processutils
from nova.virt.baremetal import baremetal_states
from nova.virt.baremetal import base
from nova.virt.baremetal import common as connection
from nova.virt.baremetal import db

opts = [
    cfg.StrOpt('virtual_power_ssh_host',
               default='',
               help='IP or name to virtual power host'),
    cfg.IntOpt('virtual_power_ssh_port',
               default=22,
               help='Port to use for ssh to virtual power host'),
    cfg.StrOpt('virtual_power_type',
               default='virsh',
               help='Base command to use for virtual power(vbox, virsh)'),
    cfg.StrOpt('virtual_power_host_user',
               default='',
               help='User to execute virtual power commands as'),
    cfg.StrOpt('virtual_power_host_pass',
               default='',
               help='Password for virtual power host_user'),
    cfg.StrOpt('virtual_power_host_key',
               help='The ssh key for virtual power host_user'),

]

baremetal_vp = cfg.OptGroup(name='baremetal',
                            title='Baremetal Options')

CONF = cfg.CONF
CONF.register_group(baremetal_vp)
CONF.register_opts(opts, baremetal_vp)

_conn = None
_vp_cmd = None
_cmds = None

LOG = logging.getLogger(__name__)


def _normalize_mac(mac):
    return mac.replace(':', '').lower()


class VirtualPowerManager(base.PowerManager):
    """Virtual Power Driver for Baremetal Nova Compute

    This PowerManager class provides mechanism for controlling the power state
    of VMs based on their name and MAC address. It uses ssh to connect to the
    VM's host and issue commands.

    Node will be matched based on mac address

    NOTE: for use in dev/test environments only!

    """
    def __init__(self, **kwargs):
        global _conn
        global _cmds

        if _cmds is None:
            LOG.debug("Setting up %s commands.",
                      CONF.baremetal.virtual_power_type)
            _vpc = 'nova.virt.baremetal.virtual_power_driver_settings.%s' % \
                    CONF.baremetal.virtual_power_type
            _cmds = importutils.import_class(_vpc)
        self._vp_cmd = _cmds()
        self.connection_data = _conn
        node = kwargs.pop('node', {})
        instance = kwargs.pop('instance', {})
        self._node_name = instance.get('hostname', "")
        context = nova_context.get_admin_context()
        ifs = db.bm_interface_get_all_by_bm_node_id(context, node['id'])
        self._mac_addresses = [_normalize_mac(i['address']) for i in ifs]
        self._connection = None
        self._matched_name = ''
        self.state = None

    def _get_conn(self):
        if not CONF.baremetal.virtual_power_ssh_host:
            raise exception.NovaException(
                _('virtual_power_ssh_host not defined. Can not Start'))

        if not CONF.baremetal.virtual_power_host_user:
            raise exception.NovaException(
                _('virtual_power_host_user not defined. Can not Start'))

        if not CONF.baremetal.virtual_power_host_pass:
            # it is ok to not have a password if you have a keyfile
            if CONF.baremetal.virtual_power_host_key is None:
                raise exception.NovaException(
                    _('virtual_power_host_pass/key not set. Can not Start'))

        _conn = connection.Connection(
            CONF.baremetal.virtual_power_ssh_host,
            CONF.baremetal.virtual_power_host_user,
            CONF.baremetal.virtual_power_host_pass,
            CONF.baremetal.virtual_power_ssh_port,
            CONF.baremetal.virtual_power_host_key)
        return _conn

    def _set_connection(self):
        if self._connection is None:
            if self.connection_data is None:
                self.connection_data = self._get_conn()

            self._connection = connection.ssh_connect(self.connection_data)

    def _get_full_node_list(self):
        LOG.debug("Getting full node list.")
        cmd = self._vp_cmd.list_cmd
        full_list = self._run_command(cmd)
        return full_list

    def _check_for_node(self):
        LOG.debug("Looking up Name for Mac address %s.",
                  self._mac_addresses)
        self._matched_name = ''
        full_node_list = self._get_full_node_list()

        for node in full_node_list:
            cmd = self._vp_cmd.get_node_macs.replace('{_NodeName_}', node)
            mac_address_list = self._run_command(cmd)

            for mac in mac_address_list:
                if _normalize_mac(mac) in self._mac_addresses:
                    self._matched_name = ('"%s"' % node)
                    break
        return self._matched_name

    def activate_node(self):
        LOG.info(_("activate_node name %s"), self._node_name)
        if self._check_for_node():
            cmd = self._vp_cmd.start_cmd
            self._run_command(cmd)

        if self.is_power_on():
            self.state = baremetal_states.ACTIVE
        else:
            self.state = baremetal_states.ERROR
        return self.state

    def reboot_node(self):
        LOG.info(_("reset node: %s"), self._node_name)
        if self._check_for_node():
            cmd = self._vp_cmd.reboot_cmd
            self._run_command(cmd)
        if self.is_power_on():
            self.state = baremetal_states.ACTIVE
        else:
            self.state = baremetal_states.ERROR
        return self.state

    def deactivate_node(self):
        LOG.info(_("deactivate_node name %s"), self._node_name)
        if self._check_for_node():
            if self.is_power_on():
                cmd = self._vp_cmd.stop_cmd
                self._run_command(cmd)

        if self.is_power_on():
            self.state = baremetal_states.ERROR
        else:
            self.state = baremetal_states.DELETED
        return self.state

    def is_power_on(self):
        LOG.debug("Checking if %s is running", self._node_name)

        if not self._check_for_node():
            err_msg = _('Node "%(name)s" with MAC address %(mac)s not found.')
            LOG.error(err_msg, {'name': self._node_name,
                                'mac': self._mac_addresses})
            # in our case the _node_name is the node_id
            raise exception.NodeNotFound(node_id=self._node_name)

        cmd = self._vp_cmd.list_running_cmd
        running_node_list = self._run_command(cmd)

        for node in running_node_list:
            if self._matched_name in node:
                return True
        return False

    def _run_command(self, cmd, check_exit_code=True):
        """Run a remote command using an active ssh connection.

        :param command: String with the command to run.

        If {_NodeName_} is in the command it will get replaced by
        the _matched_name value.

        base_cmd will also get prepended to the command.
        """
        self._set_connection()

        cmd = cmd.replace('{_NodeName_}', self._matched_name)

        cmd = '%s %s' % (self._vp_cmd.base_cmd, cmd)

        try:
            stdout, stderr = processutils.ssh_execute(
                self._connection, cmd, check_exit_code=check_exit_code)
            result = stdout.strip().splitlines()
            LOG.debug('Result for run_command: %s', result)
        except processutils.ProcessExecutionError:
            result = []
            LOG.exception(_("Error running command: %s"), cmd)
        return result