Merge "Remove baremetal virt driver"
This commit is contained in:
@@ -30,8 +30,6 @@ Reference
|
||||
nova-api-metadata
|
||||
nova-api-os-compute
|
||||
nova-api
|
||||
nova-baremetal-deploy-helper
|
||||
nova-baremetal-manage
|
||||
nova-cert
|
||||
nova-compute
|
||||
nova-conductor
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
============================
|
||||
nova-baremetal-deploy-helper
|
||||
============================
|
||||
|
||||
------------------------------------------------------------------
|
||||
Writes images to a bare-metal node and switch it to instance-mode
|
||||
------------------------------------------------------------------
|
||||
|
||||
:Author: openstack@lists.openstack.org
|
||||
:Date: 2012-10-17
|
||||
:Copyright: OpenStack Foundation
|
||||
:Version: 2013.1
|
||||
:Manual section: 1
|
||||
:Manual group: cloud computing
|
||||
|
||||
SYNOPSIS
|
||||
========
|
||||
|
||||
nova-baremetal-deploy-helper
|
||||
|
||||
DESCRIPTION
|
||||
===========
|
||||
|
||||
This is a service which should run on nova-compute host when using the
|
||||
baremetal driver. During a baremetal node's first boot,
|
||||
nova-baremetal-deploy-helper works in conjunction with diskimage-builder's
|
||||
"deploy" ramdisk to write an image from glance onto the baremetal node's disks
|
||||
using iSCSI. After that is complete, nova-baremetal-deploy-helper switches the
|
||||
PXE config to reference the kernel and ramdisk which correspond to the running
|
||||
image.
|
||||
|
||||
OPTIONS
|
||||
=======
|
||||
|
||||
**General options**
|
||||
|
||||
FILES
|
||||
========
|
||||
|
||||
* /etc/nova/nova.conf
|
||||
* /etc/nova/rootwrap.conf
|
||||
* /etc/nova/rootwrap.d/
|
||||
|
||||
SEE ALSO
|
||||
========
|
||||
|
||||
* `OpenStack Nova <http://nova.openstack.org>`__
|
||||
|
||||
BUGS
|
||||
====
|
||||
|
||||
* Nova bugs are managed at Launchpad `Bugs : Nova <https://bugs.launchpad.net/nova>`__
|
||||
@@ -1,67 +0,0 @@
|
||||
=====================
|
||||
nova-baremetal-manage
|
||||
=====================
|
||||
|
||||
------------------------------------------------------
|
||||
Manage bare-metal DB in OpenStack Nova
|
||||
------------------------------------------------------
|
||||
|
||||
:Author: openstack@lists.openstack.org
|
||||
:Date: 2012-10-17
|
||||
:Copyright: OpenStack Foundation
|
||||
:Version: 2013.1
|
||||
:Manual section: 1
|
||||
:Manual group: cloud computing
|
||||
|
||||
SYNOPSIS
|
||||
========
|
||||
|
||||
nova-baremetal-manage <category> <action> [<args>]
|
||||
|
||||
DESCRIPTION
|
||||
===========
|
||||
|
||||
nova-baremetal-manage manages bare-metal DB schema.
|
||||
|
||||
OPTIONS
|
||||
=======
|
||||
|
||||
The standard pattern for executing a nova-baremetal-manage command is:
|
||||
``nova-baremetal-manage <category> <command> [<args>]``
|
||||
|
||||
Run without arguments to see a list of available command categories:
|
||||
``nova-baremetal-manage``
|
||||
|
||||
Categories are db. Detailed descriptions are below.
|
||||
|
||||
You can also run with a category argument such as "db" to see a list of all commands in that category:
|
||||
``nova-baremetal-manage db``
|
||||
|
||||
These sections describe the available categories and arguments for nova-baremetal-manage.
|
||||
|
||||
Bare-Metal DB
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
``nova-baremetal-manage db version``
|
||||
|
||||
Print the current database version.
|
||||
|
||||
``nova-baremetal-manage db sync``
|
||||
|
||||
Sync the database up to the most recent version. This is the standard way to create the db as well.
|
||||
|
||||
FILES
|
||||
========
|
||||
|
||||
/etc/nova/nova.conf: get location of bare-metal DB
|
||||
|
||||
SEE ALSO
|
||||
========
|
||||
|
||||
* `OpenStack Nova <http://nova.openstack.org>`__
|
||||
|
||||
BUGS
|
||||
====
|
||||
|
||||
* Nova bugs are managed at Launchpad `Bugs : Nova <https://bugs.launchpad.net/nova>`__
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
|
||||
"""The bare-metal admin extension with Ironic Proxy."""
|
||||
|
||||
import netaddr
|
||||
from oslo.config import cfg
|
||||
from oslo.utils import importutils
|
||||
import webob
|
||||
@@ -23,10 +22,8 @@ import webob
|
||||
from nova.api.openstack import extensions
|
||||
from nova.api.openstack import wsgi
|
||||
from nova.api.openstack import xmlutil
|
||||
from nova import exception
|
||||
from nova.i18n import _
|
||||
from nova.openstack.common import log as logging
|
||||
from nova.virt.baremetal import db
|
||||
|
||||
ironic_client = importutils.try_import('ironicclient.client')
|
||||
|
||||
@@ -80,14 +77,6 @@ def _make_interface_elem(elem):
|
||||
elem.set(f)
|
||||
|
||||
|
||||
def _use_ironic():
|
||||
# TODO(lucasagomes): This switch this should also be deleted as
|
||||
# part of the Nova Baremetal removal effort. At that point, any
|
||||
# code that checks it should assume True, the False case should be
|
||||
# removed, and this API will only/always proxy to Ironic.
|
||||
return 'ironic' in CONF.compute_driver
|
||||
|
||||
|
||||
def _get_ironic_client():
|
||||
"""return an Ironic client."""
|
||||
# TODO(NobodyCam): Fix insecure setting
|
||||
@@ -110,20 +99,6 @@ def _no_ironic_proxy(cmd):
|
||||
"action.") % {'cmd': cmd})
|
||||
|
||||
|
||||
def is_valid_mac(address):
|
||||
"""Verify the format of a MAC address."""
|
||||
|
||||
class mac_dialect(netaddr.mac_eui48):
|
||||
word_fmt = '%.02x'
|
||||
word_sep = ':'
|
||||
|
||||
try:
|
||||
na = netaddr.EUI(address, dialect=mac_dialect)
|
||||
except Exception:
|
||||
return False
|
||||
return str(na) == address.lower()
|
||||
|
||||
|
||||
class NodeTemplate(xmlutil.TemplateBuilder):
|
||||
def construct(self):
|
||||
node_elem = xmlutil.TemplateElement('node', selector='node')
|
||||
@@ -149,13 +124,6 @@ class NodesTemplate(xmlutil.TemplateBuilder):
|
||||
return xmlutil.MasterTemplate(root, 1)
|
||||
|
||||
|
||||
class InterfaceTemplate(xmlutil.TemplateBuilder):
|
||||
def construct(self):
|
||||
root = xmlutil.TemplateElement('interface', selector='interface')
|
||||
_make_interface_elem(root)
|
||||
return xmlutil.MasterTemplate(root, 1)
|
||||
|
||||
|
||||
class BareMetalNodeController(wsgi.Controller):
|
||||
"""The Bare-Metal Node API controller for the OpenStack API.
|
||||
|
||||
@@ -182,161 +150,54 @@ class BareMetalNodeController(wsgi.Controller):
|
||||
context = req.environ['nova.context']
|
||||
authorize(context)
|
||||
nodes = []
|
||||
if _use_ironic():
|
||||
# proxy command to Ironic
|
||||
icli = _get_ironic_client()
|
||||
ironic_nodes = icli.node.list(detail=True)
|
||||
for inode in ironic_nodes:
|
||||
node = {'id': inode.uuid,
|
||||
'interfaces': [],
|
||||
'host': 'IRONIC MANAGED',
|
||||
'task_state': inode.provision_state,
|
||||
'cpus': inode.properties['cpus'],
|
||||
'memory_mb': inode.properties['memory_mb'],
|
||||
'disk_gb': inode.properties['local_gb']}
|
||||
nodes.append(node)
|
||||
else:
|
||||
# use nova baremetal
|
||||
nodes_from_db = db.bm_node_get_all(context)
|
||||
for node_from_db in nodes_from_db:
|
||||
try:
|
||||
ifs = db.bm_interface_get_all_by_bm_node_id(
|
||||
context, node_from_db['id'])
|
||||
except exception.NodeNotFound:
|
||||
ifs = []
|
||||
node = self._node_dict(node_from_db)
|
||||
node['interfaces'] = [_interface_dict(i) for i in ifs]
|
||||
nodes.append(node)
|
||||
return {'nodes': nodes}
|
||||
|
||||
@wsgi.serializers(xml=NodeTemplate)
|
||||
def show(self, req, id):
|
||||
context = req.environ['nova.context']
|
||||
authorize(context)
|
||||
if _use_ironic():
|
||||
# proxy command to Ironic
|
||||
icli = _get_ironic_client()
|
||||
inode = icli.node.get(id)
|
||||
iports = icli.node.list_ports(id)
|
||||
# proxy command to Ironic
|
||||
icli = _get_ironic_client()
|
||||
ironic_nodes = icli.node.list(detail=True)
|
||||
for inode in ironic_nodes:
|
||||
node = {'id': inode.uuid,
|
||||
'interfaces': [],
|
||||
'host': 'IRONIC MANAGED',
|
||||
'task_state': inode.provision_state,
|
||||
'cpus': inode.properties['cpus'],
|
||||
'memory_mb': inode.properties['memory_mb'],
|
||||
'disk_gb': inode.properties['local_gb'],
|
||||
'instance_uuid': inode.instance_uuid}
|
||||
for port in iports:
|
||||
node['interfaces'].append({'address': port.address})
|
||||
else:
|
||||
# use nova baremetal
|
||||
try:
|
||||
node = db.bm_node_get(context, id)
|
||||
except exception.NodeNotFound:
|
||||
raise webob.exc.HTTPNotFound()
|
||||
try:
|
||||
ifs = db.bm_interface_get_all_by_bm_node_id(context, id)
|
||||
except exception.NodeNotFound:
|
||||
ifs = []
|
||||
node = self._node_dict(node)
|
||||
node['interfaces'] = [_interface_dict(i) for i in ifs]
|
||||
'disk_gb': inode.properties['local_gb']}
|
||||
nodes.append(node)
|
||||
return {'nodes': nodes}
|
||||
|
||||
@wsgi.serializers(xml=NodeTemplate)
|
||||
def show(self, req, id):
|
||||
context = req.environ['nova.context']
|
||||
authorize(context)
|
||||
# proxy command to Ironic
|
||||
icli = _get_ironic_client()
|
||||
inode = icli.node.get(id)
|
||||
iports = icli.node.list_ports(id)
|
||||
node = {'id': inode.uuid,
|
||||
'interfaces': [],
|
||||
'host': 'IRONIC MANAGED',
|
||||
'task_state': inode.provision_state,
|
||||
'cpus': inode.properties['cpus'],
|
||||
'memory_mb': inode.properties['memory_mb'],
|
||||
'disk_gb': inode.properties['local_gb'],
|
||||
'instance_uuid': inode.instance_uuid}
|
||||
for port in iports:
|
||||
node['interfaces'].append({'address': port.address})
|
||||
return {'node': node}
|
||||
|
||||
@wsgi.serializers(xml=NodeTemplate)
|
||||
def create(self, req, body):
|
||||
if _use_ironic():
|
||||
_no_ironic_proxy("node-create")
|
||||
|
||||
context = req.environ['nova.context']
|
||||
authorize(context)
|
||||
values = body['node'].copy()
|
||||
prov_mac_address = values.pop('prov_mac_address', None)
|
||||
if (prov_mac_address is not None
|
||||
and not is_valid_mac(prov_mac_address)):
|
||||
raise webob.exc.HTTPBadRequest(
|
||||
explanation=_("Must specify address "
|
||||
"in the form of xx:xx:xx:xx:xx:xx"))
|
||||
node = db.bm_node_create(context, values)
|
||||
node = self._node_dict(node)
|
||||
if prov_mac_address:
|
||||
if_id = db.bm_interface_create(context,
|
||||
bm_node_id=node['id'],
|
||||
address=prov_mac_address,
|
||||
datapath_id=None,
|
||||
port_no=None)
|
||||
if_ref = db.bm_interface_get(context, if_id)
|
||||
node['interfaces'] = [_interface_dict(if_ref)]
|
||||
else:
|
||||
node['interfaces'] = []
|
||||
return {'node': node}
|
||||
_no_ironic_proxy("port-create")
|
||||
|
||||
def delete(self, req, id):
|
||||
if _use_ironic():
|
||||
_no_ironic_proxy("node-delete")
|
||||
_no_ironic_proxy("port-create")
|
||||
|
||||
context = req.environ['nova.context']
|
||||
authorize(context)
|
||||
try:
|
||||
db.bm_node_destroy(context, id)
|
||||
except exception.NodeNotFound:
|
||||
raise webob.exc.HTTPNotFound()
|
||||
return webob.Response(status_int=202)
|
||||
|
||||
def _check_node_exists(self, context, node_id):
|
||||
try:
|
||||
db.bm_node_get(context, node_id)
|
||||
except exception.NodeNotFound:
|
||||
raise webob.exc.HTTPNotFound()
|
||||
|
||||
@wsgi.serializers(xml=InterfaceTemplate)
|
||||
@wsgi.action('add_interface')
|
||||
def _add_interface(self, req, id, body):
|
||||
if _use_ironic():
|
||||
_no_ironic_proxy("port-create")
|
||||
_no_ironic_proxy("port-create")
|
||||
|
||||
context = req.environ['nova.context']
|
||||
authorize(context)
|
||||
self._check_node_exists(context, id)
|
||||
body = body['add_interface']
|
||||
address = body['address']
|
||||
datapath_id = body.get('datapath_id')
|
||||
port_no = body.get('port_no')
|
||||
if not is_valid_mac(address):
|
||||
raise webob.exc.HTTPBadRequest(
|
||||
explanation=_("Must specify address "
|
||||
"in the form of xx:xx:xx:xx:xx:xx"))
|
||||
if_id = db.bm_interface_create(context,
|
||||
bm_node_id=id,
|
||||
address=address,
|
||||
datapath_id=datapath_id,
|
||||
port_no=port_no)
|
||||
if_ref = db.bm_interface_get(context, if_id)
|
||||
return {'interface': _interface_dict(if_ref)}
|
||||
|
||||
@wsgi.response(202)
|
||||
@wsgi.action('remove_interface')
|
||||
def _remove_interface(self, req, id, body):
|
||||
if _use_ironic():
|
||||
_no_ironic_proxy("port-delete")
|
||||
|
||||
context = req.environ['nova.context']
|
||||
authorize(context)
|
||||
self._check_node_exists(context, id)
|
||||
body = body['remove_interface']
|
||||
if_id = body.get('id')
|
||||
address = body.get('address')
|
||||
if not if_id and not address:
|
||||
raise webob.exc.HTTPBadRequest(
|
||||
explanation=_("Must specify id or address"))
|
||||
ifs = db.bm_interface_get_all_by_bm_node_id(context, id)
|
||||
for i in ifs:
|
||||
if if_id and if_id != i['id']:
|
||||
continue
|
||||
if address and address != i['address']:
|
||||
continue
|
||||
db.bm_interface_destroy(context, i['id'])
|
||||
return webob.Response(status_int=202)
|
||||
raise webob.exc.HTTPNotFound()
|
||||
_no_ironic_proxy("port-delete")
|
||||
|
||||
|
||||
class Baremetal_nodes(extensions.ExtensionDescriptor):
|
||||
|
||||
@@ -1,376 +0,0 @@
|
||||
# Copyright (c) 2012 NTT DOCOMO, INC.
|
||||
# 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.
|
||||
|
||||
"""Starter script for Bare-Metal Deployment Service."""
|
||||
|
||||
|
||||
import cgi
|
||||
import os
|
||||
import Queue
|
||||
import re
|
||||
import socket
|
||||
import stat
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
from wsgiref import simple_server
|
||||
|
||||
from oslo.utils import excutils
|
||||
from oslo.utils import units
|
||||
|
||||
from nova import config
|
||||
from nova import context as nova_context
|
||||
from nova.i18n import _
|
||||
from nova import objects
|
||||
from nova.openstack.common import log as logging
|
||||
from nova.openstack.common import processutils
|
||||
from nova import utils
|
||||
from nova.virt.baremetal import baremetal_states
|
||||
from nova.virt.baremetal import db
|
||||
from nova.virt.disk import api as disk
|
||||
|
||||
|
||||
QUEUE = Queue.Queue()
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class BareMetalDeployException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
# All functions are called from deploy() directly or indirectly.
|
||||
# They are split for stub-out.
|
||||
|
||||
def discovery(portal_address, portal_port):
|
||||
"""Do iSCSI discovery on portal."""
|
||||
utils.execute('iscsiadm',
|
||||
'-m', 'discovery',
|
||||
'-t', 'st',
|
||||
'-p', '%s:%s' % (portal_address, portal_port),
|
||||
run_as_root=True,
|
||||
check_exit_code=[0])
|
||||
|
||||
|
||||
def login_iscsi(portal_address, portal_port, target_iqn):
|
||||
"""Login to an iSCSI target."""
|
||||
utils.execute('iscsiadm',
|
||||
'-m', 'node',
|
||||
'-p', '%s:%s' % (portal_address, portal_port),
|
||||
'-T', target_iqn,
|
||||
'--login',
|
||||
run_as_root=True,
|
||||
check_exit_code=[0])
|
||||
# Ensure the login complete
|
||||
time.sleep(10)
|
||||
|
||||
|
||||
def logout_iscsi(portal_address, portal_port, target_iqn):
|
||||
"""Logout from an iSCSI target."""
|
||||
utils.execute('iscsiadm',
|
||||
'-m', 'node',
|
||||
'-p', '%s:%s' % (portal_address, portal_port),
|
||||
'-T', target_iqn,
|
||||
'--logout',
|
||||
run_as_root=True,
|
||||
check_exit_code=[0])
|
||||
|
||||
|
||||
def make_partitions(dev, root_mb, swap_mb, ephemeral_mb):
|
||||
"""Create partitions for root, ephemeral and swap on a disk device."""
|
||||
# Lead in with 1MB to allow room for the partition table itself, otherwise
|
||||
# the way sfdisk adjusts doesn't shift the partition up to compensate, and
|
||||
# we lose the space.
|
||||
# http://bazaar.launchpad.net/~ubuntu-branches/ubuntu/raring/util-linux/
|
||||
# raring/view/head:/fdisk/sfdisk.c#L1940
|
||||
if ephemeral_mb:
|
||||
stdin_command = ('1,%d,83;\n,%d,82;\n,%d,83;\n0,0;\n' %
|
||||
(ephemeral_mb, swap_mb, root_mb))
|
||||
else:
|
||||
stdin_command = ('1,%d,83;\n,%d,82;\n0,0;\n0,0;\n' %
|
||||
(root_mb, swap_mb))
|
||||
utils.execute('sfdisk', '-uM', dev, process_input=stdin_command,
|
||||
run_as_root=True,
|
||||
attempts=3,
|
||||
check_exit_code=[0])
|
||||
# avoid "device is busy"
|
||||
time.sleep(10)
|
||||
|
||||
|
||||
def is_block_device(dev):
|
||||
"""Check whether a device is block or not."""
|
||||
s = os.stat(dev)
|
||||
return stat.S_ISBLK(s.st_mode)
|
||||
|
||||
|
||||
def dd(src, dst):
|
||||
"""Execute dd from src to dst."""
|
||||
utils.execute('dd',
|
||||
'if=%s' % src,
|
||||
'of=%s' % dst,
|
||||
'bs=1M',
|
||||
'oflag=direct',
|
||||
run_as_root=True,
|
||||
check_exit_code=[0])
|
||||
|
||||
|
||||
def mkswap(dev, label='swap1'):
|
||||
"""Execute mkswap on a device."""
|
||||
utils.execute('mkswap',
|
||||
'-L', label,
|
||||
dev,
|
||||
run_as_root=True,
|
||||
check_exit_code=[0])
|
||||
|
||||
|
||||
def mkfs_ephemeral(dev, label="ephemeral0"):
|
||||
# TODO(jogo) support non-default mkfs options as well
|
||||
disk.mkfs("default", label, dev)
|
||||
|
||||
|
||||
def block_uuid(dev):
|
||||
"""Get UUID of a block device."""
|
||||
out, _ = utils.execute('blkid', '-s', 'UUID', '-o', 'value', dev,
|
||||
run_as_root=True,
|
||||
check_exit_code=[0])
|
||||
return out.strip()
|
||||
|
||||
|
||||
def switch_pxe_config(path, root_uuid):
|
||||
"""Switch a pxe config from deployment mode to service mode."""
|
||||
with open(path) as f:
|
||||
lines = f.readlines()
|
||||
root = 'UUID=%s' % root_uuid
|
||||
rre = re.compile(r'\$\{ROOT\}')
|
||||
dre = re.compile('^default .*$')
|
||||
with open(path, 'w') as f:
|
||||
for line in lines:
|
||||
line = rre.sub(root, line)
|
||||
line = dre.sub('default boot', line)
|
||||
f.write(line)
|
||||
|
||||
|
||||
def notify(address, port):
|
||||
"""Notify a node that it becomes ready to reboot."""
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
try:
|
||||
s.connect((address, port))
|
||||
s.send('done')
|
||||
finally:
|
||||
s.close()
|
||||
|
||||
|
||||
def get_dev(address, port, iqn, lun):
|
||||
"""Returns a device path for given parameters."""
|
||||
dev = "/dev/disk/by-path/ip-%s:%s-iscsi-%s-lun-%s" \
|
||||
% (address, port, iqn, lun)
|
||||
return dev
|
||||
|
||||
|
||||
def get_image_mb(image_path):
|
||||
"""Get size of an image in Megabyte."""
|
||||
mb = units.Mi
|
||||
image_byte = os.path.getsize(image_path)
|
||||
# round up size to MB
|
||||
image_mb = int((image_byte + mb - 1) / mb)
|
||||
return image_mb
|
||||
|
||||
|
||||
def work_on_disk(dev, root_mb, swap_mb, ephemeral_mb, image_path,
|
||||
preserve_ephemeral):
|
||||
"""Creates partitions and write an image to the root partition.
|
||||
|
||||
:param preserve_ephemeral: If True, no filesystem is written to the
|
||||
ephemeral block device, preserving whatever content it had (if the
|
||||
partition table has not changed).
|
||||
"""
|
||||
def raise_exception(msg):
|
||||
LOG.error(msg)
|
||||
raise BareMetalDeployException(msg)
|
||||
|
||||
if ephemeral_mb:
|
||||
ephemeral_part = "%s-part1" % dev
|
||||
swap_part = "%s-part2" % dev
|
||||
root_part = "%s-part3" % dev
|
||||
else:
|
||||
root_part = "%s-part1" % dev
|
||||
swap_part = "%s-part2" % dev
|
||||
|
||||
if not is_block_device(dev):
|
||||
raise_exception(_("parent device '%s' not found") % dev)
|
||||
make_partitions(dev, root_mb, swap_mb, ephemeral_mb)
|
||||
if not is_block_device(root_part):
|
||||
raise_exception(_("root device '%s' not found") % root_part)
|
||||
if not is_block_device(swap_part):
|
||||
raise_exception(_("swap device '%s' not found") % swap_part)
|
||||
if ephemeral_mb and not is_block_device(ephemeral_part):
|
||||
raise_exception(_("ephemeral device '%s' not found") % ephemeral_part)
|
||||
dd(image_path, root_part)
|
||||
mkswap(swap_part)
|
||||
if ephemeral_mb and not preserve_ephemeral:
|
||||
mkfs_ephemeral(ephemeral_part)
|
||||
|
||||
try:
|
||||
root_uuid = block_uuid(root_part)
|
||||
except processutils.ProcessExecutionError:
|
||||
with excutils.save_and_reraise_exception():
|
||||
LOG.error(_("Failed to detect root device UUID."))
|
||||
return root_uuid
|
||||
|
||||
|
||||
def deploy(address, port, iqn, lun, image_path, pxe_config_path,
|
||||
root_mb, swap_mb, ephemeral_mb, preserve_ephemeral=False):
|
||||
"""All-in-one function to deploy a node.
|
||||
|
||||
:param preserve_ephemeral: If True, no filesystem is written to the
|
||||
ephemeral block device, preserving whatever content it had (if the
|
||||
partition table has not changed).
|
||||
"""
|
||||
dev = get_dev(address, port, iqn, lun)
|
||||
image_mb = get_image_mb(image_path)
|
||||
if image_mb > root_mb:
|
||||
root_mb = image_mb
|
||||
discovery(address, port)
|
||||
login_iscsi(address, port, iqn)
|
||||
try:
|
||||
root_uuid = work_on_disk(dev, root_mb, swap_mb, ephemeral_mb,
|
||||
image_path, preserve_ephemeral)
|
||||
except processutils.ProcessExecutionError as err:
|
||||
with excutils.save_and_reraise_exception():
|
||||
# Log output if there was a error
|
||||
LOG.error(_("Cmd : %s"), err.cmd)
|
||||
LOG.error(_("StdOut : %r"), err.stdout)
|
||||
LOG.error(_("StdErr : %r"), err.stderr)
|
||||
finally:
|
||||
logout_iscsi(address, port, iqn)
|
||||
switch_pxe_config(pxe_config_path, root_uuid)
|
||||
# Ensure the node started netcat on the port after POST the request.
|
||||
time.sleep(3)
|
||||
notify(address, 10000)
|
||||
|
||||
|
||||
class Worker(threading.Thread):
|
||||
"""Thread that handles requests in queue."""
|
||||
|
||||
def __init__(self):
|
||||
super(Worker, self).__init__()
|
||||
self.setDaemon(True)
|
||||
self.stop = False
|
||||
self.queue_timeout = 1
|
||||
|
||||
def run(self):
|
||||
while not self.stop:
|
||||
try:
|
||||
# Set timeout to check self.stop periodically
|
||||
(node_id, params) = QUEUE.get(block=True,
|
||||
timeout=self.queue_timeout)
|
||||
except Queue.Empty:
|
||||
pass
|
||||
else:
|
||||
# Requests comes here from BareMetalDeploy.post()
|
||||
LOG.info(_('start deployment for node %(node_id)s, '
|
||||
'params %(params)s'),
|
||||
{'node_id': node_id, 'params': params})
|
||||
context = nova_context.get_admin_context()
|
||||
try:
|
||||
db.bm_node_update(context, node_id,
|
||||
{'task_state': baremetal_states.DEPLOYING})
|
||||
deploy(**params)
|
||||
except Exception:
|
||||
LOG.exception(_('deployment to node %s failed'), node_id)
|
||||
db.bm_node_update(context, node_id,
|
||||
{'task_state': baremetal_states.DEPLOYFAIL})
|
||||
else:
|
||||
LOG.info(_('deployment to node %s done'), node_id)
|
||||
db.bm_node_update(context, node_id,
|
||||
{'task_state': baremetal_states.DEPLOYDONE})
|
||||
|
||||
|
||||
class BareMetalDeploy(object):
|
||||
"""WSGI server for bare-metal deployment."""
|
||||
|
||||
def __init__(self):
|
||||
self.worker = Worker()
|
||||
self.worker.start()
|
||||
|
||||
def __call__(self, environ, start_response):
|
||||
method = environ['REQUEST_METHOD']
|
||||
if method == 'POST':
|
||||
return self.post(environ, start_response)
|
||||
else:
|
||||
start_response('501 Not Implemented',
|
||||
[('Content-type', 'text/plain')])
|
||||
return 'Not Implemented'
|
||||
|
||||
def post(self, environ, start_response):
|
||||
LOG.info(_("post: environ=%s"), environ)
|
||||
inpt = environ['wsgi.input']
|
||||
length = int(environ.get('CONTENT_LENGTH', 0))
|
||||
|
||||
x = inpt.read(length)
|
||||
q = dict(cgi.parse_qsl(x))
|
||||
try:
|
||||
node_id = q['i']
|
||||
deploy_key = q['k']
|
||||
address = q['a']
|
||||
port = q.get('p', '3260')
|
||||
iqn = q['n']
|
||||
lun = q.get('l', '1')
|
||||
err_msg = q.get('e')
|
||||
except KeyError as e:
|
||||
start_response('400 Bad Request', [('Content-type', 'text/plain')])
|
||||
return "parameter '%s' is not defined" % e
|
||||
|
||||
if err_msg:
|
||||
LOG.error(_('Deploy agent error message: %s'), err_msg)
|
||||
|
||||
context = nova_context.get_admin_context()
|
||||
d = db.bm_node_get(context, node_id)
|
||||
|
||||
if d['deploy_key'] != deploy_key:
|
||||
start_response('400 Bad Request', [('Content-type', 'text/plain')])
|
||||
return 'key is not match'
|
||||
|
||||
params = {'address': address,
|
||||
'port': port,
|
||||
'iqn': iqn,
|
||||
'lun': lun,
|
||||
'image_path': d['image_path'],
|
||||
'pxe_config_path': d['pxe_config_path'],
|
||||
'root_mb': int(d['root_mb']),
|
||||
'swap_mb': int(d['swap_mb']),
|
||||
'ephemeral_mb': int(d['ephemeral_mb']),
|
||||
'preserve_ephemeral': d['preserve_ephemeral'],
|
||||
}
|
||||
# Restart worker, if needed
|
||||
if not self.worker.isAlive():
|
||||
self.worker = Worker()
|
||||
self.worker.start()
|
||||
LOG.info(_("request is queued: node %(node_id)s, params %(params)s"),
|
||||
{'node_id': node_id, 'params': params})
|
||||
QUEUE.put((node_id, params))
|
||||
# Requests go to Worker.run()
|
||||
start_response('200 OK', [('Content-type', 'text/plain')])
|
||||
return ''
|
||||
|
||||
|
||||
def main():
|
||||
config.parse_args(sys.argv)
|
||||
logging.setup("nova")
|
||||
global LOG
|
||||
LOG = logging.getLogger('nova.virt.baremetal.deploy_helper')
|
||||
objects.register_all()
|
||||
app = BareMetalDeploy()
|
||||
srv = simple_server.make_server('', 10000, app)
|
||||
srv.serve_forever()
|
||||
@@ -1,208 +0,0 @@
|
||||
# Copyright (c) 2011 X.commerce, a business unit of eBay Inc.
|
||||
# Copyright 2010 United States Government as represented by the
|
||||
# Administrator of the National Aeronautics and Space Administration.
|
||||
# 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.
|
||||
|
||||
# Interactive shell based on Django:
|
||||
#
|
||||
# Copyright (c) 2005, the Lawrence Journal-World
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
#
|
||||
# 1. Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions and the following disclaimer.
|
||||
#
|
||||
# 2. Redistributions in binary form must reproduce the above copyright
|
||||
# notice, this list of conditions and the following disclaimer in the
|
||||
# documentation and/or other materials provided with the distribution.
|
||||
#
|
||||
# 3. Neither the name of Django nor the names of its contributors may be
|
||||
# used to endorse or promote products derived from this software without
|
||||
# specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
"""
|
||||
CLI interface for nova bare-metal management.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
from oslo.config import cfg
|
||||
import six
|
||||
|
||||
from nova import config
|
||||
from nova.i18n import _
|
||||
from nova import objects
|
||||
from nova.openstack.common import cliutils
|
||||
from nova.openstack.common import log as logging
|
||||
from nova import version
|
||||
from nova.virt.baremetal.db import migration as bmdb_migration
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
# Decorators for actions
|
||||
def args(*args, **kwargs):
|
||||
def _decorator(func):
|
||||
func.__dict__.setdefault('args', []).insert(0, (args, kwargs))
|
||||
return func
|
||||
return _decorator
|
||||
|
||||
|
||||
class BareMetalDbCommands(object):
|
||||
"""Class for managing the bare-metal database."""
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@args('--version', dest='version', metavar='<version>',
|
||||
help='Bare-metal Database version')
|
||||
def sync(self, version=None):
|
||||
"""Sync the database up to the most recent version."""
|
||||
bmdb_migration.db_sync(version)
|
||||
|
||||
def version(self):
|
||||
"""Print the current database version."""
|
||||
v = bmdb_migration.db_version()
|
||||
print(v)
|
||||
# return for unittest
|
||||
return v
|
||||
|
||||
|
||||
CATEGORIES = {
|
||||
'db': BareMetalDbCommands,
|
||||
}
|
||||
|
||||
|
||||
def methods_of(obj):
|
||||
"""Get all callable methods of an object that don't start with underscore.
|
||||
|
||||
returns a list of tuples of the form (method_name, method)
|
||||
"""
|
||||
result = []
|
||||
for i in dir(obj):
|
||||
if callable(getattr(obj, i)) and not i.startswith('_'):
|
||||
result.append((i, getattr(obj, i)))
|
||||
return result
|
||||
|
||||
|
||||
def add_command_parsers(subparsers):
|
||||
parser = subparsers.add_parser('bash-completion')
|
||||
parser.add_argument('query_category', nargs='?')
|
||||
|
||||
for category in CATEGORIES:
|
||||
command_object = CATEGORIES[category]()
|
||||
|
||||
parser = subparsers.add_parser(category)
|
||||
parser.set_defaults(command_object=command_object)
|
||||
|
||||
category_subparsers = parser.add_subparsers(dest='action')
|
||||
|
||||
for (action, action_fn) in methods_of(command_object):
|
||||
parser = category_subparsers.add_parser(action)
|
||||
|
||||
action_kwargs = []
|
||||
for args, kwargs in getattr(action_fn, 'args', []):
|
||||
action_kwargs.append(kwargs['dest'])
|
||||
kwargs['dest'] = 'action_kwarg_' + kwargs['dest']
|
||||
parser.add_argument(*args, **kwargs)
|
||||
|
||||
parser.set_defaults(action_fn=action_fn)
|
||||
parser.set_defaults(action_kwargs=action_kwargs)
|
||||
|
||||
parser.add_argument('action_args', nargs='*')
|
||||
|
||||
|
||||
category_opt = cfg.SubCommandOpt('category',
|
||||
title='Command categories',
|
||||
help='Available categories',
|
||||
handler=add_command_parsers)
|
||||
|
||||
|
||||
def main():
|
||||
"""Parse options and call the appropriate class/method."""
|
||||
CONF.register_cli_opt(category_opt)
|
||||
try:
|
||||
config.parse_args(sys.argv)
|
||||
logging.setup("nova")
|
||||
except cfg.ConfigFilesNotFoundError:
|
||||
cfgfile = CONF.config_file[-1] if CONF.config_file else None
|
||||
if cfgfile and not os.access(cfgfile, os.R_OK):
|
||||
st = os.stat(cfgfile)
|
||||
print(_("Could not read %s. Re-running with sudo") % cfgfile)
|
||||
try:
|
||||
os.execvp('sudo', ['sudo', '-u', '#%s' % st.st_uid] + sys.argv)
|
||||
except Exception:
|
||||
print(_('sudo failed, continuing as if nothing happened'))
|
||||
|
||||
print(_('Please re-run nova-manage as root.'))
|
||||
return(2)
|
||||
|
||||
objects.register_all()
|
||||
|
||||
if CONF.category.name == "version":
|
||||
print(version.version_string_with_package())
|
||||
return(0)
|
||||
|
||||
if CONF.category.name == "bash-completion":
|
||||
if not CONF.category.query_category:
|
||||
print(" ".join(CATEGORIES.keys()))
|
||||
elif CONF.category.query_category in CATEGORIES:
|
||||
fn = CATEGORIES[CONF.category.query_category]
|
||||
command_object = fn()
|
||||
actions = methods_of(command_object)
|
||||
print(" ".join([k for (k, v) in actions]))
|
||||
return(0)
|
||||
|
||||
fn = CONF.category.action_fn
|
||||
fn_args = [arg.decode('utf-8') for arg in CONF.category.action_args]
|
||||
fn_kwargs = {}
|
||||
for k in CONF.category.action_kwargs:
|
||||
v = getattr(CONF.category, 'action_kwarg_' + k)
|
||||
if v is None:
|
||||
continue
|
||||
if isinstance(v, six.string_types):
|
||||
v = v.decode('utf-8')
|
||||
fn_kwargs[k] = v
|
||||
|
||||
# call the action with the remaining arguments
|
||||
# check arguments
|
||||
try:
|
||||
cliutils.validate_args(fn, *fn_args, **fn_kwargs)
|
||||
except cliutils.MissingArgs as e:
|
||||
print(fn.__doc__)
|
||||
print(e)
|
||||
return(1)
|
||||
try:
|
||||
fn(*fn_args, **fn_kwargs)
|
||||
return(0)
|
||||
except Exception:
|
||||
print(_("Command failed, please check log for more info"))
|
||||
raise
|
||||
@@ -20,10 +20,8 @@ from webob import exc
|
||||
from nova.api.openstack.compute.contrib import baremetal_nodes
|
||||
from nova.api.openstack import extensions
|
||||
from nova import context
|
||||
from nova import exception
|
||||
from nova import test
|
||||
from nova.tests.virt.ironic import utils as ironic_utils
|
||||
from nova.virt.baremetal import db
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
@@ -63,17 +61,6 @@ def fake_node_ext_status(**updates):
|
||||
return node
|
||||
|
||||
|
||||
def fake_interface(**updates):
|
||||
interface = {
|
||||
'id': 1,
|
||||
'address': '11:11:11:11:11:11',
|
||||
'datapath_id': 2,
|
||||
'port_no': 3,
|
||||
}
|
||||
if updates:
|
||||
interface.update(updates)
|
||||
return interface
|
||||
|
||||
FAKE_IRONIC_CLIENT = ironic_utils.FakeClient()
|
||||
|
||||
|
||||
@@ -89,302 +76,8 @@ class BareMetalNodesTest(test.NoDBTestCase):
|
||||
self.controller = baremetal_nodes.BareMetalNodeController(self.ext_mgr)
|
||||
self.request = FakeRequest(self.context)
|
||||
|
||||
def _test_create(self, node, ext_status=False):
|
||||
response = node.copy()
|
||||
del response['pm_password']
|
||||
response['instance_uuid'] = None
|
||||
self.mox.StubOutWithMock(db, 'bm_node_create')
|
||||
db.bm_node_create(self.context, node).AndReturn(response)
|
||||
self.ext_mgr.is_loaded('os-baremetal-ext-status').AndReturn(ext_status)
|
||||
self.mox.ReplayAll()
|
||||
res_dict = self.controller.create(self.request, {'node': node})
|
||||
self.assertEqual({'node': response}, res_dict)
|
||||
|
||||
def _test_show(self, node, ext_status=False):
|
||||
interfaces = [fake_interface(id=1, address='11:11:11:11:11:11'),
|
||||
fake_interface(id=2, address='22:22:22:22:22:22'),
|
||||
]
|
||||
node.update(interfaces=interfaces)
|
||||
response = node.copy()
|
||||
del response['pm_password']
|
||||
self.mox.StubOutWithMock(db, 'bm_node_get')
|
||||
self.mox.StubOutWithMock(db, 'bm_interface_get_all_by_bm_node_id')
|
||||
db.bm_node_get(self.context, node['id']).AndReturn(node)
|
||||
db.bm_interface_get_all_by_bm_node_id(self.context, node['id']).\
|
||||
AndReturn(interfaces)
|
||||
self.ext_mgr.is_loaded('os-baremetal-ext-status').AndReturn(ext_status)
|
||||
self.mox.ReplayAll()
|
||||
res_dict = self.controller.show(self.request, node['id'])
|
||||
self.assertEqual({'node': response}, res_dict)
|
||||
self.assertEqual(2, len(res_dict['node']['interfaces']))
|
||||
|
||||
def _test_show_no_interfaces(self, ext_status=False):
|
||||
node_id = 1
|
||||
node = {'id': node_id}
|
||||
self.mox.StubOutWithMock(db, 'bm_node_get')
|
||||
self.mox.StubOutWithMock(db, 'bm_interface_get_all_by_bm_node_id')
|
||||
db.bm_node_get(self.context, node_id).AndReturn(node)
|
||||
db.bm_interface_get_all_by_bm_node_id(self.context, node_id).\
|
||||
AndRaise(exception.NodeNotFound(node_id=node_id))
|
||||
self.ext_mgr.is_loaded('os-baremetal-ext-status').AndReturn(ext_status)
|
||||
self.mox.ReplayAll()
|
||||
res_dict = self.controller.show(self.request, node_id)
|
||||
self.assertEqual(node_id, res_dict['node']['id'])
|
||||
self.assertEqual(0, len(res_dict['node']['interfaces']))
|
||||
|
||||
def _test_index(self, ext_status=False):
|
||||
nodes = [{'id': 1},
|
||||
{'id': 2},
|
||||
]
|
||||
interfaces = [{'id': 1, 'address': '11:11:11:11:11:11'},
|
||||
{'id': 2, 'address': '22:22:22:22:22:22'},
|
||||
]
|
||||
self.mox.StubOutWithMock(db, 'bm_node_get_all')
|
||||
self.mox.StubOutWithMock(db, 'bm_interface_get_all_by_bm_node_id')
|
||||
db.bm_node_get_all(self.context).AndReturn(nodes)
|
||||
db.bm_interface_get_all_by_bm_node_id(self.context, 1).\
|
||||
AndRaise(exception.NodeNotFound(node_id=1))
|
||||
for n in nodes:
|
||||
self.ext_mgr.is_loaded('os-baremetal-ext-status').\
|
||||
AndReturn(ext_status)
|
||||
db.bm_interface_get_all_by_bm_node_id(self.context, 2).\
|
||||
AndReturn(interfaces)
|
||||
self.mox.ReplayAll()
|
||||
res_dict = self.controller.index(self.request)
|
||||
self.assertEqual(2, len(res_dict['nodes']))
|
||||
self.assertEqual([], res_dict['nodes'][0]['interfaces'])
|
||||
self.assertEqual(2, len(res_dict['nodes'][1]['interfaces']))
|
||||
|
||||
def test_create(self):
|
||||
node = fake_node(id=100)
|
||||
self._test_create(node)
|
||||
|
||||
def test_create_ext_status(self):
|
||||
node = fake_node_ext_status(id=100)
|
||||
self._test_create(node, ext_status=True)
|
||||
|
||||
def test_create_with_prov_mac_address(self):
|
||||
node = {
|
||||
'service_host': "host",
|
||||
'cpus': 8,
|
||||
'memory_mb': 8192,
|
||||
'local_gb': 128,
|
||||
'pm_address': "10.1.2.3",
|
||||
'pm_user': "pm_user",
|
||||
'pm_password': "pm_pass",
|
||||
'terminal_port': 8000,
|
||||
'interfaces': [],
|
||||
}
|
||||
intf = {
|
||||
'address': '1a:B2:3C:4d:e5:6f',
|
||||
'datapath_id': None,
|
||||
'id': None,
|
||||
'port_no': None,
|
||||
}
|
||||
|
||||
request = node.copy()
|
||||
request['prov_mac_address'] = intf['address']
|
||||
|
||||
db_node = node.copy()
|
||||
db_node['id'] = 100
|
||||
|
||||
response = node.copy()
|
||||
response.update(id=db_node['id'],
|
||||
instance_uuid=None,
|
||||
interfaces=[intf])
|
||||
del response['pm_password']
|
||||
|
||||
self.mox.StubOutWithMock(db, 'bm_node_create')
|
||||
self.mox.StubOutWithMock(db, 'bm_interface_create')
|
||||
self.mox.StubOutWithMock(db, 'bm_interface_get')
|
||||
db.bm_node_create(self.context, node).AndReturn(db_node)
|
||||
self.ext_mgr.is_loaded('os-baremetal-ext-status').AndReturn(False)
|
||||
db.bm_interface_create(self.context,
|
||||
bm_node_id=db_node['id'],
|
||||
address=intf['address'],
|
||||
datapath_id=intf['datapath_id'],
|
||||
port_no=intf['port_no']).AndReturn(1000)
|
||||
db.bm_interface_get(self.context, 1000).AndReturn(intf)
|
||||
self.mox.ReplayAll()
|
||||
res_dict = self.controller.create(self.request, {'node': request})
|
||||
self.assertEqual({'node': response}, res_dict)
|
||||
|
||||
def test_create_with_invalid_prov_mac_address(self):
|
||||
node = {
|
||||
'service_host': "host",
|
||||
'cpus': 8,
|
||||
'memory_mb': 8192,
|
||||
'local_gb': 128,
|
||||
'pm_address': "10.1.2.3",
|
||||
'pm_user': "pm_user",
|
||||
'pm_password': "pm_pass",
|
||||
'terminal_port': 8000,
|
||||
'prov_mac_address': 'INVALID!!',
|
||||
}
|
||||
self.assertRaises(exc.HTTPBadRequest,
|
||||
self.controller.create,
|
||||
self.request, {'node': node})
|
||||
|
||||
def test_delete(self):
|
||||
self.mox.StubOutWithMock(db, 'bm_node_destroy')
|
||||
db.bm_node_destroy(self.context, 1)
|
||||
self.mox.ReplayAll()
|
||||
self.controller.delete(self.request, 1)
|
||||
|
||||
def test_delete_node_not_found(self):
|
||||
self.mox.StubOutWithMock(db, 'bm_node_destroy')
|
||||
db.bm_node_destroy(self.context, 1).\
|
||||
AndRaise(exception.NodeNotFound(node_id=1))
|
||||
self.mox.ReplayAll()
|
||||
self.assertRaises(
|
||||
exc.HTTPNotFound,
|
||||
self.controller.delete,
|
||||
self.request,
|
||||
1)
|
||||
|
||||
def test_index(self):
|
||||
self._test_index()
|
||||
|
||||
def test_index_ext_status(self):
|
||||
self._test_index(ext_status=True)
|
||||
|
||||
def test_show(self):
|
||||
node = fake_node(id=1)
|
||||
self._test_show(node)
|
||||
|
||||
def test_show_ext_status(self):
|
||||
node = fake_node_ext_status(id=1)
|
||||
self._test_show(node, ext_status=True)
|
||||
|
||||
def test_show_no_interfaces(self):
|
||||
self._test_show_no_interfaces()
|
||||
|
||||
def test_show_no_interfaces_ext_status(self):
|
||||
self._test_show_no_interfaces(ext_status=True)
|
||||
|
||||
def test_add_interface(self):
|
||||
node_id = 1
|
||||
address = '11:22:33:ab:cd:ef'
|
||||
body = {'add_interface': {'address': address}}
|
||||
self.mox.StubOutWithMock(db, 'bm_node_get')
|
||||
self.mox.StubOutWithMock(db, 'bm_interface_create')
|
||||
self.mox.StubOutWithMock(db, 'bm_interface_get')
|
||||
db.bm_node_get(self.context, node_id)
|
||||
db.bm_interface_create(self.context,
|
||||
bm_node_id=node_id,
|
||||
address=address,
|
||||
datapath_id=None,
|
||||
port_no=None).\
|
||||
AndReturn(12345)
|
||||
db.bm_interface_get(self.context, 12345).\
|
||||
AndReturn({'id': 12345, 'address': address})
|
||||
self.mox.ReplayAll()
|
||||
res_dict = self.controller._add_interface(self.request, node_id, body)
|
||||
self.assertEqual(12345, res_dict['interface']['id'])
|
||||
self.assertEqual(address, res_dict['interface']['address'])
|
||||
|
||||
def test_add_interface_invalid_address(self):
|
||||
node_id = 1
|
||||
body = {'add_interface': {'address': ''}}
|
||||
self.mox.StubOutWithMock(db, 'bm_node_get')
|
||||
db.bm_node_get(self.context, node_id)
|
||||
self.mox.ReplayAll()
|
||||
self.assertRaises(exc.HTTPBadRequest,
|
||||
self.controller._add_interface,
|
||||
self.request,
|
||||
node_id,
|
||||
body)
|
||||
|
||||
def test_remove_interface(self):
|
||||
node_id = 1
|
||||
interfaces = [{'id': 1},
|
||||
{'id': 2},
|
||||
{'id': 3},
|
||||
]
|
||||
body = {'remove_interface': {'id': 2}}
|
||||
self.mox.StubOutWithMock(db, 'bm_node_get')
|
||||
self.mox.StubOutWithMock(db, 'bm_interface_get_all_by_bm_node_id')
|
||||
self.mox.StubOutWithMock(db, 'bm_interface_destroy')
|
||||
db.bm_node_get(self.context, node_id)
|
||||
db.bm_interface_get_all_by_bm_node_id(self.context, node_id).\
|
||||
AndReturn(interfaces)
|
||||
db.bm_interface_destroy(self.context, 2)
|
||||
self.mox.ReplayAll()
|
||||
self.controller._remove_interface(self.request, node_id, body)
|
||||
|
||||
def test_remove_interface_by_address(self):
|
||||
node_id = 1
|
||||
interfaces = [{'id': 1, 'address': '11:11:11:11:11:11'},
|
||||
{'id': 2, 'address': '22:22:22:22:22:22'},
|
||||
{'id': 3, 'address': '33:33:33:33:33:33'},
|
||||
]
|
||||
self.mox.StubOutWithMock(db, 'bm_node_get')
|
||||
self.mox.StubOutWithMock(db, 'bm_interface_get_all_by_bm_node_id')
|
||||
self.mox.StubOutWithMock(db, 'bm_interface_destroy')
|
||||
db.bm_node_get(self.context, node_id)
|
||||
db.bm_interface_get_all_by_bm_node_id(self.context, node_id).\
|
||||
AndReturn(interfaces)
|
||||
db.bm_interface_destroy(self.context, 2)
|
||||
self.mox.ReplayAll()
|
||||
body = {'remove_interface': {'address': '22:22:22:22:22:22'}}
|
||||
self.controller._remove_interface(self.request, node_id, body)
|
||||
|
||||
def test_remove_interface_no_id_no_address(self):
|
||||
node_id = 1
|
||||
self.mox.StubOutWithMock(db, 'bm_node_get')
|
||||
db.bm_node_get(self.context, node_id)
|
||||
self.mox.ReplayAll()
|
||||
body = {'remove_interface': {}}
|
||||
self.assertRaises(exc.HTTPBadRequest,
|
||||
self.controller._remove_interface,
|
||||
self.request,
|
||||
node_id,
|
||||
body)
|
||||
|
||||
def test_add_interface_node_not_found(self):
|
||||
node_id = 1
|
||||
self.mox.StubOutWithMock(db, 'bm_node_get')
|
||||
db.bm_node_get(self.context, node_id).\
|
||||
AndRaise(exception.NodeNotFound(node_id=node_id))
|
||||
self.mox.ReplayAll()
|
||||
body = {'add_interface': {'address': '11:11:11:11:11:11'}}
|
||||
self.assertRaises(exc.HTTPNotFound,
|
||||
self.controller._add_interface,
|
||||
self.request,
|
||||
node_id,
|
||||
body)
|
||||
|
||||
def test_remove_interface_node_not_found(self):
|
||||
node_id = 1
|
||||
self.mox.StubOutWithMock(db, 'bm_node_get')
|
||||
db.bm_node_get(self.context, node_id).\
|
||||
AndRaise(exception.NodeNotFound(node_id=node_id))
|
||||
self.mox.ReplayAll()
|
||||
body = {'remove_interface': {'address': '11:11:11:11:11:11'}}
|
||||
self.assertRaises(exc.HTTPNotFound,
|
||||
self.controller._remove_interface,
|
||||
self.request,
|
||||
node_id,
|
||||
body)
|
||||
|
||||
def test_is_valid_mac(self):
|
||||
self.assertFalse(baremetal_nodes.is_valid_mac(None))
|
||||
self.assertTrue(baremetal_nodes.is_valid_mac("52:54:00:cf:2d:31"))
|
||||
self.assertTrue(baremetal_nodes.is_valid_mac(u"52:54:00:cf:2d:31"))
|
||||
self.assertFalse(baremetal_nodes.is_valid_mac("127.0.0.1"))
|
||||
self.assertFalse(baremetal_nodes.is_valid_mac("not:a:mac:address"))
|
||||
self.assertFalse(baremetal_nodes.is_valid_mac("52-54-00-cf-2d-31"))
|
||||
self.assertFalse(baremetal_nodes.is_valid_mac("5254.00cf.2d31"))
|
||||
self.assertFalse(baremetal_nodes.is_valid_mac("52:54:0:cf:2d:31"))
|
||||
self.assertFalse(baremetal_nodes.is_valid_mac("aa bb cc dd ee ff"))
|
||||
self.assertTrue(baremetal_nodes.is_valid_mac("AA:BB:CC:DD:EE:FF"))
|
||||
self.assertFalse(baremetal_nodes.is_valid_mac("AA BB CC DD EE FF"))
|
||||
self.assertFalse(baremetal_nodes.is_valid_mac("AA-BB-CC-DD-EE-FF"))
|
||||
|
||||
@mock.patch.object(FAKE_IRONIC_CLIENT.node, 'list')
|
||||
def test_index_ironic(self, mock_list):
|
||||
CONF.set_override('compute_driver', 'nova.virt.ironic.driver')
|
||||
|
||||
properties = {'cpus': 2, 'memory_mb': 1024, 'local_gb': 20}
|
||||
node = ironic_utils.get_test_node(properties=properties)
|
||||
mock_list.return_value = [node]
|
||||
@@ -404,8 +97,6 @@ class BareMetalNodesTest(test.NoDBTestCase):
|
||||
@mock.patch.object(FAKE_IRONIC_CLIENT.node, 'list_ports')
|
||||
@mock.patch.object(FAKE_IRONIC_CLIENT.node, 'get')
|
||||
def test_show_ironic(self, mock_get, mock_list_ports):
|
||||
CONF.set_override('compute_driver', 'nova.virt.ironic.driver')
|
||||
|
||||
properties = {'cpus': 1, 'memory_mb': 512, 'local_gb': 10}
|
||||
node = ironic_utils.get_test_node(properties=properties)
|
||||
port = ironic_utils.get_test_port()
|
||||
@@ -429,8 +120,6 @@ class BareMetalNodesTest(test.NoDBTestCase):
|
||||
@mock.patch.object(FAKE_IRONIC_CLIENT.node, 'list_ports')
|
||||
@mock.patch.object(FAKE_IRONIC_CLIENT.node, 'get')
|
||||
def test_show_ironic_no_interfaces(self, mock_get, mock_list_ports):
|
||||
CONF.set_override('compute_driver', 'nova.virt.ironic.driver')
|
||||
|
||||
properties = {'cpus': 1, 'memory_mb': 512, 'local_gb': 10}
|
||||
node = ironic_utils.get_test_node(properties=properties)
|
||||
mock_get.return_value = node
|
||||
@@ -442,25 +131,21 @@ class BareMetalNodesTest(test.NoDBTestCase):
|
||||
mock_list_ports.assert_called_once_with(node.uuid)
|
||||
|
||||
def test_create_ironic_not_supported(self):
|
||||
CONF.set_override('compute_driver', 'nova.virt.ironic.driver')
|
||||
self.assertRaises(exc.HTTPBadRequest,
|
||||
self.controller.create,
|
||||
self.request, {'node': object()})
|
||||
|
||||
def test_delete_ironic_not_supported(self):
|
||||
CONF.set_override('compute_driver', 'nova.virt.ironic.driver')
|
||||
self.assertRaises(exc.HTTPBadRequest,
|
||||
self.controller.delete,
|
||||
self.request, 'fake-id')
|
||||
|
||||
def test_add_interface_ironic_not_supported(self):
|
||||
CONF.set_override('compute_driver', 'nova.virt.ironic.driver')
|
||||
self.assertRaises(exc.HTTPBadRequest,
|
||||
self.controller._add_interface,
|
||||
self.request, 'fake-id', 'fake-body')
|
||||
|
||||
def test_remove_interface_ironic_not_supported(self):
|
||||
CONF.set_override('compute_driver', 'nova.virt.ironic.driver')
|
||||
self.assertRaises(exc.HTTPBadRequest,
|
||||
self.controller._remove_interface,
|
||||
self.request, 'fake-id', 'fake-body')
|
||||
|
||||
@@ -27,9 +27,8 @@ which allows testing against all 3 databases (sqlite in memory, mysql, pg) in
|
||||
a properly configured unit test environment.
|
||||
|
||||
For the opportunistic testing you need to set up db's named 'openstack_citest'
|
||||
and 'openstack_baremetal_citest' with user 'openstack_citest' and password
|
||||
'openstack_citest' on localhost. The test will then use that db and u/p combo
|
||||
to run the tests.
|
||||
with user 'openstack_citest' and password 'openstack_citest' on localhost. The
|
||||
test will then use that db and u/p combo to run the tests.
|
||||
|
||||
For postgres on Ubuntu this can be done with the following commands::
|
||||
|
||||
@@ -37,8 +36,6 @@ For postgres on Ubuntu this can be done with the following commands::
|
||||
| postgres=# create user openstack_citest with createdb login password
|
||||
| 'openstack_citest';
|
||||
| postgres=# create database openstack_citest with owner openstack_citest;
|
||||
| postgres=# create database openstack_baremetal_citest with owner
|
||||
| openstack_citest;
|
||||
|
||||
"""
|
||||
|
||||
@@ -60,7 +57,6 @@ from nova.openstack.common import log as logging
|
||||
from nova.openstack.common import processutils
|
||||
from nova import test
|
||||
from nova import utils
|
||||
import nova.virt.baremetal.db.sqlalchemy.migrate_repo
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
@@ -111,8 +107,7 @@ def get_pgsql_connection_info(conn_pieces):
|
||||
|
||||
|
||||
class CommonTestsMixIn(object):
|
||||
"""These tests are shared between TestNovaMigrations and
|
||||
TestBaremetalMigrations.
|
||||
"""Base class for migration tests.
|
||||
|
||||
BaseMigrationTestCase is effectively an abstract class, meant to be derived
|
||||
from and not directly tested against; that's why these `test_` methods need
|
||||
@@ -889,144 +884,6 @@ class TestNovaMigrations(BaseWalkMigrationTestCase, CommonTestsMixIn):
|
||||
['host']]))
|
||||
|
||||
|
||||
class TestBaremetalMigrations(BaseWalkMigrationTestCase, CommonTestsMixIn):
|
||||
"""Test sqlalchemy-migrate migrations."""
|
||||
USER = "openstack_citest"
|
||||
PASSWD = "openstack_citest"
|
||||
DATABASE = "openstack_baremetal_citest"
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(TestBaremetalMigrations, self).__init__(*args, **kwargs)
|
||||
|
||||
self.DEFAULT_CONFIG_FILE = os.path.join(os.path.dirname(__file__),
|
||||
'../virt/baremetal/test_baremetal_migrations.conf')
|
||||
# Test machines can set the NOVA_TEST_MIGRATIONS_CONF variable
|
||||
# to override the location of the config file for migration testing
|
||||
self.CONFIG_FILE_PATH = os.environ.get(
|
||||
'BAREMETAL_TEST_MIGRATIONS_CONF',
|
||||
self.DEFAULT_CONFIG_FILE)
|
||||
self.MIGRATE_FILE = \
|
||||
nova.virt.baremetal.db.sqlalchemy.migrate_repo.__file__
|
||||
self.REPOSITORY = repository.Repository(
|
||||
os.path.abspath(os.path.dirname(self.MIGRATE_FILE)))
|
||||
|
||||
def setUp(self):
|
||||
super(TestBaremetalMigrations, self).setUp()
|
||||
|
||||
if self.migration is None:
|
||||
self.migration = __import__('nova.virt.baremetal.db.migration',
|
||||
globals(), locals(), ['db_initial_version'], -1)
|
||||
self.INIT_VERSION = self.migration.db_initial_version()
|
||||
if self.migration_api is None:
|
||||
temp = __import__('nova.virt.baremetal.db.sqlalchemy.migration',
|
||||
globals(), locals(), ['versioning_api'], -1)
|
||||
self.migration_api = temp.versioning_api
|
||||
|
||||
def _pre_upgrade_002(self, engine):
|
||||
data = [{'id': 1, 'key': 'fake-key', 'image_path': '/dev/null',
|
||||
'pxe_config_path': '/dev/null/', 'root_mb': 0, 'swap_mb': 0}]
|
||||
table = oslodbutils.get_table(engine, 'bm_deployments')
|
||||
engine.execute(table.insert(), data)
|
||||
return data
|
||||
|
||||
def _check_002(self, engine, data):
|
||||
self.assertRaises(sqlalchemy.exc.NoSuchTableError,
|
||||
oslodbutils.get_table, engine, 'bm_deployments')
|
||||
|
||||
def _post_downgrade_004(self, engine):
|
||||
bm_nodes = oslodbutils.get_table(engine, 'bm_nodes')
|
||||
self.assertNotIn(u'instance_name', [c.name for c in bm_nodes.columns])
|
||||
|
||||
def _check_005(self, engine, data):
|
||||
bm_nodes = oslodbutils.get_table(engine, 'bm_nodes')
|
||||
columns = [c.name for c in bm_nodes.columns]
|
||||
self.assertNotIn(u'prov_vlan_id', columns)
|
||||
self.assertNotIn(u'registration_status', columns)
|
||||
|
||||
def _pre_upgrade_006(self, engine):
|
||||
nodes = oslodbutils.get_table(engine, 'bm_nodes')
|
||||
ifs = oslodbutils.get_table(engine, 'bm_interfaces')
|
||||
# node 1 has two different addresses in bm_nodes and bm_interfaces
|
||||
engine.execute(nodes.insert(),
|
||||
[{'id': 1,
|
||||
'prov_mac_address': 'aa:aa:aa:aa:aa:aa'}])
|
||||
engine.execute(ifs.insert(),
|
||||
[{'id': 101,
|
||||
'bm_node_id': 1,
|
||||
'address': 'bb:bb:bb:bb:bb:bb'}])
|
||||
# node 2 has one same address both in bm_nodes and bm_interfaces
|
||||
engine.execute(nodes.insert(),
|
||||
[{'id': 2,
|
||||
'prov_mac_address': 'cc:cc:cc:cc:cc:cc'}])
|
||||
engine.execute(ifs.insert(),
|
||||
[{'id': 201,
|
||||
'bm_node_id': 2,
|
||||
'address': 'cc:cc:cc:cc:cc:cc'}])
|
||||
|
||||
def _check_006(self, engine, data):
|
||||
ifs = oslodbutils.get_table(engine, 'bm_interfaces')
|
||||
rows = ifs.select().\
|
||||
where(ifs.c.bm_node_id == 1).\
|
||||
execute().\
|
||||
fetchall()
|
||||
self.assertEqual(len(rows), 2)
|
||||
rows = ifs.select().\
|
||||
where(ifs.c.bm_node_id == 2).\
|
||||
execute().\
|
||||
fetchall()
|
||||
self.assertEqual(len(rows), 1)
|
||||
self.assertEqual(rows[0]['address'], 'cc:cc:cc:cc:cc:cc')
|
||||
|
||||
def _post_downgrade_006(self, engine):
|
||||
ifs = oslodbutils.get_table(engine, 'bm_interfaces')
|
||||
rows = ifs.select().where(ifs.c.bm_node_id == 1).execute().fetchall()
|
||||
self.assertEqual(len(rows), 1)
|
||||
self.assertEqual(rows[0]['address'], 'bb:bb:bb:bb:bb:bb')
|
||||
|
||||
rows = ifs.select().where(ifs.c.bm_node_id == 2).execute().fetchall()
|
||||
self.assertEqual(len(rows), 0)
|
||||
|
||||
def _check_007(self, engine, data):
|
||||
bm_nodes = oslodbutils.get_table(engine, 'bm_nodes')
|
||||
columns = [c.name for c in bm_nodes.columns]
|
||||
self.assertNotIn(u'prov_mac_address', columns)
|
||||
|
||||
def _check_008(self, engine, data):
|
||||
self.assertRaises(sqlalchemy.exc.NoSuchTableError,
|
||||
oslodbutils.get_table, engine, 'bm_pxe_ips')
|
||||
|
||||
def _post_downgrade_008(self, engine):
|
||||
oslodbutils.get_table(engine, 'bm_pxe_ips')
|
||||
|
||||
def _pre_upgrade_010(self, engine):
|
||||
bm_nodes = oslodbutils.get_table(engine, 'bm_nodes')
|
||||
data = [{'id': 10, 'prov_mac_address': 'cc:cc:cc:cc:cc:cc'}]
|
||||
engine.execute(bm_nodes.insert(), data)
|
||||
|
||||
return data
|
||||
|
||||
def _check_010(self, engine, data):
|
||||
bm_nodes = oslodbutils.get_table(engine, 'bm_nodes')
|
||||
self.assertIn('preserve_ephemeral', bm_nodes.columns)
|
||||
|
||||
default = engine.execute(
|
||||
sqlalchemy.select([bm_nodes.c.preserve_ephemeral])
|
||||
.where(bm_nodes.c.id == data[0]['id'])
|
||||
).scalar()
|
||||
self.assertEqual(default, False)
|
||||
|
||||
bm_nodes.delete().where(bm_nodes.c.id == data[0]['id']).execute()
|
||||
|
||||
def _post_downgrade_010(self, engine):
|
||||
bm_nodes = oslodbutils.get_table(engine, 'bm_nodes')
|
||||
self.assertNotIn('preserve_ephemeral', bm_nodes.columns)
|
||||
|
||||
def _skippable_migrations(self):
|
||||
# NOTE(danms): This is deprecated code, soon to be removed, so don't
|
||||
# obsess about tests here.
|
||||
return range(1, 100)
|
||||
|
||||
|
||||
class ProjectTestCase(test.NoDBTestCase):
|
||||
|
||||
def test_all_migrations_have_downgrade(self):
|
||||
|
||||
-5
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"add_interface": {
|
||||
"address": "%(address)s"
|
||||
}
|
||||
}
|
||||
-4
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<add_interface
|
||||
address="%(address)s"
|
||||
/>
|
||||
-8
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"interface": {
|
||||
"id": %(interface_id)s,
|
||||
"address": "aa:aa:aa:aa:aa:aa",
|
||||
"datapath_id": null,
|
||||
"port_no": null
|
||||
}
|
||||
}
|
||||
-7
@@ -1,7 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<interface
|
||||
id="%(interface_id)s"
|
||||
address="aa:aa:aa:aa:aa:aa"
|
||||
datapath_id="None"
|
||||
port_no="None"
|
||||
/>
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"node": {
|
||||
"service_host": "host",
|
||||
"cpus": 8,
|
||||
"memory_mb": 8192,
|
||||
"local_gb": 128,
|
||||
"pm_address": "10.1.2.3",
|
||||
"pm_user": "pm_user",
|
||||
"pm_password": "pm_pass",
|
||||
"terminal_port": 8000
|
||||
}
|
||||
}
|
||||
-10
@@ -1,10 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<node
|
||||
service_host="host"
|
||||
cpus="8"
|
||||
memory_mb="8192"
|
||||
local_gb="128"
|
||||
pm_address="10.1.2.3"
|
||||
pm_user="pm_user"
|
||||
terminal_port="8000"
|
||||
/>
|
||||
-18
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"node": {
|
||||
"cpus": 8,
|
||||
"id": %(node_id)s,
|
||||
"instance_uuid": null,
|
||||
"interfaces": [],
|
||||
"local_gb": 128,
|
||||
"memory_mb": 8192,
|
||||
"pm_address": "10.1.2.3",
|
||||
"pm_user": "pm_user",
|
||||
"pxe_config_path": null,
|
||||
"service_host": "host",
|
||||
"task_state": null,
|
||||
"terminal_port": 8000,
|
||||
"updated_at": null,
|
||||
"uuid": "%(node_uuid)s"
|
||||
}
|
||||
}
|
||||
-4
@@ -1,4 +0,0 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<node instance_uuid="None" pm_address="10.1.2.3" task_state="None" uuid="%(node_uuid)s" pxe_config_path="None" cpus="8" updated_at="None" memory_mb="8192" service_host="host" local_gb="128" id="%(node_id)s" pm_user="pm_user" terminal_port="8000">
|
||||
<interfaces/>
|
||||
</node>
|
||||
-13
@@ -1,13 +0,0 @@
|
||||
{
|
||||
"node": {
|
||||
"service_host": "host",
|
||||
"cpus": 8,
|
||||
"memory_mb": 8192,
|
||||
"local_gb": 128,
|
||||
"pm_address": "10.1.2.3",
|
||||
"pm_user": "pm_user",
|
||||
"pm_password": "pm_pass",
|
||||
"prov_mac_address": "%(address)s",
|
||||
"terminal_port": 8000
|
||||
}
|
||||
}
|
||||
-11
@@ -1,11 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<node
|
||||
service_host="host"
|
||||
cpus="8"
|
||||
memory_mb="8192"
|
||||
local_gb="128"
|
||||
pm_address="10.1.2.3"
|
||||
pm_user="pm_user"
|
||||
prov_mac_address="%(address)s"
|
||||
terminal_port="8000"
|
||||
/>
|
||||
-25
@@ -1,25 +0,0 @@
|
||||
{
|
||||
"node": {
|
||||
"cpus": 8,
|
||||
"id": %(node_id)s,
|
||||
"instance_uuid": null,
|
||||
"interfaces": [
|
||||
{
|
||||
"address": "%(address)s",
|
||||
"datapath_id": null,
|
||||
"id": %(interface_id)s,
|
||||
"port_no": null
|
||||
}
|
||||
],
|
||||
"local_gb": 128,
|
||||
"memory_mb": 8192,
|
||||
"pm_address": "10.1.2.3",
|
||||
"pm_user": "pm_user",
|
||||
"pxe_config_path": null,
|
||||
"service_host": "host",
|
||||
"task_state": null,
|
||||
"terminal_port": 8000,
|
||||
"updated_at": null,
|
||||
"uuid": "%(node_uuid)s"
|
||||
}
|
||||
}
|
||||
-6
@@ -1,6 +0,0 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<node instance_uuid="None" pm_address="10.1.2.3" task_state="None" uuid="%(node_uuid)s" pxe_config_path="None" cpus="8" updated_at="None" memory_mb="8192" service_host="host" local_gb="128" id="%(node_id)s" pm_user="pm_user" terminal_port="8000">
|
||||
<interfaces>
|
||||
<interface datapath_id="None" id="%(interface_id)s" port_no="None" address="%(address)s"/>
|
||||
</interfaces>
|
||||
</node>
|
||||
-27
@@ -1,27 +0,0 @@
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"cpus": 8,
|
||||
"id": %(node_id)s,
|
||||
"instance_uuid": null,
|
||||
"interfaces": [
|
||||
{
|
||||
"address": "%(address)s",
|
||||
"datapath_id": null,
|
||||
"id": %(interface_id)s,
|
||||
"port_no": null
|
||||
}
|
||||
],
|
||||
"local_gb": 128,
|
||||
"memory_mb": 8192,
|
||||
"pm_address": "10.1.2.3",
|
||||
"pm_user": "pm_user",
|
||||
"pxe_config_path": null,
|
||||
"service_host": "host",
|
||||
"task_state": null,
|
||||
"terminal_port": 8000,
|
||||
"updated_at": null,
|
||||
"uuid": "%(node_uuid)s"
|
||||
}
|
||||
]
|
||||
}
|
||||
-8
@@ -1,8 +0,0 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<nodes>
|
||||
<node instance_uuid="None" pm_address="10.1.2.3" task_state="None" uuid="%(node_uuid)s" pxe_config_path="None" cpus="8" updated_at="None" memory_mb="8192" service_host="host" local_gb="128" id="%(node_id)s" pm_user="pm_user" terminal_port="8000">
|
||||
<interfaces>
|
||||
<interface datapath_id="None" id="%(interface_id)s" port_no="None" address="%(address)s"/>
|
||||
</interfaces>
|
||||
</node>
|
||||
</nodes>
|
||||
-5
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"remove_interface": {
|
||||
"address": "%(address)s"
|
||||
}
|
||||
}
|
||||
-4
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<remove_interface
|
||||
address="%(address)s"
|
||||
/>
|
||||
-25
@@ -1,25 +0,0 @@
|
||||
{
|
||||
"node": {
|
||||
"cpus": 8,
|
||||
"id": %(node_id)s,
|
||||
"instance_uuid": null,
|
||||
"interfaces": [
|
||||
{
|
||||
"address": "%(address)s",
|
||||
"datapath_id": null,
|
||||
"id": %(interface_id)s,
|
||||
"port_no": null
|
||||
}
|
||||
],
|
||||
"local_gb": 128,
|
||||
"memory_mb": 8192,
|
||||
"pm_address": "10.1.2.3",
|
||||
"pm_user": "pm_user",
|
||||
"pxe_config_path": null,
|
||||
"service_host": "host",
|
||||
"task_state": null,
|
||||
"terminal_port": 8000,
|
||||
"updated_at": null,
|
||||
"uuid": "%(node_uuid)s"
|
||||
}
|
||||
}
|
||||
-6
@@ -1,6 +0,0 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<node instance_uuid="None" pm_address="10.1.2.3" task_state="None" uuid="%(node_uuid)s" pxe_config_path="None" cpus="8" updated_at="None" memory_mb="8192" service_host="host" local_gb="128" id="%(node_id)s" pm_user="pm_user" terminal_port="8000">
|
||||
<interfaces>
|
||||
<interface datapath_id="None" id="%(interface_id)s" port_no="None" address="%(address)s"/>
|
||||
</interfaces>
|
||||
</node>
|
||||
-5
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"add_interface": {
|
||||
"address": "%(address)s"
|
||||
}
|
||||
}
|
||||
-4
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<add_interface
|
||||
address="%(address)s"
|
||||
/>
|
||||
-8
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"interface": {
|
||||
"id": %(interface_id)s,
|
||||
"address": "aa:aa:aa:aa:aa:aa",
|
||||
"datapath_id": null,
|
||||
"port_no": null
|
||||
}
|
||||
}
|
||||
-7
@@ -1,7 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<interface
|
||||
id="%(interface_id)s"
|
||||
address="aa:aa:aa:aa:aa:aa"
|
||||
datapath_id="None"
|
||||
port_no="None"
|
||||
/>
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"node": {
|
||||
"service_host": "host",
|
||||
"cpus": 8,
|
||||
"memory_mb": 8192,
|
||||
"local_gb": 128,
|
||||
"pm_address": "10.1.2.3",
|
||||
"pm_user": "pm_user",
|
||||
"pm_password": "pm_pass",
|
||||
"terminal_port": 8000
|
||||
}
|
||||
}
|
||||
-10
@@ -1,10 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<node
|
||||
service_host="host"
|
||||
cpus="8"
|
||||
memory_mb="8192"
|
||||
local_gb="128"
|
||||
pm_address="10.1.2.3"
|
||||
pm_user="pm_user"
|
||||
terminal_port="8000"
|
||||
/>
|
||||
-14
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"node": {
|
||||
"service_host": "host",
|
||||
"cpus": 8,
|
||||
"memory_mb": 8192,
|
||||
"local_gb": 128,
|
||||
"pm_address": "10.1.2.3",
|
||||
"pm_user": "pm_user",
|
||||
"terminal_port": 8000,
|
||||
"instance_uuid": null,
|
||||
"id": %(node_id)s,
|
||||
"interfaces": []
|
||||
}
|
||||
}
|
||||
-13
@@ -1,13 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<node
|
||||
service_host="host"
|
||||
cpus="8"
|
||||
memory_mb="8192"
|
||||
local_gb="128"
|
||||
pm_address="10.1.2.3"
|
||||
pm_user="pm_user"
|
||||
terminal_port="8000"
|
||||
instance_uuid="None"
|
||||
id="%(node_id)s">
|
||||
<interfaces/>
|
||||
</node>
|
||||
-13
@@ -1,13 +0,0 @@
|
||||
{
|
||||
"node": {
|
||||
"service_host": "host",
|
||||
"cpus": 8,
|
||||
"memory_mb": 8192,
|
||||
"local_gb": 128,
|
||||
"pm_address": "10.1.2.3",
|
||||
"pm_user": "pm_user",
|
||||
"pm_password": "pm_pass",
|
||||
"prov_mac_address": "%(address)s",
|
||||
"terminal_port": 8000
|
||||
}
|
||||
}
|
||||
-11
@@ -1,11 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<node
|
||||
service_host="host"
|
||||
cpus="8"
|
||||
memory_mb="8192"
|
||||
local_gb="128"
|
||||
pm_address="10.1.2.3"
|
||||
pm_user="pm_user"
|
||||
prov_mac_address="%(address)s"
|
||||
terminal_port="8000"
|
||||
/>
|
||||
-19
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"node": {
|
||||
"service_host": "host",
|
||||
"cpus": 8,
|
||||
"memory_mb": 8192,
|
||||
"local_gb": 128,
|
||||
"pm_address": "10.1.2.3",
|
||||
"pm_user": "pm_user",
|
||||
"terminal_port": 8000,
|
||||
"instance_uuid": null,
|
||||
"id": %(node_id)s,
|
||||
"interfaces": [{
|
||||
"id": %(interface_id)s,
|
||||
"address": "%(address)s",
|
||||
"datapath_id": null,
|
||||
"port_no": null
|
||||
}]
|
||||
}
|
||||
}
|
||||
-19
@@ -1,19 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<node
|
||||
service_host="host"
|
||||
cpus="8"
|
||||
memory_mb="8192"
|
||||
local_gb="128"
|
||||
pm_address="10.1.2.3"
|
||||
pm_user="pm_user"
|
||||
terminal_port="8000"
|
||||
instance_uuid="None"
|
||||
id="%(node_id)s">
|
||||
<interfaces>
|
||||
<interface
|
||||
id="%(interface_id)s"
|
||||
address="%(address)s"
|
||||
datapath_id="None"
|
||||
port_no="None"/>
|
||||
</interfaces>
|
||||
</node>
|
||||
-19
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"nodes": [{
|
||||
"service_host": "host",
|
||||
"cpus": 8,
|
||||
"memory_mb": 8192,
|
||||
"local_gb": 128,
|
||||
"pm_address": "10.1.2.3",
|
||||
"pm_user": "pm_user",
|
||||
"terminal_port": 8000,
|
||||
"instance_uuid": null,
|
||||
"id": %(node_id)s,
|
||||
"interfaces": [{
|
||||
"id": %(interface_id)s,
|
||||
"address": "%(address)s",
|
||||
"datapath_id": null,
|
||||
"port_no": null
|
||||
}]
|
||||
}]
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<nodes>
|
||||
<node
|
||||
service_host="host"
|
||||
cpus="8"
|
||||
memory_mb="8192"
|
||||
local_gb="128"
|
||||
pm_address="10.1.2.3"
|
||||
pm_user="pm_user"
|
||||
terminal_port="8000"
|
||||
instance_uuid="None"
|
||||
id="%(node_id)s">
|
||||
<interfaces>
|
||||
<interface id="%(interface_id)s" address="%(address)s" datapath_id="None" port_no="None"/>
|
||||
</interfaces>
|
||||
</node>
|
||||
</nodes>
|
||||
-5
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"remove_interface": {
|
||||
"address": "%(address)s"
|
||||
}
|
||||
}
|
||||
-4
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<remove_interface
|
||||
address="%(address)s"
|
||||
/>
|
||||
-19
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"node": {
|
||||
"service_host": "host",
|
||||
"cpus": 8,
|
||||
"memory_mb": 8192,
|
||||
"local_gb": 128,
|
||||
"pm_address": "10.1.2.3",
|
||||
"pm_user": "pm_user",
|
||||
"terminal_port": 8000,
|
||||
"instance_uuid": null,
|
||||
"id": %(node_id)s,
|
||||
"interfaces": [{
|
||||
"id": %(interface_id)s,
|
||||
"address": "%(address)s",
|
||||
"datapath_id": null,
|
||||
"port_no": null
|
||||
}]
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<node
|
||||
service_host="host"
|
||||
cpus="8"
|
||||
memory_mb="8192"
|
||||
local_gb="128"
|
||||
pm_address="10.1.2.3"
|
||||
pm_user="pm_user"
|
||||
terminal_port="8000"
|
||||
instance_uuid="None"
|
||||
id="%(node_id)s">
|
||||
<interfaces>
|
||||
<interface id="%(interface_id)s" address="%(address)s" datapath_id="None" port_no="None"/>
|
||||
</interfaces>
|
||||
</node>
|
||||
@@ -66,7 +66,6 @@ from nova.tests.integrated import api_samples_test_base
|
||||
from nova.tests.integrated import integrated_helpers
|
||||
from nova.tests.objects import test_network
|
||||
from nova.tests import utils as test_utils
|
||||
from nova.tests.virt.baremetal.db import base as bm_db_base
|
||||
from nova import utils
|
||||
from nova.volume import cinder
|
||||
|
||||
@@ -138,6 +137,8 @@ class ApiSamplesTrap(ApiSampleTestBaseV2):
|
||||
# removed) soon.
|
||||
do_not_approve_additions = []
|
||||
do_not_approve_additions.append('os-create-server-ext')
|
||||
do_not_approve_additions.append('os-baremetal-ext-status')
|
||||
do_not_approve_additions.append('os-baremetal-nodes')
|
||||
|
||||
tests = self._get_extensions_tested()
|
||||
extensions = self._get_extensions()
|
||||
@@ -2952,120 +2953,6 @@ class CellsCapacitySampleXmlTest(CellsCapacitySampleJsonTest):
|
||||
ctype = 'xml'
|
||||
|
||||
|
||||
class BareMetalNodesJsonTest(ApiSampleTestBaseV2, bm_db_base.BMDBTestCase):
|
||||
extension_name = ('nova.api.openstack.compute.contrib.baremetal_nodes.'
|
||||
'Baremetal_nodes')
|
||||
|
||||
def _get_subs(self):
|
||||
subs = {}
|
||||
return subs
|
||||
|
||||
def _create_node(self):
|
||||
response = self._do_post("os-baremetal-nodes",
|
||||
"baremetal-node-create-req",
|
||||
{})
|
||||
subs = self._get_subs()
|
||||
subs.update({'node_id': '(?P<id>\d+)'})
|
||||
return self._verify_response("baremetal-node-create-resp", subs,
|
||||
response, 200)
|
||||
|
||||
def _create_node_with_address(self):
|
||||
address = '12:34:56:78:90:ab'
|
||||
req_subs = {'address': address}
|
||||
response = self._do_post("os-baremetal-nodes",
|
||||
"baremetal-node-create-with-address-req",
|
||||
req_subs)
|
||||
subs = self._get_subs()
|
||||
subs.update({'node_id': '(?P<id>\d+)',
|
||||
'interface_id': '\d+',
|
||||
'address': address,
|
||||
})
|
||||
self._verify_response("baremetal-node-create-with-address-resp",
|
||||
subs, response, 200)
|
||||
|
||||
def test_create_node(self):
|
||||
self._create_node()
|
||||
|
||||
def test_create_node_with_address(self):
|
||||
self._create_node_with_address()
|
||||
|
||||
def test_list_nodes(self):
|
||||
node_id = self._create_node()
|
||||
interface_id = self._add_interface(node_id)
|
||||
response = self._do_get('os-baremetal-nodes')
|
||||
subs = self._get_subs()
|
||||
subs.update({'node_id': node_id,
|
||||
'interface_id': interface_id,
|
||||
'address': 'aa:aa:aa:aa:aa:aa',
|
||||
})
|
||||
self._verify_response('baremetal-node-list-resp', subs,
|
||||
response, 200)
|
||||
|
||||
def test_show_node(self):
|
||||
node_id = self._create_node()
|
||||
interface_id = self._add_interface(node_id)
|
||||
response = self._do_get('os-baremetal-nodes/%s' % node_id)
|
||||
subs = self._get_subs()
|
||||
subs.update({'node_id': node_id,
|
||||
'interface_id': interface_id,
|
||||
'address': 'aa:aa:aa:aa:aa:aa',
|
||||
})
|
||||
self._verify_response('baremetal-node-show-resp', subs, response, 200)
|
||||
|
||||
def test_delete_node(self):
|
||||
node_id = self._create_node()
|
||||
response = self._do_delete("os-baremetal-nodes/%s" % node_id)
|
||||
self.assertEqual(response.status_code, 202)
|
||||
|
||||
def _add_interface(self, node_id):
|
||||
response = self._do_post("os-baremetal-nodes/%s/action" % node_id,
|
||||
"baremetal-node-add-interface-req",
|
||||
{'address': 'aa:aa:aa:aa:aa:aa'})
|
||||
subs = {'interface_id': r'(?P<id>\d+)'}
|
||||
return self._verify_response("baremetal-node-add-interface-resp", subs,
|
||||
response, 200)
|
||||
|
||||
def test_add_interface(self):
|
||||
node_id = self._create_node()
|
||||
self._add_interface(node_id)
|
||||
|
||||
def test_remove_interface(self):
|
||||
node_id = self._create_node()
|
||||
self._add_interface(node_id)
|
||||
response = self._do_post("os-baremetal-nodes/%s/action" % node_id,
|
||||
"baremetal-node-remove-interface-req",
|
||||
{'address': 'aa:aa:aa:aa:aa:aa'})
|
||||
self.assertEqual(response.status_code, 202)
|
||||
self.assertEqual(response.content, "")
|
||||
|
||||
|
||||
class BareMetalNodesXmlTest(BareMetalNodesJsonTest):
|
||||
ctype = 'xml'
|
||||
|
||||
|
||||
class BareMetalExtStatusJsonTest(BareMetalNodesJsonTest):
|
||||
extension_name = ('nova.api.openstack.compute.contrib.'
|
||||
'baremetal_ext_status.Baremetal_ext_status')
|
||||
|
||||
def _get_flags(self):
|
||||
f = super(BareMetalExtStatusJsonTest, self)._get_flags()
|
||||
f['osapi_compute_extension'] = CONF.osapi_compute_extension[:]
|
||||
# BareMetalExtStatus extension also needs BareMetalNodes to be loaded.
|
||||
f['osapi_compute_extension'].append(
|
||||
'nova.api.openstack.compute.contrib.baremetal_nodes.'
|
||||
'Baremetal_nodes')
|
||||
return f
|
||||
|
||||
def _get_subs(self):
|
||||
vanilla_regexes = self._get_regexes()
|
||||
subs = {'node_uuid': vanilla_regexes['uuid']}
|
||||
return subs
|
||||
|
||||
|
||||
class BareMetalExtStatusXmlTest(BareMetalExtStatusJsonTest):
|
||||
ctype = 'xml'
|
||||
|
||||
|
||||
class BlockDeviceMappingV2BootJsonTest(ServersSampleBase):
|
||||
extension_name = ('nova.api.openstack.compute.contrib.'
|
||||
'block_device_mapping_v2_boot.'
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
# Copyright (c) 2012 NTT DOCOMO, INC.
|
||||
# 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.
|
||||
|
||||
"""Bare-metal DB test base class."""
|
||||
|
||||
from oslo.config import cfg
|
||||
|
||||
from nova import context as nova_context
|
||||
from nova import test
|
||||
from nova.virt.baremetal.db import migration as bm_migration
|
||||
from nova.virt.baremetal.db.sqlalchemy import session as bm_session
|
||||
|
||||
_DB_CACHE = None
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.import_opt('sql_connection',
|
||||
'nova.virt.baremetal.db.sqlalchemy.session',
|
||||
group='baremetal')
|
||||
|
||||
|
||||
class Database(test.Database):
|
||||
|
||||
def post_migrations(self):
|
||||
pass
|
||||
|
||||
|
||||
class BMDBTestCase(test.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(BMDBTestCase, self).setUp()
|
||||
self.flags(sql_connection='sqlite://', group='baremetal')
|
||||
global _DB_CACHE
|
||||
if not _DB_CACHE:
|
||||
_DB_CACHE = Database(bm_session, bm_migration,
|
||||
sql_connection=CONF.baremetal.sql_connection,
|
||||
sqlite_db=None,
|
||||
sqlite_clean_db=None)
|
||||
self.useFixture(_DB_CACHE)
|
||||
self.context = nova_context.get_admin_context()
|
||||
@@ -1,56 +0,0 @@
|
||||
# Copyright (c) 2012 NTT DOCOMO, INC.
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Bare-metal DB testcase for BareMetalInterface
|
||||
"""
|
||||
|
||||
from oslo.db import exception as db_exc
|
||||
|
||||
from nova import exception
|
||||
from nova.tests.virt.baremetal.db import base
|
||||
from nova.virt.baremetal import db
|
||||
|
||||
|
||||
class BareMetalInterfaceTestCase(base.BMDBTestCase):
|
||||
|
||||
def test_unique_address(self):
|
||||
pif1_id = db.bm_interface_create(self.context, 1, '11:11:11:11:11:11',
|
||||
'0x1', 1)
|
||||
self.assertRaises(db_exc.DBError,
|
||||
db.bm_interface_create,
|
||||
self.context, 2, '11:11:11:11:11:11', '0x2', 2)
|
||||
# succeed after delete pif1
|
||||
db.bm_interface_destroy(self.context, pif1_id)
|
||||
pif2_id = db.bm_interface_create(self.context, 2, '11:11:11:11:11:11',
|
||||
'0x2', 2)
|
||||
self.assertIsNotNone(pif2_id)
|
||||
|
||||
def test_unique_vif_uuid(self):
|
||||
pif1_id = db.bm_interface_create(self.context, 1, '11:11:11:11:11:11',
|
||||
'0x1', 1)
|
||||
pif2_id = db.bm_interface_create(self.context, 2, '22:22:22:22:22:22',
|
||||
'0x2', 2)
|
||||
db.bm_interface_set_vif_uuid(self.context, pif1_id, 'AAAA')
|
||||
self.assertRaises(exception.NovaException,
|
||||
db.bm_interface_set_vif_uuid,
|
||||
self.context, pif2_id, 'AAAA')
|
||||
|
||||
def test_vif_not_found(self):
|
||||
pif_id = db.bm_interface_create(self.context, 1, '11:11:11:11:11:11',
|
||||
'0x1', 1)
|
||||
self.assertRaises(exception.NovaException,
|
||||
db.bm_interface_set_vif_uuid,
|
||||
self.context, pif_id + 1, 'AAAA')
|
||||
@@ -1,191 +0,0 @@
|
||||
# Copyright (c) 2012 NTT DOCOMO, INC.
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Bare-Metal DB testcase for BareMetalNode
|
||||
"""
|
||||
|
||||
from nova import exception
|
||||
from nova.tests.virt.baremetal.db import base
|
||||
from nova.tests.virt.baremetal.db import utils
|
||||
from nova.virt.baremetal import db
|
||||
|
||||
|
||||
class BareMetalNodesTestCase(base.BMDBTestCase):
|
||||
|
||||
def _create_nodes(self):
|
||||
nodes = [
|
||||
utils.new_bm_node(pm_address='0', service_host="host1",
|
||||
memory_mb=100000, cpus=100, local_gb=10000),
|
||||
utils.new_bm_node(pm_address='1', service_host="host2",
|
||||
instance_uuid='A',
|
||||
memory_mb=100000, cpus=100, local_gb=10000),
|
||||
utils.new_bm_node(pm_address='2', service_host="host2",
|
||||
memory_mb=1000, cpus=1, local_gb=1000),
|
||||
utils.new_bm_node(pm_address='3', service_host="host2",
|
||||
memory_mb=1000, cpus=2, local_gb=1000),
|
||||
utils.new_bm_node(pm_address='4', service_host="host2",
|
||||
memory_mb=2000, cpus=1, local_gb=1000),
|
||||
utils.new_bm_node(pm_address='5', service_host="host2",
|
||||
memory_mb=2000, cpus=2, local_gb=1000),
|
||||
]
|
||||
self.ids = []
|
||||
for n in nodes:
|
||||
ref = db.bm_node_create(self.context, n)
|
||||
self.ids.append(ref['id'])
|
||||
|
||||
def test_get_all(self):
|
||||
r = db.bm_node_get_all(self.context)
|
||||
self.assertEqual(r, [])
|
||||
|
||||
self._create_nodes()
|
||||
|
||||
r = db.bm_node_get_all(self.context)
|
||||
self.assertEqual(len(r), 6)
|
||||
|
||||
def test_get(self):
|
||||
self._create_nodes()
|
||||
|
||||
r = db.bm_node_get(self.context, self.ids[0])
|
||||
self.assertEqual(r['pm_address'], '0')
|
||||
|
||||
r = db.bm_node_get(self.context, self.ids[1])
|
||||
self.assertEqual(r['pm_address'], '1')
|
||||
|
||||
self.assertRaises(
|
||||
exception.NodeNotFound,
|
||||
db.bm_node_get,
|
||||
self.context, -1)
|
||||
|
||||
def test_get_by_service_host(self):
|
||||
self._create_nodes()
|
||||
|
||||
r = db.bm_node_get_all(self.context, service_host=None)
|
||||
self.assertEqual(len(r), 6)
|
||||
|
||||
r = db.bm_node_get_all(self.context, service_host="host1")
|
||||
self.assertEqual(len(r), 1)
|
||||
self.assertEqual(r[0]['pm_address'], '0')
|
||||
|
||||
r = db.bm_node_get_all(self.context, service_host="host2")
|
||||
self.assertEqual(len(r), 5)
|
||||
pmaddrs = [x['pm_address'] for x in r]
|
||||
self.assertIn('1', pmaddrs)
|
||||
self.assertIn('2', pmaddrs)
|
||||
self.assertIn('3', pmaddrs)
|
||||
self.assertIn('4', pmaddrs)
|
||||
self.assertIn('5', pmaddrs)
|
||||
|
||||
r = db.bm_node_get_all(self.context, service_host="host3")
|
||||
self.assertEqual(r, [])
|
||||
|
||||
def test_get_associated(self):
|
||||
self._create_nodes()
|
||||
|
||||
r = db.bm_node_get_associated(self.context, service_host=None)
|
||||
self.assertEqual(len(r), 1)
|
||||
self.assertEqual(r[0]['pm_address'], '1')
|
||||
|
||||
r = db.bm_node_get_unassociated(self.context, service_host=None)
|
||||
self.assertEqual(len(r), 5)
|
||||
pmaddrs = [x['pm_address'] for x in r]
|
||||
self.assertIn('0', pmaddrs)
|
||||
self.assertIn('2', pmaddrs)
|
||||
self.assertIn('3', pmaddrs)
|
||||
self.assertIn('4', pmaddrs)
|
||||
self.assertIn('5', pmaddrs)
|
||||
|
||||
def test_destroy(self):
|
||||
self._create_nodes()
|
||||
|
||||
db.bm_node_destroy(self.context, self.ids[0])
|
||||
|
||||
self.assertRaises(
|
||||
exception.NodeNotFound,
|
||||
db.bm_node_get,
|
||||
self.context, self.ids[0])
|
||||
|
||||
r = db.bm_node_get_all(self.context)
|
||||
self.assertEqual(len(r), 5)
|
||||
|
||||
def test_destroy_with_interfaces(self):
|
||||
self._create_nodes()
|
||||
|
||||
if_a_id = db.bm_interface_create(self.context, self.ids[0],
|
||||
'aa:aa:aa:aa:aa:aa', None, None)
|
||||
if_b_id = db.bm_interface_create(self.context, self.ids[0],
|
||||
'bb:bb:bb:bb:bb:bb', None, None)
|
||||
if_x_id = db.bm_interface_create(self.context, self.ids[1],
|
||||
'11:22:33:44:55:66', None, None)
|
||||
|
||||
db.bm_node_destroy(self.context, self.ids[0])
|
||||
|
||||
self.assertRaises(
|
||||
exception.NovaException,
|
||||
db.bm_interface_get,
|
||||
self.context, if_a_id)
|
||||
|
||||
self.assertRaises(
|
||||
exception.NovaException,
|
||||
db.bm_interface_get,
|
||||
self.context, if_b_id)
|
||||
|
||||
# Another node's interface is not affected
|
||||
if_x = db.bm_interface_get(self.context, if_x_id)
|
||||
self.assertEqual(self.ids[1], if_x['bm_node_id'])
|
||||
|
||||
self.assertRaises(
|
||||
exception.NodeNotFound,
|
||||
db.bm_node_get,
|
||||
self.context, self.ids[0])
|
||||
|
||||
r = db.bm_node_get_all(self.context)
|
||||
self.assertEqual(len(r), 5)
|
||||
|
||||
def test_find_free(self):
|
||||
self._create_nodes()
|
||||
fn = db.bm_node_find_free(self.context, 'host2')
|
||||
self.assertEqual(fn['pm_address'], '2')
|
||||
|
||||
fn = db.bm_node_find_free(self.context, 'host2',
|
||||
memory_mb=500, cpus=2, local_gb=100)
|
||||
self.assertEqual(fn['pm_address'], '3')
|
||||
|
||||
fn = db.bm_node_find_free(self.context, 'host2',
|
||||
memory_mb=1001, cpus=1, local_gb=1000)
|
||||
self.assertEqual(fn['pm_address'], '4')
|
||||
|
||||
fn = db.bm_node_find_free(self.context, 'host2',
|
||||
memory_mb=2000, cpus=1, local_gb=1000)
|
||||
self.assertEqual(fn['pm_address'], '4')
|
||||
|
||||
fn = db.bm_node_find_free(self.context, 'host2',
|
||||
memory_mb=2000, cpus=2, local_gb=1000)
|
||||
self.assertEqual(fn['pm_address'], '5')
|
||||
|
||||
# check memory_mb
|
||||
fn = db.bm_node_find_free(self.context, 'host2',
|
||||
memory_mb=2001, cpus=2, local_gb=1000)
|
||||
self.assertIsNone(fn)
|
||||
|
||||
# check cpus
|
||||
fn = db.bm_node_find_free(self.context, 'host2',
|
||||
memory_mb=2000, cpus=3, local_gb=1000)
|
||||
self.assertIsNone(fn)
|
||||
|
||||
# check local_gb
|
||||
fn = db.bm_node_find_free(self.context, 'host2',
|
||||
memory_mb=2000, cpus=2, local_gb=1001)
|
||||
self.assertIsNone(fn)
|
||||
@@ -1,53 +0,0 @@
|
||||
# Copyright (c) 2012 NTT DOCOMO, INC.
|
||||
# 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.
|
||||
|
||||
"""Bare-metal test utils."""
|
||||
|
||||
from nova import test
|
||||
from nova.virt.baremetal.db.sqlalchemy import models as bm_models
|
||||
|
||||
|
||||
def new_bm_node(**kwargs):
|
||||
h = bm_models.BareMetalNode()
|
||||
h.id = kwargs.pop('id', None)
|
||||
h.uuid = kwargs.pop('uuid', None)
|
||||
h.service_host = kwargs.pop('service_host', None)
|
||||
h.instance_uuid = kwargs.pop('instance_uuid', None)
|
||||
h.cpus = kwargs.pop('cpus', 1)
|
||||
h.memory_mb = kwargs.pop('memory_mb', 1024)
|
||||
h.local_gb = kwargs.pop('local_gb', 64)
|
||||
h.pm_address = kwargs.pop('pm_address', '192.168.1.1')
|
||||
h.pm_user = kwargs.pop('pm_user', 'ipmi_user')
|
||||
h.pm_password = kwargs.pop('pm_password', 'ipmi_password')
|
||||
h.task_state = kwargs.pop('task_state', None)
|
||||
h.terminal_port = kwargs.pop('terminal_port', 8000)
|
||||
if len(kwargs) > 0:
|
||||
raise test.TestingException("unknown field: %s"
|
||||
% ','.join(kwargs.keys()))
|
||||
return h
|
||||
|
||||
|
||||
def new_bm_interface(**kwargs):
|
||||
x = bm_models.BareMetalInterface()
|
||||
x.id = kwargs.pop('id', None)
|
||||
x.bm_node_id = kwargs.pop('bm_node_id', None)
|
||||
x.address = kwargs.pop('address', None)
|
||||
x.datapath_id = kwargs.pop('datapath_id', None)
|
||||
x.port_no = kwargs.pop('port_no', None)
|
||||
x.vif_uuid = kwargs.pop('vif_uuid', None)
|
||||
if len(kwargs) > 0:
|
||||
raise test.TestingException("unknown field: %s"
|
||||
% ','.join(kwargs.keys()))
|
||||
return x
|
||||
@@ -1,24 +0,0 @@
|
||||
[unit_tests]
|
||||
# Set up any number of databases to test concurrently.
|
||||
# The "name" used in the test is the config variable key.
|
||||
|
||||
sqlite=sqlite://
|
||||
#sqlitefile=sqlite:///test_baremetal_migrations_utils.db
|
||||
#mysql=mysql+mysqldb://user:pass@localhost/test_baremetal_migrations_utils
|
||||
#postgresql=postgresql+psycopg2://user:pass@localhost/test_migrations_utils
|
||||
|
||||
[migration_dbs]
|
||||
# Migration DB details are listed separately as they can't be connected to
|
||||
# concurrently. These databases can't be the same as above
|
||||
|
||||
# Note, sqlite:// is in-memory and unique each time it is spawned.
|
||||
# However file sqlite's are not unique.
|
||||
|
||||
sqlite=sqlite://
|
||||
#sqlitefile=sqlite:///test_baremetal_migrations.db
|
||||
#mysql=mysql+mysqldb://user:pass@localhost/test_baremetal_migrations
|
||||
#postgresql=postgresql+psycopg2://user:pass@localhost/test_baremetal_migrations
|
||||
|
||||
[walk_style]
|
||||
snake_walk=yes
|
||||
downgrade=yes
|
||||
@@ -1,541 +0,0 @@
|
||||
# coding=utf-8
|
||||
|
||||
# Copyright 2012 Hewlett-Packard Development Company, L.P.
|
||||
# Copyright (c) 2012 NTT DOCOMO, INC.
|
||||
# Copyright (c) 2011 University of Southern California / ISI
|
||||
# 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.
|
||||
|
||||
"""Tests for the base baremetal driver class."""
|
||||
|
||||
import mock
|
||||
import mox
|
||||
from oslo.config import cfg
|
||||
|
||||
from nova.compute import flavors
|
||||
from nova.compute import power_state
|
||||
from nova.compute import task_states
|
||||
from nova import db as main_db
|
||||
from nova import exception
|
||||
from nova import objects
|
||||
from nova.openstack.common import jsonutils
|
||||
from nova import test
|
||||
from nova.tests.image import fake as fake_image
|
||||
from nova.tests import utils
|
||||
from nova.tests.virt.baremetal.db import base as bm_db_base
|
||||
from nova.tests.virt.baremetal.db import utils as bm_db_utils
|
||||
from nova.virt.baremetal import baremetal_states
|
||||
from nova.virt.baremetal import db
|
||||
from nova.virt.baremetal import driver as bm_driver
|
||||
from nova.virt.baremetal import fake
|
||||
from nova.virt import fake as fake_virt
|
||||
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
COMMON_FLAGS = dict(
|
||||
firewall_driver='nova.virt.baremetal.fake.FakeFirewallDriver',
|
||||
host='test_host',
|
||||
)
|
||||
|
||||
BAREMETAL_FLAGS = dict(
|
||||
driver='nova.virt.baremetal.fake.FakeDriver',
|
||||
flavor_extra_specs=['cpu_arch:x86_64', 'test_spec:test_value'],
|
||||
power_manager='nova.virt.baremetal.fake.FakePowerManager',
|
||||
vif_driver='nova.virt.baremetal.fake.FakeVifDriver',
|
||||
volume_driver='nova.virt.baremetal.fake.FakeVolumeDriver',
|
||||
group='baremetal',
|
||||
)
|
||||
|
||||
|
||||
class BareMetalDriverNoDBTestCase(test.NoDBTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(BareMetalDriverNoDBTestCase, self).setUp()
|
||||
self.flags(**COMMON_FLAGS)
|
||||
self.flags(**BAREMETAL_FLAGS)
|
||||
self.driver = bm_driver.BareMetalDriver(None)
|
||||
|
||||
def test_validate_driver_loading(self):
|
||||
self.assertIsInstance(self.driver.driver, fake.FakeDriver)
|
||||
self.assertIsInstance(self.driver.vif_driver, fake.FakeVifDriver)
|
||||
self.assertIsInstance(self.driver.volume_driver, fake.FakeVolumeDriver)
|
||||
self.assertIsInstance(self.driver.firewall_driver,
|
||||
fake.FakeFirewallDriver)
|
||||
|
||||
def test_driver_capabilities(self):
|
||||
self.assertTrue(self.driver.capabilities['has_imagecache'])
|
||||
self.assertFalse(self.driver.capabilities['supports_recreate'])
|
||||
|
||||
|
||||
class BareMetalDriverWithDBTestCase(bm_db_base.BMDBTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(BareMetalDriverWithDBTestCase, self).setUp()
|
||||
self.flags(**COMMON_FLAGS)
|
||||
self.flags(**BAREMETAL_FLAGS)
|
||||
|
||||
fake_image.stub_out_image_service(self.stubs)
|
||||
self.context = utils.get_test_admin_context()
|
||||
self.driver = bm_driver.BareMetalDriver(fake_virt.FakeVirtAPI())
|
||||
self.addCleanup(fake_image.FakeImageService_reset)
|
||||
|
||||
def _create_node(self, node_info=None, nic_info=None, ephemeral=True):
|
||||
result = {}
|
||||
if node_info is None:
|
||||
node_info = bm_db_utils.new_bm_node(
|
||||
id=123,
|
||||
service_host='test_host',
|
||||
cpus=2,
|
||||
memory_mb=2048,
|
||||
)
|
||||
if nic_info is None:
|
||||
nic_info = [
|
||||
{'address': '01:23:45:67:89:01', 'datapath_id': '0x1',
|
||||
'port_no': 1},
|
||||
{'address': '01:23:45:67:89:02', 'datapath_id': '0x2',
|
||||
'port_no': 2},
|
||||
]
|
||||
result['node_info'] = node_info
|
||||
result['nic_info'] = nic_info
|
||||
result['node'] = db.bm_node_create(self.context, node_info)
|
||||
|
||||
for nic in nic_info:
|
||||
db.bm_interface_create(
|
||||
self.context,
|
||||
result['node']['id'],
|
||||
nic['address'],
|
||||
nic['datapath_id'],
|
||||
nic['port_no'],
|
||||
)
|
||||
if ephemeral:
|
||||
result['instance'] = utils.get_test_instance()
|
||||
else:
|
||||
flavor = utils.get_test_flavor(options={'ephemeral_gb': 0})
|
||||
result['instance'] = utils.get_test_instance(flavor=flavor)
|
||||
result['instance']['node'] = result['node']['uuid']
|
||||
result['spawn_params'] = dict(
|
||||
admin_password='test_pass',
|
||||
block_device_info=None,
|
||||
context=self.context,
|
||||
image_meta=utils.get_test_image_info(
|
||||
None, result['instance']),
|
||||
injected_files=[('/fake/path', 'hello world')],
|
||||
instance=result['instance'],
|
||||
network_info=utils.get_test_network_info(),
|
||||
)
|
||||
result['destroy_params'] = dict(
|
||||
context=self.context,
|
||||
instance=result['instance'],
|
||||
network_info=result['spawn_params']['network_info'],
|
||||
block_device_info=result['spawn_params']['block_device_info'],
|
||||
)
|
||||
|
||||
instance = objects.Instance._from_db_object(
|
||||
self.context, objects.Instance(), result['instance'])
|
||||
instance.node = result['node']['uuid']
|
||||
|
||||
result['rebuild_params'] = dict(
|
||||
context=self.context,
|
||||
instance=instance,
|
||||
image_meta=utils.get_test_image_info(None, result['instance']),
|
||||
injected_files=[('/fake/path', 'hello world')],
|
||||
admin_password='test_pass',
|
||||
bdms={},
|
||||
detach_block_devices=self.mox.CreateMockAnything(),
|
||||
attach_block_devices=self.mox.CreateMockAnything(),
|
||||
network_info=result['spawn_params']['network_info'],
|
||||
block_device_info=result['spawn_params']['block_device_info'],
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
def test_spawn_ok(self):
|
||||
node = self._create_node()
|
||||
self.driver.spawn(**node['spawn_params'])
|
||||
row = db.bm_node_get(self.context, node['node']['id'])
|
||||
self.assertEqual(row['task_state'], baremetal_states.ACTIVE)
|
||||
self.assertEqual(row['instance_uuid'], node['instance']['uuid'])
|
||||
self.assertEqual(row['instance_name'], node['instance']['hostname'])
|
||||
instance = main_db.instance_get_by_uuid(self.context,
|
||||
node['instance']['uuid'])
|
||||
self.assertEqual(instance['default_ephemeral_device'], '/dev/sda1')
|
||||
|
||||
def test_set_default_ephemeral_device(self):
|
||||
instance = objects.Instance(context=self.context)
|
||||
instance.system_metadata = flavors.save_flavor_info(
|
||||
{}, flavors.get_default_flavor())
|
||||
instance.system_metadata['instance_type_ephemeral_gb'] = 1
|
||||
with mock.patch.object(instance, 'save') as mock_save:
|
||||
self.driver._set_default_ephemeral_device(instance)
|
||||
mock_save.assert_called_once_with()
|
||||
self.assertEqual('/dev/sda1', instance.default_ephemeral_device)
|
||||
|
||||
def test_spawn_no_ephemeral_ok(self):
|
||||
node = self._create_node(ephemeral=False)
|
||||
self.driver.spawn(**node['spawn_params'])
|
||||
row = db.bm_node_get(self.context, node['node']['id'])
|
||||
self.assertEqual(row['task_state'], baremetal_states.ACTIVE)
|
||||
self.assertEqual(row['instance_uuid'], node['instance']['uuid'])
|
||||
self.assertEqual(row['instance_name'], node['instance']['hostname'])
|
||||
instance = main_db.instance_get_by_uuid(self.context,
|
||||
node['instance']['uuid'])
|
||||
self.assertIsNone(instance['default_ephemeral_device'])
|
||||
|
||||
def _test_rebuild(self, ephemeral):
|
||||
node = self._create_node(ephemeral=ephemeral)
|
||||
self.driver.spawn(**node['spawn_params'])
|
||||
after_spawn = db.bm_node_get(self.context, node['node']['id'])
|
||||
|
||||
instance = node['rebuild_params']['instance']
|
||||
instance.task_state = task_states.REBUILDING
|
||||
instance.save(expected_task_state=[None])
|
||||
self.driver.rebuild(preserve_ephemeral=ephemeral,
|
||||
**node['rebuild_params'])
|
||||
after_rebuild = db.bm_node_get(self.context, node['node']['id'])
|
||||
|
||||
self.assertEqual(after_rebuild['task_state'], baremetal_states.ACTIVE)
|
||||
self.assertEqual(after_rebuild['preserve_ephemeral'], ephemeral)
|
||||
self.assertEqual(after_spawn['instance_uuid'],
|
||||
after_rebuild['instance_uuid'])
|
||||
|
||||
def test_rebuild_ok(self):
|
||||
self._test_rebuild(ephemeral=False)
|
||||
|
||||
def test_rebuild_preserve_ephemeral(self):
|
||||
self._test_rebuild(ephemeral=True)
|
||||
|
||||
def test_macs_from_nic_for_instance(self):
|
||||
node = self._create_node()
|
||||
expected = set([nic['address'] for nic in node['nic_info']])
|
||||
self.assertEqual(
|
||||
expected, self.driver.macs_for_instance(node['instance']))
|
||||
|
||||
def test_macs_for_instance_after_spawn(self):
|
||||
node = self._create_node()
|
||||
self.driver.spawn(**node['spawn_params'])
|
||||
|
||||
expected = set([nic['address'] for nic in node['nic_info']])
|
||||
self.assertEqual(
|
||||
expected, self.driver.macs_for_instance(node['instance']))
|
||||
|
||||
def test_macs_for_instance(self):
|
||||
node = self._create_node()
|
||||
expected = set(['01:23:45:67:89:01', '01:23:45:67:89:02'])
|
||||
self.assertEqual(
|
||||
expected, self.driver.macs_for_instance(node['instance']))
|
||||
|
||||
def test_macs_for_instance_no_interfaces(self):
|
||||
# Nodes cannot boot with no MACs, so we raise an error if that happens.
|
||||
node = self._create_node(nic_info=[])
|
||||
self.assertRaises(exception.NovaException,
|
||||
self.driver.macs_for_instance, node['instance'])
|
||||
|
||||
def test_spawn_node_already_associated(self):
|
||||
node = self._create_node()
|
||||
db.bm_node_update(self.context, node['node']['id'],
|
||||
{'instance_uuid': '1234-5678'})
|
||||
|
||||
self.assertRaises(exception.NovaException,
|
||||
self.driver.spawn, **node['spawn_params'])
|
||||
|
||||
row = db.bm_node_get(self.context, node['node']['id'])
|
||||
self.assertIsNone(row['task_state'])
|
||||
|
||||
def test_spawn_node_in_use(self):
|
||||
node = self._create_node()
|
||||
|
||||
self.driver.spawn(**node['spawn_params'])
|
||||
self.assertRaises(exception.NovaException,
|
||||
self.driver.spawn, **node['spawn_params'])
|
||||
|
||||
def test_spawn_node_not_found(self):
|
||||
node = self._create_node()
|
||||
db.bm_node_update(self.context, node['node']['id'],
|
||||
{'uuid': 'hide-this-node'})
|
||||
|
||||
self.assertRaises(exception.NovaException,
|
||||
self.driver.spawn, **node['spawn_params'])
|
||||
|
||||
row = db.bm_node_get(self.context, node['node']['id'])
|
||||
self.assertIsNone(row['task_state'])
|
||||
|
||||
def test_spawn_fails(self):
|
||||
node = self._create_node()
|
||||
|
||||
self.mox.StubOutWithMock(fake.FakePowerManager, 'activate_node')
|
||||
fake.FakePowerManager.activate_node().AndRaise(test.TestingException)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
self.assertRaises(test.TestingException,
|
||||
self.driver.spawn, **node['spawn_params'])
|
||||
|
||||
row = db.bm_node_get(self.context, node['node']['id'])
|
||||
self.assertEqual(row['task_state'], baremetal_states.DELETED)
|
||||
|
||||
def test_spawn_prepared(self):
|
||||
node = self._create_node()
|
||||
|
||||
def update_2prepared(context, node, instance, state):
|
||||
row = db.bm_node_get(context, node['id'])
|
||||
self.assertEqual(row['task_state'], baremetal_states.BUILDING)
|
||||
db.bm_node_update(
|
||||
context, node['id'],
|
||||
{'task_state': baremetal_states.PREPARED})
|
||||
|
||||
self.mox.StubOutWithMock(fake.FakeDriver, 'activate_node')
|
||||
self.mox.StubOutWithMock(bm_driver, '_update_state')
|
||||
|
||||
bm_driver._update_state(
|
||||
self.context,
|
||||
mox.IsA(node['node']),
|
||||
node['instance'],
|
||||
baremetal_states.PREPARED).WithSideEffects(update_2prepared)
|
||||
fake.FakeDriver.activate_node(
|
||||
self.context,
|
||||
mox.IsA(node['node']),
|
||||
node['instance']).AndRaise(test.TestingException)
|
||||
bm_driver._update_state(
|
||||
self.context,
|
||||
mox.IsA(node['node']),
|
||||
node['instance'],
|
||||
baremetal_states.ERROR).AndRaise(test.TestingException)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
self.assertRaises(test.TestingException,
|
||||
self.driver.spawn, **node['spawn_params'])
|
||||
|
||||
row = db.bm_node_get(self.context, node['node']['id'])
|
||||
self.assertEqual(row['task_state'], baremetal_states.PREPARED)
|
||||
|
||||
def test_spawn_fails_to_cleanup(self):
|
||||
node = self._create_node()
|
||||
|
||||
self.mox.StubOutWithMock(fake.FakePowerManager, 'activate_node')
|
||||
self.mox.StubOutWithMock(fake.FakePowerManager, 'deactivate_node')
|
||||
fake.FakePowerManager.deactivate_node().AndReturn(None)
|
||||
fake.FakePowerManager.activate_node().AndRaise(test.TestingException)
|
||||
fake.FakePowerManager.deactivate_node().AndRaise(test.TestingException)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
self.assertRaises(test.TestingException,
|
||||
self.driver.spawn, **node['spawn_params'])
|
||||
|
||||
row = db.bm_node_get(self.context, node['node']['id'])
|
||||
self.assertEqual(row['task_state'], baremetal_states.ERROR)
|
||||
|
||||
def test_spawn_destroy_images_on_deploy(self):
|
||||
node = self._create_node()
|
||||
self.driver.driver.destroy_images = mock.MagicMock()
|
||||
self.driver.spawn(**node['spawn_params'])
|
||||
row = db.bm_node_get(self.context, node['node']['id'])
|
||||
self.assertEqual(row['task_state'], baremetal_states.ACTIVE)
|
||||
self.assertEqual(row['instance_uuid'], node['instance']['uuid'])
|
||||
self.assertEqual(row['instance_name'], node['instance']['hostname'])
|
||||
instance = main_db.instance_get_by_uuid(self.context,
|
||||
node['instance']['uuid'])
|
||||
self.assertIsNotNone(instance)
|
||||
self.assertEqual(1, self.driver.driver.destroy_images.call_count)
|
||||
|
||||
def test_destroy_ok(self):
|
||||
node = self._create_node()
|
||||
self.driver.spawn(**node['spawn_params'])
|
||||
self.driver.destroy(**node['destroy_params'])
|
||||
|
||||
row = db.bm_node_get(self.context, node['node']['id'])
|
||||
self.assertEqual(row['task_state'], baremetal_states.DELETED)
|
||||
self.assertIsNone(row['instance_uuid'])
|
||||
self.assertIsNone(row['instance_name'])
|
||||
|
||||
def test_destroy_fails(self):
|
||||
node = self._create_node()
|
||||
|
||||
self.mox.StubOutWithMock(fake.FakePowerManager, 'deactivate_node')
|
||||
fake.FakePowerManager.deactivate_node().AndReturn(None)
|
||||
fake.FakePowerManager.deactivate_node().AndRaise(test.TestingException)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
self.driver.spawn(**node['spawn_params'])
|
||||
self.assertRaises(test.TestingException,
|
||||
self.driver.destroy, **node['destroy_params'])
|
||||
|
||||
row = db.bm_node_get(self.context, node['node']['id'])
|
||||
self.assertEqual(row['task_state'], baremetal_states.ERROR)
|
||||
self.assertEqual(row['instance_uuid'], node['instance']['uuid'])
|
||||
|
||||
def test_get_available_resources(self):
|
||||
node = self._create_node()
|
||||
|
||||
resources = self.driver.get_available_resource(node['node']['uuid'])
|
||||
self.assertEqual(resources['memory_mb'],
|
||||
node['node_info']['memory_mb'])
|
||||
self.assertEqual(resources['memory_mb_used'], 0)
|
||||
self.assertEqual(resources['supported_instances'],
|
||||
'[["x86_64", "baremetal", "hvm"]]')
|
||||
self.assertEqual(resources['stats'],
|
||||
'{"cpu_arch": "x86_64", "baremetal_driver": '
|
||||
'"nova.virt.baremetal.fake.FakeDriver", '
|
||||
'"test_spec": "test_value"}')
|
||||
|
||||
self.driver.spawn(**node['spawn_params'])
|
||||
resources = self.driver.get_available_resource(node['node']['uuid'])
|
||||
self.assertEqual(resources['memory_mb_used'],
|
||||
node['node_info']['memory_mb'])
|
||||
|
||||
self.driver.destroy(**node['destroy_params'])
|
||||
resources = self.driver.get_available_resource(node['node']['uuid'])
|
||||
self.assertEqual(resources['memory_mb_used'], 0)
|
||||
stats = jsonutils.loads(resources['stats'])
|
||||
self.assertEqual(stats['test_spec'], 'test_value')
|
||||
|
||||
def test_get_available_nodes(self):
|
||||
self.assertEqual(0, len(self.driver.get_available_nodes()))
|
||||
self.assertEqual(0, len(self.driver.get_available_nodes(refresh=True)))
|
||||
|
||||
node1 = self._create_node()
|
||||
self.assertEqual(1, len(self.driver.get_available_nodes()))
|
||||
|
||||
node1['instance']['hostname'] = 'test-host-1'
|
||||
self.driver.spawn(**node1['spawn_params'])
|
||||
self.assertEqual(1, len(self.driver.get_available_nodes()))
|
||||
self.assertEqual([node1['node']['uuid']],
|
||||
self.driver.get_available_nodes())
|
||||
|
||||
def test_list_instances(self):
|
||||
self.assertEqual([], self.driver.list_instances())
|
||||
|
||||
node1 = self._create_node()
|
||||
self.assertEqual([], self.driver.list_instances())
|
||||
|
||||
node_info = bm_db_utils.new_bm_node(
|
||||
id=456,
|
||||
service_host='test_host',
|
||||
cpus=2,
|
||||
memory_mb=2048,
|
||||
)
|
||||
nic_info = [
|
||||
{'address': 'cc:cc:cc', 'datapath_id': '0x1',
|
||||
'port_no': 1},
|
||||
{'address': 'dd:dd:dd', 'datapath_id': '0x2',
|
||||
'port_no': 2},
|
||||
]
|
||||
node2 = self._create_node(node_info=node_info, nic_info=nic_info)
|
||||
self.assertEqual([], self.driver.list_instances())
|
||||
|
||||
node1['instance']['hostname'] = 'test-host-1'
|
||||
node2['instance']['hostname'] = 'test-host-2'
|
||||
|
||||
self.driver.spawn(**node1['spawn_params'])
|
||||
self.assertEqual(['test-host-1'],
|
||||
self.driver.list_instances())
|
||||
|
||||
self.driver.spawn(**node2['spawn_params'])
|
||||
self.assertEqual(['test-host-1', 'test-host-2'],
|
||||
self.driver.list_instances())
|
||||
|
||||
self.driver.destroy(**node1['destroy_params'])
|
||||
self.assertEqual(['test-host-2'],
|
||||
self.driver.list_instances())
|
||||
|
||||
self.driver.destroy(**node2['destroy_params'])
|
||||
self.assertEqual([], self.driver.list_instances())
|
||||
|
||||
def test_get_info_no_such_node(self):
|
||||
node = self._create_node()
|
||||
self.assertRaises(exception.InstanceNotFound,
|
||||
self.driver.get_info,
|
||||
node['instance'])
|
||||
|
||||
def test_get_info_ok(self):
|
||||
node = self._create_node()
|
||||
db.bm_node_associate_and_update(self.context, node['node']['uuid'],
|
||||
{'instance_uuid': node['instance']['uuid'],
|
||||
'instance_name': node['instance']['hostname'],
|
||||
'task_state': baremetal_states.ACTIVE})
|
||||
res = self.driver.get_info(node['instance'])
|
||||
self.assertEqual(res['state'], power_state.RUNNING)
|
||||
|
||||
def test_get_info_with_defunct_pm(self):
|
||||
# test fix for bug 1178378
|
||||
node = self._create_node()
|
||||
db.bm_node_associate_and_update(self.context, node['node']['uuid'],
|
||||
{'instance_uuid': node['instance']['uuid'],
|
||||
'instance_name': node['instance']['hostname'],
|
||||
'task_state': baremetal_states.ACTIVE})
|
||||
|
||||
# fake the power manager and don't get a power state
|
||||
self.mox.StubOutWithMock(fake.FakePowerManager, 'is_power_on')
|
||||
fake.FakePowerManager.is_power_on().AndReturn(None)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
res = self.driver.get_info(node['instance'])
|
||||
# prior to the fix, returned power_state was SHUTDOWN
|
||||
self.assertEqual(res['state'], power_state.NOSTATE)
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test_attach_volume(self):
|
||||
connection_info = {'_fake_connection_info': None}
|
||||
instance = utils.get_test_instance()
|
||||
mountpoint = '/dev/sdd'
|
||||
self.mox.StubOutWithMock(self.driver.volume_driver, 'attach_volume')
|
||||
self.driver.volume_driver.attach_volume(connection_info,
|
||||
instance,
|
||||
mountpoint)
|
||||
self.mox.ReplayAll()
|
||||
self.driver.attach_volume(None, connection_info, instance, mountpoint)
|
||||
|
||||
def test_detach_volume(self):
|
||||
connection_info = {'_fake_connection_info': None}
|
||||
instance = utils.get_test_instance()
|
||||
mountpoint = '/dev/sdd'
|
||||
self.mox.StubOutWithMock(self.driver.volume_driver, 'detach_volume')
|
||||
self.driver.volume_driver.detach_volume(connection_info,
|
||||
instance,
|
||||
mountpoint)
|
||||
self.mox.ReplayAll()
|
||||
self.driver.detach_volume(connection_info, instance, mountpoint)
|
||||
|
||||
def test_attach_block_devices(self):
|
||||
connection_info_1 = {'_fake_connection_info_1': None}
|
||||
connection_info_2 = {'_fake_connection_info_2': None}
|
||||
block_device_mapping = [{'connection_info': connection_info_1,
|
||||
'mount_device': '/dev/sde'},
|
||||
{'connection_info': connection_info_2,
|
||||
'mount_device': '/dev/sdf'}]
|
||||
block_device_info = {'block_device_mapping': block_device_mapping}
|
||||
instance = utils.get_test_instance()
|
||||
|
||||
self.mox.StubOutWithMock(self.driver, 'attach_volume')
|
||||
self.driver.attach_volume(None, connection_info_1, instance,
|
||||
'/dev/sde')
|
||||
self.driver.attach_volume(None, connection_info_2, instance,
|
||||
'/dev/sdf')
|
||||
self.mox.ReplayAll()
|
||||
self.driver._attach_block_devices(instance, block_device_info)
|
||||
|
||||
def test_detach_block_devices(self):
|
||||
connection_info_1 = {'_fake_connection_info_1': None}
|
||||
connection_info_2 = {'_fake_connection_info_2': None}
|
||||
block_device_mapping = [{'connection_info': connection_info_1,
|
||||
'mount_device': '/dev/sde'},
|
||||
{'connection_info': connection_info_2,
|
||||
'mount_device': '/dev/sdf'}]
|
||||
block_device_info = {'block_device_mapping': block_device_mapping}
|
||||
instance = utils.get_test_instance()
|
||||
|
||||
self.mox.StubOutWithMock(self.driver, 'detach_volume')
|
||||
self.driver.detach_volume(connection_info_1, instance, '/dev/sde')
|
||||
self.driver.detach_volume(connection_info_2, instance, '/dev/sdf')
|
||||
self.mox.ReplayAll()
|
||||
self.driver._detach_block_devices(instance, block_device_info)
|
||||
@@ -1,120 +0,0 @@
|
||||
# Copyright 2013 Red Hat Inc.
|
||||
# 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.
|
||||
|
||||
"""Test class for baremetal iBoot power manager."""
|
||||
|
||||
from nova import exception
|
||||
from nova import test
|
||||
from nova.tests.virt.baremetal.db import utils as bm_db_utils
|
||||
from nova.virt.baremetal import iboot_pdu
|
||||
|
||||
|
||||
class BareMetalIbootPDUTestCase(test.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(BareMetalIbootPDUTestCase, self).setUp()
|
||||
self.node = bm_db_utils.new_bm_node(
|
||||
id=123,
|
||||
pm_address='192.168.1.254',
|
||||
pm_user='foo',
|
||||
pm_password='bar')
|
||||
self.pm = iboot_pdu.IBootManager(node=self.node)
|
||||
|
||||
def test_construct(self):
|
||||
self.assertEqual(self.pm.address, '192.168.1.254')
|
||||
self.assertEqual(self.pm.port, 9100)
|
||||
self.assertEqual(self.pm.relay_id, 1)
|
||||
self.assertEqual(self.pm.user, 'foo')
|
||||
self.assertEqual(self.pm.password, 'bar')
|
||||
|
||||
def test_construct_with_port_and_relay(self):
|
||||
self.node = bm_db_utils.new_bm_node(
|
||||
id=123,
|
||||
pm_address='192.168.1.254:1234,8',
|
||||
pm_user='foo',
|
||||
pm_password='bar')
|
||||
self.pm = iboot_pdu.IBootManager(node=self.node)
|
||||
|
||||
self.assertEqual(self.pm.address, '192.168.1.254')
|
||||
self.assertEqual(self.pm.port, 1234)
|
||||
self.assertEqual(self.pm.relay_id, 8)
|
||||
self.assertEqual(self.pm.user, 'foo')
|
||||
self.assertEqual(self.pm.password, 'bar')
|
||||
|
||||
def test_construct_with_invalid_port(self):
|
||||
self.node = bm_db_utils.new_bm_node(
|
||||
id=123,
|
||||
pm_address='192.168.1.254:not_a_number',
|
||||
pm_user='foo',
|
||||
pm_password='bar')
|
||||
self.assertRaises(exception.InvalidParameterValue,
|
||||
iboot_pdu.IBootManager, node=self.node)
|
||||
|
||||
def test_construct_with_relay_id(self):
|
||||
self.node = bm_db_utils.new_bm_node(
|
||||
id=123,
|
||||
pm_address='192.168.1.254:1234,not_a_number',
|
||||
pm_user='foo',
|
||||
pm_password='bar')
|
||||
self.assertRaises(exception.InvalidParameterValue,
|
||||
iboot_pdu.IBootManager, node=self.node)
|
||||
|
||||
def test_activate_node(self):
|
||||
self.mox.StubOutWithMock(self.pm, '_create_connection')
|
||||
self.mox.StubOutWithMock(self.pm, '_switch')
|
||||
self.mox.StubOutWithMock(self.pm, 'is_power_on')
|
||||
self.pm._create_connection().AndReturn(True)
|
||||
self.pm._switch(1, True).AndReturn(True)
|
||||
self.pm.is_power_on().AndReturn(True)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
self.pm.activate_node()
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test_deactivate_node(self):
|
||||
self.mox.StubOutWithMock(self.pm, '_create_connection')
|
||||
self.mox.StubOutWithMock(self.pm, '_switch')
|
||||
self.mox.StubOutWithMock(self.pm, 'is_power_on')
|
||||
self.pm._create_connection().AndReturn(True)
|
||||
self.pm.is_power_on().AndReturn(True)
|
||||
self.pm._switch(1, False).AndReturn(True)
|
||||
self.pm.is_power_on().AndReturn(False)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
self.pm.deactivate_node()
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test_reboot_node(self):
|
||||
self.mox.StubOutWithMock(self.pm, '_create_connection')
|
||||
self.mox.StubOutWithMock(self.pm, '_switch')
|
||||
self.mox.StubOutWithMock(self.pm, 'is_power_on')
|
||||
self.pm._create_connection().AndReturn(True)
|
||||
self.pm._switch(1, False).AndReturn(True)
|
||||
self.pm._switch(1, True).AndReturn(True)
|
||||
self.pm.is_power_on().AndReturn(True)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
self.pm.reboot_node()
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test_is_power_on(self):
|
||||
self.mox.StubOutWithMock(self.pm, '_create_connection')
|
||||
self.mox.StubOutWithMock(self.pm, '_get_relay')
|
||||
self.pm._create_connection().AndReturn(True)
|
||||
self.pm._get_relay(1).AndReturn(True)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
self.pm.is_power_on()
|
||||
self.mox.VerifyAll()
|
||||
@@ -1,242 +0,0 @@
|
||||
# coding=utf-8
|
||||
|
||||
# Copyright 2012 Hewlett-Packard Development Company, L.P.
|
||||
# Copyright (c) 2012 NTT DOCOMO, INC.
|
||||
# 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.
|
||||
|
||||
"""Test class for baremetal IPMI power manager."""
|
||||
|
||||
import os
|
||||
import stat
|
||||
import tempfile
|
||||
|
||||
from oslo.config import cfg
|
||||
|
||||
from nova import test
|
||||
from nova.tests.virt.baremetal.db import utils as bm_db_utils
|
||||
from nova import utils
|
||||
from nova.virt.baremetal import baremetal_states
|
||||
from nova.virt.baremetal import ipmi
|
||||
from nova.virt.baremetal import utils as bm_utils
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
class BareMetalIPMITestCase(test.NoDBTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(BareMetalIPMITestCase, self).setUp()
|
||||
self.node = bm_db_utils.new_bm_node(
|
||||
id=123,
|
||||
pm_address='fake-address',
|
||||
pm_user='fake-user',
|
||||
pm_password='fake-password')
|
||||
self.ipmi = ipmi.IPMI(self.node)
|
||||
|
||||
def test_construct(self):
|
||||
self.assertEqual(self.ipmi.node_id, 123)
|
||||
self.assertEqual(self.ipmi.address, 'fake-address')
|
||||
self.assertEqual(self.ipmi.user, 'fake-user')
|
||||
self.assertEqual(self.ipmi.password, 'fake-password')
|
||||
|
||||
def test_make_password_file(self):
|
||||
pw_file = ipmi._make_password_file(self.node['pm_password'])
|
||||
try:
|
||||
self.assertTrue(os.path.isfile(pw_file))
|
||||
self.assertEqual(os.stat(pw_file)[stat.ST_MODE] & 0o777, 0o600)
|
||||
with open(pw_file, "r") as f:
|
||||
pm_password = f.read()
|
||||
self.assertEqual(pm_password, self.node['pm_password'])
|
||||
finally:
|
||||
os.unlink(pw_file)
|
||||
|
||||
def test_make_empty_password_file(self):
|
||||
pw_file = ipmi._make_password_file('')
|
||||
try:
|
||||
self.assertTrue(os.path.isfile(pw_file))
|
||||
self.assertEqual(os.stat(pw_file)[stat.ST_MODE] & 0o777, 0o600)
|
||||
with open(pw_file, "rb") as f:
|
||||
pm_password = f.read()
|
||||
self.assertEqual(b"\0", pm_password)
|
||||
finally:
|
||||
os.unlink(pw_file)
|
||||
|
||||
def test_exec_ipmitool(self):
|
||||
pw_file = '/tmp/password_file'
|
||||
|
||||
self.mox.StubOutWithMock(ipmi, '_make_password_file')
|
||||
self.mox.StubOutWithMock(utils, 'execute')
|
||||
self.mox.StubOutWithMock(bm_utils, 'unlink_without_raise')
|
||||
ipmi._make_password_file(self.ipmi.password).AndReturn(pw_file)
|
||||
args = [
|
||||
'ipmitool',
|
||||
'-I', 'lanplus',
|
||||
'-H', self.ipmi.address,
|
||||
'-U', self.ipmi.user,
|
||||
'-f', pw_file,
|
||||
'A', 'B', 'C',
|
||||
]
|
||||
utils.execute(*args, attempts=3).AndReturn(('', ''))
|
||||
bm_utils.unlink_without_raise(pw_file).AndReturn(None)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
self.ipmi._exec_ipmitool('A B C')
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test_is_power_on_ok(self):
|
||||
self.mox.StubOutWithMock(self.ipmi, '_exec_ipmitool')
|
||||
self.ipmi._exec_ipmitool("power status").AndReturn(
|
||||
["Chassis Power is on\n"])
|
||||
self.mox.ReplayAll()
|
||||
|
||||
res = self.ipmi.is_power_on()
|
||||
self.assertEqual(res, True)
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test_is_power_no_answer(self):
|
||||
self.mox.StubOutWithMock(self.ipmi, '_exec_ipmitool')
|
||||
self.ipmi._exec_ipmitool("power status").AndReturn(
|
||||
["Fake reply\n"])
|
||||
self.mox.ReplayAll()
|
||||
|
||||
res = self.ipmi.is_power_on()
|
||||
self.assertIsNone(res)
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test_power_already_on(self):
|
||||
self.flags(ipmi_power_retry=0, group='baremetal')
|
||||
self.mox.StubOutWithMock(self.ipmi, '_exec_ipmitool')
|
||||
|
||||
self.ipmi._exec_ipmitool("power status").AndReturn(
|
||||
["Chassis Power is on\n"])
|
||||
self.mox.ReplayAll()
|
||||
|
||||
self.ipmi.state = baremetal_states.DELETED
|
||||
self.ipmi._power_on()
|
||||
self.mox.VerifyAll()
|
||||
self.assertEqual(self.ipmi.state, baremetal_states.ACTIVE)
|
||||
|
||||
def test_power_on_ok(self):
|
||||
self.flags(ipmi_power_retry=0, group='baremetal')
|
||||
self.mox.StubOutWithMock(self.ipmi, '_exec_ipmitool')
|
||||
|
||||
self.ipmi._exec_ipmitool("power status").AndReturn(
|
||||
["Chassis Power is off\n"])
|
||||
self.ipmi._exec_ipmitool("power on").AndReturn([])
|
||||
self.ipmi._exec_ipmitool("power status").AndReturn(
|
||||
["Chassis Power is on\n"])
|
||||
self.mox.ReplayAll()
|
||||
|
||||
self.ipmi.state = baremetal_states.DELETED
|
||||
self.ipmi._power_on()
|
||||
self.mox.VerifyAll()
|
||||
self.assertEqual(self.ipmi.state, baremetal_states.ACTIVE)
|
||||
|
||||
def test_power_on_fail(self):
|
||||
self.flags(ipmi_power_retry=0, group='baremetal')
|
||||
self.mox.StubOutWithMock(self.ipmi, '_exec_ipmitool')
|
||||
|
||||
self.ipmi._exec_ipmitool("power status").AndReturn(
|
||||
["Chassis Power is off\n"])
|
||||
self.ipmi._exec_ipmitool("power on").AndReturn([])
|
||||
self.ipmi._exec_ipmitool("power status").AndReturn(
|
||||
["Chassis Power is off\n"])
|
||||
self.mox.ReplayAll()
|
||||
|
||||
self.ipmi.state = baremetal_states.DELETED
|
||||
self.ipmi._power_on()
|
||||
self.mox.VerifyAll()
|
||||
self.assertEqual(self.ipmi.state, baremetal_states.ERROR)
|
||||
|
||||
def test_power_on_max_retries(self):
|
||||
self.flags(ipmi_power_retry=2, group='baremetal')
|
||||
self.mox.StubOutWithMock(self.ipmi, '_exec_ipmitool')
|
||||
|
||||
self.ipmi._exec_ipmitool("power status").AndReturn(
|
||||
["Chassis Power is off\n"])
|
||||
self.ipmi._exec_ipmitool("power on").AndReturn([])
|
||||
self.ipmi._exec_ipmitool("power status").AndReturn(
|
||||
["Chassis Power is off\n"])
|
||||
self.ipmi._exec_ipmitool("power status").AndReturn(
|
||||
["Chassis Power is off\n"])
|
||||
self.ipmi._exec_ipmitool("power status").AndReturn(
|
||||
["Chassis Power is off\n"])
|
||||
self.mox.ReplayAll()
|
||||
|
||||
self.ipmi.state = baremetal_states.DELETED
|
||||
self.ipmi._power_on()
|
||||
self.mox.VerifyAll()
|
||||
self.assertEqual(self.ipmi.state, baremetal_states.ERROR)
|
||||
self.assertEqual(self.ipmi.retries, 3)
|
||||
|
||||
def test_power_off_ok(self):
|
||||
self.flags(ipmi_power_retry=0, group='baremetal')
|
||||
self.mox.StubOutWithMock(self.ipmi, '_exec_ipmitool')
|
||||
|
||||
self.ipmi._exec_ipmitool("power status").AndReturn(
|
||||
["Chassis Power is on\n"])
|
||||
self.ipmi._exec_ipmitool("power off").AndReturn([])
|
||||
self.ipmi._exec_ipmitool("power status").AndReturn(
|
||||
["Chassis Power is off\n"])
|
||||
self.mox.ReplayAll()
|
||||
|
||||
self.ipmi.state = baremetal_states.ACTIVE
|
||||
self.ipmi._power_off()
|
||||
self.mox.VerifyAll()
|
||||
self.assertEqual(self.ipmi.state, baremetal_states.DELETED)
|
||||
|
||||
def test_get_console_pid_path(self):
|
||||
self.flags(terminal_pid_dir='/tmp', group='baremetal')
|
||||
path = ipmi._get_console_pid_path(self.ipmi.node_id)
|
||||
self.assertEqual(path, '/tmp/%s.pid' % self.ipmi.node_id)
|
||||
|
||||
def test_console_pid(self):
|
||||
fd, path = tempfile.mkstemp()
|
||||
with os.fdopen(fd, 'w') as f:
|
||||
f.write("12345\n")
|
||||
|
||||
self.mox.StubOutWithMock(ipmi, '_get_console_pid_path')
|
||||
ipmi._get_console_pid_path(self.ipmi.node_id).AndReturn(path)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
pid = ipmi._get_console_pid(self.ipmi.node_id)
|
||||
bm_utils.unlink_without_raise(path)
|
||||
self.mox.VerifyAll()
|
||||
self.assertEqual(pid, 12345)
|
||||
|
||||
def test_console_pid_nan(self):
|
||||
fd, path = tempfile.mkstemp()
|
||||
with os.fdopen(fd, 'w') as f:
|
||||
f.write("hello world\n")
|
||||
|
||||
self.mox.StubOutWithMock(ipmi, '_get_console_pid_path')
|
||||
ipmi._get_console_pid_path(self.ipmi.node_id).AndReturn(path)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
pid = ipmi._get_console_pid(self.ipmi.node_id)
|
||||
bm_utils.unlink_without_raise(path)
|
||||
self.mox.VerifyAll()
|
||||
self.assertIsNone(pid)
|
||||
|
||||
def test_console_pid_file_not_found(self):
|
||||
pid_path = ipmi._get_console_pid_path(self.ipmi.node_id)
|
||||
|
||||
self.mox.StubOutWithMock(os.path, 'exists')
|
||||
os.path.exists(pid_path).AndReturn(False)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
pid = ipmi._get_console_pid(self.ipmi.node_id)
|
||||
self.mox.VerifyAll()
|
||||
self.assertIsNone(pid)
|
||||
@@ -1,420 +0,0 @@
|
||||
# Copyright (c) 2012 NTT DOCOMO, INC.
|
||||
# Copyright 2011 OpenStack Foundation
|
||||
# Copyright 2011 Ilya Alekseyev
|
||||
#
|
||||
# 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.
|
||||
|
||||
import os
|
||||
import tempfile
|
||||
import time
|
||||
|
||||
import mock
|
||||
import mox
|
||||
from oslo.utils import units
|
||||
|
||||
from nova.cmd import baremetal_deploy_helper as bmdh
|
||||
from nova.openstack.common import log as logging
|
||||
from nova import test
|
||||
from nova.tests.virt.baremetal.db import base as bm_db_base
|
||||
from nova.virt.baremetal import db as bm_db
|
||||
|
||||
bmdh.LOG = logging.getLogger('nova.virt.baremetal.deploy_helper')
|
||||
|
||||
_PXECONF_DEPLOY = """
|
||||
default deploy
|
||||
|
||||
label deploy
|
||||
kernel deploy_kernel
|
||||
append initrd=deploy_ramdisk
|
||||
ipappend 3
|
||||
|
||||
label boot
|
||||
kernel kernel
|
||||
append initrd=ramdisk root=${ROOT}
|
||||
"""
|
||||
|
||||
_PXECONF_BOOT = """
|
||||
default boot
|
||||
|
||||
label deploy
|
||||
kernel deploy_kernel
|
||||
append initrd=deploy_ramdisk
|
||||
ipappend 3
|
||||
|
||||
label boot
|
||||
kernel kernel
|
||||
append initrd=ramdisk root=UUID=12345678-1234-1234-1234-1234567890abcdef
|
||||
"""
|
||||
|
||||
|
||||
class WorkerTestCase(bm_db_base.BMDBTestCase):
|
||||
def setUp(self):
|
||||
super(WorkerTestCase, self).setUp()
|
||||
self.worker = bmdh.Worker()
|
||||
# Make tearDown() fast
|
||||
self.worker.queue_timeout = 0.1
|
||||
self.worker.start()
|
||||
|
||||
def tearDown(self):
|
||||
if self.worker.isAlive():
|
||||
self.worker.stop = True
|
||||
self.worker.join(timeout=1)
|
||||
super(WorkerTestCase, self).tearDown()
|
||||
|
||||
def wait_queue_empty(self, timeout):
|
||||
for _ in xrange(int(timeout / 0.1)):
|
||||
if bmdh.QUEUE.empty():
|
||||
break
|
||||
time.sleep(0.1)
|
||||
|
||||
def test_run_calls_deploy(self):
|
||||
"""Check all queued requests are passed to deploy()."""
|
||||
history = []
|
||||
|
||||
def fake_deploy(**params):
|
||||
history.append(params)
|
||||
|
||||
self.stubs.Set(bmdh, 'deploy', fake_deploy)
|
||||
self.mox.StubOutWithMock(bm_db, 'bm_node_update')
|
||||
# update is called twice inside Worker.run
|
||||
for i in range(6):
|
||||
bm_db.bm_node_update(mox.IgnoreArg(), mox.IgnoreArg(),
|
||||
mox.IgnoreArg())
|
||||
self.mox.ReplayAll()
|
||||
|
||||
params_list = [{'fake1': ''}, {'fake2': ''}, {'fake3': ''}]
|
||||
for (dep_id, params) in enumerate(params_list):
|
||||
bmdh.QUEUE.put((dep_id, params))
|
||||
self.wait_queue_empty(1)
|
||||
self.assertEqual(params_list, history)
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test_run_with_failing_deploy(self):
|
||||
"""Check a worker keeps on running even if deploy() raises
|
||||
an exception.
|
||||
"""
|
||||
history = []
|
||||
|
||||
def fake_deploy(**params):
|
||||
history.append(params)
|
||||
# always fail
|
||||
raise Exception('test')
|
||||
|
||||
self.stubs.Set(bmdh, 'deploy', fake_deploy)
|
||||
self.mox.StubOutWithMock(bm_db, 'bm_node_update')
|
||||
# update is called twice inside Worker.run
|
||||
for i in range(6):
|
||||
bm_db.bm_node_update(mox.IgnoreArg(), mox.IgnoreArg(),
|
||||
mox.IgnoreArg())
|
||||
self.mox.ReplayAll()
|
||||
|
||||
params_list = [{'fake1': ''}, {'fake2': ''}, {'fake3': ''}]
|
||||
for (dep_id, params) in enumerate(params_list):
|
||||
bmdh.QUEUE.put((dep_id, params))
|
||||
self.wait_queue_empty(1)
|
||||
self.assertEqual(params_list, history)
|
||||
self.mox.VerifyAll()
|
||||
|
||||
|
||||
class PhysicalWorkTestCase(test.NoDBTestCase):
|
||||
def setUp(self):
|
||||
super(PhysicalWorkTestCase, self).setUp()
|
||||
|
||||
def noop(*args, **kwargs):
|
||||
pass
|
||||
|
||||
self.stubs.Set(time, 'sleep', noop)
|
||||
|
||||
def _deploy_mox(self):
|
||||
self.mox.StubOutWithMock(bmdh, 'get_dev')
|
||||
self.mox.StubOutWithMock(bmdh, 'get_image_mb')
|
||||
self.mox.StubOutWithMock(bmdh, 'discovery')
|
||||
self.mox.StubOutWithMock(bmdh, 'login_iscsi')
|
||||
self.mox.StubOutWithMock(bmdh, 'logout_iscsi')
|
||||
self.mox.StubOutWithMock(bmdh, 'make_partitions')
|
||||
self.mox.StubOutWithMock(bmdh, 'is_block_device')
|
||||
self.mox.StubOutWithMock(bmdh, 'dd')
|
||||
self.mox.StubOutWithMock(bmdh, 'mkswap')
|
||||
self.mox.StubOutWithMock(bmdh, 'block_uuid')
|
||||
self.mox.StubOutWithMock(bmdh, 'switch_pxe_config')
|
||||
self.mox.StubOutWithMock(bmdh, 'notify')
|
||||
|
||||
def test_deploy_no_ephemeral(self):
|
||||
address = '127.0.0.1'
|
||||
port = 3306
|
||||
iqn = 'iqn.xyz'
|
||||
lun = 1
|
||||
image_path = '/tmp/xyz/image'
|
||||
pxe_config_path = '/tmp/abc/pxeconfig'
|
||||
root_mb = 128
|
||||
swap_mb = 64
|
||||
ephemeral_mb = 0
|
||||
|
||||
dev = '/dev/fake'
|
||||
root_part = '/dev/fake-part1'
|
||||
swap_part = '/dev/fake-part2'
|
||||
root_uuid = '12345678-1234-1234-12345678-12345678abcdef'
|
||||
|
||||
self._deploy_mox()
|
||||
|
||||
bmdh.get_dev(address, port, iqn, lun).AndReturn(dev)
|
||||
bmdh.get_image_mb(image_path).AndReturn(1) # < root_mb
|
||||
bmdh.discovery(address, port)
|
||||
bmdh.login_iscsi(address, port, iqn)
|
||||
bmdh.is_block_device(dev).AndReturn(True)
|
||||
bmdh.make_partitions(dev, root_mb, swap_mb, ephemeral_mb)
|
||||
bmdh.is_block_device(root_part).AndReturn(True)
|
||||
bmdh.is_block_device(swap_part).AndReturn(True)
|
||||
bmdh.dd(image_path, root_part)
|
||||
bmdh.mkswap(swap_part)
|
||||
bmdh.block_uuid(root_part).AndReturn(root_uuid)
|
||||
bmdh.logout_iscsi(address, port, iqn)
|
||||
bmdh.switch_pxe_config(pxe_config_path, root_uuid)
|
||||
bmdh.notify(address, 10000)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
bmdh.deploy(address, port, iqn, lun, image_path, pxe_config_path,
|
||||
root_mb, swap_mb, ephemeral_mb)
|
||||
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test_deploy_with_ephemeral(self):
|
||||
address = '127.0.0.1'
|
||||
port = 3306
|
||||
iqn = 'iqn.xyz'
|
||||
lun = 1
|
||||
image_path = '/tmp/xyz/image'
|
||||
pxe_config_path = '/tmp/abc/pxeconfig'
|
||||
root_mb = 128
|
||||
swap_mb = 64
|
||||
ephemeral_mb = 256
|
||||
|
||||
dev = '/dev/fake'
|
||||
ephemeral_part = '/dev/fake-part1'
|
||||
swap_part = '/dev/fake-part2'
|
||||
root_part = '/dev/fake-part3'
|
||||
root_uuid = '12345678-1234-1234-12345678-12345678abcdef'
|
||||
|
||||
self._deploy_mox()
|
||||
self.mox.StubOutWithMock(bmdh, 'mkfs_ephemeral')
|
||||
|
||||
bmdh.get_dev(address, port, iqn, lun).AndReturn(dev)
|
||||
bmdh.get_image_mb(image_path).AndReturn(1) # < root_mb
|
||||
bmdh.discovery(address, port)
|
||||
bmdh.login_iscsi(address, port, iqn)
|
||||
bmdh.is_block_device(dev).AndReturn(True)
|
||||
bmdh.make_partitions(dev, root_mb, swap_mb, ephemeral_mb)
|
||||
bmdh.is_block_device(root_part).AndReturn(True)
|
||||
bmdh.is_block_device(swap_part).AndReturn(True)
|
||||
bmdh.is_block_device(ephemeral_part).AndReturn(True)
|
||||
bmdh.dd(image_path, root_part)
|
||||
bmdh.mkswap(swap_part)
|
||||
bmdh.mkfs_ephemeral(ephemeral_part)
|
||||
bmdh.block_uuid(root_part).AndReturn(root_uuid)
|
||||
bmdh.logout_iscsi(address, port, iqn)
|
||||
bmdh.switch_pxe_config(pxe_config_path, root_uuid)
|
||||
bmdh.notify(address, 10000)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
bmdh.deploy(address, port, iqn, lun, image_path, pxe_config_path,
|
||||
root_mb, swap_mb, ephemeral_mb)
|
||||
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test_deploy_preserve_ephemeral(self):
|
||||
address = '127.0.0.1'
|
||||
port = 3306
|
||||
iqn = 'iqn.xyz'
|
||||
lun = 1
|
||||
image_path = '/tmp/xyz/image'
|
||||
pxe_config_path = '/tmp/abc/pxeconfig'
|
||||
root_mb = 128
|
||||
swap_mb = 64
|
||||
ephemeral_mb = 128
|
||||
|
||||
dev = '/dev/fake'
|
||||
ephemeral_part = '/dev/fake-part1'
|
||||
swap_part = '/dev/fake-part2'
|
||||
root_part = '/dev/fake-part3'
|
||||
root_uuid = '12345678-1234-1234-12345678-12345678abcdef'
|
||||
|
||||
self._deploy_mox()
|
||||
self.mox.StubOutWithMock(bmdh, 'mkfs_ephemeral')
|
||||
|
||||
bmdh.get_dev(address, port, iqn, lun).AndReturn(dev)
|
||||
bmdh.get_image_mb(image_path).AndReturn(1) # < root_mb
|
||||
bmdh.discovery(address, port)
|
||||
bmdh.login_iscsi(address, port, iqn)
|
||||
bmdh.is_block_device(dev).AndReturn(True)
|
||||
bmdh.make_partitions(dev, root_mb, swap_mb, ephemeral_mb)
|
||||
bmdh.is_block_device(root_part).AndReturn(True)
|
||||
bmdh.is_block_device(swap_part).AndReturn(True)
|
||||
bmdh.is_block_device(ephemeral_part).AndReturn(True)
|
||||
bmdh.dd(image_path, root_part)
|
||||
bmdh.mkswap(swap_part)
|
||||
bmdh.block_uuid(root_part).AndReturn(root_uuid)
|
||||
bmdh.logout_iscsi(address, port, iqn)
|
||||
bmdh.switch_pxe_config(pxe_config_path, root_uuid)
|
||||
bmdh.notify(address, 10000)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
bmdh.deploy(address, port, iqn, lun, image_path, pxe_config_path,
|
||||
root_mb, swap_mb, ephemeral_mb, True)
|
||||
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test_always_logout_iscsi(self):
|
||||
"""logout_iscsi() must be called once login_iscsi() is called."""
|
||||
address = '127.0.0.1'
|
||||
port = 3306
|
||||
iqn = 'iqn.xyz'
|
||||
lun = 1
|
||||
image_path = '/tmp/xyz/image'
|
||||
pxe_config_path = '/tmp/abc/pxeconfig'
|
||||
root_mb = 128
|
||||
swap_mb = 64
|
||||
ephemeral_mb = 256
|
||||
|
||||
dev = '/dev/fake'
|
||||
|
||||
self.mox.StubOutWithMock(bmdh, 'get_dev')
|
||||
self.mox.StubOutWithMock(bmdh, 'get_image_mb')
|
||||
self.mox.StubOutWithMock(bmdh, 'discovery')
|
||||
self.mox.StubOutWithMock(bmdh, 'login_iscsi')
|
||||
self.mox.StubOutWithMock(bmdh, 'logout_iscsi')
|
||||
self.mox.StubOutWithMock(bmdh, 'work_on_disk')
|
||||
|
||||
class TestException(Exception):
|
||||
pass
|
||||
|
||||
bmdh.get_dev(address, port, iqn, lun).AndReturn(dev)
|
||||
bmdh.get_image_mb(image_path).AndReturn(1) # < root_mb
|
||||
bmdh.discovery(address, port)
|
||||
bmdh.login_iscsi(address, port, iqn)
|
||||
bmdh.work_on_disk(dev, root_mb, swap_mb, ephemeral_mb, image_path,
|
||||
False).AndRaise(TestException)
|
||||
bmdh.logout_iscsi(address, port, iqn)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
self.assertRaises(TestException,
|
||||
bmdh.deploy,
|
||||
address, port, iqn, lun, image_path,
|
||||
pxe_config_path, root_mb, swap_mb, ephemeral_mb)
|
||||
|
||||
|
||||
class WorkOnDiskTestCase(test.NoDBTestCase):
|
||||
def setUp(self):
|
||||
super(WorkOnDiskTestCase, self).setUp()
|
||||
self.image_path = '/tmp/xyz/image'
|
||||
self.root_mb = 128
|
||||
self.swap_mb = 64
|
||||
self.ephemeral_mb = 256
|
||||
self.dev = '/dev/fake'
|
||||
self.ephemeral_part = '/dev/fake-part1'
|
||||
self.swap_part = '/dev/fake-part2'
|
||||
self.root_part = '/dev/fake-part3'
|
||||
|
||||
self.m_ibd = mock.Mock()
|
||||
self.m_mp = mock.Mock()
|
||||
self.stubs.Set(bmdh, 'is_block_device', self.m_ibd)
|
||||
self.stubs.Set(bmdh, 'make_partitions', self.m_mp)
|
||||
|
||||
def test_no_parent_device(self):
|
||||
self.m_ibd.return_value = False
|
||||
self.assertRaises(bmdh.BareMetalDeployException,
|
||||
bmdh.work_on_disk,
|
||||
self.dev, self.root_mb, self.swap_mb,
|
||||
self.ephemeral_mb, self.image_path, False)
|
||||
self.m_ibd.assert_called_once_with(self.dev)
|
||||
self.assertFalse(self.m_mp.called)
|
||||
|
||||
def test_no_root_partition(self):
|
||||
self.m_ibd.side_effect = [True, False]
|
||||
calls = [mock.call(self.dev),
|
||||
mock.call(self.root_part)]
|
||||
self.assertRaises(bmdh.BareMetalDeployException,
|
||||
bmdh.work_on_disk,
|
||||
self.dev, self.root_mb, self.swap_mb,
|
||||
self.ephemeral_mb, self.image_path, False)
|
||||
self.assertEqual(self.m_ibd.call_args_list, calls)
|
||||
self.m_mp.assert_called_once_with(self.dev, self.root_mb, self.swap_mb,
|
||||
self.ephemeral_mb)
|
||||
|
||||
def test_no_swap_partition(self):
|
||||
self.m_ibd.side_effect = [True, True, False]
|
||||
calls = [mock.call(self.dev),
|
||||
mock.call(self.root_part),
|
||||
mock.call(self.swap_part)]
|
||||
self.assertRaises(bmdh.BareMetalDeployException,
|
||||
bmdh.work_on_disk,
|
||||
self.dev, self.root_mb, self.swap_mb,
|
||||
self.ephemeral_mb, self.image_path, False)
|
||||
self.assertEqual(self.m_ibd.call_args_list, calls)
|
||||
self.m_mp.assert_called_once_with(self.dev, self.root_mb, self.swap_mb,
|
||||
self.ephemeral_mb)
|
||||
|
||||
def test_no_ephemeral_partition(self):
|
||||
self.m_ibd.side_effect = [True, True, True, False]
|
||||
calls = [mock.call(self.dev),
|
||||
mock.call(self.root_part),
|
||||
mock.call(self.swap_part),
|
||||
mock.call(self.ephemeral_part)]
|
||||
self.assertRaises(bmdh.BareMetalDeployException,
|
||||
bmdh.work_on_disk,
|
||||
self.dev, self.root_mb, self.swap_mb,
|
||||
self.ephemeral_mb, self.image_path, False)
|
||||
self.assertEqual(self.m_ibd.call_args_list, calls)
|
||||
self.m_mp.assert_called_once_with(self.dev, self.root_mb, self.swap_mb,
|
||||
self.ephemeral_mb)
|
||||
|
||||
|
||||
class SwitchPxeConfigTestCase(test.NoDBTestCase):
|
||||
def setUp(self):
|
||||
super(SwitchPxeConfigTestCase, self).setUp()
|
||||
(fd, self.fname) = tempfile.mkstemp()
|
||||
os.write(fd, _PXECONF_DEPLOY)
|
||||
os.close(fd)
|
||||
|
||||
def tearDown(self):
|
||||
os.unlink(self.fname)
|
||||
super(SwitchPxeConfigTestCase, self).tearDown()
|
||||
|
||||
def test_switch_pxe_config(self):
|
||||
bmdh.switch_pxe_config(self.fname,
|
||||
'12345678-1234-1234-1234-1234567890abcdef')
|
||||
with open(self.fname, 'r') as f:
|
||||
pxeconf = f.read()
|
||||
self.assertEqual(pxeconf, _PXECONF_BOOT)
|
||||
|
||||
|
||||
class OtherFunctionTestCase(test.NoDBTestCase):
|
||||
def test_get_dev(self):
|
||||
expected = '/dev/disk/by-path/ip-1.2.3.4:5678-iscsi-iqn.fake-lun-9'
|
||||
actual = bmdh.get_dev('1.2.3.4', 5678, 'iqn.fake', 9)
|
||||
self.assertEqual(expected, actual)
|
||||
|
||||
def test_get_image_mb(self):
|
||||
size = None
|
||||
|
||||
def fake_getsize(path):
|
||||
return size
|
||||
|
||||
self.stubs.Set(os.path, 'getsize', fake_getsize)
|
||||
size = 0
|
||||
self.assertEqual(bmdh.get_image_mb('x'), 0)
|
||||
size = 1
|
||||
self.assertEqual(bmdh.get_image_mb('x'), 1)
|
||||
size = units.Mi
|
||||
self.assertEqual(bmdh.get_image_mb('x'), 1)
|
||||
size = units.Mi + 1
|
||||
self.assertEqual(bmdh.get_image_mb('x'), 2)
|
||||
@@ -1,29 +0,0 @@
|
||||
# Copyright (c) 2012 NTT DOCOMO, INC.
|
||||
# Copyright 2011 OpenStack Foundation
|
||||
# Copyright 2011 Ilya Alekseyev
|
||||
#
|
||||
# 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.
|
||||
|
||||
from nova.cmd import baremetal_manage as bm_man
|
||||
from nova.tests.virt.baremetal.db import base as bm_db_base
|
||||
|
||||
|
||||
class BareMetalDbCommandsTestCase(bm_db_base.BMDBTestCase):
|
||||
def setUp(self):
|
||||
super(BareMetalDbCommandsTestCase, self).setUp()
|
||||
self.commands = bm_man.BareMetalDbCommands()
|
||||
|
||||
def test_sync_and_version(self):
|
||||
self.commands.sync()
|
||||
v = self.commands.version()
|
||||
self.assertTrue(v > 0)
|
||||
@@ -1,645 +0,0 @@
|
||||
# coding=utf-8
|
||||
|
||||
# Copyright 2012 Hewlett-Packard Development Company, L.P.
|
||||
# Copyright (c) 2012 NTT DOCOMO, INC.
|
||||
# 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.
|
||||
|
||||
"""Tests for baremetal pxe driver."""
|
||||
|
||||
import os
|
||||
|
||||
import mox
|
||||
from oslo.config import cfg
|
||||
from oslo.db import exception as db_exc
|
||||
from testtools import matchers
|
||||
|
||||
from nova import exception
|
||||
from nova import objects
|
||||
from nova.tests.image import fake as fake_image
|
||||
from nova.tests import utils
|
||||
from nova.tests.virt.baremetal.db import base as bm_db_base
|
||||
from nova.tests.virt.baremetal.db import utils as bm_db_utils
|
||||
from nova.virt.baremetal import baremetal_states
|
||||
from nova.virt.baremetal import db
|
||||
from nova.virt.baremetal import pxe
|
||||
from nova.virt.baremetal import utils as bm_utils
|
||||
from nova.virt.disk import api as disk_api
|
||||
from nova.virt import fake as fake_virt
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
COMMON_FLAGS = dict(
|
||||
firewall_driver='nova.virt.baremetal.fake.FakeFirewallDriver',
|
||||
host='test_host',
|
||||
)
|
||||
|
||||
BAREMETAL_FLAGS = dict(
|
||||
driver='nova.virt.baremetal.pxe.PXE',
|
||||
flavor_extra_specs=['cpu_arch:test', 'test_spec:test_value'],
|
||||
power_manager='nova.virt.baremetal.fake.FakePowerManager',
|
||||
vif_driver='nova.virt.baremetal.fake.FakeVifDriver',
|
||||
volume_driver='nova.virt.baremetal.fake.FakeVolumeDriver',
|
||||
group='baremetal',
|
||||
)
|
||||
|
||||
|
||||
class BareMetalPXETestCase(bm_db_base.BMDBTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(BareMetalPXETestCase, self).setUp()
|
||||
self.flags(**COMMON_FLAGS)
|
||||
self.flags(**BAREMETAL_FLAGS)
|
||||
self.driver = pxe.PXE(fake_virt.FakeVirtAPI())
|
||||
|
||||
fake_image.stub_out_image_service(self.stubs)
|
||||
self.addCleanup(fake_image.FakeImageService_reset)
|
||||
self.context = utils.get_test_admin_context()
|
||||
self.test_block_device_info = None,
|
||||
self.instance = utils.get_test_instance()
|
||||
self.test_network_info = utils.get_test_network_info()
|
||||
self.node_info = bm_db_utils.new_bm_node(
|
||||
service_host='test_host',
|
||||
cpus=4,
|
||||
memory_mb=2048,
|
||||
)
|
||||
self.nic_info = [
|
||||
{'address': '22:22:22:22:22:22', 'datapath_id': '0x1',
|
||||
'port_no': 1},
|
||||
{'address': '33:33:33:33:33:33', 'datapath_id': '0x2',
|
||||
'port_no': 2},
|
||||
]
|
||||
|
||||
def _create_node(self):
|
||||
# File injection is off by default, but we should continue to test it
|
||||
# until it is removed.
|
||||
CONF.set_override('use_file_injection', True, 'baremetal')
|
||||
self.node = db.bm_node_create(self.context, self.node_info)
|
||||
for nic in self.nic_info:
|
||||
db.bm_interface_create(
|
||||
self.context,
|
||||
self.node['id'],
|
||||
nic['address'],
|
||||
nic['datapath_id'],
|
||||
nic['port_no'],
|
||||
)
|
||||
self.instance['node'] = self.node['id']
|
||||
self.spawn_params = dict(
|
||||
admin_password='test_pass',
|
||||
block_device_info=self.test_block_device_info,
|
||||
context=self.context,
|
||||
image_meta=utils.get_test_image_info(None,
|
||||
self.instance),
|
||||
injected_files=[('/fake/path', 'hello world')],
|
||||
instance=self.instance,
|
||||
network_info=self.test_network_info,
|
||||
)
|
||||
|
||||
|
||||
class PXEClassMethodsTestCase(BareMetalPXETestCase):
|
||||
|
||||
def test_build_pxe_config(self):
|
||||
args = {
|
||||
'deployment_id': 'aaa',
|
||||
'deployment_key': 'bbb',
|
||||
'deployment_iscsi_iqn': 'ccc',
|
||||
'deployment_aki_path': 'ddd',
|
||||
'deployment_ari_path': 'eee',
|
||||
'aki_path': 'fff',
|
||||
'ari_path': 'ggg',
|
||||
'network_info': self.test_network_info,
|
||||
}
|
||||
config = pxe.build_pxe_config(**args)
|
||||
self.assertThat(config, matchers.StartsWith('default deploy'))
|
||||
|
||||
# deploy bits are in the deploy section
|
||||
start = config.index('label deploy')
|
||||
end = config.index('label boot')
|
||||
self.assertThat(config[start:end], matchers.MatchesAll(
|
||||
matchers.Contains('kernel ddd'),
|
||||
matchers.Contains('initrd=eee'),
|
||||
matchers.Contains('deployment_id=aaa'),
|
||||
matchers.Contains('deployment_key=bbb'),
|
||||
matchers.Contains('iscsi_target_iqn=ccc'),
|
||||
matchers.Not(matchers.Contains('kernel fff')),
|
||||
))
|
||||
|
||||
# boot bits are in the boot section
|
||||
start = config.index('label boot')
|
||||
self.assertThat(config[start:], matchers.MatchesAll(
|
||||
matchers.Contains('kernel fff'),
|
||||
matchers.Contains('initrd=ggg'),
|
||||
matchers.Not(matchers.Contains('kernel ddd')),
|
||||
))
|
||||
|
||||
def test_build_pxe_network_config(self):
|
||||
self.flags(
|
||||
pxe_network_config=True,
|
||||
group='baremetal',
|
||||
)
|
||||
net = utils.get_test_network_info(1)
|
||||
config = pxe.build_pxe_network_config(net)
|
||||
self.assertIn('eth0:off', config)
|
||||
self.assertNotIn('eth1', config)
|
||||
|
||||
net = utils.get_test_network_info(2)
|
||||
config = pxe.build_pxe_network_config(net)
|
||||
self.assertIn('eth0:off', config)
|
||||
self.assertIn('eth1:off', config)
|
||||
|
||||
def test_build_network_config(self):
|
||||
net = utils.get_test_network_info(1)
|
||||
config = pxe.build_network_config(net)
|
||||
self.assertIn('eth0', config)
|
||||
self.assertNotIn('eth1', config)
|
||||
|
||||
net = utils.get_test_network_info(2)
|
||||
config = pxe.build_network_config(net)
|
||||
self.assertIn('eth0', config)
|
||||
self.assertIn('eth1', config)
|
||||
|
||||
def test_build_network_config_dhcp(self):
|
||||
self.flags(
|
||||
net_config_template='$pybasedir/nova/virt/baremetal/'
|
||||
'net-dhcp.ubuntu.template',
|
||||
group='baremetal',
|
||||
)
|
||||
net = utils.get_test_network_info()
|
||||
net[0]['network']['subnets'][0]['ips'][0]['address'] = '1.2.3.4'
|
||||
config = pxe.build_network_config(net)
|
||||
self.assertIn('iface eth0 inet dhcp', config)
|
||||
self.assertNotIn('address 1.2.3.4', config)
|
||||
|
||||
def test_build_network_config_static(self):
|
||||
self.flags(
|
||||
net_config_template='$pybasedir/nova/virt/baremetal/'
|
||||
'net-static.ubuntu.template',
|
||||
group='baremetal',
|
||||
)
|
||||
net = utils.get_test_network_info()
|
||||
net[0]['network']['subnets'][0]['ips'][0]['address'] = '1.2.3.4'
|
||||
config = pxe.build_network_config(net)
|
||||
self.assertIn('iface eth0 inet static', config)
|
||||
self.assertIn('address 1.2.3.4', config)
|
||||
|
||||
def test_build_network_config_static_parameters(self):
|
||||
self.flags(use_ipv6=True)
|
||||
self.flags(
|
||||
net_config_template='$pybasedir/nova/virt/baremetal/'
|
||||
'net-static.ubuntu.template',
|
||||
group='baremetal'
|
||||
)
|
||||
|
||||
net = utils.get_test_network_info()
|
||||
net[0]['network']['subnets'][0]['cidr'] = '10.1.1.0/24'
|
||||
net[0]['network']['subnets'][0]['gateway']['address'] = '10.1.1.1'
|
||||
net[0]['network']['subnets'][0]['dns'][0]['address'] = '10.1.1.2'
|
||||
net[0]['network']['subnets'][0]['dns'][1]['address'] = '10.1.1.3'
|
||||
|
||||
net[0]['network']['subnets'][1]['cidr'] = 'fc00::/7'
|
||||
net[0]['network']['subnets'][1]['ips'][0]['address'] = 'fc00::1'
|
||||
net[0]['network']['subnets'][1]['gateway']['address'] = 'fc00::2'
|
||||
config = pxe.build_network_config(net)
|
||||
|
||||
self.assertIn('iface eth0 inet static', config)
|
||||
self.assertIn('gateway 10.1.1.1', config)
|
||||
self.assertIn('dns-nameservers 10.1.1.2 10.1.1.3', config)
|
||||
|
||||
self.assertIn('iface eth0 inet6 static', config)
|
||||
self.assertIn('address fc00::1', config)
|
||||
self.assertIn('netmask 7', config)
|
||||
self.assertIn('gateway fc00::2', config)
|
||||
|
||||
def test_image_dir_path(self):
|
||||
self.assertEqual(
|
||||
os.path.join(CONF.instances_path, 'instance-00000001'),
|
||||
pxe.get_image_dir_path(self.instance))
|
||||
|
||||
def test_image_file_path(self):
|
||||
self.assertEqual(
|
||||
os.path.join(
|
||||
CONF.instances_path, 'instance-00000001', 'disk'),
|
||||
pxe.get_image_file_path(self.instance))
|
||||
|
||||
def test_pxe_config_file_path(self):
|
||||
self.instance['uuid'] = 'aaaa-bbbb-cccc'
|
||||
self.assertEqual(
|
||||
os.path.join(CONF.baremetal.tftp_root,
|
||||
'aaaa-bbbb-cccc', 'config'),
|
||||
pxe.get_pxe_config_file_path(self.instance))
|
||||
|
||||
def test_pxe_mac_path(self):
|
||||
self.assertEqual(
|
||||
os.path.join(CONF.baremetal.tftp_root,
|
||||
'pxelinux.cfg', '01-23-45-67-89-ab'),
|
||||
pxe.get_pxe_mac_path('23:45:67:89:AB'))
|
||||
|
||||
def test_get_instance_deploy_ids(self):
|
||||
self.instance['extra_specs'] = {
|
||||
'baremetal:deploy_kernel_id': 'aaaa',
|
||||
'baremetal:deploy_ramdisk_id': 'bbbb',
|
||||
}
|
||||
self.flags(deploy_kernel="fail", group='baremetal')
|
||||
self.flags(deploy_ramdisk="fail", group='baremetal')
|
||||
|
||||
self.assertEqual('aaaa', pxe.get_deploy_aki_id(self.instance))
|
||||
self.assertEqual('bbbb', pxe.get_deploy_ari_id(self.instance))
|
||||
|
||||
def test_get_default_deploy_ids(self):
|
||||
self.instance['extra_specs'] = {}
|
||||
self.flags(deploy_kernel="aaaa", group='baremetal')
|
||||
self.flags(deploy_ramdisk="bbbb", group='baremetal')
|
||||
|
||||
self.assertEqual('aaaa', pxe.get_deploy_aki_id(self.instance))
|
||||
self.assertEqual('bbbb', pxe.get_deploy_ari_id(self.instance))
|
||||
|
||||
def test_get_partition_sizes(self):
|
||||
# default "kinda.big" instance
|
||||
sizes = pxe.get_partition_sizes(self.instance)
|
||||
self.assertEqual(40960, sizes[0])
|
||||
self.assertEqual(1024, sizes[1])
|
||||
|
||||
def test_swap_not_zero(self):
|
||||
# override swap to 0
|
||||
flavor = utils.get_test_flavor(self.context)
|
||||
flavor['swap'] = 0
|
||||
self.instance = utils.get_test_instance(self.context, flavor)
|
||||
|
||||
sizes = pxe.get_partition_sizes(self.instance)
|
||||
self.assertEqual(40960, sizes[0])
|
||||
self.assertEqual(1, sizes[1])
|
||||
|
||||
def test_get_tftp_image_info(self):
|
||||
flavor = utils.get_test_flavor()
|
||||
# Raises an exception when options are neither specified
|
||||
# on the instance nor in configuration file
|
||||
self.assertRaises(exception.NovaException,
|
||||
pxe.get_tftp_image_info,
|
||||
self.instance, flavor)
|
||||
|
||||
# Test that other non-true values also raise an exception
|
||||
self.flags(deploy_kernel='', deploy_ramdisk='', group='baremetal')
|
||||
self.assertRaises(exception.NovaException,
|
||||
pxe.get_tftp_image_info,
|
||||
self.instance, flavor)
|
||||
|
||||
# Even if the instance includes kernel_id and ramdisk_id,
|
||||
# we still need deploy_kernel_id and deploy_ramdisk_id.
|
||||
# If those aren't present in instance[], and not specified in
|
||||
# config file, then we raise an exception.
|
||||
self.instance['kernel_id'] = 'aaaa'
|
||||
self.instance['ramdisk_id'] = 'bbbb'
|
||||
self.assertRaises(exception.NovaException,
|
||||
pxe.get_tftp_image_info,
|
||||
self.instance, flavor)
|
||||
|
||||
# If an instance doesn't specify deploy_kernel_id or deploy_ramdisk_id,
|
||||
# but defaults are set in the config file, we should use those.
|
||||
|
||||
# Here, we confirm both that all four values were set
|
||||
# and that the proper paths are getting set for all of them
|
||||
self.flags(deploy_kernel='cccc', deploy_ramdisk='dddd',
|
||||
group='baremetal')
|
||||
base = os.path.join(CONF.baremetal.tftp_root, self.instance['uuid'])
|
||||
res = pxe.get_tftp_image_info(self.instance, flavor)
|
||||
expected = {
|
||||
'kernel': ['aaaa', os.path.join(base, 'kernel')],
|
||||
'ramdisk': ['bbbb', os.path.join(base, 'ramdisk')],
|
||||
'deploy_kernel': ['cccc', os.path.join(base, 'deploy_kernel')],
|
||||
'deploy_ramdisk': ['dddd',
|
||||
os.path.join(base, 'deploy_ramdisk')],
|
||||
}
|
||||
self.assertEqual(expected, res)
|
||||
|
||||
# If deploy_kernel_id and deploy_ramdisk_id are specified on
|
||||
# image extra_specs, this should override any default configuration.
|
||||
# Note that it is passed on the 'instance' object, despite being
|
||||
# inherited from the flavor_extra_specs table.
|
||||
extra_specs = {
|
||||
'baremetal:deploy_kernel_id': 'eeee',
|
||||
'baremetal:deploy_ramdisk_id': 'ffff',
|
||||
}
|
||||
flavor['extra_specs'] = extra_specs
|
||||
res = pxe.get_tftp_image_info(self.instance, flavor)
|
||||
self.assertEqual('eeee', res['deploy_kernel'][0])
|
||||
self.assertEqual('ffff', res['deploy_ramdisk'][0])
|
||||
|
||||
# However, if invalid values are passed on the image extra_specs,
|
||||
# this should still raise an exception.
|
||||
extra_specs = {
|
||||
'baremetal:deploy_kernel_id': '',
|
||||
'baremetal:deploy_ramdisk_id': '',
|
||||
}
|
||||
flavor['extra_specs'] = extra_specs
|
||||
self.assertRaises(exception.NovaException,
|
||||
pxe.get_tftp_image_info,
|
||||
self.instance, flavor)
|
||||
|
||||
|
||||
class PXEPrivateMethodsTestCase(BareMetalPXETestCase):
|
||||
|
||||
def test_collect_mac_addresses(self):
|
||||
self._create_node()
|
||||
address_list = [nic['address'] for nic in self.nic_info]
|
||||
address_list.sort()
|
||||
macs = self.driver._collect_mac_addresses(self.context, self.node)
|
||||
self.assertEqual(address_list, macs)
|
||||
|
||||
def test_cache_tftp_images(self):
|
||||
self.instance['kernel_id'] = 'aaaa'
|
||||
self.instance['ramdisk_id'] = 'bbbb'
|
||||
flavor = utils.get_test_flavor()
|
||||
extra_specs = {
|
||||
'baremetal:deploy_kernel_id': 'cccc',
|
||||
'baremetal:deploy_ramdisk_id': 'dddd',
|
||||
}
|
||||
flavor['extra_specs'] = extra_specs
|
||||
image_info = pxe.get_tftp_image_info(self.instance, flavor)
|
||||
|
||||
self.mox.StubOutWithMock(os, 'makedirs')
|
||||
self.mox.StubOutWithMock(os.path, 'exists')
|
||||
os.makedirs(os.path.join(CONF.baremetal.tftp_root,
|
||||
self.instance['uuid'])).AndReturn(True)
|
||||
for uuid, path in [image_info[label] for label in image_info]:
|
||||
os.path.exists(path).AndReturn(True)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
self.driver._cache_tftp_images(
|
||||
self.context, self.instance, image_info)
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test_cache_image(self):
|
||||
self.mox.StubOutWithMock(os, 'makedirs')
|
||||
self.mox.StubOutWithMock(os, 'unlink')
|
||||
self.mox.StubOutWithMock(os.path, 'exists')
|
||||
os.makedirs(pxe.get_image_dir_path(self.instance)).AndReturn(True)
|
||||
disk_path = os.path.join(
|
||||
pxe.get_image_dir_path(self.instance), 'disk')
|
||||
os.unlink(disk_path).AndReturn(None)
|
||||
os.path.exists(disk_path).AndReturn(True)
|
||||
os.path.exists(pxe.get_image_file_path(self.instance)).\
|
||||
AndReturn(True)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
image_meta = utils.get_test_image_info(
|
||||
self.context, self.instance)
|
||||
self.driver._cache_image(
|
||||
self.context, self.instance, image_meta)
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test_inject_into_image(self):
|
||||
# NOTE(deva): we could also test this method by stubbing
|
||||
# nova.virt.disk.api._inject_*_into_fs
|
||||
self._create_node()
|
||||
files = []
|
||||
self.instance['hostname'] = 'fake hostname'
|
||||
files.append(('/etc/hostname', 'fake hostname'))
|
||||
self.instance['key_data'] = 'fake ssh key'
|
||||
net_info = utils.get_test_network_info(1)
|
||||
net = pxe.build_network_config(net_info)
|
||||
admin_password = 'fake password'
|
||||
|
||||
self.mox.StubOutWithMock(os.path, 'exists')
|
||||
os.path.exists(mox.IgnoreArg()).AndReturn(True)
|
||||
|
||||
self.mox.StubOutWithMock(disk_api, 'inject_data')
|
||||
disk_api.inject_data(
|
||||
admin_password=admin_password,
|
||||
image=pxe.get_image_file_path(self.instance),
|
||||
key='fake ssh key',
|
||||
metadata=None,
|
||||
partition=None,
|
||||
net=net,
|
||||
files=files, # this is what we're really testing
|
||||
).AndReturn(True)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
self.driver._inject_into_image(
|
||||
self.context, self.node, self.instance,
|
||||
network_info=net_info,
|
||||
admin_password=admin_password,
|
||||
injected_files=None)
|
||||
self.mox.VerifyAll()
|
||||
|
||||
|
||||
class PXEPublicMethodsTestCase(BareMetalPXETestCase):
|
||||
|
||||
def test_cache_images(self):
|
||||
self._create_node()
|
||||
self.mox.StubOutWithMock(objects.Flavor, 'get_by_id')
|
||||
self.mox.StubOutWithMock(pxe, "get_tftp_image_info")
|
||||
self.mox.StubOutWithMock(self.driver, "_cache_tftp_images")
|
||||
self.mox.StubOutWithMock(self.driver, "_cache_image")
|
||||
self.mox.StubOutWithMock(self.driver, "_inject_into_image")
|
||||
|
||||
objects.Flavor.get_by_id(self.context,
|
||||
self.instance['instance_type_id']
|
||||
).AndReturn({})
|
||||
pxe.get_tftp_image_info(self.instance, {}).AndReturn([])
|
||||
self.driver._cache_tftp_images(self.context, self.instance, [])
|
||||
self.driver._cache_image(self.context, self.instance, [])
|
||||
self.driver._inject_into_image(self.context, self.node, self.instance,
|
||||
self.test_network_info, None, '')
|
||||
self.mox.ReplayAll()
|
||||
|
||||
self.driver.cache_images(
|
||||
self.context, self.node, self.instance,
|
||||
admin_password='',
|
||||
image_meta=[],
|
||||
injected_files=None,
|
||||
network_info=self.test_network_info,
|
||||
)
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test_destroy_images(self):
|
||||
self._create_node()
|
||||
self.mox.StubOutWithMock(bm_utils, 'unlink_without_raise')
|
||||
self.mox.StubOutWithMock(bm_utils, 'rmtree_without_raise')
|
||||
|
||||
bm_utils.unlink_without_raise(pxe.get_image_file_path(self.instance))
|
||||
bm_utils.rmtree_without_raise(pxe.get_image_dir_path(self.instance))
|
||||
self.mox.ReplayAll()
|
||||
|
||||
self.driver.destroy_images(self.context, self.node, self.instance)
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test_dhcp_options_for_instance(self):
|
||||
self._create_node()
|
||||
self.mox.ReplayAll()
|
||||
expected = [{'opt_name': 'bootfile-name',
|
||||
'opt_value': CONF.baremetal.pxe_bootfile_name},
|
||||
{'opt_name': 'server-ip-address', 'opt_value': CONF.my_ip},
|
||||
{'opt_name': 'tftp-server', 'opt_value': CONF.my_ip}]
|
||||
res = self.driver.dhcp_options_for_instance(self.instance)
|
||||
self.assertEqual(expected.sort(), res.sort())
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test_activate_bootloader_passes_details(self):
|
||||
self._create_node()
|
||||
macs = [nic['address'] for nic in self.nic_info]
|
||||
macs.sort()
|
||||
image_info = {
|
||||
'deploy_kernel': [None, 'aaaa'],
|
||||
'deploy_ramdisk': [None, 'bbbb'],
|
||||
'kernel': [None, 'cccc'],
|
||||
'ramdisk': [None, 'dddd'],
|
||||
}
|
||||
self.instance['uuid'] = 'fake-uuid'
|
||||
iqn = "iqn-%s" % self.instance['uuid']
|
||||
pxe_config = 'this is a fake pxe config'
|
||||
pxe_path = pxe.get_pxe_config_file_path(self.instance)
|
||||
pxe.get_image_file_path(self.instance)
|
||||
|
||||
self.mox.StubOutWithMock(objects.Flavor, 'get_by_id')
|
||||
self.mox.StubOutWithMock(pxe, 'get_tftp_image_info')
|
||||
self.mox.StubOutWithMock(pxe, 'get_partition_sizes')
|
||||
self.mox.StubOutWithMock(bm_utils, 'random_alnum')
|
||||
self.mox.StubOutWithMock(pxe, 'build_pxe_config')
|
||||
self.mox.StubOutWithMock(bm_utils, 'write_to_file')
|
||||
self.mox.StubOutWithMock(bm_utils, 'create_link_without_raise')
|
||||
|
||||
objects.Flavor.get_by_id(self.context,
|
||||
self.instance['instance_type_id']
|
||||
).AndReturn({})
|
||||
pxe.get_tftp_image_info(self.instance, {}).AndReturn(image_info)
|
||||
pxe.get_partition_sizes(self.instance).AndReturn((0, 0, 0))
|
||||
bm_utils.random_alnum(32).AndReturn('alnum')
|
||||
pxe.build_pxe_config(
|
||||
self.node['id'], 'alnum', iqn,
|
||||
'aaaa', 'bbbb', 'cccc', 'dddd',
|
||||
self.test_network_info).AndReturn(pxe_config)
|
||||
bm_utils.write_to_file(pxe_path, pxe_config)
|
||||
for mac in macs:
|
||||
bm_utils.create_link_without_raise(
|
||||
pxe_path, pxe.get_pxe_mac_path(mac))
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
self.driver.activate_bootloader(self.context, self.node, self.instance,
|
||||
network_info=self.test_network_info)
|
||||
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test_activate_and_deactivate_bootloader(self):
|
||||
self._create_node()
|
||||
flavor = objects.Flavor(
|
||||
context=self.context,
|
||||
extra_specs={
|
||||
'baremetal:deploy_kernel_id': 'eeee',
|
||||
'baremetal:deploy_ramdisk_id': 'ffff',
|
||||
})
|
||||
self.instance['uuid'] = 'fake-uuid'
|
||||
|
||||
self.mox.StubOutWithMock(objects.Flavor, 'get_by_id')
|
||||
self.mox.StubOutWithMock(bm_utils, 'write_to_file')
|
||||
self.mox.StubOutWithMock(bm_utils, 'create_link_without_raise')
|
||||
self.mox.StubOutWithMock(bm_utils, 'unlink_without_raise')
|
||||
self.mox.StubOutWithMock(bm_utils, 'rmtree_without_raise')
|
||||
|
||||
objects.Flavor.get_by_id(
|
||||
self.context, self.instance['instance_type_id']).AndReturn(
|
||||
flavor)
|
||||
|
||||
# create the config file
|
||||
bm_utils.write_to_file(mox.StrContains('fake-uuid'),
|
||||
mox.StrContains(CONF.baremetal.tftp_root))
|
||||
# unlink and link the 2 interfaces
|
||||
for i in range(2):
|
||||
bm_utils.unlink_without_raise(mox.Or(
|
||||
mox.StrContains('fake-uuid'),
|
||||
mox.StrContains(CONF.baremetal.tftp_root)))
|
||||
bm_utils.create_link_without_raise(
|
||||
mox.StrContains('fake-uuid'),
|
||||
mox.StrContains(CONF.baremetal.tftp_root))
|
||||
# unlink all 2 interfaces, 4 images, and the config file
|
||||
for i in range(7):
|
||||
bm_utils.unlink_without_raise(mox.Or(
|
||||
mox.StrContains('fake-uuid'),
|
||||
mox.StrContains(CONF.baremetal.tftp_root)))
|
||||
bm_utils.rmtree_without_raise(mox.StrContains('fake-uuid'))
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
# activate and deactivate the bootloader
|
||||
# and check the deployment task_state in the database
|
||||
row = db.bm_node_get(self.context, 1)
|
||||
self.assertIsNone(row['deploy_key'])
|
||||
|
||||
self.driver.activate_bootloader(self.context, self.node, self.instance,
|
||||
network_info=self.test_network_info)
|
||||
row = db.bm_node_get(self.context, 1)
|
||||
self.assertIsNotNone(row['deploy_key'])
|
||||
|
||||
self.driver.deactivate_bootloader(self.context, self.node,
|
||||
self.instance)
|
||||
row = db.bm_node_get(self.context, 1)
|
||||
self.assertIsNone(row['deploy_key'])
|
||||
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test_deactivate_bootloader_for_nonexistent_instance(self):
|
||||
self._create_node()
|
||||
self.instance['uuid'] = 'fake-uuid'
|
||||
pxe_path = pxe.get_pxe_config_file_path(self.instance)
|
||||
|
||||
self.mox.StubOutWithMock(bm_utils, 'unlink_without_raise')
|
||||
self.mox.StubOutWithMock(bm_utils, 'rmtree_without_raise')
|
||||
self.mox.StubOutWithMock(pxe, 'get_tftp_image_info')
|
||||
self.mox.StubOutWithMock(self.driver, '_collect_mac_addresses')
|
||||
|
||||
extra_specs = dict(extra_specs={
|
||||
'baremetal:deploy_ramdisk_id': 'ignore',
|
||||
'baremetal:deploy_kernel_id': 'ignore'})
|
||||
pxe.get_tftp_image_info(self.instance, extra_specs).\
|
||||
AndRaise(exception.NovaException)
|
||||
bm_utils.unlink_without_raise(pxe_path)
|
||||
self.driver._collect_mac_addresses(self.context, self.node).\
|
||||
AndRaise(db_exc.DBError)
|
||||
bm_utils.rmtree_without_raise(
|
||||
os.path.join(CONF.baremetal.tftp_root, 'fake-uuid'))
|
||||
self.mox.ReplayAll()
|
||||
|
||||
self.driver.deactivate_bootloader(
|
||||
self.context, self.node, self.instance)
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test_activate_node(self):
|
||||
self._create_node()
|
||||
self.instance['uuid'] = 'fake-uuid'
|
||||
self.flags(pxe_deploy_timeout=1, group='baremetal')
|
||||
|
||||
db.bm_node_update(self.context, 1,
|
||||
{'task_state': baremetal_states.DEPLOYING,
|
||||
'instance_uuid': 'fake-uuid'})
|
||||
|
||||
# test timeout
|
||||
self.assertRaises(exception.InstanceDeployFailure,
|
||||
self.driver.activate_node,
|
||||
self.context, self.node, self.instance)
|
||||
|
||||
# test DEPLOYDONE
|
||||
db.bm_node_update(self.context, 1,
|
||||
{'task_state': baremetal_states.DEPLOYDONE})
|
||||
self.driver.activate_node(self.context, self.node, self.instance)
|
||||
|
||||
# test no deploy -- state is just ACTIVE
|
||||
db.bm_node_update(self.context, 1,
|
||||
{'task_state': baremetal_states.ACTIVE})
|
||||
self.driver.activate_node(self.context, self.node, self.instance)
|
||||
|
||||
# test node gone
|
||||
db.bm_node_destroy(self.context, 1)
|
||||
self.assertRaises(exception.InstanceDeployFailure,
|
||||
self.driver.activate_node,
|
||||
self.context, self.node, self.instance)
|
||||
@@ -1,398 +0,0 @@
|
||||
# coding=utf-8
|
||||
|
||||
# Copyright (c) 2011-2013 University of Southern California / ISI
|
||||
# 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.
|
||||
|
||||
"""Tests for baremetal tilera driver."""
|
||||
|
||||
import os
|
||||
|
||||
import mox
|
||||
from oslo.config import cfg
|
||||
from oslo.db import exception as db_exc
|
||||
|
||||
from nova import exception
|
||||
from nova.tests.image import fake as fake_image
|
||||
from nova.tests import utils
|
||||
from nova.tests.virt.baremetal.db import base as bm_db_base
|
||||
from nova.tests.virt.baremetal.db import utils as bm_db_utils
|
||||
from nova.virt.baremetal import baremetal_states
|
||||
from nova.virt.baremetal import db
|
||||
from nova.virt.baremetal import tilera
|
||||
from nova.virt.baremetal import utils as bm_utils
|
||||
from nova.virt.disk import api as disk_api
|
||||
from nova.virt import fake as fake_virt
|
||||
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
COMMON_FLAGS = dict(
|
||||
firewall_driver='nova.virt.baremetal.fake.FakeFirewallDriver',
|
||||
host='test_host',
|
||||
)
|
||||
|
||||
BAREMETAL_FLAGS = dict(
|
||||
driver='nova.virt.baremetal.tilera.Tilera',
|
||||
flavor_extra_specs=['cpu_arch:test', 'test_spec:test_value'],
|
||||
power_manager='nova.virt.baremetal.fake.FakePowerManager',
|
||||
vif_driver='nova.virt.baremetal.fake.FakeVifDriver',
|
||||
volume_driver='nova.virt.baremetal.fake.FakeVolumeDriver',
|
||||
group='baremetal',
|
||||
)
|
||||
|
||||
|
||||
class BareMetalTileraTestCase(bm_db_base.BMDBTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(BareMetalTileraTestCase, self).setUp()
|
||||
self.flags(**COMMON_FLAGS)
|
||||
self.flags(**BAREMETAL_FLAGS)
|
||||
self.driver = tilera.Tilera(fake_virt.FakeVirtAPI())
|
||||
|
||||
fake_image.stub_out_image_service(self.stubs)
|
||||
self.addCleanup(fake_image.FakeImageService_reset)
|
||||
self.context = utils.get_test_admin_context()
|
||||
self.test_block_device_info = None,
|
||||
self.instance = utils.get_test_instance()
|
||||
self.test_network_info = utils.get_test_network_info()
|
||||
self.node_info = bm_db_utils.new_bm_node(
|
||||
service_host='test_host',
|
||||
cpus=4,
|
||||
memory_mb=2048,
|
||||
)
|
||||
self.nic_info = [
|
||||
{'address': '22:22:22:22:22:22', 'datapath_id': '0x1',
|
||||
'port_no': 1},
|
||||
{'address': '33:33:33:33:33:33', 'datapath_id': '0x2',
|
||||
'port_no': 2},
|
||||
]
|
||||
|
||||
def _create_node(self):
|
||||
self.node = db.bm_node_create(self.context, self.node_info)
|
||||
for nic in self.nic_info:
|
||||
db.bm_interface_create(
|
||||
self.context,
|
||||
self.node['id'],
|
||||
nic['address'],
|
||||
nic['datapath_id'],
|
||||
nic['port_no'],
|
||||
)
|
||||
self.instance['node'] = self.node['id']
|
||||
self.spawn_params = dict(
|
||||
admin_password='test_pass',
|
||||
block_device_info=self.test_block_device_info,
|
||||
context=self.context,
|
||||
image_meta=utils.get_test_image_info(None,
|
||||
self.instance),
|
||||
injected_files=[('/fake/path', 'hello world')],
|
||||
instance=self.instance,
|
||||
network_info=self.test_network_info,
|
||||
)
|
||||
|
||||
|
||||
class TileraClassMethodsTestCase(BareMetalTileraTestCase):
|
||||
|
||||
def test_build_network_config(self):
|
||||
net = utils.get_test_network_info(1)
|
||||
config = tilera.build_network_config(net)
|
||||
self.assertIn('eth0', config)
|
||||
self.assertNotIn('eth1', config)
|
||||
|
||||
net = utils.get_test_network_info(2)
|
||||
config = tilera.build_network_config(net)
|
||||
self.assertIn('eth0', config)
|
||||
self.assertIn('eth1', config)
|
||||
|
||||
def test_build_network_config_dhcp(self):
|
||||
self.flags(
|
||||
net_config_template='$pybasedir/nova/virt/baremetal/'
|
||||
'net-dhcp.ubuntu.template',
|
||||
group='baremetal',
|
||||
)
|
||||
net = utils.get_test_network_info()
|
||||
net[0]['network']['subnets'][0]['ips'][0]['address'] = '1.2.3.4'
|
||||
config = tilera.build_network_config(net)
|
||||
self.assertIn('iface eth0 inet dhcp', config)
|
||||
self.assertNotIn('address 1.2.3.4', config)
|
||||
|
||||
def test_build_network_config_static(self):
|
||||
self.flags(
|
||||
net_config_template='$pybasedir/nova/virt/baremetal/'
|
||||
'net-static.ubuntu.template',
|
||||
group='baremetal',
|
||||
)
|
||||
net = utils.get_test_network_info()
|
||||
net[0]['network']['subnets'][0]['ips'][0]['address'] = '1.2.3.4'
|
||||
config = tilera.build_network_config(net)
|
||||
self.assertIn('iface eth0 inet static', config)
|
||||
self.assertIn('address 1.2.3.4', config)
|
||||
|
||||
def test_image_dir_path(self):
|
||||
self.assertEqual(
|
||||
tilera.get_image_dir_path(self.instance),
|
||||
os.path.join(CONF.instances_path, 'instance-00000001'))
|
||||
|
||||
def test_image_file_path(self):
|
||||
self.assertEqual(
|
||||
tilera.get_image_file_path(self.instance),
|
||||
os.path.join(
|
||||
CONF.instances_path, 'instance-00000001', 'disk'))
|
||||
|
||||
def test_tilera_nfs_path(self):
|
||||
self._create_node()
|
||||
self.node['id'] = '123'
|
||||
tilera_nfs_dir = "fs_" + self.node['id']
|
||||
self.assertEqual(
|
||||
tilera.get_tilera_nfs_path(self.node['id']),
|
||||
os.path.join(CONF.baremetal.tftp_root,
|
||||
tilera_nfs_dir))
|
||||
|
||||
def test_get_partition_sizes(self):
|
||||
# default "kinda.big" instance
|
||||
sizes = tilera.get_partition_sizes(self.instance)
|
||||
self.assertEqual(sizes[0], 40960)
|
||||
self.assertEqual(sizes[1], 1024)
|
||||
|
||||
def test_swap_not_zero(self):
|
||||
# override swap to 0
|
||||
flavor = utils.get_test_flavor(self.context)
|
||||
flavor['swap'] = 0
|
||||
self.instance = utils.get_test_instance(self.context, flavor)
|
||||
|
||||
sizes = tilera.get_partition_sizes(self.instance)
|
||||
self.assertEqual(sizes[0], 40960)
|
||||
self.assertEqual(sizes[1], 1)
|
||||
|
||||
def test_get_tftp_image_info(self):
|
||||
# Tilera case needs only kernel_id.
|
||||
self.instance['kernel_id'] = 'aaaa'
|
||||
self.instance['uuid'] = 'fake-uuid'
|
||||
|
||||
# Here, we confirm both that kernel_id was set
|
||||
# and that the proper paths are getting set for all of them
|
||||
base = os.path.join(CONF.baremetal.tftp_root, self.instance['uuid'])
|
||||
res = tilera.get_tftp_image_info(self.instance)
|
||||
expected = {
|
||||
'kernel': ['aaaa', os.path.join(base, 'kernel')],
|
||||
}
|
||||
self.assertEqual(res, expected)
|
||||
|
||||
|
||||
class TileraPrivateMethodsTestCase(BareMetalTileraTestCase):
|
||||
|
||||
def test_collect_mac_addresses(self):
|
||||
self._create_node()
|
||||
address_list = [nic['address'] for nic in self.nic_info]
|
||||
address_list.sort()
|
||||
macs = self.driver._collect_mac_addresses(self.context, self.node)
|
||||
self.assertEqual(macs, address_list)
|
||||
|
||||
def test_cache_tftp_images(self):
|
||||
self.instance['kernel_id'] = 'aaaa'
|
||||
image_info = tilera.get_tftp_image_info(self.instance)
|
||||
|
||||
self.mox.StubOutWithMock(os, 'makedirs')
|
||||
self.mox.StubOutWithMock(os.path, 'exists')
|
||||
os.makedirs(os.path.join(CONF.baremetal.tftp_root,
|
||||
self.instance['uuid'])).AndReturn(True)
|
||||
for uuid, path in [image_info[label] for label in image_info]:
|
||||
os.path.exists(path).AndReturn(True)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
self.driver._cache_tftp_images(
|
||||
self.context, self.instance, image_info)
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test_cache_image(self):
|
||||
self.mox.StubOutWithMock(os, 'makedirs')
|
||||
self.mox.StubOutWithMock(os, 'unlink')
|
||||
self.mox.StubOutWithMock(os.path, 'exists')
|
||||
os.makedirs(tilera.get_image_dir_path(self.instance)).AndReturn(True)
|
||||
disk_path = os.path.join(
|
||||
tilera.get_image_dir_path(self.instance), 'disk')
|
||||
os.path.exists(disk_path).AndReturn(True)
|
||||
os.unlink(disk_path).AndReturn(None)
|
||||
os.path.exists(tilera.get_image_file_path(self.instance)).\
|
||||
AndReturn(True)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
image_meta = utils.get_test_image_info(
|
||||
self.context, self.instance)
|
||||
self.driver._cache_image(
|
||||
self.context, self.instance, image_meta)
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test_inject_into_image(self):
|
||||
self._create_node()
|
||||
files = []
|
||||
self.instance['hostname'] = 'fake hostname'
|
||||
files.append(('/etc/hostname', 'fake hostname'))
|
||||
self.instance['key_data'] = 'fake ssh key'
|
||||
net_info = utils.get_test_network_info(1)
|
||||
net = tilera.build_network_config(net_info)
|
||||
admin_password = 'fake password'
|
||||
|
||||
self.mox.StubOutWithMock(os.path, 'exists')
|
||||
os.path.exists(mox.IgnoreArg()).AndReturn(True)
|
||||
|
||||
self.mox.StubOutWithMock(disk_api, 'inject_data')
|
||||
disk_api.inject_data(
|
||||
admin_password=admin_password,
|
||||
image=tilera.get_image_file_path(self.instance),
|
||||
key='fake ssh key',
|
||||
metadata=None,
|
||||
partition=None,
|
||||
net=net,
|
||||
files=files,
|
||||
).AndReturn(True)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
self.driver._inject_into_image(
|
||||
self.context, self.node, self.instance,
|
||||
network_info=net_info,
|
||||
admin_password=admin_password,
|
||||
injected_files=None)
|
||||
self.mox.VerifyAll()
|
||||
|
||||
|
||||
class TileraPublicMethodsTestCase(BareMetalTileraTestCase):
|
||||
|
||||
def test_cache_images(self):
|
||||
self._create_node()
|
||||
self.mox.StubOutWithMock(tilera, "get_tftp_image_info")
|
||||
self.mox.StubOutWithMock(self.driver, "_cache_tftp_images")
|
||||
self.mox.StubOutWithMock(self.driver, "_cache_image")
|
||||
self.mox.StubOutWithMock(self.driver, "_inject_into_image")
|
||||
|
||||
tilera.get_tftp_image_info(self.instance).AndReturn([])
|
||||
self.driver._cache_tftp_images(self.context, self.instance, [])
|
||||
self.driver._cache_image(self.context, self.instance, [])
|
||||
self.driver._inject_into_image(self.context, self.node, self.instance,
|
||||
self.test_network_info, None, '')
|
||||
self.mox.ReplayAll()
|
||||
|
||||
self.driver.cache_images(
|
||||
self.context, self.node, self.instance,
|
||||
admin_password='',
|
||||
image_meta=[],
|
||||
injected_files=None,
|
||||
network_info=self.test_network_info,
|
||||
)
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test_destroy_images(self):
|
||||
self._create_node()
|
||||
self.mox.StubOutWithMock(bm_utils, 'unlink_without_raise')
|
||||
self.mox.StubOutWithMock(bm_utils, 'rmtree_without_raise')
|
||||
|
||||
bm_utils.unlink_without_raise(tilera.get_image_file_path(
|
||||
self.instance))
|
||||
bm_utils.rmtree_without_raise(tilera.get_image_dir_path(self.instance))
|
||||
self.mox.ReplayAll()
|
||||
|
||||
self.driver.destroy_images(self.context, self.node, self.instance)
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test_activate_bootloader_passes_details(self):
|
||||
self._create_node()
|
||||
image_info = {
|
||||
'kernel': [None, 'cccc'],
|
||||
}
|
||||
self.instance['uuid'] = 'fake-uuid'
|
||||
tilera.get_tilera_nfs_path(self.instance)
|
||||
tilera.get_image_file_path(self.instance)
|
||||
|
||||
self.mox.StubOutWithMock(tilera, 'get_tftp_image_info')
|
||||
self.mox.StubOutWithMock(tilera, 'get_partition_sizes')
|
||||
|
||||
tilera.get_tftp_image_info(self.instance).AndReturn(image_info)
|
||||
tilera.get_partition_sizes(self.instance).AndReturn((0, 0))
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
self.driver.activate_bootloader(self.context, self.node, self.instance,
|
||||
network_info=self.test_network_info)
|
||||
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test_activate_and_deactivate_bootloader(self):
|
||||
self._create_node()
|
||||
self.instance['uuid'] = 'fake-uuid'
|
||||
tilera.get_tilera_nfs_path(self.instance)
|
||||
tilera.get_image_file_path(self.instance)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
# activate and deactivate the bootloader
|
||||
# and check the deployment task_state in the database
|
||||
row = db.bm_node_get(self.context, 1)
|
||||
self.assertIsNone(row['deploy_key'])
|
||||
|
||||
self.driver.activate_bootloader(self.context, self.node, self.instance,
|
||||
network_info=self.test_network_info)
|
||||
row = db.bm_node_get(self.context, 1)
|
||||
self.assertIsNotNone(row['deploy_key'])
|
||||
|
||||
self.driver.deactivate_bootloader(self.context, self.node,
|
||||
self.instance)
|
||||
row = db.bm_node_get(self.context, 1)
|
||||
self.assertIsNone(row['deploy_key'])
|
||||
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test_deactivate_bootloader_for_nonexistent_instance(self):
|
||||
self._create_node()
|
||||
self.node['id'] = 'fake-node-id'
|
||||
|
||||
self.mox.StubOutWithMock(bm_utils, 'unlink_without_raise')
|
||||
self.mox.StubOutWithMock(bm_utils, 'rmtree_without_raise')
|
||||
self.mox.StubOutWithMock(tilera, 'get_tftp_image_info')
|
||||
self.mox.StubOutWithMock(self.driver, '_collect_mac_addresses')
|
||||
|
||||
tilera.get_tilera_nfs_path(self.node['id'])
|
||||
|
||||
tilera.get_tftp_image_info(self.instance).\
|
||||
AndRaise(exception.NovaException)
|
||||
self.driver._collect_mac_addresses(self.context, self.node).\
|
||||
AndRaise(db_exc.DBError)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
self.driver.deactivate_bootloader(
|
||||
self.context, self.node, self.instance)
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test_activate_node(self):
|
||||
self._create_node()
|
||||
self.instance['uuid'] = 'fake-uuid'
|
||||
|
||||
db.bm_node_update(self.context, 1,
|
||||
{'task_state': baremetal_states.DEPLOYING,
|
||||
'instance_uuid': 'fake-uuid'})
|
||||
|
||||
# test DEPLOYDONE
|
||||
db.bm_node_update(self.context, 1,
|
||||
{'task_state': baremetal_states.DEPLOYDONE})
|
||||
self.driver.activate_node(self.context, self.node, self.instance)
|
||||
|
||||
# test no deploy -- state is just ACTIVE
|
||||
db.bm_node_update(self.context, 1,
|
||||
{'task_state': baremetal_states.ACTIVE})
|
||||
self.driver.activate_node(self.context, self.node, self.instance)
|
||||
|
||||
# test node gone
|
||||
db.bm_node_destroy(self.context, 1)
|
||||
self.assertRaises(exception.InstanceDeployFailure,
|
||||
self.driver.activate_node,
|
||||
self.context, self.node, self.instance)
|
||||
@@ -1,141 +0,0 @@
|
||||
# coding=utf-8
|
||||
|
||||
# Copyright (c) 2011-2013 University of Southern California / ISI
|
||||
# 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.
|
||||
|
||||
"""Test class for baremetal PDU power manager."""
|
||||
|
||||
from oslo.config import cfg
|
||||
|
||||
from nova import test
|
||||
from nova.tests.virt.baremetal.db import utils as bm_db_utils
|
||||
from nova import utils
|
||||
from nova.virt.baremetal import baremetal_states
|
||||
from nova.virt.baremetal import tilera_pdu
|
||||
from nova.virt.baremetal import utils as bm_utils
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
class BareMetalPduTestCase(test.NoDBTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(BareMetalPduTestCase, self).setUp()
|
||||
self.flags(tile_power_wait=0, group='baremetal')
|
||||
self.node = bm_db_utils.new_bm_node(
|
||||
id=123,
|
||||
pm_address='fake-address',
|
||||
pm_user='fake-user',
|
||||
pm_password='fake-password')
|
||||
self.tilera_pdu = tilera_pdu.Pdu(self.node)
|
||||
self.tile_pdu_on = 1
|
||||
self.tile_pdu_off = 2
|
||||
self.tile_pdu_status = 9
|
||||
|
||||
def test_construct(self):
|
||||
self.assertEqual(self.tilera_pdu.node_id, 123)
|
||||
self.assertEqual(self.tilera_pdu.address, 'fake-address')
|
||||
self.assertEqual(self.tilera_pdu.user, 'fake-user')
|
||||
self.assertEqual(self.tilera_pdu.password, 'fake-password')
|
||||
|
||||
def test_exec_pdutool(self):
|
||||
self.flags(tile_pdu_mgr='fake-pdu-mgr', group='baremetal')
|
||||
self.flags(tile_pdu_ip='fake-address', group='baremetal')
|
||||
self.mox.StubOutWithMock(utils, 'execute')
|
||||
self.mox.StubOutWithMock(bm_utils, 'unlink_without_raise')
|
||||
args = [
|
||||
'fake-pdu-mgr',
|
||||
'fake-address',
|
||||
self.tile_pdu_on,
|
||||
]
|
||||
utils.execute(*args).AndReturn('')
|
||||
self.mox.ReplayAll()
|
||||
|
||||
self.tilera_pdu._exec_pdutool(self.tile_pdu_on)
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test_is_power(self):
|
||||
self.mox.StubOutWithMock(self.tilera_pdu, '_exec_pdutool')
|
||||
self.tilera_pdu._exec_pdutool(self.tile_pdu_status).AndReturn(
|
||||
self.tile_pdu_on)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
self.tilera_pdu._is_power(self.tile_pdu_on)
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test_power_already_on(self):
|
||||
self.mox.StubOutWithMock(self.tilera_pdu, '_exec_pdutool')
|
||||
|
||||
self.tilera_pdu._exec_pdutool(self.tile_pdu_on).AndReturn(None)
|
||||
self.tilera_pdu._exec_pdutool(self.tile_pdu_status).AndReturn(
|
||||
self.tile_pdu_on)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
self.tilera_pdu.state = baremetal_states.DELETED
|
||||
self.tilera_pdu._power_on()
|
||||
self.mox.VerifyAll()
|
||||
self.assertEqual(self.tilera_pdu.state, baremetal_states.ACTIVE)
|
||||
|
||||
def test_power_on_ok(self):
|
||||
self.mox.StubOutWithMock(self.tilera_pdu, '_exec_pdutool')
|
||||
|
||||
self.tilera_pdu._exec_pdutool(self.tile_pdu_on).AndReturn(None)
|
||||
self.tilera_pdu._exec_pdutool(self.tile_pdu_status).AndReturn(
|
||||
self.tile_pdu_on)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
self.tilera_pdu.state = baremetal_states.DELETED
|
||||
self.tilera_pdu._power_on()
|
||||
self.mox.VerifyAll()
|
||||
self.assertEqual(self.tilera_pdu.state, baremetal_states.ACTIVE)
|
||||
|
||||
def test_power_on_fail(self):
|
||||
self.mox.StubOutWithMock(self.tilera_pdu, '_exec_pdutool')
|
||||
|
||||
self.tilera_pdu._exec_pdutool(self.tile_pdu_on).AndReturn(None)
|
||||
self.tilera_pdu._exec_pdutool(self.tile_pdu_status).AndReturn(
|
||||
self.tile_pdu_off)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
self.tilera_pdu.state = baremetal_states.DELETED
|
||||
self.tilera_pdu._power_on()
|
||||
self.mox.VerifyAll()
|
||||
self.assertEqual(self.tilera_pdu.state, baremetal_states.ERROR)
|
||||
|
||||
def test_power_on_max_retries(self):
|
||||
self.mox.StubOutWithMock(self.tilera_pdu, '_exec_pdutool')
|
||||
|
||||
self.tilera_pdu._exec_pdutool(self.tile_pdu_on).AndReturn(None)
|
||||
self.tilera_pdu._exec_pdutool(self.tile_pdu_status).AndReturn(
|
||||
self.tile_pdu_off)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
self.tilera_pdu.state = baremetal_states.DELETED
|
||||
self.tilera_pdu._power_on()
|
||||
self.mox.VerifyAll()
|
||||
self.assertEqual(self.tilera_pdu.state, baremetal_states.ERROR)
|
||||
|
||||
def test_power_off_ok(self):
|
||||
self.mox.StubOutWithMock(self.tilera_pdu, '_exec_pdutool')
|
||||
|
||||
self.tilera_pdu._exec_pdutool(self.tile_pdu_off).AndReturn(None)
|
||||
self.tilera_pdu._exec_pdutool(self.tile_pdu_status).AndReturn(
|
||||
self.tile_pdu_off)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
self.tilera_pdu.state = baremetal_states.ACTIVE
|
||||
self.tilera_pdu._power_off()
|
||||
self.mox.VerifyAll()
|
||||
self.assertEqual(self.tilera_pdu.state, baremetal_states.DELETED)
|
||||
@@ -1,77 +0,0 @@
|
||||
# coding=utf-8
|
||||
|
||||
# Copyright 2012,2014 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.
|
||||
|
||||
"""Tests for baremetal utils."""
|
||||
|
||||
import errno
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
from nova import test
|
||||
from nova.virt.baremetal import utils
|
||||
from nova.virt import images
|
||||
|
||||
|
||||
class BareMetalUtilsTestCase(test.NoDBTestCase):
|
||||
|
||||
def test_random_alnum(self):
|
||||
s = utils.random_alnum(10)
|
||||
self.assertEqual(len(s), 10)
|
||||
s = utils.random_alnum(100)
|
||||
self.assertEqual(len(s), 100)
|
||||
|
||||
def test_unlink(self):
|
||||
self.mox.StubOutWithMock(os, "unlink")
|
||||
os.unlink("/fake/path")
|
||||
|
||||
self.mox.ReplayAll()
|
||||
utils.unlink_without_raise("/fake/path")
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test_unlink_ENOENT(self):
|
||||
self.mox.StubOutWithMock(os, "unlink")
|
||||
os.unlink("/fake/path").AndRaise(OSError(errno.ENOENT))
|
||||
|
||||
self.mox.ReplayAll()
|
||||
utils.unlink_without_raise("/fake/path")
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test_create_link(self):
|
||||
self.mox.StubOutWithMock(os, "symlink")
|
||||
os.symlink("/fake/source", "/fake/link")
|
||||
|
||||
self.mox.ReplayAll()
|
||||
utils.create_link_without_raise("/fake/source", "/fake/link")
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test_create_link_EEXIST(self):
|
||||
self.mox.StubOutWithMock(os, "symlink")
|
||||
os.symlink("/fake/source", "/fake/link").AndRaise(
|
||||
OSError(errno.EEXIST))
|
||||
|
||||
self.mox.ReplayAll()
|
||||
utils.create_link_without_raise("/fake/source", "/fake/link")
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test_cache_image_with_clean(self):
|
||||
self.mox.StubOutWithMock(images, "fetch_to_raw")
|
||||
temp_f, temp_file = tempfile.mkstemp()
|
||||
images.fetch_to_raw(None, None, temp_file, None, None)
|
||||
self.mox.ReplayAll()
|
||||
utils.cache_image(None, temp_file, None, None, None, clean=True)
|
||||
self.mox.VerifyAll()
|
||||
self.assertFalse(os.path.exists(temp_file))
|
||||
@@ -1,392 +0,0 @@
|
||||
# coding=utf-8
|
||||
|
||||
# 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.
|
||||
|
||||
"""Tests for baremetal virtual power driver."""
|
||||
|
||||
import mox
|
||||
from oslo.config import cfg
|
||||
|
||||
from nova import exception
|
||||
from nova.openstack.common import processutils
|
||||
from nova.tests.image import fake as fake_image
|
||||
from nova.tests import utils
|
||||
from nova.tests.virt.baremetal.db import base as bm_db_base
|
||||
from nova.tests.virt.baremetal.db import utils as bm_db_utils
|
||||
from nova.virt.baremetal import common as connection
|
||||
from nova.virt.baremetal import db
|
||||
from nova.virt.baremetal import virtual_power_driver
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
COMMON_FLAGS = dict(
|
||||
firewall_driver='nova.virt.baremetal.fake.FakeFirewallDriver',
|
||||
host='test_host',
|
||||
)
|
||||
|
||||
BAREMETAL_FLAGS = dict(
|
||||
driver='nova.virt.baremetal.pxe.PXE',
|
||||
flavor_extra_specs=['cpu_arch:test', 'test_spec:test_value'],
|
||||
power_manager=
|
||||
'nova.virt.baremetal.virtual_power_driver.VirtualPowerManager',
|
||||
vif_driver='nova.virt.baremetal.fake.FakeVifDriver',
|
||||
volume_driver='nova.virt.baremetal.fake.FakeVolumeDriver',
|
||||
virtual_power_ssh_host=None,
|
||||
virtual_power_type='vbox',
|
||||
virtual_power_host_user=None,
|
||||
virtual_power_host_pass=None,
|
||||
virtual_power_host_key=None,
|
||||
group='baremetal',
|
||||
)
|
||||
|
||||
|
||||
class BareMetalVPDTestCase(bm_db_base.BMDBTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(BareMetalVPDTestCase, self).setUp()
|
||||
self.flags(**COMMON_FLAGS)
|
||||
self.flags(**BAREMETAL_FLAGS)
|
||||
|
||||
fake_image.stub_out_image_service(self.stubs)
|
||||
self.context = utils.get_test_admin_context()
|
||||
self.test_block_device_info = None,
|
||||
self.instance = utils.get_test_instance()
|
||||
self.test_network_info = utils.get_test_network_info(),
|
||||
self.node_info = bm_db_utils.new_bm_node(
|
||||
id=123,
|
||||
service_host='test_host',
|
||||
cpus=2,
|
||||
memory_mb=2048,
|
||||
)
|
||||
self.nic_info = [
|
||||
{'address': '11:11:11:11:11:11', 'datapath_id': '0x1',
|
||||
'port_no': 1},
|
||||
{'address': '22:22:22:22:22:22', 'datapath_id': '0x2',
|
||||
'port_no': 2},
|
||||
]
|
||||
self.addCleanup(fake_image.FakeImageService_reset)
|
||||
|
||||
def _create_node(self):
|
||||
self.node = db.bm_node_create(self.context, self.node_info)
|
||||
for nic in self.nic_info:
|
||||
db.bm_interface_create(
|
||||
self.context,
|
||||
self.node['id'],
|
||||
nic['address'],
|
||||
nic['datapath_id'],
|
||||
nic['port_no'],
|
||||
)
|
||||
self.instance['node'] = self.node['id']
|
||||
|
||||
def _create_pm(self):
|
||||
self.pm = virtual_power_driver.VirtualPowerManager(
|
||||
node=self.node,
|
||||
instance=self.instance)
|
||||
return self.pm
|
||||
|
||||
|
||||
class VPDMissingOptionsTestCase(BareMetalVPDTestCase):
|
||||
|
||||
def test_get_conn_missing_options(self):
|
||||
self.flags(virtual_power_ssh_host=None, group="baremetal")
|
||||
self.flags(virtual_power_host_user=None, group="baremetal")
|
||||
self.flags(virtual_power_host_pass=None, group="baremetal")
|
||||
self._create_node()
|
||||
self._create_pm()
|
||||
self._conn = None
|
||||
self.assertRaises(exception.NovaException,
|
||||
self.pm._get_conn)
|
||||
self._conn = None
|
||||
self.flags(virtual_power_ssh_host='127.0.0.1', group="baremetal")
|
||||
self.assertRaises(exception.NovaException,
|
||||
self.pm._get_conn)
|
||||
self._conn = None
|
||||
self.flags(virtual_power_host_user='user', group="baremetal")
|
||||
self.assertRaises(exception.NovaException,
|
||||
self.pm._get_conn)
|
||||
|
||||
|
||||
class VPDClassMethodsTestCase(BareMetalVPDTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(VPDClassMethodsTestCase, self).setUp()
|
||||
self.flags(virtual_power_ssh_host='127.0.0.1', group="baremetal")
|
||||
self.flags(virtual_power_host_user='user', group="baremetal")
|
||||
self.flags(virtual_power_host_pass='password', group="baremetal")
|
||||
|
||||
def test_get_conn_success_pass(self):
|
||||
self._create_node()
|
||||
self._create_pm()
|
||||
self._conn = self.pm._get_conn()
|
||||
self.mox.StubOutWithMock(connection, 'ssh_connect')
|
||||
connection.ssh_connect(mox.IsA(self._conn)).AndReturn(True)
|
||||
self.mox.ReplayAll()
|
||||
self.pm._set_connection()
|
||||
self.assertEqual(self.pm.connection_data.host, '127.0.0.1')
|
||||
self.assertEqual(self.pm.connection_data.username, 'user')
|
||||
self.assertEqual(self.pm.connection_data.password, 'password')
|
||||
self.assertIsNone(self.pm.connection_data.keyfile)
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test_get_conn_success_key(self):
|
||||
self.flags(virtual_power_host_pass='', group="baremetal")
|
||||
self.flags(virtual_power_host_key='/id_rsa_file.txt',
|
||||
group="baremetal")
|
||||
self._create_node()
|
||||
self._create_pm()
|
||||
self._conn = self.pm._get_conn()
|
||||
self.mox.StubOutWithMock(connection, 'ssh_connect')
|
||||
connection.ssh_connect(mox.IsA(self._conn)).AndReturn(True)
|
||||
self.mox.ReplayAll()
|
||||
self.pm._set_connection()
|
||||
self.assertEqual(self.pm.connection_data.host, '127.0.0.1')
|
||||
self.assertEqual(self.pm.connection_data.username, 'user')
|
||||
self.assertEqual(self.pm.connection_data.password, '')
|
||||
self.assertEqual(self.pm.connection_data.keyfile, '/id_rsa_file.txt')
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test_get_full_node_list(self):
|
||||
self._create_node()
|
||||
self._create_pm()
|
||||
|
||||
self.mox.StubOutWithMock(self.pm, '_run_command')
|
||||
cmd = self.pm._vp_cmd.list_cmd
|
||||
self.pm._run_command(cmd).AndReturn("testNode")
|
||||
|
||||
self.mox.ReplayAll()
|
||||
name = self.pm._get_full_node_list()
|
||||
self.assertEqual(name, 'testNode')
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test_check_for_node(self):
|
||||
self._create_node()
|
||||
self._create_pm()
|
||||
|
||||
self.mox.StubOutWithMock(self.pm, '_get_full_node_list')
|
||||
self.pm._get_full_node_list().\
|
||||
AndReturn(["testNode"])
|
||||
|
||||
self.mox.StubOutWithMock(self.pm, '_run_command')
|
||||
cmd = self.pm._vp_cmd.get_node_macs.replace('{_NodeName_}', 'testNode')
|
||||
self.pm._run_command(cmd).\
|
||||
AndReturn(["111111111111", "ffeeddccbbaa"])
|
||||
|
||||
self.mox.ReplayAll()
|
||||
name = self.pm._check_for_node()
|
||||
self.assertEqual(name, '"testNode"')
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test_check_for_node_not_found(self):
|
||||
self._create_node()
|
||||
self._create_pm()
|
||||
|
||||
self.mox.StubOutWithMock(self.pm, '_get_full_node_list')
|
||||
self.pm._get_full_node_list().AndReturn(["testNode"])
|
||||
|
||||
self.mox.StubOutWithMock(self.pm, '_run_command')
|
||||
cmd = self.pm._vp_cmd.get_node_macs.replace('{_NodeName_}', 'testNode')
|
||||
self.pm._run_command(cmd).AndReturn(["ffeeddccbbaa"])
|
||||
|
||||
self.mox.ReplayAll()
|
||||
name = self.pm._check_for_node()
|
||||
self.assertEqual(name, '')
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test_activate_node(self):
|
||||
self._create_node()
|
||||
self._create_pm()
|
||||
|
||||
self.mox.StubOutWithMock(self.pm, '_check_for_node')
|
||||
self.mox.StubOutWithMock(self.pm, '_run_command')
|
||||
self.mox.StubOutWithMock(self.pm, 'is_power_on')
|
||||
self.pm._check_for_node().AndReturn('"testNode"')
|
||||
self.pm._run_command(self.pm._vp_cmd.start_cmd).AndReturn("Started")
|
||||
self.pm.is_power_on().AndReturn(True)
|
||||
self.mox.ReplayAll()
|
||||
state = self.pm.activate_node()
|
||||
self.assertEqual(state, 'active')
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test_activate_node_fail(self):
|
||||
self._create_node()
|
||||
self._create_pm()
|
||||
|
||||
self.mox.StubOutWithMock(self.pm, '_check_for_node')
|
||||
self.mox.StubOutWithMock(self.pm, '_run_command')
|
||||
self.mox.StubOutWithMock(self.pm, 'is_power_on')
|
||||
self.pm._check_for_node().AndReturn('"testNode"')
|
||||
self.pm._run_command(self.pm._vp_cmd.start_cmd).AndReturn("Started")
|
||||
self.pm.is_power_on().AndReturn(False)
|
||||
self.mox.ReplayAll()
|
||||
state = self.pm.activate_node()
|
||||
self.assertEqual(state, 'error')
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test_deactivate_node(self):
|
||||
self._create_node()
|
||||
self._create_pm()
|
||||
|
||||
self.mox.StubOutWithMock(self.pm, '_check_for_node')
|
||||
self.mox.StubOutWithMock(self.pm, '_run_command')
|
||||
self.mox.StubOutWithMock(self.pm, 'is_power_on')
|
||||
self.pm._check_for_node().AndReturn('"testNode"')
|
||||
self.pm.is_power_on().AndReturn(True)
|
||||
self.pm._run_command(self.pm._vp_cmd.stop_cmd).AndReturn("Stopped")
|
||||
self.pm.is_power_on().AndReturn(False)
|
||||
self.mox.ReplayAll()
|
||||
state = self.pm.deactivate_node()
|
||||
self.assertEqual(state, 'deleted')
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test_deactivate_node_fail(self):
|
||||
self._create_node()
|
||||
self._create_pm()
|
||||
|
||||
self.mox.StubOutWithMock(self.pm, '_check_for_node')
|
||||
self.mox.StubOutWithMock(self.pm, '_run_command')
|
||||
self.mox.StubOutWithMock(self.pm, 'is_power_on')
|
||||
self.pm._check_for_node().AndReturn('"testNode"')
|
||||
self.pm.is_power_on().AndReturn(True)
|
||||
self.pm._run_command(self.pm._vp_cmd.stop_cmd).AndReturn("Stopped")
|
||||
self.pm.is_power_on().AndReturn(True)
|
||||
self.mox.ReplayAll()
|
||||
state = self.pm.deactivate_node()
|
||||
self.assertEqual(state, 'error')
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test_reboot_node(self):
|
||||
self._create_node()
|
||||
self._create_pm()
|
||||
|
||||
self.mox.StubOutWithMock(self.pm, '_check_for_node')
|
||||
self.mox.StubOutWithMock(self.pm, '_run_command')
|
||||
self.mox.StubOutWithMock(self.pm, 'is_power_on')
|
||||
self.pm._check_for_node().AndReturn(['"testNode"'])
|
||||
self.pm._run_command(self.pm._vp_cmd.reboot_cmd).AndReturn("Restarted")
|
||||
self.pm.is_power_on().AndReturn(True)
|
||||
self.mox.ReplayAll()
|
||||
state = self.pm.reboot_node()
|
||||
self.assertEqual(state, 'active')
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test_reboot_node_fail(self):
|
||||
self._create_node()
|
||||
self._create_pm()
|
||||
|
||||
self.mox.StubOutWithMock(self.pm, '_check_for_node')
|
||||
self.mox.StubOutWithMock(self.pm, '_run_command')
|
||||
self.mox.StubOutWithMock(self.pm, 'is_power_on')
|
||||
self.pm._check_for_node().AndReturn(['"testNode"'])
|
||||
self.pm._run_command(self.pm._vp_cmd.reboot_cmd).AndReturn("Restarted")
|
||||
self.pm.is_power_on().AndReturn(False)
|
||||
self.mox.ReplayAll()
|
||||
state = self.pm.reboot_node()
|
||||
self.assertEqual(state, 'error')
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test_is_power_on(self):
|
||||
self._create_node()
|
||||
self._create_pm()
|
||||
|
||||
self.mox.StubOutWithMock(self.pm, '_check_for_node')
|
||||
self.mox.StubOutWithMock(self.pm, '_run_command')
|
||||
self.pm._check_for_node().AndReturn(['"testNode"'])
|
||||
self.pm._run_command(self.pm._vp_cmd.list_running_cmd).\
|
||||
AndReturn(['"testNode"'])
|
||||
self.pm._matched_name = 'testNode'
|
||||
self.mox.ReplayAll()
|
||||
state = self.pm.is_power_on()
|
||||
self.assertEqual(state, True)
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test_is_power_on_fail(self):
|
||||
self._create_node()
|
||||
self._create_pm()
|
||||
|
||||
self.mox.StubOutWithMock(self.pm, '_check_for_node')
|
||||
self.pm._check_for_node().AndReturn(None)
|
||||
self.mox.ReplayAll()
|
||||
self.assertRaises(exception.NodeNotFound, self.pm.is_power_on)
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test_is_power_on_match_subname(self):
|
||||
self._create_node()
|
||||
self._create_pm()
|
||||
|
||||
self.mox.StubOutWithMock(self.pm, '_check_for_node')
|
||||
self.mox.StubOutWithMock(self.pm, '_run_command')
|
||||
self.pm._check_for_node().AndReturn(['"testNode"'])
|
||||
self.pm._run_command(self.pm._vp_cmd.list_running_cmd).\
|
||||
AndReturn(['"testNode01"'])
|
||||
self.pm._matched_name = '"testNode"'
|
||||
self.mox.ReplayAll()
|
||||
state = self.pm.is_power_on()
|
||||
self.assertEqual(state, False)
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test_run_command(self):
|
||||
self._create_node()
|
||||
self._create_pm()
|
||||
|
||||
self.mox.StubOutWithMock(self.pm, '_set_connection')
|
||||
self.mox.StubOutWithMock(processutils, 'ssh_execute')
|
||||
self.pm._set_connection().AndReturn(True)
|
||||
processutils.ssh_execute(None, '/usr/bin/VBoxManage test return',
|
||||
check_exit_code=True).AndReturn(("test\nreturn", ""))
|
||||
self.pm._matched_name = 'testNode'
|
||||
self.mox.ReplayAll()
|
||||
result = self.pm._run_command("test return")
|
||||
self.assertEqual(result, ['test', 'return'])
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test_run_command_raises_exception(self):
|
||||
self._create_node()
|
||||
self._create_pm()
|
||||
|
||||
self.mox.StubOutWithMock(self.pm, '_set_connection')
|
||||
self.mox.StubOutWithMock(processutils, 'ssh_execute')
|
||||
|
||||
self.pm._set_connection().AndReturn(True)
|
||||
processutils.ssh_execute(None, '/usr/bin/VBoxManage test return',
|
||||
check_exit_code=True).\
|
||||
AndRaise(processutils.ProcessExecutionError)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
result = self.pm._run_command("test return")
|
||||
self.assertEqual(result, [])
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test_activate_node_with_exception(self):
|
||||
self._create_node()
|
||||
self._create_pm()
|
||||
|
||||
self.mox.StubOutWithMock(self.pm, '_check_for_node')
|
||||
self.mox.StubOutWithMock(processutils, 'ssh_execute')
|
||||
|
||||
self.pm._check_for_node().AndReturn(['"testNode"'])
|
||||
self.pm._check_for_node().AndReturn(['"testNode"'])
|
||||
processutils.ssh_execute('test', '/usr/bin/VBoxManage startvm ',
|
||||
check_exit_code=True).\
|
||||
AndRaise(processutils.ProcessExecutionError)
|
||||
processutils.ssh_execute('test', '/usr/bin/VBoxManage list runningvms',
|
||||
check_exit_code=True).\
|
||||
AndRaise(processutils.ProcessExecutionError)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
self.pm._connection = 'test'
|
||||
state = self.pm.activate_node()
|
||||
self.assertEqual(state, 'error')
|
||||
self.mox.VerifyAll()
|
||||
@@ -1,294 +0,0 @@
|
||||
# Copyright (c) 2012 NTT DOCOMO, INC.
|
||||
# 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.
|
||||
|
||||
"""Tests for baremetal volume driver."""
|
||||
|
||||
from oslo.config import cfg
|
||||
|
||||
from nova import exception
|
||||
from nova import test
|
||||
from nova.virt.baremetal import volume_driver
|
||||
from nova.virt import fake
|
||||
from nova.virt.libvirt import volume as libvirt_volume
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
SHOW_OUTPUT = """Target 1: iqn.2010-10.org.openstack:volume-00000001
|
||||
System information:
|
||||
Driver: iscsi
|
||||
State: ready
|
||||
I_T nexus information:
|
||||
I_T nexus: 8
|
||||
Initiator: iqn.1993-08.org.debian:01:7780c6a16b4
|
||||
Connection: 0
|
||||
IP Address: 172.17.12.10
|
||||
LUN information:
|
||||
LUN: 0
|
||||
Type: controller
|
||||
SCSI ID: IET 00010000
|
||||
SCSI SN: beaf10
|
||||
Size: 0 MB, Block size: 1
|
||||
Online: Yes
|
||||
Removable media: No
|
||||
Readonly: No
|
||||
Backing store type: null
|
||||
Backing store path: None
|
||||
Backing store flags:
|
||||
LUN: 1
|
||||
Type: disk
|
||||
SCSI ID: IET 00010001
|
||||
SCSI SN: beaf11
|
||||
Size: 1074 MB, Block size: 512
|
||||
Online: Yes
|
||||
Removable media: No
|
||||
Readonly: No
|
||||
Backing store type: rdwr
|
||||
Backing store path: /dev/nova-volumes/volume-00000001
|
||||
Backing store flags:
|
||||
Account information:
|
||||
ACL information:
|
||||
ALL
|
||||
Target 2: iqn.2010-10.org.openstack:volume-00000002
|
||||
System information:
|
||||
Driver: iscsi
|
||||
State: ready
|
||||
I_T nexus information:
|
||||
LUN information:
|
||||
LUN: 0
|
||||
Type: controller
|
||||
SCSI ID: IET 00020000
|
||||
SCSI SN: beaf20
|
||||
Size: 0 MB, Block size: 1
|
||||
Online: Yes
|
||||
Removable media: No
|
||||
Readonly: No
|
||||
Backing store type: null
|
||||
Backing store path: None
|
||||
Backing store flags:
|
||||
LUN: 1
|
||||
Type: disk
|
||||
SCSI ID: IET 00020001
|
||||
SCSI SN: beaf21
|
||||
Size: 2147 MB, Block size: 512
|
||||
Online: Yes
|
||||
Removable media: No
|
||||
Readonly: No
|
||||
Backing store type: rdwr
|
||||
Backing store path: /dev/nova-volumes/volume-00000002
|
||||
Backing store flags:
|
||||
Account information:
|
||||
ACL information:
|
||||
ALL
|
||||
Target 1000001: iqn.2010-10.org.openstack.baremetal:1000001-dev.vdc
|
||||
System information:
|
||||
Driver: iscsi
|
||||
State: ready
|
||||
I_T nexus information:
|
||||
LUN information:
|
||||
LUN: 0
|
||||
Type: controller
|
||||
SCSI ID: IET f42410000
|
||||
SCSI SN: beaf10000010
|
||||
Size: 0 MB, Block size: 1
|
||||
Online: Yes
|
||||
Removable media: No
|
||||
Readonly: No
|
||||
Backing store type: null
|
||||
Backing store path: None
|
||||
Backing store flags:
|
||||
LUN: 1
|
||||
Type: disk
|
||||
SCSI ID: IET f42410001
|
||||
SCSI SN: beaf10000011
|
||||
Size: 1074 MB, Block size: 512
|
||||
Online: Yes
|
||||
Removable media: No
|
||||
Readonly: No
|
||||
Backing store type: rdwr
|
||||
Backing store path: /dev/disk/by-path/ip-172.17.12.10:3260-iscsi-\
|
||||
iqn.2010-10.org.openstack:volume-00000001-lun-1
|
||||
Backing store flags:
|
||||
Account information:
|
||||
ACL information:
|
||||
ALL
|
||||
"""
|
||||
|
||||
|
||||
def fake_show_tgtadm():
|
||||
return SHOW_OUTPUT
|
||||
|
||||
|
||||
class BareMetalVolumeTestCase(test.NoDBTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(BareMetalVolumeTestCase, self).setUp()
|
||||
self.stubs.Set(volume_driver, '_show_tgtadm', fake_show_tgtadm)
|
||||
|
||||
def test_list_backingstore_path(self):
|
||||
l = volume_driver._list_backingstore_path()
|
||||
self.assertEqual(len(l), 3)
|
||||
self.assertIn('/dev/nova-volumes/volume-00000001', l)
|
||||
self.assertIn('/dev/nova-volumes/volume-00000002', l)
|
||||
self.assertIn('/dev/disk/by-path/ip-172.17.12.10:3260-iscsi-'
|
||||
'iqn.2010-10.org.openstack:volume-00000001-lun-1', l)
|
||||
|
||||
def test_get_next_tid(self):
|
||||
tid = volume_driver._get_next_tid()
|
||||
self.assertEqual(1000002, tid)
|
||||
|
||||
def test_find_tid_found(self):
|
||||
tid = volume_driver._find_tid(
|
||||
'iqn.2010-10.org.openstack.baremetal:1000001-dev.vdc')
|
||||
self.assertEqual(1000001, tid)
|
||||
|
||||
def test_find_tid_not_found(self):
|
||||
tid = volume_driver._find_tid(
|
||||
'iqn.2010-10.org.openstack.baremetal:1000002-dev.vdc')
|
||||
self.assertIsNone(tid)
|
||||
|
||||
def test_get_iqn(self):
|
||||
self.flags(iscsi_iqn_prefix='iqn.2012-12.a.b', group='baremetal')
|
||||
iqn = volume_driver._get_iqn('instname', '/dev/vdx')
|
||||
self.assertEqual('iqn.2012-12.a.b:instname-dev-vdx', iqn)
|
||||
|
||||
|
||||
class FakeConf(object):
|
||||
def __init__(self, source_path):
|
||||
self.source_path = source_path
|
||||
|
||||
|
||||
class BareMetalLibVirtVolumeDriverTestCase(test.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(BareMetalLibVirtVolumeDriverTestCase, self).setUp()
|
||||
self.flags(volume_drivers=[
|
||||
'fake=nova.virt.libvirt.volume.LibvirtFakeVolumeDriver',
|
||||
'fake2=nova.virt.libvirt.volume.LibvirtFakeVolumeDriver',
|
||||
], group='libvirt')
|
||||
self.driver = volume_driver.LibvirtVolumeDriver(fake.FakeVirtAPI())
|
||||
self.disk_info = {
|
||||
'dev': 'vdc',
|
||||
'bus': 'baremetal',
|
||||
'type': 'baremetal',
|
||||
}
|
||||
self.connection_info = {'driver_volume_type': 'fake'}
|
||||
self.mount_point = '/dev/vdc'
|
||||
self.mount_device = 'vdc'
|
||||
self.source_path = '/dev/sdx'
|
||||
self.instance = {'uuid': '12345678-1234-1234-1234-123467890123456',
|
||||
'name': 'instance-00000001'}
|
||||
self.fixed_ips = [{'address': '10.2.3.4'},
|
||||
{'address': '172.16.17.18'},
|
||||
]
|
||||
self.iqn = 'iqn.fake:instance-00000001-dev-vdc'
|
||||
self.tid = 100
|
||||
|
||||
def test_init_loads_volume_drivers(self):
|
||||
self.assertIsInstance(self.driver.volume_drivers['fake'],
|
||||
libvirt_volume.LibvirtFakeVolumeDriver)
|
||||
self.assertIsInstance(self.driver.volume_drivers['fake2'],
|
||||
libvirt_volume.LibvirtFakeVolumeDriver)
|
||||
self.assertEqual(len(self.driver.volume_drivers), 2)
|
||||
|
||||
def test_fake_connect_volume(self):
|
||||
"""Check connect_volume returns without exceptions."""
|
||||
self.driver._connect_volume(self.connection_info,
|
||||
self.disk_info)
|
||||
|
||||
def test_volume_driver_method_ok(self):
|
||||
fake_driver = self.driver.volume_drivers['fake']
|
||||
self.mox.StubOutWithMock(fake_driver, 'connect_volume')
|
||||
fake_driver.connect_volume(self.connection_info, self.disk_info)
|
||||
self.mox.ReplayAll()
|
||||
self.driver._connect_volume(self.connection_info,
|
||||
self.disk_info)
|
||||
|
||||
def test_volume_driver_method_driver_type_not_found(self):
|
||||
self.connection_info['driver_volume_type'] = 'qwerty'
|
||||
self.assertRaises(exception.VolumeDriverNotFound,
|
||||
self.driver._connect_volume,
|
||||
self.connection_info,
|
||||
self.disk_info)
|
||||
|
||||
def test_publish_iscsi(self):
|
||||
self.mox.StubOutWithMock(volume_driver, '_get_iqn')
|
||||
self.mox.StubOutWithMock(volume_driver, '_get_next_tid')
|
||||
self.mox.StubOutWithMock(volume_driver, '_create_iscsi_export_tgtadm')
|
||||
self.mox.StubOutWithMock(volume_driver, '_allow_iscsi_tgtadm')
|
||||
volume_driver._get_iqn(self.instance['name'], self.mount_point).\
|
||||
AndReturn(self.iqn)
|
||||
volume_driver._get_next_tid().AndReturn(self.tid)
|
||||
volume_driver._create_iscsi_export_tgtadm(self.source_path,
|
||||
self.tid,
|
||||
self.iqn)
|
||||
volume_driver._allow_iscsi_tgtadm(self.tid,
|
||||
self.fixed_ips[0]['address'])
|
||||
volume_driver._allow_iscsi_tgtadm(self.tid,
|
||||
self.fixed_ips[1]['address'])
|
||||
self.mox.ReplayAll()
|
||||
self.driver._publish_iscsi(self.instance,
|
||||
self.mount_point,
|
||||
self.fixed_ips,
|
||||
self.source_path)
|
||||
|
||||
def test_depublish_iscsi_ok(self):
|
||||
self.mox.StubOutWithMock(volume_driver, '_get_iqn')
|
||||
self.mox.StubOutWithMock(volume_driver, '_find_tid')
|
||||
self.mox.StubOutWithMock(volume_driver, '_delete_iscsi_export_tgtadm')
|
||||
volume_driver._get_iqn(self.instance['name'], self.mount_point).\
|
||||
AndReturn(self.iqn)
|
||||
volume_driver._find_tid(self.iqn).AndReturn(self.tid)
|
||||
volume_driver._delete_iscsi_export_tgtadm(self.tid)
|
||||
self.mox.ReplayAll()
|
||||
self.driver._depublish_iscsi(self.instance, self.mount_point)
|
||||
|
||||
def test_depublish_iscsi_do_nothing_if_tid_is_not_found(self):
|
||||
self.mox.StubOutWithMock(volume_driver, '_get_iqn')
|
||||
self.mox.StubOutWithMock(volume_driver, '_find_tid')
|
||||
volume_driver._get_iqn(self.instance['name'], self.mount_point).\
|
||||
AndReturn(self.iqn)
|
||||
volume_driver._find_tid(self.iqn).AndReturn(None)
|
||||
self.mox.ReplayAll()
|
||||
self.driver._depublish_iscsi(self.instance, self.mount_point)
|
||||
|
||||
def test_attach_volume(self):
|
||||
self.mox.StubOutWithMock(volume_driver, '_get_fixed_ips')
|
||||
self.mox.StubOutWithMock(self.driver, '_connect_volume')
|
||||
self.mox.StubOutWithMock(self.driver, '_publish_iscsi')
|
||||
volume_driver._get_fixed_ips(self.instance).AndReturn(self.fixed_ips)
|
||||
self.driver._connect_volume(self.connection_info, self.disk_info).\
|
||||
AndReturn(FakeConf(self.source_path))
|
||||
self.driver._publish_iscsi(self.instance, self.mount_point,
|
||||
self.fixed_ips, self.source_path)
|
||||
self.mox.ReplayAll()
|
||||
self.driver.attach_volume(self.connection_info,
|
||||
self.instance,
|
||||
self.mount_point)
|
||||
|
||||
def test_detach_volume(self):
|
||||
self.mox.StubOutWithMock(volume_driver, '_get_iqn')
|
||||
self.mox.StubOutWithMock(volume_driver, '_find_tid')
|
||||
self.mox.StubOutWithMock(volume_driver, '_delete_iscsi_export_tgtadm')
|
||||
self.mox.StubOutWithMock(self.driver, '_disconnect_volume')
|
||||
volume_driver._get_iqn(self.instance['name'], self.mount_point).\
|
||||
AndReturn(self.iqn)
|
||||
volume_driver._find_tid(self.iqn).AndReturn(self.tid)
|
||||
volume_driver._delete_iscsi_export_tgtadm(self.tid)
|
||||
self.driver._disconnect_volume(self.connection_info,
|
||||
self.mount_device)
|
||||
self.mox.ReplayAll()
|
||||
self.driver.detach_volume(self.connection_info,
|
||||
self.instance,
|
||||
self.mount_point)
|
||||
@@ -1,17 +0,0 @@
|
||||
# Copyright (c) 2012 NTT DOCOMO, INC.
|
||||
# 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.
|
||||
from nova.virt.baremetal import driver
|
||||
|
||||
BareMetalDriver = driver.BareMetalDriver
|
||||
@@ -1,34 +0,0 @@
|
||||
# Copyright (c) 2012 NTT DOCOMO, INC.
|
||||
# Copyright 2010 OpenStack Foundation
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Possible baremetal node states for instances.
|
||||
|
||||
Compute instance baremetal states represent the state of an instance as it
|
||||
pertains to a user or administrator. When combined with task states
|
||||
(task_states.py), a better picture can be formed regarding the instance's
|
||||
health.
|
||||
|
||||
"""
|
||||
|
||||
ACTIVE = 'active'
|
||||
BUILDING = 'building'
|
||||
DEPLOYING = 'deploying'
|
||||
DEPLOYFAIL = 'deploy failed'
|
||||
DEPLOYDONE = 'deploy complete'
|
||||
DELETED = 'deleted'
|
||||
ERROR = 'error'
|
||||
PREPARED = 'prepared'
|
||||
@@ -1,84 +0,0 @@
|
||||
# Copyright (c) 2012 NTT DOCOMO, INC.
|
||||
# Copyright (c) 2011 University of Southern California / ISI
|
||||
# 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.
|
||||
|
||||
from nova.virt.baremetal import baremetal_states
|
||||
|
||||
|
||||
class NodeDriver(object):
|
||||
|
||||
def __init__(self, virtapi):
|
||||
self.virtapi = virtapi
|
||||
|
||||
def cache_images(self, context, node, instance, **kwargs):
|
||||
raise NotImplementedError()
|
||||
|
||||
def destroy_images(self, context, node, instance):
|
||||
raise NotImplementedError()
|
||||
|
||||
def activate_bootloader(self, context, node, instance, **kwargs):
|
||||
raise NotImplementedError()
|
||||
|
||||
def deactivate_bootloader(self, context, node, instance):
|
||||
raise NotImplementedError()
|
||||
|
||||
def activate_node(self, context, node, instance):
|
||||
"""For operations after power on."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def deactivate_node(self, context, node, instance):
|
||||
"""For operations before power off."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_console_output(self, node, instance):
|
||||
raise NotImplementedError()
|
||||
|
||||
def dhcp_options_for_instance(self, instance):
|
||||
"""Optional override to return the DHCP options to use for instance.
|
||||
|
||||
If no DHCP options are needed, this should not be overridden or None
|
||||
should be returned.
|
||||
"""
|
||||
return None
|
||||
|
||||
|
||||
class PowerManager(object):
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.state = baremetal_states.DELETED
|
||||
pass
|
||||
|
||||
def activate_node(self):
|
||||
self.state = baremetal_states.ACTIVE
|
||||
return self.state
|
||||
|
||||
def reboot_node(self):
|
||||
self.state = baremetal_states.ACTIVE
|
||||
return self.state
|
||||
|
||||
def deactivate_node(self):
|
||||
self.state = baremetal_states.DELETED
|
||||
return self.state
|
||||
|
||||
def is_power_on(self):
|
||||
"""Returns True or False according as the node's power state."""
|
||||
return True
|
||||
|
||||
# TODO(NTTdocomo): split out console methods to its own class
|
||||
def start_console(self):
|
||||
pass
|
||||
|
||||
def stop_console(self):
|
||||
pass
|
||||
@@ -1,66 +0,0 @@
|
||||
# Copyright 2013 IBM Corp.
|
||||
#
|
||||
# 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.
|
||||
|
||||
import paramiko
|
||||
|
||||
from nova import exception
|
||||
from nova.i18n import _
|
||||
from nova.openstack.common import log as logging
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
CONNECTION_TIMEOUT = 60
|
||||
|
||||
|
||||
class ConnectionFailed(exception.NovaException):
|
||||
msg_fmt = _('Connection failed')
|
||||
|
||||
|
||||
class Connection(object):
|
||||
|
||||
def __init__(self, host, username, password, port=22, keyfile=None):
|
||||
self.host = host
|
||||
self.username = username
|
||||
self.password = password
|
||||
self.port = port
|
||||
self.keyfile = keyfile
|
||||
|
||||
|
||||
def ssh_connect(connection):
|
||||
"""Method to connect to remote system using ssh protocol.
|
||||
|
||||
:param connection: a Connection object.
|
||||
:returns: paramiko.SSHClient -- an active ssh connection.
|
||||
:raises: ConnectionFailed
|
||||
"""
|
||||
try:
|
||||
ssh = paramiko.SSHClient()
|
||||
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
ssh.connect(connection.host,
|
||||
username=connection.username,
|
||||
password=connection.password,
|
||||
port=connection.port,
|
||||
key_filename=connection.keyfile,
|
||||
timeout=CONNECTION_TIMEOUT)
|
||||
|
||||
LOG.debug("SSH connection with %s established successfully." %
|
||||
connection.host)
|
||||
|
||||
# send TCP keepalive packets every 20 seconds
|
||||
ssh.get_transport().set_keepalive(20)
|
||||
|
||||
return ssh
|
||||
except Exception:
|
||||
LOG.exception(_('Connection error'))
|
||||
raise ConnectionFailed()
|
||||
@@ -1,16 +0,0 @@
|
||||
# Copyright (c) 2012 NTT DOCOMO, INC.
|
||||
# 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.
|
||||
|
||||
from nova.virt.baremetal.db.api import * # noqa
|
||||
@@ -1,148 +0,0 @@
|
||||
# Copyright (c) 2012 NTT DOCOMO, INC.
|
||||
# Copyright (c) 2011 X.commerce, a business unit of eBay Inc.
|
||||
# Copyright 2010 United States Government as represented by the
|
||||
# Administrator of the National Aeronautics and Space Administration.
|
||||
# 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.
|
||||
|
||||
"""Defines interface for DB access.
|
||||
|
||||
The underlying driver is loaded as a :class:`LazyPluggable`.
|
||||
|
||||
Functions in this module are imported into the nova.virt.baremetal.db
|
||||
namespace. Call these functions from nova.virt.baremetal.db namespace, not
|
||||
the nova.virt.baremetal.db.api namespace.
|
||||
|
||||
All functions in this module return objects that implement a dictionary-like
|
||||
interface. Currently, many of these objects are sqlalchemy objects that
|
||||
implement a dictionary interface. However, a future goal is to have all of
|
||||
these objects be simple dictionaries.
|
||||
|
||||
|
||||
**Related Flags**
|
||||
|
||||
:baremetal_db_backend: string to lookup in the list of LazyPluggable backends.
|
||||
`sqlalchemy` is the only supported backend right now.
|
||||
|
||||
:[BAREMETAL] sql_connection: string specifying the sqlalchemy connection to
|
||||
use, like: `sqlite:///var/lib/nova/nova.sqlite`.
|
||||
|
||||
"""
|
||||
|
||||
from oslo.config import cfg
|
||||
|
||||
from nova import utils
|
||||
|
||||
# NOTE(deva): we can't move baremetal_db_backend into an OptGroup yet
|
||||
# because utils.LazyPluggable doesn't support reading from
|
||||
# option groups. See bug #1093043.
|
||||
db_opts = [
|
||||
cfg.StrOpt('db_backend',
|
||||
default='sqlalchemy',
|
||||
help='The backend to use for bare-metal database'),
|
||||
]
|
||||
|
||||
baremetal_group = cfg.OptGroup(name='baremetal',
|
||||
title='Baremetal Options')
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.register_group(baremetal_group)
|
||||
CONF.register_opts(db_opts, baremetal_group)
|
||||
|
||||
IMPL = utils.LazyPluggable(
|
||||
'db_backend',
|
||||
config_group='baremetal',
|
||||
sqlalchemy='nova.virt.baremetal.db.sqlalchemy.api')
|
||||
|
||||
|
||||
def bm_node_get_all(context, service_host=None):
|
||||
return IMPL.bm_node_get_all(context,
|
||||
service_host=service_host)
|
||||
|
||||
|
||||
def bm_node_get_associated(context, service_host=None):
|
||||
return IMPL.bm_node_get_associated(context,
|
||||
service_host=service_host)
|
||||
|
||||
|
||||
def bm_node_get_unassociated(context, service_host=None):
|
||||
return IMPL.bm_node_get_unassociated(context,
|
||||
service_host=service_host)
|
||||
|
||||
|
||||
def bm_node_find_free(context, service_host=None,
|
||||
memory_mb=None, cpus=None, local_gb=None):
|
||||
return IMPL.bm_node_find_free(context,
|
||||
service_host=service_host,
|
||||
memory_mb=memory_mb,
|
||||
cpus=cpus,
|
||||
local_gb=local_gb)
|
||||
|
||||
|
||||
def bm_node_get(context, bm_node_id):
|
||||
return IMPL.bm_node_get(context, bm_node_id)
|
||||
|
||||
|
||||
def bm_node_get_by_instance_uuid(context, instance_uuid):
|
||||
return IMPL.bm_node_get_by_instance_uuid(context,
|
||||
instance_uuid)
|
||||
|
||||
|
||||
def bm_node_get_by_node_uuid(context, node_uuid):
|
||||
return IMPL.bm_node_get_by_node_uuid(context, node_uuid)
|
||||
|
||||
|
||||
def bm_node_create(context, values):
|
||||
return IMPL.bm_node_create(context, values)
|
||||
|
||||
|
||||
def bm_node_destroy(context, bm_node_id):
|
||||
return IMPL.bm_node_destroy(context, bm_node_id)
|
||||
|
||||
|
||||
def bm_node_update(context, bm_node_id, values):
|
||||
return IMPL.bm_node_update(context, bm_node_id, values)
|
||||
|
||||
|
||||
def bm_node_associate_and_update(context, node_uuid, values):
|
||||
return IMPL.bm_node_associate_and_update(context, node_uuid, values)
|
||||
|
||||
|
||||
def bm_interface_get(context, if_id):
|
||||
return IMPL.bm_interface_get(context, if_id)
|
||||
|
||||
|
||||
def bm_interface_get_all(context):
|
||||
return IMPL.bm_interface_get_all(context)
|
||||
|
||||
|
||||
def bm_interface_destroy(context, if_id):
|
||||
return IMPL.bm_interface_destroy(context, if_id)
|
||||
|
||||
|
||||
def bm_interface_create(context, bm_node_id, address, datapath_id, port_no):
|
||||
return IMPL.bm_interface_create(context, bm_node_id, address,
|
||||
datapath_id, port_no)
|
||||
|
||||
|
||||
def bm_interface_set_vif_uuid(context, if_id, vif_uuid):
|
||||
return IMPL.bm_interface_set_vif_uuid(context, if_id, vif_uuid)
|
||||
|
||||
|
||||
def bm_interface_get_by_vif_uuid(context, vif_uuid):
|
||||
return IMPL.bm_interface_get_by_vif_uuid(context, vif_uuid)
|
||||
|
||||
|
||||
def bm_interface_get_all_by_bm_node_id(context, bm_node_id):
|
||||
return IMPL.bm_interface_get_all_by_bm_node_id(context, bm_node_id)
|
||||
@@ -1,40 +0,0 @@
|
||||
# Copyright 2010 United States Government as represented by the
|
||||
# Administrator of the National Aeronautics and Space Administration.
|
||||
# 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.
|
||||
|
||||
"""Database setup and migration commands."""
|
||||
|
||||
from nova import utils
|
||||
|
||||
|
||||
IMPL = utils.LazyPluggable(
|
||||
'db_backend',
|
||||
config_group='baremetal',
|
||||
sqlalchemy='nova.virt.baremetal.db.sqlalchemy.migration')
|
||||
|
||||
|
||||
def db_sync(version=None):
|
||||
"""Migrate the database to `version` or the most recent version."""
|
||||
return IMPL.db_sync(version=version)
|
||||
|
||||
|
||||
def db_version():
|
||||
"""Display the current database version."""
|
||||
return IMPL.db_version()
|
||||
|
||||
|
||||
def db_initial_version():
|
||||
"""The starting version for the database."""
|
||||
return IMPL.db_initial_version()
|
||||
@@ -1,328 +0,0 @@
|
||||
# Copyright (c) 2012 NTT DOCOMO, INC.
|
||||
# Copyright (c) 2011 X.commerce, a business unit of eBay Inc.
|
||||
# Copyright 2010 United States Government as represented by the
|
||||
# Administrator of the National Aeronautics and Space Administration.
|
||||
# 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.
|
||||
|
||||
"""Implementation of SQLAlchemy backend."""
|
||||
|
||||
import uuid
|
||||
|
||||
from oslo.db import exception as db_exc
|
||||
from oslo.utils import timeutils
|
||||
import six
|
||||
from sqlalchemy.sql.expression import asc
|
||||
from sqlalchemy.sql.expression import literal_column
|
||||
from sqlalchemy.sql import null
|
||||
|
||||
import nova.context
|
||||
from nova.db.sqlalchemy import api as sqlalchemy_api
|
||||
from nova import exception
|
||||
from nova.i18n import _
|
||||
from nova.openstack.common import uuidutils
|
||||
from nova.virt.baremetal.db.sqlalchemy import models
|
||||
from nova.virt.baremetal.db.sqlalchemy import session as db_session
|
||||
|
||||
|
||||
def model_query(context, *args, **kwargs):
|
||||
"""Query helper that accounts for context's `read_deleted` field.
|
||||
|
||||
:param context: context to query under
|
||||
:param session: if present, the session to use
|
||||
:param read_deleted: if present, overrides context's read_deleted field.
|
||||
:param project_only: if present and context is user-type, then restrict
|
||||
query to match the context's project_id.
|
||||
"""
|
||||
session = kwargs.get('session') or db_session.get_session()
|
||||
read_deleted = kwargs.get('read_deleted') or context.read_deleted
|
||||
project_only = kwargs.get('project_only')
|
||||
|
||||
query = session.query(*args)
|
||||
|
||||
if read_deleted == 'no':
|
||||
query = query.filter_by(deleted=False)
|
||||
elif read_deleted == 'yes':
|
||||
pass # omit the filter to include deleted and active
|
||||
elif read_deleted == 'only':
|
||||
query = query.filter_by(deleted=True)
|
||||
else:
|
||||
raise Exception(
|
||||
_("Unrecognized read_deleted value '%s'") % read_deleted)
|
||||
|
||||
if project_only and nova.context.is_user_context(context):
|
||||
query = query.filter_by(project_id=context.project_id)
|
||||
|
||||
return query
|
||||
|
||||
|
||||
def _save(ref, session=None):
|
||||
if not session:
|
||||
session = db_session.get_session()
|
||||
# We must not call ref.save() with session=None, otherwise NovaBase
|
||||
# uses nova-db's session, which cannot access bm-db.
|
||||
ref.save(session=session)
|
||||
|
||||
|
||||
def _build_node_order_by(query):
|
||||
query = query.order_by(asc(models.BareMetalNode.memory_mb))
|
||||
query = query.order_by(asc(models.BareMetalNode.cpus))
|
||||
query = query.order_by(asc(models.BareMetalNode.local_gb))
|
||||
return query
|
||||
|
||||
|
||||
@sqlalchemy_api.require_admin_context
|
||||
def bm_node_get_all(context, service_host=None):
|
||||
query = model_query(context, models.BareMetalNode, read_deleted="no")
|
||||
if service_host:
|
||||
query = query.filter_by(service_host=service_host)
|
||||
return query.all()
|
||||
|
||||
|
||||
@sqlalchemy_api.require_admin_context
|
||||
def bm_node_get_associated(context, service_host=None):
|
||||
query = model_query(context, models.BareMetalNode, read_deleted="no").\
|
||||
filter(models.BareMetalNode.instance_uuid != null())
|
||||
if service_host:
|
||||
query = query.filter_by(service_host=service_host)
|
||||
return query.all()
|
||||
|
||||
|
||||
@sqlalchemy_api.require_admin_context
|
||||
def bm_node_get_unassociated(context, service_host=None):
|
||||
query = model_query(context, models.BareMetalNode, read_deleted="no").\
|
||||
filter(models.BareMetalNode.instance_uuid == null())
|
||||
if service_host:
|
||||
query = query.filter_by(service_host=service_host)
|
||||
return query.all()
|
||||
|
||||
|
||||
@sqlalchemy_api.require_admin_context
|
||||
def bm_node_find_free(context, service_host=None,
|
||||
cpus=None, memory_mb=None, local_gb=None):
|
||||
query = model_query(context, models.BareMetalNode, read_deleted="no")
|
||||
query = query.filter(models.BareMetalNode.instance_uuid == null())
|
||||
if service_host:
|
||||
query = query.filter_by(service_host=service_host)
|
||||
if cpus is not None:
|
||||
query = query.filter(models.BareMetalNode.cpus >= cpus)
|
||||
if memory_mb is not None:
|
||||
query = query.filter(models.BareMetalNode.memory_mb >= memory_mb)
|
||||
if local_gb is not None:
|
||||
query = query.filter(models.BareMetalNode.local_gb >= local_gb)
|
||||
query = _build_node_order_by(query)
|
||||
return query.first()
|
||||
|
||||
|
||||
@sqlalchemy_api.require_admin_context
|
||||
def bm_node_get(context, bm_node_id):
|
||||
# bm_node_id may be passed as a string. Convert to INT to improve DB perf.
|
||||
bm_node_id = int(bm_node_id)
|
||||
result = model_query(context, models.BareMetalNode, read_deleted="no").\
|
||||
filter_by(id=bm_node_id).\
|
||||
first()
|
||||
|
||||
if not result:
|
||||
raise exception.NodeNotFound(node_id=bm_node_id)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@sqlalchemy_api.require_admin_context
|
||||
def bm_node_get_by_instance_uuid(context, instance_uuid):
|
||||
if not uuidutils.is_uuid_like(instance_uuid):
|
||||
raise exception.InstanceNotFound(instance_id=instance_uuid)
|
||||
|
||||
result = model_query(context, models.BareMetalNode, read_deleted="no").\
|
||||
filter_by(instance_uuid=instance_uuid).\
|
||||
first()
|
||||
|
||||
if not result:
|
||||
raise exception.InstanceNotFound(instance_id=instance_uuid)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@sqlalchemy_api.require_admin_context
|
||||
def bm_node_get_by_node_uuid(context, bm_node_uuid):
|
||||
result = model_query(context, models.BareMetalNode, read_deleted="no").\
|
||||
filter_by(uuid=bm_node_uuid).\
|
||||
first()
|
||||
|
||||
if not result:
|
||||
raise exception.NodeNotFoundByUUID(node_uuid=bm_node_uuid)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@sqlalchemy_api.require_admin_context
|
||||
def bm_node_create(context, values):
|
||||
if not values.get('uuid'):
|
||||
values['uuid'] = str(uuid.uuid4())
|
||||
bm_node_ref = models.BareMetalNode()
|
||||
bm_node_ref.update(values)
|
||||
_save(bm_node_ref)
|
||||
return bm_node_ref
|
||||
|
||||
|
||||
@sqlalchemy_api.require_admin_context
|
||||
def bm_node_update(context, bm_node_id, values):
|
||||
rows = model_query(context, models.BareMetalNode, read_deleted="no").\
|
||||
filter_by(id=bm_node_id).\
|
||||
update(values)
|
||||
|
||||
if not rows:
|
||||
raise exception.NodeNotFound(node_id=bm_node_id)
|
||||
|
||||
|
||||
@sqlalchemy_api.require_admin_context
|
||||
def bm_node_associate_and_update(context, node_uuid, values):
|
||||
"""Associate an instance to a node safely
|
||||
|
||||
Associate an instance to a node only if that node is not yet associated.
|
||||
Allow the caller to set any other fields they require in the same
|
||||
operation. For example, this is used to set the node's task_state to
|
||||
BUILDING at the beginning of driver.spawn().
|
||||
|
||||
"""
|
||||
if 'instance_uuid' not in values:
|
||||
raise exception.NovaException(_(
|
||||
"instance_uuid must be supplied to bm_node_associate_and_update"))
|
||||
|
||||
session = db_session.get_session()
|
||||
with session.begin():
|
||||
query = model_query(context, models.BareMetalNode,
|
||||
session=session, read_deleted="no").\
|
||||
filter_by(uuid=node_uuid)
|
||||
|
||||
count = query.filter_by(instance_uuid=None).\
|
||||
update(values, synchronize_session=False)
|
||||
if count != 1:
|
||||
raise exception.NovaException(_(
|
||||
"Failed to associate instance %(i_uuid)s to baremetal node "
|
||||
"%(n_uuid)s.") % {'i_uuid': values['instance_uuid'],
|
||||
'n_uuid': node_uuid})
|
||||
ref = query.first()
|
||||
return ref
|
||||
|
||||
|
||||
@sqlalchemy_api.require_admin_context
|
||||
def bm_node_destroy(context, bm_node_id):
|
||||
# First, delete all interfaces belonging to the node.
|
||||
# Delete physically since these have unique columns.
|
||||
session = db_session.get_session()
|
||||
with session.begin():
|
||||
model_query(context, models.BareMetalInterface, read_deleted="no").\
|
||||
filter_by(bm_node_id=bm_node_id).\
|
||||
delete()
|
||||
rows = model_query(context, models.BareMetalNode, read_deleted="no").\
|
||||
filter_by(id=bm_node_id).\
|
||||
update({'deleted': True,
|
||||
'deleted_at': timeutils.utcnow(),
|
||||
'updated_at': literal_column('updated_at')})
|
||||
|
||||
if not rows:
|
||||
raise exception.NodeNotFound(node_id=bm_node_id)
|
||||
|
||||
|
||||
@sqlalchemy_api.require_admin_context
|
||||
def bm_interface_get(context, if_id):
|
||||
result = model_query(context, models.BareMetalInterface,
|
||||
read_deleted="no").\
|
||||
filter_by(id=if_id).\
|
||||
first()
|
||||
|
||||
if not result:
|
||||
raise exception.NovaException(_("Baremetal interface %s "
|
||||
"not found") % if_id)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@sqlalchemy_api.require_admin_context
|
||||
def bm_interface_get_all(context):
|
||||
query = model_query(context, models.BareMetalInterface,
|
||||
read_deleted="no")
|
||||
return query.all()
|
||||
|
||||
|
||||
@sqlalchemy_api.require_admin_context
|
||||
def bm_interface_destroy(context, if_id):
|
||||
# Delete physically since it has unique columns
|
||||
model_query(context, models.BareMetalInterface, read_deleted="no").\
|
||||
filter_by(id=if_id).\
|
||||
delete()
|
||||
|
||||
|
||||
@sqlalchemy_api.require_admin_context
|
||||
def bm_interface_create(context, bm_node_id, address, datapath_id, port_no):
|
||||
ref = models.BareMetalInterface()
|
||||
ref.bm_node_id = bm_node_id
|
||||
ref.address = address
|
||||
ref.datapath_id = datapath_id
|
||||
ref.port_no = port_no
|
||||
_save(ref)
|
||||
return ref.id
|
||||
|
||||
|
||||
@sqlalchemy_api.require_admin_context
|
||||
def bm_interface_set_vif_uuid(context, if_id, vif_uuid):
|
||||
session = db_session.get_session()
|
||||
with session.begin():
|
||||
bm_interface = model_query(context, models.BareMetalInterface,
|
||||
read_deleted="no", session=session).\
|
||||
filter_by(id=if_id).\
|
||||
with_lockmode('update').\
|
||||
first()
|
||||
if not bm_interface:
|
||||
raise exception.NovaException(_("Baremetal interface %s "
|
||||
"not found") % if_id)
|
||||
|
||||
bm_interface.vif_uuid = vif_uuid
|
||||
try:
|
||||
session.add(bm_interface)
|
||||
session.flush()
|
||||
except db_exc.DBError as e:
|
||||
# TODO(deva): clean up when db layer raises DuplicateKeyError
|
||||
if six.text_type(e).find('IntegrityError') != -1:
|
||||
raise exception.NovaException(_("Baremetal interface %s "
|
||||
"already in use") % vif_uuid)
|
||||
raise
|
||||
|
||||
|
||||
@sqlalchemy_api.require_admin_context
|
||||
def bm_interface_get_by_vif_uuid(context, vif_uuid):
|
||||
result = model_query(context, models.BareMetalInterface,
|
||||
read_deleted="no").\
|
||||
filter_by(vif_uuid=vif_uuid).\
|
||||
first()
|
||||
|
||||
if not result:
|
||||
raise exception.NovaException(_("Baremetal virtual interface %s "
|
||||
"not found") % vif_uuid)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@sqlalchemy_api.require_admin_context
|
||||
def bm_interface_get_all_by_bm_node_id(context, bm_node_id):
|
||||
result = model_query(context, models.BareMetalInterface,
|
||||
read_deleted="no").\
|
||||
filter_by(bm_node_id=bm_node_id).\
|
||||
all()
|
||||
|
||||
if not result:
|
||||
raise exception.NodeNotFound(node_id=bm_node_id)
|
||||
|
||||
return result
|
||||
@@ -1,20 +0,0 @@
|
||||
[db_settings]
|
||||
# Used to identify which repository this database is versioned under.
|
||||
# You can use the name of your project.
|
||||
repository_id=nova_bm
|
||||
|
||||
# The name of the database table used to track the schema version.
|
||||
# This name shouldn't already be used by your project.
|
||||
# If this is changed once a database is under version control, you'll need to
|
||||
# change the table name in each database too.
|
||||
version_table=migrate_version
|
||||
|
||||
# When committing a change script, Migrate will attempt to generate the
|
||||
# sql for all supported databases; normally, if one of them fails - probably
|
||||
# because you don't have that database installed - it is ignored and the
|
||||
# commit continues, perhaps ending successfully.
|
||||
# Databases in this list MUST compile successfully during a commit, or the
|
||||
# entire commit will fail. List the databases your application will actually
|
||||
# be using to ensure your updates to that database work properly.
|
||||
# This must be a list; example: ['postgres','sqlite']
|
||||
required_dbs=[]
|
||||
@@ -1,113 +0,0 @@
|
||||
# Copyright (c) 2012 NTT DOCOMO, INC.
|
||||
# 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.
|
||||
|
||||
from sqlalchemy import Boolean, Column, DateTime
|
||||
from sqlalchemy import Index, Integer, MetaData, String, Table
|
||||
|
||||
|
||||
def upgrade(migrate_engine):
|
||||
meta = MetaData()
|
||||
meta.bind = migrate_engine
|
||||
|
||||
bm_nodes = Table('bm_nodes', meta,
|
||||
Column('created_at', DateTime),
|
||||
Column('updated_at', DateTime),
|
||||
Column('deleted_at', DateTime),
|
||||
Column('deleted', Boolean),
|
||||
Column('id', Integer, primary_key=True, nullable=False),
|
||||
Column('cpus', Integer),
|
||||
Column('memory_mb', Integer),
|
||||
Column('local_gb', Integer),
|
||||
Column('pm_address', String(length=255)),
|
||||
Column('pm_user', String(length=255)),
|
||||
Column('pm_password', String(length=255)),
|
||||
Column('service_host', String(length=255)),
|
||||
Column('prov_mac_address', String(length=255)),
|
||||
Column('instance_uuid', String(length=36)),
|
||||
Column('registration_status', String(length=16)),
|
||||
Column('task_state', String(length=255)),
|
||||
Column('prov_vlan_id', Integer),
|
||||
Column('terminal_port', Integer),
|
||||
mysql_engine='InnoDB',
|
||||
)
|
||||
|
||||
bm_interfaces = Table('bm_interfaces', meta,
|
||||
Column('created_at', DateTime),
|
||||
Column('updated_at', DateTime),
|
||||
Column('deleted_at', DateTime),
|
||||
Column('deleted', Boolean),
|
||||
Column('id', Integer, primary_key=True, nullable=False),
|
||||
Column('bm_node_id', Integer),
|
||||
Column('address', String(length=255), unique=True),
|
||||
Column('datapath_id', String(length=255)),
|
||||
Column('port_no', Integer),
|
||||
Column('vif_uuid', String(length=36), unique=True),
|
||||
mysql_engine='InnoDB',
|
||||
)
|
||||
|
||||
bm_pxe_ips = Table('bm_pxe_ips', meta,
|
||||
Column('created_at', DateTime),
|
||||
Column('updated_at', DateTime),
|
||||
Column('deleted_at', DateTime),
|
||||
Column('deleted', Boolean),
|
||||
Column('id', Integer, primary_key=True, nullable=False),
|
||||
Column('address', String(length=255), unique=True),
|
||||
Column('bm_node_id', Integer),
|
||||
Column('server_address', String(length=255), unique=True),
|
||||
mysql_engine='InnoDB',
|
||||
)
|
||||
|
||||
bm_deployments = Table('bm_deployments', meta,
|
||||
Column('created_at', DateTime),
|
||||
Column('updated_at', DateTime),
|
||||
Column('deleted_at', DateTime),
|
||||
Column('deleted', Boolean),
|
||||
Column('id', Integer, primary_key=True, nullable=False),
|
||||
Column('bm_node_id', Integer),
|
||||
Column('key', String(length=255)),
|
||||
Column('image_path', String(length=255)),
|
||||
Column('pxe_config_path', String(length=255)),
|
||||
Column('root_mb', Integer),
|
||||
Column('swap_mb', Integer),
|
||||
mysql_engine='InnoDB',
|
||||
)
|
||||
|
||||
bm_nodes.create()
|
||||
bm_interfaces.create()
|
||||
bm_pxe_ips.create()
|
||||
bm_deployments.create()
|
||||
|
||||
Index('idx_bm_nodes_service_host_deleted',
|
||||
bm_nodes.c.service_host, bm_nodes.c.deleted)\
|
||||
.create(migrate_engine)
|
||||
Index('idx_bm_nodes_instance_uuid_deleted',
|
||||
bm_nodes.c.instance_uuid, bm_nodes.c.deleted)\
|
||||
.create(migrate_engine)
|
||||
Index('idx_bm_nodes_hmcld',
|
||||
bm_nodes.c.service_host, bm_nodes.c.memory_mb, bm_nodes.c.cpus,
|
||||
bm_nodes.c.local_gb, bm_nodes.c.deleted)\
|
||||
.create(migrate_engine)
|
||||
|
||||
Index('idx_bm_interfaces_bm_node_id_deleted',
|
||||
bm_interfaces.c.bm_node_id, bm_interfaces.c.deleted)\
|
||||
.create(migrate_engine)
|
||||
|
||||
Index('idx_bm_pxe_ips_bm_node_id_deleted',
|
||||
bm_pxe_ips.c.bm_node_id, bm_pxe_ips.c.deleted)\
|
||||
.create(migrate_engine)
|
||||
|
||||
|
||||
def downgrade(migrate_engine):
|
||||
raise NotImplementedError('Downgrade from 001_init is unsupported.')
|
||||
@@ -1,66 +0,0 @@
|
||||
# Copyright 2013 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.
|
||||
|
||||
from sqlalchemy import Column, Index, MetaData, Table
|
||||
from sqlalchemy import Integer, String, DateTime, Boolean
|
||||
|
||||
|
||||
def upgrade(migrate_engine):
|
||||
meta = MetaData()
|
||||
meta.bind = migrate_engine
|
||||
|
||||
bm_nodes = Table('bm_nodes', meta, autoload=True)
|
||||
|
||||
image_path = Column('image_path', String(length=255))
|
||||
pxe_config_path = Column('pxe_config_path', String(length=255))
|
||||
deploy_key = Column('deploy_key', String(length=255))
|
||||
root_mb = Column('root_mb', Integer())
|
||||
swap_mb = Column('swap_mb', Integer())
|
||||
|
||||
for c in [image_path, pxe_config_path, deploy_key, root_mb, swap_mb]:
|
||||
bm_nodes.create_column(c)
|
||||
|
||||
deploy_key_idx = Index('deploy_key_idx', bm_nodes.c.deploy_key)
|
||||
deploy_key_idx.create(migrate_engine)
|
||||
|
||||
bm_deployments = Table('bm_deployments', meta, autoload=True)
|
||||
bm_deployments.drop()
|
||||
|
||||
|
||||
def downgrade(migrate_engine):
|
||||
meta = MetaData()
|
||||
meta.bind = migrate_engine
|
||||
|
||||
bm_nodes = Table('bm_nodes', meta, autoload=True)
|
||||
|
||||
for c in ['image_path', 'pxe_config_path', 'deploy_key', 'root_mb',
|
||||
'swap_mb']:
|
||||
bm_nodes.drop_column(c)
|
||||
|
||||
bm_deployments = Table('bm_deployments', meta,
|
||||
Column('created_at', DateTime),
|
||||
Column('updated_at', DateTime),
|
||||
Column('deleted_at', DateTime),
|
||||
Column('deleted', Boolean),
|
||||
Column('id', Integer, primary_key=True, nullable=False),
|
||||
Column('bm_node_id', Integer),
|
||||
Column('key', String(length=255)),
|
||||
Column('image_path', String(length=255)),
|
||||
Column('pxe_config_path', String(length=255)),
|
||||
Column('root_mb', Integer),
|
||||
Column('swap_mb', Integer),
|
||||
mysql_engine='InnoDB',
|
||||
)
|
||||
bm_deployments.create()
|
||||
@@ -1,37 +0,0 @@
|
||||
# Copyright 2013 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.
|
||||
|
||||
from sqlalchemy import Column, MetaData, String, Table, Index
|
||||
|
||||
|
||||
def upgrade(migrate_engine):
|
||||
meta = MetaData()
|
||||
meta.bind = migrate_engine
|
||||
|
||||
t = Table('bm_nodes', meta, autoload=True)
|
||||
uuid_col = Column('uuid', String(36))
|
||||
t.create_column(uuid_col)
|
||||
|
||||
uuid_ux = Index('uuid_ux', t.c.uuid, unique=True)
|
||||
uuid_ux.create(migrate_engine)
|
||||
|
||||
|
||||
def downgrade(migrate_engine):
|
||||
meta = MetaData()
|
||||
meta.bind = migrate_engine
|
||||
|
||||
t = Table('bm_nodes', meta, autoload=True)
|
||||
|
||||
t.drop_column('uuid')
|
||||
-33
@@ -1,33 +0,0 @@
|
||||
# Copyright 2013 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.
|
||||
|
||||
from sqlalchemy import Column, MetaData, String, Table
|
||||
|
||||
|
||||
def upgrade(migrate_engine):
|
||||
meta = MetaData()
|
||||
meta.bind = migrate_engine
|
||||
|
||||
t = Table('bm_nodes', meta, autoload=True)
|
||||
name_col = Column('instance_name', String(255))
|
||||
t.create_column(name_col)
|
||||
|
||||
|
||||
def downgrade(migrate_engine):
|
||||
meta = MetaData()
|
||||
meta.bind = migrate_engine
|
||||
|
||||
t = Table('bm_nodes', meta, autoload=True)
|
||||
t.drop_column('instance_name')
|
||||
-33
@@ -1,33 +0,0 @@
|
||||
# Copyright (c) 2013 NTT DOCOMO, INC.
|
||||
#
|
||||
# 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.
|
||||
|
||||
from sqlalchemy import Column, String, Integer, MetaData, Table
|
||||
|
||||
|
||||
def upgrade(migrate_engine):
|
||||
meta = MetaData()
|
||||
meta.bind = migrate_engine
|
||||
|
||||
nodes = Table('bm_nodes', meta, autoload=True)
|
||||
nodes.drop_column('prov_vlan_id')
|
||||
nodes.drop_column('registration_status')
|
||||
|
||||
|
||||
def downgrade(migrate_engine):
|
||||
meta = MetaData()
|
||||
meta.bind = migrate_engine
|
||||
|
||||
nodes = Table('bm_nodes', meta, autoload=True)
|
||||
nodes.create_column(Column('prov_vlan_id', Integer))
|
||||
nodes.create_column(Column('registration_status', String(length=16)))
|
||||
@@ -1,87 +0,0 @@
|
||||
# Copyright (c) 2013 NTT DOCOMO, INC.
|
||||
#
|
||||
# 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.
|
||||
|
||||
from oslo.db import exception as db_exc
|
||||
from sqlalchemy import MetaData, Table, exists
|
||||
from sqlalchemy import sql
|
||||
|
||||
from nova.openstack.common import log as logging
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def upgrade(migrate_engine):
|
||||
meta = MetaData()
|
||||
meta.bind = migrate_engine
|
||||
|
||||
nodes = Table('bm_nodes', meta, autoload=True)
|
||||
ifs = Table('bm_interfaces', meta, autoload=True)
|
||||
|
||||
q = sql.select([nodes.c.id, nodes.c.prov_mac_address],
|
||||
from_obj=nodes)
|
||||
|
||||
# Iterate all elements before starting insert since IntegrityError
|
||||
# may disturb the iteration.
|
||||
node_address = {}
|
||||
for node_id, address in q.execute():
|
||||
node_address[node_id] = address
|
||||
|
||||
i = ifs.insert()
|
||||
for node_id, address in node_address.iteritems():
|
||||
try:
|
||||
i.execute({'bm_node_id': node_id, 'address': address})
|
||||
except db_exc.DBError:
|
||||
# TODO(ekudryashova): replace by DBReferenceError when db layer
|
||||
# raise it.
|
||||
# The address is registered in both bm_nodes and bm_interfaces.
|
||||
# It is expected.
|
||||
pass
|
||||
|
||||
|
||||
def downgrade(migrate_engine):
|
||||
meta = MetaData()
|
||||
meta.bind = migrate_engine
|
||||
|
||||
nodes = Table('bm_nodes', meta, autoload=True)
|
||||
ifs = Table('bm_interfaces', meta, autoload=True)
|
||||
|
||||
subq = exists().where(sql.and_(
|
||||
ifs.c.bm_node_id == nodes.c.id,
|
||||
ifs.c.address == nodes.c.prov_mac_address))
|
||||
|
||||
ifs.delete().where(subq).execute()
|
||||
|
||||
# NOTE(arata):
|
||||
# In fact, this downgrade may not return the db to the previous state.
|
||||
# It seems to be not so match a problem, so this is just for memo.
|
||||
#
|
||||
# Think these two state before upgrading:
|
||||
#
|
||||
# (A) address 'x' is duplicate
|
||||
# bm_nodes.prov_mac_address='x'
|
||||
# bm_interfaces.address=['x', 'y']
|
||||
#
|
||||
# (B) no address is duplicate
|
||||
# bm_nodes.prov_mac_address='x'
|
||||
# bm_interfaces.address=['y']
|
||||
#
|
||||
# Upgrading them results in the same state:
|
||||
#
|
||||
# bm_nodes.prov_mac_address='x'
|
||||
# bm_interfaces.address=['x', 'y']
|
||||
#
|
||||
# Downgrading this results in B, even if the actual initial status was A
|
||||
# Of course we can change it to downgrade to B, but then we cannot
|
||||
# downgrade to A; it is an exclusive choice since we do not have
|
||||
# information about the initial state.
|
||||
@@ -1,34 +0,0 @@
|
||||
# Copyright (c) 2013 NTT DOCOMO, INC.
|
||||
#
|
||||
# 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.
|
||||
|
||||
from sqlalchemy import Column, MetaData, String, Table
|
||||
|
||||
|
||||
def upgrade(migrate_engine):
|
||||
meta = MetaData()
|
||||
meta.bind = migrate_engine
|
||||
|
||||
nodes = Table('bm_nodes', meta, autoload=True)
|
||||
nodes.drop_column('prov_mac_address')
|
||||
|
||||
|
||||
def downgrade(migrate_engine):
|
||||
meta = MetaData()
|
||||
meta.bind = migrate_engine
|
||||
|
||||
nodes = Table('bm_nodes', meta, autoload=True)
|
||||
nodes.create_column(Column('prov_mac_address', String(length=255)))
|
||||
|
||||
# NOTE(arata): The values held by prov_mac_address are lost in upgrade.
|
||||
# So downgrade has no other choice but to set the column to NULL.
|
||||
-59
@@ -1,59 +0,0 @@
|
||||
# Copyright 2013 Mirantis Inc.
|
||||
# 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.
|
||||
|
||||
from sqlalchemy import Boolean
|
||||
from sqlalchemy import Column
|
||||
from sqlalchemy import DateTime
|
||||
from sqlalchemy import Index
|
||||
from sqlalchemy import Integer
|
||||
from sqlalchemy import MetaData
|
||||
from sqlalchemy import String
|
||||
from sqlalchemy import Table
|
||||
|
||||
|
||||
table_name = 'bm_pxe_ips'
|
||||
|
||||
|
||||
def upgrade(migrate_engine):
|
||||
meta = MetaData()
|
||||
meta.bind = migrate_engine
|
||||
|
||||
table = Table(table_name, meta, autoload=True)
|
||||
table.drop()
|
||||
|
||||
|
||||
def downgrade(migrate_engine):
|
||||
meta = MetaData()
|
||||
meta.bind = migrate_engine
|
||||
|
||||
bm_pxe_ips = Table(table_name, meta,
|
||||
Column('created_at', DateTime),
|
||||
Column('updated_at', DateTime),
|
||||
Column('deleted_at', DateTime),
|
||||
Column('deleted', Boolean),
|
||||
Column('id', Integer, primary_key=True, nullable=False),
|
||||
Column('address', String(length=255), unique=True),
|
||||
Column('bm_node_id', Integer),
|
||||
Column('server_address',
|
||||
String(length=255), unique=True),
|
||||
mysql_engine='InnoDB',
|
||||
)
|
||||
bm_pxe_ips.create()
|
||||
|
||||
Index(
|
||||
'idx_bm_pxe_ips_bm_node_id_deleted',
|
||||
bm_pxe_ips.c.bm_node_id,
|
||||
bm_pxe_ips.c.deleted
|
||||
).create(migrate_engine)
|
||||
-34
@@ -1,34 +0,0 @@
|
||||
# Copyright 2013 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.
|
||||
|
||||
from sqlalchemy import Column, MetaData, Integer, Table
|
||||
|
||||
|
||||
def upgrade(migrate_engine):
|
||||
meta = MetaData()
|
||||
meta.bind = migrate_engine
|
||||
|
||||
t = Table('bm_nodes', meta, autoload=True)
|
||||
ephemeral_mb_col = Column('ephemeral_mb', Integer)
|
||||
t.create_column(ephemeral_mb_col)
|
||||
|
||||
|
||||
def downgrade(migrate_engine):
|
||||
meta = MetaData()
|
||||
meta.bind = migrate_engine
|
||||
|
||||
t = Table('bm_nodes', meta, autoload=True)
|
||||
|
||||
t.drop_column('ephemeral_mb')
|
||||
@@ -1,53 +0,0 @@
|
||||
# Copyright 2013 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.
|
||||
|
||||
from sqlalchemy import Column, MetaData, Boolean, Table
|
||||
from sqlalchemy.sql import expression
|
||||
|
||||
|
||||
COLUMN_NAME = 'preserve_ephemeral'
|
||||
TABLE_NAME = 'bm_nodes'
|
||||
|
||||
|
||||
def upgrade(migrate_engine):
|
||||
meta = MetaData()
|
||||
meta.bind = migrate_engine
|
||||
|
||||
t = Table(TABLE_NAME, meta, autoload=True)
|
||||
default = (expression.text('0') if migrate_engine.name == 'sqlite'
|
||||
else expression.text('false'))
|
||||
preserve_ephemeral_col = Column(COLUMN_NAME, Boolean,
|
||||
server_default=default)
|
||||
t.create_column(preserve_ephemeral_col)
|
||||
|
||||
|
||||
def downgrade(migrate_engine):
|
||||
meta = MetaData()
|
||||
meta.bind = migrate_engine
|
||||
|
||||
t = Table(TABLE_NAME, meta, autoload=True)
|
||||
# NOTE(rpodolyaka): SQLite doesn't have native BOOLEAN type, so it's
|
||||
# emulated by adding a CHECK constraint. We must
|
||||
# explicitly omit that constraint here so we don't
|
||||
# receive 'no such column' error when dropping the
|
||||
# column
|
||||
if migrate_engine.name == 'sqlite':
|
||||
t.constraints = set([
|
||||
c
|
||||
for c in t.constraints
|
||||
if not (hasattr(c, 'sqltext') and COLUMN_NAME in str(c.sqltext))
|
||||
])
|
||||
|
||||
t.drop_column(COLUMN_NAME)
|
||||
@@ -1,86 +0,0 @@
|
||||
# Copyright 2010 United States Government as represented by the
|
||||
# Administrator of the National Aeronautics and Space Administration.
|
||||
# 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.
|
||||
|
||||
import os
|
||||
|
||||
from migrate import exceptions as versioning_exceptions
|
||||
from migrate.versioning import api as versioning_api
|
||||
from migrate.versioning.repository import Repository
|
||||
import sqlalchemy
|
||||
|
||||
from nova import exception
|
||||
from nova.i18n import _
|
||||
from nova.virt.baremetal.db.sqlalchemy import session
|
||||
|
||||
INIT_VERSION = 0
|
||||
_REPOSITORY = None
|
||||
|
||||
|
||||
def db_sync(version=None):
|
||||
if version is not None:
|
||||
try:
|
||||
version = int(version)
|
||||
except ValueError:
|
||||
raise exception.NovaException(_("version should be an integer"))
|
||||
|
||||
current_version = db_version()
|
||||
repository = _find_migrate_repo()
|
||||
if version is None or version > current_version:
|
||||
return versioning_api.upgrade(session.get_engine(), repository,
|
||||
version)
|
||||
else:
|
||||
return versioning_api.downgrade(session.get_engine(), repository,
|
||||
version)
|
||||
|
||||
|
||||
def db_version():
|
||||
repository = _find_migrate_repo()
|
||||
try:
|
||||
return versioning_api.db_version(session.get_engine(), repository)
|
||||
except versioning_exceptions.DatabaseNotControlledError:
|
||||
meta = sqlalchemy.MetaData()
|
||||
engine = session.get_engine()
|
||||
meta.reflect(bind=engine)
|
||||
tables = meta.tables
|
||||
if len(tables) == 0:
|
||||
db_version_control(INIT_VERSION)
|
||||
return versioning_api.db_version(session.get_engine(), repository)
|
||||
else:
|
||||
# Some pre-Essex DB's may not be version controlled.
|
||||
# Require them to upgrade using Essex first.
|
||||
raise exception.NovaException(
|
||||
_("Upgrade DB using Essex release first."))
|
||||
|
||||
|
||||
def db_initial_version():
|
||||
return INIT_VERSION
|
||||
|
||||
|
||||
def db_version_control(version=None):
|
||||
repository = _find_migrate_repo()
|
||||
versioning_api.version_control(session.get_engine(), repository, version)
|
||||
return version
|
||||
|
||||
|
||||
def _find_migrate_repo():
|
||||
"""Get the path for the migrate repository."""
|
||||
global _REPOSITORY
|
||||
path = os.path.join(os.path.abspath(os.path.dirname(__file__)),
|
||||
'migrate_repo')
|
||||
assert os.path.exists(path)
|
||||
if _REPOSITORY is None:
|
||||
_REPOSITORY = Repository(path)
|
||||
return _REPOSITORY
|
||||
@@ -1,67 +0,0 @@
|
||||
# Copyright (c) 2012 NTT DOCOMO, INC.
|
||||
# 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.
|
||||
|
||||
"""
|
||||
SQLAlchemy models for baremetal data.
|
||||
"""
|
||||
|
||||
from sqlalchemy import Column, Boolean, Integer, String
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy import ForeignKey, Text
|
||||
|
||||
from nova.db.sqlalchemy import models
|
||||
|
||||
|
||||
BASE = declarative_base()
|
||||
|
||||
|
||||
class BareMetalNode(BASE, models.NovaBase):
|
||||
"""Represents a bare metal node."""
|
||||
|
||||
__tablename__ = 'bm_nodes'
|
||||
id = Column(Integer, primary_key=True)
|
||||
deleted = Column(Boolean, default=False)
|
||||
uuid = Column(String(36))
|
||||
service_host = Column(String(255))
|
||||
instance_uuid = Column(String(36))
|
||||
instance_name = Column(String(255))
|
||||
cpus = Column(Integer)
|
||||
memory_mb = Column(Integer)
|
||||
local_gb = Column(Integer)
|
||||
preserve_ephemeral = Column(Boolean)
|
||||
pm_address = Column(Text)
|
||||
pm_user = Column(Text)
|
||||
pm_password = Column(Text)
|
||||
task_state = Column(String(255))
|
||||
terminal_port = Column(Integer)
|
||||
image_path = Column(String(255))
|
||||
pxe_config_path = Column(String(255))
|
||||
deploy_key = Column(String(255))
|
||||
# root_mb, swap_mb and ephemeral_mb are cached flavor values for the
|
||||
# current deployment not attributes of the node.
|
||||
root_mb = Column(Integer)
|
||||
swap_mb = Column(Integer)
|
||||
ephemeral_mb = Column(Integer)
|
||||
|
||||
|
||||
class BareMetalInterface(BASE, models.NovaBase):
|
||||
__tablename__ = 'bm_interfaces'
|
||||
id = Column(Integer, primary_key=True)
|
||||
deleted = Column(Boolean, default=False)
|
||||
bm_node_id = Column(Integer, ForeignKey('bm_nodes.id'))
|
||||
address = Column(String(255), unique=True)
|
||||
datapath_id = Column(String(255))
|
||||
port_no = Column(Integer)
|
||||
vif_uuid = Column(String(36), unique=True)
|
||||
@@ -1,66 +0,0 @@
|
||||
# Copyright (c) 2012 NTT DOCOMO, INC.
|
||||
# Copyright 2010 United States Government as represented by the
|
||||
# Administrator of the National Aeronautics and Space Administration.
|
||||
# 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.
|
||||
|
||||
"""Session Handling for SQLAlchemy backend."""
|
||||
|
||||
from oslo.config import cfg
|
||||
from oslo.db.sqlalchemy import session as db_session
|
||||
|
||||
from nova import paths
|
||||
|
||||
opts = [
|
||||
cfg.StrOpt('sql_connection',
|
||||
default=('sqlite:///' +
|
||||
paths.state_path_def('baremetal_nova.sqlite')),
|
||||
help='The SQLAlchemy connection string used to connect to the '
|
||||
'bare-metal database'),
|
||||
]
|
||||
|
||||
baremetal_group = cfg.OptGroup(name='baremetal',
|
||||
title='Baremetal Options')
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.register_group(baremetal_group)
|
||||
CONF.register_opts(opts, baremetal_group)
|
||||
|
||||
|
||||
_FACADE = None
|
||||
|
||||
|
||||
def _create_facade_lazily():
|
||||
global _FACADE
|
||||
|
||||
if _FACADE is None:
|
||||
_FACADE = db_session.EngineFacade(CONF.baremetal.sql_connection,
|
||||
**dict(CONF.database.iteritems()))
|
||||
|
||||
return _FACADE
|
||||
|
||||
|
||||
def get_session(autocommit=True, expire_on_commit=False, **kwargs):
|
||||
"""Return a SQLAlchemy session."""
|
||||
|
||||
facade = _create_facade_lazily()
|
||||
return facade.get_session(autocommit=autocommit,
|
||||
expire_on_commit=expire_on_commit, **kwargs)
|
||||
|
||||
|
||||
def get_engine():
|
||||
"""Return a SQLAlchemy engine."""
|
||||
|
||||
facade = _create_facade_lazily()
|
||||
return facade.get_engine()
|
||||
@@ -1,69 +0,0 @@
|
||||
General Bare-metal Provisioning README
|
||||
======================================
|
||||
|
||||
:Authors:
|
||||
[USC/ISI] Mikyung Kang <mkkang@isi.edu>, David Kang <dkang@isi.edu>
|
||||
|
||||
[NTT DOCOMO] Ken Igarashi <igarashik@nttdocomo.co.jp>
|
||||
|
||||
[VirtualTech Japan Inc.] Arata Notsu <notsu@virtualtech.jp>
|
||||
:Date: 2012-08-02
|
||||
:Version: 2012.8
|
||||
:Wiki: http://wiki.openstack.org/GeneralBareMetalProvisioningFramework
|
||||
|
||||
Code changes
|
||||
------------
|
||||
|
||||
::
|
||||
|
||||
nova/nova/virt/baremetal/*
|
||||
nova/nova/virt/driver.py
|
||||
nova/nova/tests/baremetal/*
|
||||
nova/nova/tests/compute/test_compute.py
|
||||
nova/nova/compute/manager.py
|
||||
nova/nova/compute/resource_tracker.py
|
||||
nova/nova/manager.py
|
||||
nova/nova/scheduler/driver.py
|
||||
nova/nova/scheduler/filter_scheduler.py
|
||||
nova/nova/scheduler/host_manager.py
|
||||
nova/nova/scheduler/baremetal_host_manager.py
|
||||
nova/bin/bm_deploy_server
|
||||
nova/bin/nova-bm-manage
|
||||
|
||||
Additional setting for bare-metal provisioning [nova.conf]
|
||||
----------------------------------------------------------
|
||||
|
||||
::
|
||||
|
||||
# baremetal database connection
|
||||
baremetal_sql_connection = mysql://$ID:$Password@$IP/nova_bm
|
||||
|
||||
# baremetal compute driver
|
||||
compute_driver = nova.virt.baremetal.driver.BareMetalDriver
|
||||
baremetal_driver = {nova.virt.baremetal.tilera.Tilera | nova.virt.baremetal.pxe.PXE}
|
||||
power_manager = {nova.virt.baremetal.tilera_pdu.Pdu | nova.virt.baremetal.ipmi.Ipmi}
|
||||
|
||||
# flavor_extra_specs this baremetal compute
|
||||
flavor_extra_specs = cpu_arch:{tilepro64 | x86_64 | arm}
|
||||
|
||||
# TFTP root
|
||||
baremetal_tftp_root = /tftpboot
|
||||
|
||||
# baremetal scheduler host manager
|
||||
scheduler_host_manager = nova.scheduler.baremetal_host_manager.BaremetalHostManager
|
||||
|
||||
|
||||
Non-PXE (Tilera) Bare-metal Provisioning
|
||||
----------------------------------------
|
||||
|
||||
1. tilera-bm-instance-creation.rst
|
||||
|
||||
2. tilera-bm-installation.rst
|
||||
|
||||
PXE Bare-metal Provisioning
|
||||
---------------------------
|
||||
|
||||
1. pxe-bm-instance-creation.rst
|
||||
|
||||
2. pxe-bm-installation.rst
|
||||
|
||||
@@ -1,571 +0,0 @@
|
||||
# coding=utf-8
|
||||
#
|
||||
# Copyright (c) 2012 NTT DOCOMO, INC
|
||||
# Copyright (c) 2011 University of Southern California / ISI
|
||||
# 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.
|
||||
|
||||
"""
|
||||
A driver for Bare-metal platform.
|
||||
"""
|
||||
|
||||
from oslo.config import cfg
|
||||
from oslo.utils import excutils
|
||||
from oslo.utils import importutils
|
||||
|
||||
from nova.compute import arch
|
||||
from nova.compute import flavors
|
||||
from nova.compute import hvtype
|
||||
from nova.compute import power_state
|
||||
from nova.compute import task_states
|
||||
from nova.compute import vm_mode
|
||||
from nova import context as nova_context
|
||||
from nova import exception
|
||||
from nova.i18n import _
|
||||
from nova.i18n import _LW
|
||||
from nova.openstack.common import jsonutils
|
||||
from nova.openstack.common import lockutils
|
||||
from nova.openstack.common import log as logging
|
||||
from nova.virt.baremetal import baremetal_states
|
||||
from nova.virt.baremetal import db
|
||||
from nova.virt import driver
|
||||
from nova.virt import firewall
|
||||
from nova.virt.libvirt import imagecache
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
opts = [
|
||||
cfg.StrOpt('vif_driver',
|
||||
default='nova.virt.baremetal.vif_driver.BareMetalVIFDriver',
|
||||
help='Baremetal VIF driver.'),
|
||||
cfg.StrOpt('volume_driver',
|
||||
default='nova.virt.baremetal.volume_driver.LibvirtVolumeDriver',
|
||||
help='Baremetal volume driver.'),
|
||||
cfg.ListOpt('flavor_extra_specs',
|
||||
default=[],
|
||||
help='A list of additional capabilities corresponding to '
|
||||
'flavor_extra_specs for this compute '
|
||||
'host to advertise. Valid entries are name=value, pairs '
|
||||
'For example, "key1:val1, key2:val2"'),
|
||||
cfg.StrOpt('driver',
|
||||
default='nova.virt.baremetal.pxe.PXE',
|
||||
help='Baremetal driver back-end (pxe or tilera)'),
|
||||
cfg.StrOpt('power_manager',
|
||||
default='nova.virt.baremetal.ipmi.IPMI',
|
||||
help='Baremetal power management method'),
|
||||
cfg.StrOpt('tftp_root',
|
||||
default='/tftpboot',
|
||||
help='Baremetal compute node\'s tftp root path'),
|
||||
]
|
||||
|
||||
baremetal_group = cfg.OptGroup(name='baremetal',
|
||||
title='Baremetal Options')
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.register_group(baremetal_group)
|
||||
CONF.register_opts(opts, baremetal_group)
|
||||
CONF.import_opt('host', 'nova.netconf')
|
||||
CONF.import_opt('my_ip', 'nova.netconf')
|
||||
|
||||
DEFAULT_FIREWALL_DRIVER = "%s.%s" % (
|
||||
firewall.__name__,
|
||||
firewall.NoopFirewallDriver.__name__)
|
||||
|
||||
|
||||
def _get_baremetal_node_by_instance_uuid(instance_uuid):
|
||||
ctx = nova_context.get_admin_context()
|
||||
node = db.bm_node_get_by_instance_uuid(ctx, instance_uuid)
|
||||
if node['service_host'] != CONF.host:
|
||||
LOG.error(_("Request for baremetal node %s "
|
||||
"sent to wrong service host") % instance_uuid)
|
||||
raise exception.InstanceNotFound(instance_id=instance_uuid)
|
||||
return node
|
||||
|
||||
|
||||
def _update_state(context, node, instance, state):
|
||||
"""Update the node state in baremetal DB
|
||||
|
||||
If instance is not supplied, reset the instance_uuid field for this node.
|
||||
|
||||
"""
|
||||
values = {'task_state': state}
|
||||
if not instance:
|
||||
values['instance_uuid'] = None
|
||||
values['instance_name'] = None
|
||||
db.bm_node_update(context, node['id'], values)
|
||||
|
||||
|
||||
def get_power_manager(**kwargs):
|
||||
cls = importutils.import_class(CONF.baremetal.power_manager)
|
||||
return cls(**kwargs)
|
||||
|
||||
|
||||
class BareMetalDriver(driver.ComputeDriver):
|
||||
"""BareMetal hypervisor driver."""
|
||||
|
||||
capabilities = {
|
||||
"has_imagecache": True,
|
||||
"supports_recreate": False,
|
||||
}
|
||||
|
||||
def __init__(self, virtapi, read_only=False):
|
||||
super(BareMetalDriver, self).__init__(virtapi)
|
||||
|
||||
self.driver = importutils.import_object(
|
||||
CONF.baremetal.driver, virtapi)
|
||||
self.vif_driver = importutils.import_object(
|
||||
CONF.baremetal.vif_driver)
|
||||
self.firewall_driver = firewall.load_driver(
|
||||
default=DEFAULT_FIREWALL_DRIVER)
|
||||
self.volume_driver = importutils.import_object(
|
||||
CONF.baremetal.volume_driver, virtapi)
|
||||
self.image_cache_manager = imagecache.ImageCacheManager()
|
||||
|
||||
extra_specs = {}
|
||||
extra_specs["baremetal_driver"] = CONF.baremetal.driver
|
||||
for pair in CONF.baremetal.flavor_extra_specs:
|
||||
keyval = pair.split(':', 1)
|
||||
keyval[0] = keyval[0].strip()
|
||||
keyval[1] = keyval[1].strip()
|
||||
extra_specs[keyval[0]] = keyval[1]
|
||||
|
||||
self.extra_specs = extra_specs
|
||||
|
||||
if 'cpu_arch' not in extra_specs:
|
||||
LOG.info(
|
||||
_('cpu_arch is not found in flavor_extra_specs'))
|
||||
self.supported_instances = []
|
||||
else:
|
||||
self.supported_instances = [(
|
||||
arch.canonicalize(extra_specs['cpu_arch']),
|
||||
hvtype.BAREMETAL,
|
||||
vm_mode.HVM
|
||||
), ]
|
||||
|
||||
@classmethod
|
||||
def instance(cls):
|
||||
if not hasattr(cls, '_instance'):
|
||||
cls._instance = cls()
|
||||
return cls._instance
|
||||
|
||||
def init_host(self, host):
|
||||
LOG.warn(_LW('The baremetal driver is deprecated in Juno and will be '
|
||||
'removed before the next release. Please plan to '
|
||||
'transition to Ironic as soon as possible. See '
|
||||
'https://wiki.openstack.org/wiki/Ironic for more '
|
||||
'information'))
|
||||
|
||||
def get_hypervisor_type(self):
|
||||
return 'baremetal'
|
||||
|
||||
def get_hypervisor_version(self):
|
||||
# TODO(deva): define the version properly elsewhere
|
||||
return 1
|
||||
|
||||
def list_instances(self):
|
||||
l = []
|
||||
context = nova_context.get_admin_context()
|
||||
for node in db.bm_node_get_associated(context, service_host=CONF.host):
|
||||
l.append(node['instance_name'])
|
||||
return l
|
||||
|
||||
def _require_node(self, instance):
|
||||
"""Get a node's uuid out of a manager instance dict.
|
||||
|
||||
The compute manager is meant to know the node uuid, so missing uuid
|
||||
a significant issue - it may mean we've been passed someone elses data.
|
||||
"""
|
||||
node_uuid = instance.get('node')
|
||||
if not node_uuid:
|
||||
raise exception.NovaException(_(
|
||||
"Baremetal node id not supplied to driver for %r")
|
||||
% instance['uuid'])
|
||||
return node_uuid
|
||||
|
||||
def _attach_block_devices(self, instance, block_device_info):
|
||||
block_device_mapping = driver.\
|
||||
block_device_info_get_mapping(block_device_info)
|
||||
for vol in block_device_mapping:
|
||||
connection_info = vol['connection_info']
|
||||
mountpoint = vol['mount_device']
|
||||
self.attach_volume(None,
|
||||
connection_info, instance, mountpoint)
|
||||
|
||||
def _detach_block_devices(self, instance, block_device_info):
|
||||
block_device_mapping = driver.\
|
||||
block_device_info_get_mapping(block_device_info)
|
||||
for vol in block_device_mapping:
|
||||
connection_info = vol['connection_info']
|
||||
mountpoint = vol['mount_device']
|
||||
self.detach_volume(
|
||||
connection_info, instance, mountpoint)
|
||||
|
||||
def _start_firewall(self, instance, network_info):
|
||||
self.firewall_driver.setup_basic_filtering(
|
||||
instance, network_info)
|
||||
self.firewall_driver.prepare_instance_filter(
|
||||
instance, network_info)
|
||||
self.firewall_driver.apply_instance_filter(
|
||||
instance, network_info)
|
||||
|
||||
def _stop_firewall(self, instance, network_info):
|
||||
self.firewall_driver.unfilter_instance(
|
||||
instance, network_info)
|
||||
|
||||
def deallocate_networks_on_reschedule(self, instance):
|
||||
return True
|
||||
|
||||
def macs_for_instance(self, instance):
|
||||
context = nova_context.get_admin_context()
|
||||
node_uuid = self._require_node(instance)
|
||||
node = db.bm_node_get_by_node_uuid(context, node_uuid)
|
||||
ifaces = db.bm_interface_get_all_by_bm_node_id(context, node['id'])
|
||||
return set(iface['address'] for iface in ifaces)
|
||||
|
||||
def _set_default_ephemeral_device(self, instance):
|
||||
flavor = flavors.extract_flavor(instance)
|
||||
if flavor['ephemeral_gb']:
|
||||
instance.default_ephemeral_device = '/dev/sda1'
|
||||
instance.save()
|
||||
|
||||
def spawn(self, context, instance, image_meta, injected_files,
|
||||
admin_password, network_info=None, block_device_info=None):
|
||||
node_uuid = self._require_node(instance)
|
||||
self._set_default_ephemeral_device(instance)
|
||||
|
||||
# NOTE(deva): this db method will raise an exception if the node is
|
||||
# already in use. We call it here to ensure no one else
|
||||
# allocates this node before we begin provisioning it.
|
||||
node = db.bm_node_associate_and_update(context, node_uuid,
|
||||
{'instance_uuid': instance['uuid'],
|
||||
'instance_name': instance['hostname'],
|
||||
'task_state': baremetal_states.BUILDING,
|
||||
'preserve_ephemeral': False})
|
||||
self._spawn(node, context, instance, image_meta, injected_files,
|
||||
admin_password, network_info=network_info,
|
||||
block_device_info=block_device_info)
|
||||
|
||||
def _spawn(self, node, context, instance, image_meta, injected_files,
|
||||
admin_password, network_info=None, block_device_info=None):
|
||||
try:
|
||||
self._plug_vifs(instance, network_info, context=context)
|
||||
self._attach_block_devices(instance, block_device_info)
|
||||
self._start_firewall(instance, network_info)
|
||||
|
||||
# Caching images is both CPU and I/O expensive. When running many
|
||||
# machines from a single nova-compute server, deploys of multiple
|
||||
# machines can easily thrash the nova-compute server - unlike a
|
||||
# virt hypervisor which is limited by CPU for VMs, baremetal only
|
||||
# uses CPU and I/O when deploying. By only downloading one image
|
||||
# at a time we serialise rather than thrashing, which leads to a
|
||||
# lower average time-to-complete during overload situations, and
|
||||
# a (relatively) insignificant delay for compute servers which
|
||||
# have sufficient IOPS to handle multiple concurrent image
|
||||
# conversions.
|
||||
with lockutils.lock('nova-baremetal-cache-images', external=True):
|
||||
self.driver.cache_images(
|
||||
context, node, instance,
|
||||
admin_password=admin_password,
|
||||
image_meta=image_meta,
|
||||
injected_files=injected_files,
|
||||
network_info=network_info,
|
||||
)
|
||||
self.driver.activate_bootloader(context, node, instance,
|
||||
network_info=network_info)
|
||||
# NOTE(deva): ensure node is really off before we turn it on
|
||||
# fixes bug https://code.launchpad.net/bugs/1178919
|
||||
self.power_off(instance, node)
|
||||
self.power_on(context, instance, network_info, block_device_info,
|
||||
node)
|
||||
_update_state(context, node, instance, baremetal_states.PREPARED)
|
||||
|
||||
self.driver.activate_node(context, node, instance)
|
||||
_update_state(context, node, instance, baremetal_states.ACTIVE)
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception():
|
||||
LOG.error(_("Error deploying instance %(instance)s "
|
||||
"on baremetal node %(node)s.") %
|
||||
{'instance': instance['uuid'],
|
||||
'node': node['uuid']})
|
||||
|
||||
# Do not set instance=None yet. This prevents another
|
||||
# spawn() while we are cleaning up.
|
||||
_update_state(context, node, instance, baremetal_states.ERROR)
|
||||
|
||||
self.driver.deactivate_node(context, node, instance)
|
||||
self.power_off(instance, node)
|
||||
self.driver.deactivate_bootloader(context, node, instance)
|
||||
self.driver.destroy_images(context, node, instance)
|
||||
|
||||
self._detach_block_devices(instance, block_device_info)
|
||||
self._stop_firewall(instance, network_info)
|
||||
self._unplug_vifs(instance, network_info)
|
||||
|
||||
_update_state(context, node, None, baremetal_states.DELETED)
|
||||
else:
|
||||
# We no longer need the image since we successfully deployed.
|
||||
self.driver.destroy_images(context, node, instance)
|
||||
|
||||
def rebuild(self, context, instance, image_meta, injected_files,
|
||||
admin_password, bdms, detach_block_devices,
|
||||
attach_block_devices, network_info=None, recreate=False,
|
||||
block_device_info=None, preserve_ephemeral=False):
|
||||
"""Destroy and re-make this instance.
|
||||
|
||||
A 'rebuild' effectively purges all existing data from the system and
|
||||
remakes the VM with given 'metadata' and 'personalities'.
|
||||
|
||||
:param context: Security context.
|
||||
:param instance: Instance object.
|
||||
:param image_meta: Image object returned by nova.image.glance that
|
||||
defines the image from which to boot this instance.
|
||||
:param injected_files: User files to inject into instance.
|
||||
:param admin_password: Administrator password to set in instance.
|
||||
:param bdms: block-device-mappings to use for rebuild
|
||||
:param detach_block_devices: function to detach block devices. See
|
||||
nova.compute.manager.ComputeManager:_rebuild_default_impl for
|
||||
usage.
|
||||
:param attach_block_devices: function to attach block devices. See
|
||||
nova.compute.manager.ComputeManager:_rebuild_default_impl for
|
||||
usage.
|
||||
:param network_info:
|
||||
:py:meth:`~nova.network.manager.NetworkManager.get_instance_nw_info`
|
||||
:param block_device_info: Information about block devices to be
|
||||
attached to the instance.
|
||||
:param recreate: True if instance should be recreated with same disk.
|
||||
:param preserve_ephemeral: True if the default ephemeral storage
|
||||
partition must be preserved on rebuild.
|
||||
"""
|
||||
|
||||
instance.task_state = task_states.REBUILD_SPAWNING
|
||||
instance.save(expected_task_state=[task_states.REBUILDING])
|
||||
|
||||
node_uuid = self._require_node(instance)
|
||||
node = db.bm_node_get_by_node_uuid(context, node_uuid)
|
||||
db.bm_node_update(
|
||||
context, node['id'],
|
||||
{'task_state': baremetal_states.BUILDING,
|
||||
'preserve_ephemeral': preserve_ephemeral}
|
||||
)
|
||||
self._spawn(node, context, instance, image_meta, injected_files,
|
||||
admin_password, network_info=network_info,
|
||||
block_device_info=block_device_info)
|
||||
|
||||
def reboot(self, context, instance, network_info, reboot_type,
|
||||
block_device_info=None, bad_volumes_callback=None):
|
||||
node = _get_baremetal_node_by_instance_uuid(instance['uuid'])
|
||||
ctx = nova_context.get_admin_context()
|
||||
pm = get_power_manager(node=node, instance=instance)
|
||||
state = pm.reboot_node()
|
||||
if pm.state != baremetal_states.ACTIVE:
|
||||
raise exception.InstanceRebootFailure(_(
|
||||
"Baremetal power manager failed to restart node "
|
||||
"for instance %r") % instance['uuid'])
|
||||
_update_state(ctx, node, instance, state)
|
||||
|
||||
def destroy(self, context, instance, network_info, block_device_info=None,
|
||||
destroy_disks=True, migrate_data=None):
|
||||
context = nova_context.get_admin_context()
|
||||
|
||||
try:
|
||||
node = _get_baremetal_node_by_instance_uuid(instance['uuid'])
|
||||
except exception.InstanceNotFound:
|
||||
LOG.warning(_("Destroy called on non-existing instance %s")
|
||||
% instance['uuid'])
|
||||
return
|
||||
|
||||
try:
|
||||
self.driver.deactivate_node(context, node, instance)
|
||||
self.power_off(instance, node)
|
||||
self.driver.deactivate_bootloader(context, node, instance)
|
||||
self.driver.destroy_images(context, node, instance)
|
||||
|
||||
self._detach_block_devices(instance, block_device_info)
|
||||
self._stop_firewall(instance, network_info)
|
||||
self._unplug_vifs(instance, network_info)
|
||||
|
||||
_update_state(context, node, None, baremetal_states.DELETED)
|
||||
except Exception as e:
|
||||
with excutils.save_and_reraise_exception():
|
||||
try:
|
||||
LOG.error(_("Error from baremetal driver "
|
||||
"during destroy: %s") % e)
|
||||
_update_state(context, node, instance,
|
||||
baremetal_states.ERROR)
|
||||
except Exception:
|
||||
LOG.error(_("Error while recording destroy failure in "
|
||||
"baremetal database: %s") % e)
|
||||
|
||||
def cleanup(self, context, instance, network_info, block_device_info=None,
|
||||
destroy_disks=True, migrate_data=None, destroy_vifs=True):
|
||||
"""Cleanup after instance being destroyed."""
|
||||
pass
|
||||
|
||||
def power_off(self, instance, timeout=0, retry_interval=0, node=None):
|
||||
"""Power off the specified instance."""
|
||||
# TODO(PhilDay): Add support for timeout (clean shutdown)
|
||||
if not node:
|
||||
node = _get_baremetal_node_by_instance_uuid(instance['uuid'])
|
||||
pm = get_power_manager(node=node, instance=instance)
|
||||
pm.deactivate_node()
|
||||
if pm.state != baremetal_states.DELETED:
|
||||
raise exception.InstancePowerOffFailure(_(
|
||||
"Baremetal power manager failed to stop node "
|
||||
"for instance %r") % instance['uuid'])
|
||||
pm.stop_console()
|
||||
|
||||
def power_on(self, context, instance, network_info, block_device_info=None,
|
||||
node=None):
|
||||
"""Power on the specified instance."""
|
||||
if not node:
|
||||
node = _get_baremetal_node_by_instance_uuid(instance['uuid'])
|
||||
pm = get_power_manager(node=node, instance=instance)
|
||||
pm.activate_node()
|
||||
if pm.state != baremetal_states.ACTIVE:
|
||||
raise exception.InstancePowerOnFailure(_(
|
||||
"Baremetal power manager failed to start node "
|
||||
"for instance %r") % instance['uuid'])
|
||||
pm.start_console()
|
||||
|
||||
def get_volume_connector(self, instance):
|
||||
return self.volume_driver.get_volume_connector(instance)
|
||||
|
||||
def attach_volume(self, context, connection_info, instance, mountpoint,
|
||||
disk_bus=None, device_type=None, encryption=None):
|
||||
return self.volume_driver.attach_volume(connection_info,
|
||||
instance, mountpoint)
|
||||
|
||||
def detach_volume(self, connection_info, instance, mountpoint,
|
||||
encryption=None):
|
||||
return self.volume_driver.detach_volume(connection_info,
|
||||
instance, mountpoint)
|
||||
|
||||
def get_info(self, instance):
|
||||
inst_uuid = instance.get('uuid')
|
||||
node = _get_baremetal_node_by_instance_uuid(inst_uuid)
|
||||
pm = get_power_manager(node=node, instance=instance)
|
||||
|
||||
# NOTE(deva): Power manager may not be able to determine power state
|
||||
# in which case it may return "None" here.
|
||||
ps = pm.is_power_on()
|
||||
if ps:
|
||||
pstate = power_state.RUNNING
|
||||
elif ps is False:
|
||||
pstate = power_state.SHUTDOWN
|
||||
else:
|
||||
pstate = power_state.NOSTATE
|
||||
|
||||
return {'state': pstate,
|
||||
'max_mem': node['memory_mb'],
|
||||
'mem': node['memory_mb'],
|
||||
'num_cpu': node['cpus'],
|
||||
'cpu_time': 0}
|
||||
|
||||
def refresh_security_group_rules(self, security_group_id):
|
||||
self.firewall_driver.refresh_security_group_rules(security_group_id)
|
||||
return True
|
||||
|
||||
def refresh_security_group_members(self, security_group_id):
|
||||
self.firewall_driver.refresh_security_group_members(security_group_id)
|
||||
return True
|
||||
|
||||
def refresh_provider_fw_rules(self):
|
||||
self.firewall_driver.refresh_provider_fw_rules()
|
||||
|
||||
def _node_resource(self, node):
|
||||
vcpus_used = 0
|
||||
memory_mb_used = 0
|
||||
local_gb_used = 0
|
||||
|
||||
vcpus = node['cpus']
|
||||
memory_mb = node['memory_mb']
|
||||
local_gb = node['local_gb']
|
||||
if node['instance_uuid']:
|
||||
vcpus_used = node['cpus']
|
||||
memory_mb_used = node['memory_mb']
|
||||
local_gb_used = node['local_gb']
|
||||
|
||||
dic = {'vcpus': vcpus,
|
||||
'memory_mb': memory_mb,
|
||||
'local_gb': local_gb,
|
||||
'vcpus_used': vcpus_used,
|
||||
'memory_mb_used': memory_mb_used,
|
||||
'local_gb_used': local_gb_used,
|
||||
'hypervisor_type': self.get_hypervisor_type(),
|
||||
'hypervisor_version': self.get_hypervisor_version(),
|
||||
'hypervisor_hostname': str(node['uuid']),
|
||||
'cpu_info': 'baremetal cpu',
|
||||
'supported_instances':
|
||||
jsonutils.dumps(self.supported_instances),
|
||||
'stats': jsonutils.dumps(self.extra_specs)
|
||||
}
|
||||
return dic
|
||||
|
||||
def refresh_instance_security_rules(self, instance):
|
||||
self.firewall_driver.refresh_instance_security_rules(instance)
|
||||
|
||||
def get_available_resource(self, nodename):
|
||||
context = nova_context.get_admin_context()
|
||||
resource = {}
|
||||
try:
|
||||
node = db.bm_node_get_by_node_uuid(context, nodename)
|
||||
resource = self._node_resource(node)
|
||||
except exception.NodeNotFoundByUUID:
|
||||
pass
|
||||
return resource
|
||||
|
||||
def ensure_filtering_rules_for_instance(self, instance_ref, network_info):
|
||||
self.firewall_driver.setup_basic_filtering(instance_ref, network_info)
|
||||
self.firewall_driver.prepare_instance_filter(instance_ref,
|
||||
network_info)
|
||||
|
||||
def unfilter_instance(self, instance_ref, network_info):
|
||||
self.firewall_driver.unfilter_instance(instance_ref,
|
||||
network_info=network_info)
|
||||
|
||||
def plug_vifs(self, instance, network_info):
|
||||
"""Plugin VIFs into networks."""
|
||||
self._plug_vifs(instance, network_info)
|
||||
|
||||
def _plug_vifs(self, instance, network_info, context=None):
|
||||
if not context:
|
||||
context = nova_context.get_admin_context()
|
||||
node = _get_baremetal_node_by_instance_uuid(instance['uuid'])
|
||||
if node:
|
||||
pifs = db.bm_interface_get_all_by_bm_node_id(context, node['id'])
|
||||
for pif in pifs:
|
||||
if pif['vif_uuid']:
|
||||
db.bm_interface_set_vif_uuid(context, pif['id'], None)
|
||||
for vif in network_info:
|
||||
self.vif_driver.plug(instance, vif)
|
||||
|
||||
def _unplug_vifs(self, instance, network_info):
|
||||
for vif in network_info:
|
||||
self.vif_driver.unplug(instance, vif)
|
||||
|
||||
def manage_image_cache(self, context, all_instances):
|
||||
"""Manage the local cache of images."""
|
||||
self.image_cache_manager.update(context, all_instances)
|
||||
|
||||
def get_console_output(self, context, instance):
|
||||
node = _get_baremetal_node_by_instance_uuid(instance.uuid)
|
||||
return self.driver.get_console_output(node, instance)
|
||||
|
||||
def get_available_nodes(self, refresh=False):
|
||||
context = nova_context.get_admin_context()
|
||||
return [str(n['uuid']) for n in
|
||||
db.bm_node_get_all(context, service_host=CONF.host)]
|
||||
|
||||
def dhcp_options_for_instance(self, instance):
|
||||
return self.driver.dhcp_options_for_instance(instance)
|
||||
@@ -1,82 +0,0 @@
|
||||
# Copyright (c) 2012 NTT DOCOMO, INC.
|
||||
# Copyright (c) 2011 University of Southern California / ISI
|
||||
# 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.
|
||||
|
||||
from nova.virt.baremetal import base
|
||||
from nova.virt import firewall
|
||||
|
||||
|
||||
class FakeDriver(base.NodeDriver):
|
||||
|
||||
def cache_images(self, context, node, instance, **kwargs):
|
||||
pass
|
||||
|
||||
def destroy_images(self, context, node, instance):
|
||||
pass
|
||||
|
||||
def activate_bootloader(self, context, node, instance, **kwargs):
|
||||
pass
|
||||
|
||||
def deactivate_bootloader(self, context, node, instance):
|
||||
pass
|
||||
|
||||
def activate_node(self, context, node, instance):
|
||||
"""For operations after power on."""
|
||||
pass
|
||||
|
||||
def deactivate_node(self, context, node, instance):
|
||||
"""For operations before power off."""
|
||||
pass
|
||||
|
||||
def get_console_output(self, node, instance):
|
||||
return 'fake\nconsole\noutput for instance %s' % instance.id
|
||||
|
||||
|
||||
class FakePowerManager(base.PowerManager):
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(FakePowerManager, self).__init__(**kwargs)
|
||||
|
||||
|
||||
class FakeFirewallDriver(firewall.NoopFirewallDriver):
|
||||
|
||||
def __init__(self):
|
||||
super(FakeFirewallDriver, self).__init__()
|
||||
|
||||
|
||||
class FakeVifDriver(object):
|
||||
|
||||
def __init__(self):
|
||||
super(FakeVifDriver, self).__init__()
|
||||
|
||||
def plug(self, instance, vif):
|
||||
pass
|
||||
|
||||
def unplug(self, instance, vif):
|
||||
pass
|
||||
|
||||
|
||||
class FakeVolumeDriver(object):
|
||||
|
||||
def __init__(self, virtapi):
|
||||
super(FakeVolumeDriver, self).__init__()
|
||||
self.virtapi = virtapi
|
||||
self._initiator = "fake_initiator"
|
||||
|
||||
def attach_volume(self, connection_info, instance, mountpoint):
|
||||
pass
|
||||
|
||||
def detach_volume(self, connection_info, instance, mountpoint):
|
||||
pass
|
||||
@@ -1,128 +0,0 @@
|
||||
# Copyright 2013 Red Hat Inc.
|
||||
# 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.
|
||||
#
|
||||
# iBoot Power Driver
|
||||
|
||||
from oslo.utils import importutils
|
||||
|
||||
from nova import exception
|
||||
from nova.i18n import _
|
||||
from nova.openstack.common import log as logging
|
||||
from nova.virt.baremetal import baremetal_states
|
||||
from nova.virt.baremetal import base
|
||||
|
||||
iboot = importutils.try_import('iboot')
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class IBootManager(base.PowerManager):
|
||||
"""iBoot Power Driver for Baremetal Nova Compute
|
||||
|
||||
This PowerManager class provides a mechanism for controlling power state
|
||||
via an iBoot capable device (tested with an iBoot G2).
|
||||
|
||||
Requires installation of python-iboot:
|
||||
|
||||
https://github.com/darkip/python-iboot
|
||||
|
||||
"""
|
||||
def __init__(self, **kwargs):
|
||||
node = kwargs.pop('node', {})
|
||||
addr_relay = str(node['pm_address']).split(',')
|
||||
|
||||
if len(addr_relay) > 1:
|
||||
try:
|
||||
self.relay_id = int(addr_relay[1])
|
||||
except ValueError:
|
||||
msg = _("iboot PDU relay ID must be an integer.")
|
||||
raise exception.InvalidParameterValue(msg)
|
||||
else:
|
||||
self.relay_id = 1
|
||||
|
||||
addr_port = addr_relay[0].split(':')
|
||||
self.address = addr_port[0]
|
||||
if len(addr_port) > 1:
|
||||
try:
|
||||
self.port = int(addr_port[1])
|
||||
except ValueError:
|
||||
msg = _("iboot PDU port must be an integer.")
|
||||
raise exception.InvalidParameterValue(msg)
|
||||
else:
|
||||
self.port = 9100
|
||||
|
||||
self.user = str(node['pm_user'])
|
||||
self.password = str(node['pm_password'])
|
||||
instance = kwargs.pop('instance', {})
|
||||
self.node_name = instance.get('hostname', "")
|
||||
self.state = None
|
||||
self.conn = None
|
||||
|
||||
def _create_connection(self):
|
||||
if not self.conn:
|
||||
self.conn = iboot.iBootInterface(self.address, self.user,
|
||||
self.password, port=self.port,
|
||||
num_relays=self.relay_id)
|
||||
return self.conn
|
||||
|
||||
def _switch(self, relay_id, enabled):
|
||||
return self.conn.switch(relay_id, enabled)
|
||||
|
||||
def _get_relay(self, relay_id):
|
||||
return self.conn.get_relays()[relay_id - 1]
|
||||
|
||||
def activate_node(self):
|
||||
LOG.info(_("activate_node name %s"), self.node_name)
|
||||
self._create_connection()
|
||||
self._switch(self.relay_id, True)
|
||||
|
||||
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(_("reboot_node: %s"), self.node_name)
|
||||
self._create_connection()
|
||||
self._switch(self.relay_id, False)
|
||||
self._switch(self.relay_id, True)
|
||||
|
||||
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)
|
||||
self._create_connection()
|
||||
if self.is_power_on():
|
||||
self._switch(self.relay_id, False)
|
||||
|
||||
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)
|
||||
self._create_connection()
|
||||
return self._get_relay(self.relay_id)
|
||||
@@ -1,303 +0,0 @@
|
||||
# coding=utf-8
|
||||
|
||||
# Copyright 2012 Hewlett-Packard Development Company, L.P.
|
||||
# Copyright (c) 2012 NTT DOCOMO, INC.
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Baremetal IPMI power manager.
|
||||
"""
|
||||
|
||||
import os
|
||||
import stat
|
||||
import tempfile
|
||||
|
||||
from oslo.config import cfg
|
||||
|
||||
from nova import exception
|
||||
from nova.i18n import _
|
||||
from nova.openstack.common import log as logging
|
||||
from nova.openstack.common import loopingcall
|
||||
from nova import paths
|
||||
from nova import utils
|
||||
from nova.virt.baremetal import baremetal_states
|
||||
from nova.virt.baremetal import base
|
||||
from nova.virt.baremetal import utils as bm_utils
|
||||
|
||||
opts = [
|
||||
cfg.StrOpt('terminal',
|
||||
default='shellinaboxd',
|
||||
help='Path to baremetal terminal program'),
|
||||
cfg.StrOpt('terminal_cert_dir',
|
||||
help='Path to baremetal terminal SSL cert(PEM)'),
|
||||
cfg.StrOpt('terminal_pid_dir',
|
||||
default=paths.state_path_def('baremetal/console'),
|
||||
help='Path to directory stores pidfiles of baremetal_terminal'),
|
||||
cfg.IntOpt('ipmi_power_retry',
|
||||
default=10,
|
||||
help='Maximal number of retries for IPMI operations'),
|
||||
]
|
||||
|
||||
baremetal_group = cfg.OptGroup(name='baremetal',
|
||||
title='Baremetal Options')
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.register_group(baremetal_group)
|
||||
CONF.register_opts(opts, baremetal_group)
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _make_password_file(password):
|
||||
fd, path = tempfile.mkstemp()
|
||||
os.fchmod(fd, stat.S_IRUSR | stat.S_IWUSR)
|
||||
with os.fdopen(fd, "w") as f:
|
||||
# NOTE(r-mibu): Since ipmitool hangs with an empty password file,
|
||||
# we have to write '\0' if password was empty.
|
||||
# see https://bugs.launchpad.net/nova/+bug/1237802 for more details
|
||||
f.write(password or b"\0")
|
||||
return path
|
||||
|
||||
|
||||
def _get_console_pid_path(node_id):
|
||||
name = "%s.pid" % node_id
|
||||
path = os.path.join(CONF.baremetal.terminal_pid_dir, name)
|
||||
return path
|
||||
|
||||
|
||||
def _get_console_pid(node_id):
|
||||
pid_path = _get_console_pid_path(node_id)
|
||||
if os.path.exists(pid_path):
|
||||
with open(pid_path, 'r') as f:
|
||||
pid_str = f.read()
|
||||
try:
|
||||
return int(pid_str)
|
||||
except ValueError:
|
||||
LOG.warn(_("pid file %s does not contain any pid"), pid_path)
|
||||
return None
|
||||
|
||||
|
||||
class IPMI(base.PowerManager):
|
||||
"""IPMI Power Driver for Baremetal Nova Compute
|
||||
|
||||
This PowerManager class provides mechanism for controlling the power state
|
||||
of physical hardware via IPMI calls. It also provides serial console access
|
||||
where available.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, node, **kwargs):
|
||||
self.state = None
|
||||
self.retries = None
|
||||
self.node_id = node['id']
|
||||
self.address = node['pm_address']
|
||||
self.user = node['pm_user']
|
||||
self.password = node['pm_password']
|
||||
self.port = node['terminal_port']
|
||||
|
||||
if self.node_id is None:
|
||||
raise exception.InvalidParameterValue(_("Node id not supplied "
|
||||
"to IPMI"))
|
||||
if self.address is None:
|
||||
raise exception.InvalidParameterValue(_("Address not supplied "
|
||||
"to IPMI"))
|
||||
if self.user is None:
|
||||
raise exception.InvalidParameterValue(_("User not supplied "
|
||||
"to IPMI"))
|
||||
if self.password is None:
|
||||
raise exception.InvalidParameterValue(_("Password not supplied "
|
||||
"to IPMI"))
|
||||
|
||||
def _exec_ipmitool(self, command):
|
||||
args = ['ipmitool',
|
||||
'-I',
|
||||
'lanplus',
|
||||
'-H',
|
||||
self.address,
|
||||
'-U',
|
||||
self.user,
|
||||
'-f']
|
||||
pwfile = _make_password_file(self.password)
|
||||
try:
|
||||
args.append(pwfile)
|
||||
args.extend(command.split(" "))
|
||||
out, err = utils.execute(*args, attempts=3)
|
||||
LOG.debug("ipmitool stdout: '%(out)s', stderr: '%(err)s'",
|
||||
{'out': out, 'err': err})
|
||||
return out, err
|
||||
finally:
|
||||
bm_utils.unlink_without_raise(pwfile)
|
||||
|
||||
def _power_on(self):
|
||||
"""Turn the power to this node ON."""
|
||||
|
||||
def _wait_for_power_on():
|
||||
"""Called at an interval until the node's power is on."""
|
||||
|
||||
if self.is_power_on():
|
||||
self.state = baremetal_states.ACTIVE
|
||||
raise loopingcall.LoopingCallDone()
|
||||
if self.retries > CONF.baremetal.ipmi_power_retry:
|
||||
LOG.error(_("IPMI power on failed after %d tries") % (
|
||||
CONF.baremetal.ipmi_power_retry))
|
||||
self.state = baremetal_states.ERROR
|
||||
raise loopingcall.LoopingCallDone()
|
||||
try:
|
||||
self.retries += 1
|
||||
if not self.power_on_called:
|
||||
self._exec_ipmitool("power on")
|
||||
self.power_on_called = True
|
||||
except Exception:
|
||||
LOG.exception(_("IPMI power on failed"))
|
||||
|
||||
self.retries = 0
|
||||
self.power_on_called = False
|
||||
timer = loopingcall.FixedIntervalLoopingCall(_wait_for_power_on)
|
||||
timer.start(interval=1.0).wait()
|
||||
|
||||
def _power_off(self):
|
||||
"""Turn the power to this node OFF."""
|
||||
|
||||
def _wait_for_power_off():
|
||||
"""Called at an interval until the node's power is off."""
|
||||
|
||||
if self.is_power_on() is False:
|
||||
self.state = baremetal_states.DELETED
|
||||
raise loopingcall.LoopingCallDone()
|
||||
if self.retries > CONF.baremetal.ipmi_power_retry:
|
||||
LOG.error(_("IPMI power off failed after %d tries") % (
|
||||
CONF.baremetal.ipmi_power_retry))
|
||||
self.state = baremetal_states.ERROR
|
||||
raise loopingcall.LoopingCallDone()
|
||||
try:
|
||||
self.retries += 1
|
||||
if not self.power_off_called:
|
||||
self._exec_ipmitool("power off")
|
||||
self.power_off_called = True
|
||||
except Exception:
|
||||
LOG.exception(_("IPMI power off failed"))
|
||||
|
||||
self.retries = 0
|
||||
self.power_off_called = False
|
||||
timer = loopingcall.FixedIntervalLoopingCall(_wait_for_power_off)
|
||||
timer.start(interval=1.0).wait()
|
||||
|
||||
def _set_pxe_for_next_boot(self):
|
||||
try:
|
||||
self._exec_ipmitool("chassis bootdev pxe options=persistent")
|
||||
except Exception:
|
||||
LOG.exception(_("IPMI set next bootdev failed"))
|
||||
|
||||
def activate_node(self):
|
||||
"""Turns the power to node ON.
|
||||
|
||||
Sets node next-boot to PXE and turns the power on,
|
||||
waiting up to ipmi_power_retry/2 seconds for confirmation
|
||||
that the power is on.
|
||||
|
||||
:returns: One of baremetal_states.py, representing the new state.
|
||||
"""
|
||||
if self.is_power_on() and self.state == baremetal_states.ACTIVE:
|
||||
LOG.warning(_("Activate node called, but node %s "
|
||||
"is already active") % self.address)
|
||||
self._set_pxe_for_next_boot()
|
||||
self._power_on()
|
||||
return self.state
|
||||
|
||||
def reboot_node(self):
|
||||
"""Cycles the power to a node.
|
||||
|
||||
Turns the power off, sets next-boot to PXE, and turns the power on.
|
||||
Each action waits up to ipmi_power_retry/2 seconds for confirmation
|
||||
that the power state has changed.
|
||||
|
||||
:returns: One of baremetal_states.py, representing the new state.
|
||||
"""
|
||||
self._power_off()
|
||||
self._set_pxe_for_next_boot()
|
||||
self._power_on()
|
||||
return self.state
|
||||
|
||||
def deactivate_node(self):
|
||||
"""Turns the power to node OFF.
|
||||
|
||||
Turns the power off, and waits up to ipmi_power_retry/2 seconds
|
||||
for confirmation that the power is off.
|
||||
|
||||
:returns: One of baremetal_states.py, representing the new state.
|
||||
"""
|
||||
self._power_off()
|
||||
return self.state
|
||||
|
||||
def is_power_on(self):
|
||||
"""Check if the power is currently on.
|
||||
|
||||
:returns: True if on; False if off; None if unable to determine.
|
||||
"""
|
||||
# NOTE(deva): string matching based on
|
||||
# http://ipmitool.cvs.sourceforge.net/
|
||||
# viewvc/ipmitool/ipmitool/lib/ipmi_chassis.c
|
||||
res = self._exec_ipmitool("power status")[0]
|
||||
if res == ("Chassis Power is on\n"):
|
||||
return True
|
||||
elif res == ("Chassis Power is off\n"):
|
||||
return False
|
||||
return None
|
||||
|
||||
def start_console(self):
|
||||
if not self.port:
|
||||
return
|
||||
args = []
|
||||
args.append(CONF.baremetal.terminal)
|
||||
if CONF.baremetal.terminal_cert_dir:
|
||||
args.append("-c")
|
||||
args.append(CONF.baremetal.terminal_cert_dir)
|
||||
else:
|
||||
args.append("-t")
|
||||
args.append("-p")
|
||||
args.append(str(self.port))
|
||||
args.append("--background=%s" % _get_console_pid_path(self.node_id))
|
||||
args.append("-s")
|
||||
|
||||
try:
|
||||
pwfile = _make_password_file(self.password)
|
||||
ipmi_args = "/:%(uid)s:%(gid)s:HOME:ipmitool -H %(address)s" \
|
||||
" -I lanplus -U %(user)s -f %(pwfile)s sol activate" \
|
||||
% {'uid': os.getuid(),
|
||||
'gid': os.getgid(),
|
||||
'address': self.address,
|
||||
'user': self.user,
|
||||
'pwfile': pwfile,
|
||||
}
|
||||
|
||||
args.append(ipmi_args)
|
||||
# Run shellinaboxd without pipes. Otherwise utils.execute() waits
|
||||
# infinitely since shellinaboxd does not close passed fds.
|
||||
x = ["'" + arg.replace("'", "'\\''") + "'" for arg in args]
|
||||
x.append('</dev/null')
|
||||
x.append('>/dev/null')
|
||||
x.append('2>&1')
|
||||
utils.execute(' '.join(x), shell=True)
|
||||
finally:
|
||||
bm_utils.unlink_without_raise(pwfile)
|
||||
|
||||
def stop_console(self):
|
||||
console_pid = _get_console_pid(self.node_id)
|
||||
if console_pid:
|
||||
# Allow exitcode 99 (RC_UNAUTHORIZED)
|
||||
utils.execute('kill', '-TERM', str(console_pid),
|
||||
run_as_root=True,
|
||||
check_exit_code=[0, 99])
|
||||
bm_utils.unlink_without_raise(_get_console_pid_path(self.node_id))
|
||||
@@ -1,17 +0,0 @@
|
||||
# Injected by Nova on instance boot
|
||||
#
|
||||
# This file describes the network interfaces available on your system
|
||||
# and how to activate them. For more information, see interfaces(5).
|
||||
|
||||
# The loopback network interface
|
||||
auto lo
|
||||
iface lo inet loopback
|
||||
|
||||
{% for ifc in interfaces -%}
|
||||
auto {{ ifc.name }}
|
||||
iface {{ ifc.name }} inet dhcp
|
||||
|
||||
{% if use_ipv6 -%}
|
||||
iface {{ ifc.name }} inet6 dhcp
|
||||
{%- endif %}
|
||||
{%- endfor %}
|
||||
@@ -1,27 +0,0 @@
|
||||
# Injected by Nova on instance boot
|
||||
#
|
||||
# This file describes the network interfaces available on your system
|
||||
# and how to activate them. For more information, see interfaces(5).
|
||||
|
||||
# The loopback network interface
|
||||
auto lo
|
||||
iface lo inet loopback
|
||||
|
||||
{% for ifc in interfaces -%}
|
||||
auto {{ ifc.name }}
|
||||
iface {{ ifc.name }} inet static
|
||||
address {{ ifc.address }}
|
||||
netmask {{ ifc.netmask }}
|
||||
gateway {{ ifc.gateway }}
|
||||
{%- if ifc.dns %}
|
||||
dns-nameservers {{ ifc.dns }}
|
||||
{%- endif %}
|
||||
|
||||
{% if use_ipv6 -%}
|
||||
iface {{ ifc.name }} inet6 static
|
||||
address {{ ifc.address_v6 }}
|
||||
netmask {{ ifc.netmask_v6 }}
|
||||
gateway {{ ifc.gateway_v6 }}
|
||||
{%- endif %}
|
||||
|
||||
{%- endfor %}
|
||||
@@ -1,503 +0,0 @@
|
||||
# Copyright 2012,2014 Hewlett-Packard Development Company, L.P.
|
||||
# Copyright (c) 2012 NTT DOCOMO, INC.
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Class for PXE bare-metal nodes.
|
||||
"""
|
||||
|
||||
import datetime
|
||||
import os
|
||||
|
||||
import jinja2
|
||||
from oslo.config import cfg
|
||||
from oslo.db import exception as db_exc
|
||||
from oslo.utils import timeutils
|
||||
|
||||
from nova.compute import flavors
|
||||
from nova import exception
|
||||
from nova.i18n import _
|
||||
from nova import objects
|
||||
from nova.openstack.common import fileutils
|
||||
from nova.openstack.common import log as logging
|
||||
from nova.openstack.common import loopingcall
|
||||
from nova import utils
|
||||
from nova.virt.baremetal import baremetal_states
|
||||
from nova.virt.baremetal import base
|
||||
from nova.virt.baremetal import db
|
||||
from nova.virt.baremetal import utils as bm_utils
|
||||
|
||||
pxe_opts = [
|
||||
cfg.StrOpt('deploy_kernel',
|
||||
help='Default kernel image ID used in deployment phase'),
|
||||
cfg.StrOpt('deploy_ramdisk',
|
||||
help='Default ramdisk image ID used in deployment phase'),
|
||||
cfg.StrOpt('net_config_template',
|
||||
default='$pybasedir/nova/virt/baremetal/'
|
||||
'net-dhcp.ubuntu.template',
|
||||
help='Template file for injected network config'),
|
||||
cfg.StrOpt('pxe_append_params',
|
||||
default='nofb nomodeset vga=normal',
|
||||
help='Additional append parameters for baremetal PXE boot'),
|
||||
cfg.StrOpt('pxe_config_template',
|
||||
default='$pybasedir/nova/virt/baremetal/pxe_config.template',
|
||||
help='Template file for PXE configuration'),
|
||||
cfg.BoolOpt('use_file_injection',
|
||||
help='If True, enable file injection for network info, '
|
||||
'files and admin password',
|
||||
default=False),
|
||||
cfg.IntOpt('pxe_deploy_timeout',
|
||||
help='Timeout for PXE deployments. Default: 0 (unlimited)',
|
||||
default=0),
|
||||
cfg.BoolOpt('pxe_network_config',
|
||||
help='If set, pass the network configuration details to the '
|
||||
'initramfs via cmdline.',
|
||||
default=False),
|
||||
cfg.StrOpt('pxe_bootfile_name',
|
||||
help='This gets passed to Neutron as the bootfile dhcp '
|
||||
'parameter.',
|
||||
default='pxelinux.0'),
|
||||
]
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
baremetal_group = cfg.OptGroup(name='baremetal',
|
||||
title='Baremetal Options')
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.register_group(baremetal_group)
|
||||
CONF.register_opts(pxe_opts, baremetal_group)
|
||||
CONF.import_opt('use_ipv6', 'nova.netconf')
|
||||
|
||||
|
||||
def build_pxe_network_config(network_info):
|
||||
interfaces = bm_utils.map_network_interfaces(network_info, CONF.use_ipv6)
|
||||
template = None
|
||||
if not CONF.use_ipv6:
|
||||
template = "ip=%(address)s::%(gateway)s:%(netmask)s::%(name)s:off"
|
||||
else:
|
||||
template = ("ip=[%(address_v6)s]::[%(gateway_v6)s]:"
|
||||
"[%(netmask_v6)s]::%(name)s:off")
|
||||
|
||||
net_config = [template % iface for iface in interfaces]
|
||||
return ' '.join(net_config)
|
||||
|
||||
|
||||
def build_pxe_config(deployment_id, deployment_key, deployment_iscsi_iqn,
|
||||
deployment_aki_path, deployment_ari_path,
|
||||
aki_path, ari_path, network_info):
|
||||
"""Build the PXE config file for a node
|
||||
|
||||
This method builds the PXE boot configuration file for a node,
|
||||
given all the required parameters.
|
||||
|
||||
The resulting file has both a "deploy" and "boot" label, which correspond
|
||||
to the two phases of booting. This may be extended later.
|
||||
|
||||
"""
|
||||
LOG.debug("Building PXE config for deployment %s.", deployment_id)
|
||||
|
||||
network_config = None
|
||||
if network_info and CONF.baremetal.pxe_network_config:
|
||||
network_config = build_pxe_network_config(network_info)
|
||||
|
||||
pxe_options = {
|
||||
'deployment_id': deployment_id,
|
||||
'deployment_key': deployment_key,
|
||||
'deployment_iscsi_iqn': deployment_iscsi_iqn,
|
||||
'deployment_aki_path': deployment_aki_path,
|
||||
'deployment_ari_path': deployment_ari_path,
|
||||
'aki_path': aki_path,
|
||||
'ari_path': ari_path,
|
||||
'pxe_append_params': CONF.baremetal.pxe_append_params,
|
||||
'pxe_network_config': network_config,
|
||||
}
|
||||
tmpl_path, tmpl_file = os.path.split(CONF.baremetal.pxe_config_template)
|
||||
env = jinja2.Environment(loader=jinja2.FileSystemLoader(tmpl_path))
|
||||
template = env.get_template(tmpl_file)
|
||||
return template.render({'pxe_options': pxe_options,
|
||||
'ROOT': '${ROOT}'})
|
||||
|
||||
|
||||
def build_network_config(network_info):
|
||||
interfaces = bm_utils.map_network_interfaces(network_info, CONF.use_ipv6)
|
||||
tmpl_path, tmpl_file = os.path.split(CONF.baremetal.net_config_template)
|
||||
env = jinja2.Environment(loader=jinja2.FileSystemLoader(tmpl_path))
|
||||
template = env.get_template(tmpl_file)
|
||||
return template.render({'interfaces': interfaces,
|
||||
'use_ipv6': CONF.use_ipv6})
|
||||
|
||||
|
||||
def get_deploy_aki_id(flavor):
|
||||
return flavor.get('extra_specs', {}).\
|
||||
get('baremetal:deploy_kernel_id', CONF.baremetal.deploy_kernel)
|
||||
|
||||
|
||||
def get_deploy_ari_id(flavor):
|
||||
return flavor.get('extra_specs', {}).\
|
||||
get('baremetal:deploy_ramdisk_id', CONF.baremetal.deploy_ramdisk)
|
||||
|
||||
|
||||
def get_image_dir_path(instance):
|
||||
"""Generate the dir for an instances disk."""
|
||||
return os.path.join(CONF.instances_path, instance['name'])
|
||||
|
||||
|
||||
def get_image_file_path(instance):
|
||||
"""Generate the full path for an instances disk."""
|
||||
return os.path.join(CONF.instances_path, instance['name'], 'disk')
|
||||
|
||||
|
||||
def get_pxe_config_file_path(instance):
|
||||
"""Generate the path for an instances PXE config file."""
|
||||
return os.path.join(CONF.baremetal.tftp_root, instance['uuid'], 'config')
|
||||
|
||||
|
||||
def get_partition_sizes(instance):
|
||||
flavor = flavors.extract_flavor(instance)
|
||||
root_mb = flavor['root_gb'] * 1024
|
||||
swap_mb = flavor['swap']
|
||||
ephemeral_mb = flavor['ephemeral_gb'] * 1024
|
||||
|
||||
# NOTE(deva): For simpler code paths on the deployment side,
|
||||
# we always create a swap partition. If the flavor
|
||||
# does not specify any swap, we default to 1MB
|
||||
if swap_mb < 1:
|
||||
swap_mb = 1
|
||||
|
||||
return (root_mb, swap_mb, ephemeral_mb)
|
||||
|
||||
|
||||
def get_pxe_mac_path(mac):
|
||||
"""Convert a MAC address into a PXE config file name."""
|
||||
return os.path.join(
|
||||
CONF.baremetal.tftp_root,
|
||||
'pxelinux.cfg',
|
||||
"01-" + mac.replace(":", "-").lower()
|
||||
)
|
||||
|
||||
|
||||
def get_tftp_image_info(instance, flavor):
|
||||
"""Generate the paths for tftp files for this instance
|
||||
|
||||
Raises NovaException if
|
||||
- instance does not contain kernel_id or ramdisk_id
|
||||
- deploy_kernel_id or deploy_ramdisk_id can not be read from
|
||||
flavor['extra_specs'] and defaults are not set
|
||||
|
||||
"""
|
||||
image_info = {
|
||||
'kernel': [None, None],
|
||||
'ramdisk': [None, None],
|
||||
'deploy_kernel': [None, None],
|
||||
'deploy_ramdisk': [None, None],
|
||||
}
|
||||
try:
|
||||
image_info['kernel'][0] = str(instance['kernel_id'])
|
||||
image_info['ramdisk'][0] = str(instance['ramdisk_id'])
|
||||
image_info['deploy_kernel'][0] = get_deploy_aki_id(flavor)
|
||||
image_info['deploy_ramdisk'][0] = get_deploy_ari_id(flavor)
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
missing_labels = []
|
||||
for label in image_info.keys():
|
||||
(uuid, path) = image_info[label]
|
||||
if not uuid:
|
||||
missing_labels.append(label)
|
||||
else:
|
||||
image_info[label][1] = os.path.join(CONF.baremetal.tftp_root,
|
||||
instance['uuid'], label)
|
||||
if missing_labels:
|
||||
raise exception.NovaException(_(
|
||||
"Can not activate PXE bootloader. The following boot parameters "
|
||||
"were not passed to baremetal driver: %s") % missing_labels)
|
||||
return image_info
|
||||
|
||||
|
||||
class PXE(base.NodeDriver):
|
||||
"""PXE bare metal driver."""
|
||||
|
||||
def __init__(self, virtapi):
|
||||
super(PXE, self).__init__(virtapi)
|
||||
|
||||
def _collect_mac_addresses(self, context, node):
|
||||
macs = set()
|
||||
for nic in db.bm_interface_get_all_by_bm_node_id(context, node['id']):
|
||||
if nic['address']:
|
||||
macs.add(nic['address'])
|
||||
return sorted(macs)
|
||||
|
||||
def _cache_tftp_images(self, context, instance, image_info):
|
||||
"""Fetch the necessary kernels and ramdisks for the instance."""
|
||||
fileutils.ensure_tree(
|
||||
os.path.join(CONF.baremetal.tftp_root, instance['uuid']))
|
||||
|
||||
LOG.debug("Fetching kernel and ramdisk for instance %s",
|
||||
instance['name'])
|
||||
for label in image_info.keys():
|
||||
(uuid, path) = image_info[label]
|
||||
bm_utils.cache_image(
|
||||
context=context,
|
||||
target=path,
|
||||
image_id=uuid,
|
||||
user_id=instance['user_id'],
|
||||
project_id=instance['project_id'],
|
||||
)
|
||||
|
||||
def _cache_image(self, context, instance, image_meta):
|
||||
"""Fetch the instance's image from Glance
|
||||
|
||||
This method pulls the relevant AMI and associated kernel and ramdisk,
|
||||
and the deploy kernel and ramdisk from Glance, and writes them
|
||||
to the appropriate places on local disk.
|
||||
|
||||
Both sets of kernel and ramdisk are needed for PXE booting, so these
|
||||
are stored under CONF.baremetal.tftp_root.
|
||||
|
||||
At present, the AMI is cached and certain files are injected.
|
||||
Debian/ubuntu-specific assumptions are made regarding the injected
|
||||
files. In a future revision, this functionality will be replaced by a
|
||||
more scalable and os-agnostic approach: the deployment ramdisk will
|
||||
fetch from Glance directly, and write its own last-mile configuration.
|
||||
|
||||
"""
|
||||
fileutils.ensure_tree(get_image_dir_path(instance))
|
||||
image_path = get_image_file_path(instance)
|
||||
|
||||
LOG.debug("Fetching image %(ami)s for instance %(name)s",
|
||||
{'ami': image_meta['id'], 'name': instance['name']})
|
||||
bm_utils.cache_image(context=context,
|
||||
target=image_path,
|
||||
image_id=image_meta['id'],
|
||||
user_id=instance['user_id'],
|
||||
project_id=instance['project_id'],
|
||||
clean=True,
|
||||
)
|
||||
|
||||
return [image_meta['id'], image_path]
|
||||
|
||||
def _inject_into_image(self, context, node, instance, network_info,
|
||||
injected_files=None, admin_password=None):
|
||||
"""Inject last-mile configuration into instances image
|
||||
|
||||
Much of this method is a hack around DHCP and cloud-init
|
||||
not working together with baremetal provisioning yet.
|
||||
|
||||
"""
|
||||
# NOTE(deva): We assume that if we're not using a kernel,
|
||||
# then the target partition is the first partition
|
||||
partition = None
|
||||
if not instance['kernel_id']:
|
||||
partition = "1"
|
||||
|
||||
ssh_key = None
|
||||
if 'key_data' in instance and instance['key_data']:
|
||||
ssh_key = str(instance['key_data'])
|
||||
|
||||
if injected_files is None:
|
||||
injected_files = []
|
||||
else:
|
||||
# NOTE(deva): copy so we don't modify the original
|
||||
injected_files = list(injected_files)
|
||||
|
||||
net_config = build_network_config(network_info)
|
||||
|
||||
if instance['hostname']:
|
||||
injected_files.append(('/etc/hostname', instance['hostname']))
|
||||
|
||||
LOG.debug("Injecting files into image for instance %(name)s",
|
||||
{'name': instance['name']})
|
||||
|
||||
bm_utils.inject_into_image(
|
||||
image=get_image_file_path(instance),
|
||||
key=ssh_key,
|
||||
net=net_config,
|
||||
metadata=utils.instance_meta(instance),
|
||||
admin_password=admin_password,
|
||||
files=injected_files,
|
||||
partition=partition,
|
||||
)
|
||||
|
||||
def cache_images(self, context, node, instance,
|
||||
admin_password, image_meta, injected_files, network_info):
|
||||
"""Prepare all the images for this instance."""
|
||||
flavor = objects.Flavor.get_by_id(context,
|
||||
instance['instance_type_id'])
|
||||
tftp_image_info = get_tftp_image_info(instance, flavor)
|
||||
self._cache_tftp_images(context, instance, tftp_image_info)
|
||||
|
||||
self._cache_image(context, instance, image_meta)
|
||||
if CONF.baremetal.use_file_injection:
|
||||
self._inject_into_image(context, node, instance, network_info,
|
||||
injected_files, admin_password)
|
||||
|
||||
def destroy_images(self, context, node, instance):
|
||||
"""Delete instance's image file."""
|
||||
bm_utils.unlink_without_raise(get_image_file_path(instance))
|
||||
bm_utils.rmtree_without_raise(get_image_dir_path(instance))
|
||||
|
||||
def dhcp_options_for_instance(self, instance):
|
||||
return [{'opt_name': 'bootfile-name',
|
||||
'opt_value': CONF.baremetal.pxe_bootfile_name},
|
||||
{'opt_name': 'server-ip-address',
|
||||
'opt_value': CONF.my_ip},
|
||||
{'opt_name': 'tftp-server',
|
||||
'opt_value': CONF.my_ip}
|
||||
]
|
||||
|
||||
def activate_bootloader(self, context, node, instance, network_info):
|
||||
"""Configure PXE boot loader for an instance
|
||||
|
||||
Kernel and ramdisk images are downloaded by cache_tftp_images,
|
||||
and stored in /tftpboot/{uuid}/
|
||||
|
||||
This method writes the instances config file, and then creates
|
||||
symlinks for each MAC address in the instance.
|
||||
|
||||
By default, the complete layout looks like this:
|
||||
|
||||
/tftpboot/
|
||||
./{uuid}/
|
||||
kernel
|
||||
ramdisk
|
||||
deploy_kernel
|
||||
deploy_ramdisk
|
||||
config
|
||||
./pxelinux.cfg/
|
||||
{mac} -> ../{uuid}/config
|
||||
"""
|
||||
flavor = objects.Flavor.get_by_id(context,
|
||||
instance['instance_type_id'])
|
||||
image_info = get_tftp_image_info(instance, flavor)
|
||||
(root_mb, swap_mb, ephemeral_mb) = get_partition_sizes(instance)
|
||||
pxe_config_file_path = get_pxe_config_file_path(instance)
|
||||
image_file_path = get_image_file_path(instance)
|
||||
|
||||
deployment_key = bm_utils.random_alnum(32)
|
||||
deployment_iscsi_iqn = "iqn-%s" % instance['uuid']
|
||||
db.bm_node_update(context, node['id'],
|
||||
{'deploy_key': deployment_key,
|
||||
'image_path': image_file_path,
|
||||
'pxe_config_path': pxe_config_file_path,
|
||||
'root_mb': root_mb,
|
||||
'swap_mb': swap_mb,
|
||||
'ephemeral_mb': ephemeral_mb})
|
||||
pxe_config = build_pxe_config(
|
||||
node['id'],
|
||||
deployment_key,
|
||||
deployment_iscsi_iqn,
|
||||
image_info['deploy_kernel'][1],
|
||||
image_info['deploy_ramdisk'][1],
|
||||
image_info['kernel'][1],
|
||||
image_info['ramdisk'][1],
|
||||
network_info,
|
||||
)
|
||||
bm_utils.write_to_file(pxe_config_file_path, pxe_config)
|
||||
|
||||
macs = self._collect_mac_addresses(context, node)
|
||||
for mac in macs:
|
||||
mac_path = get_pxe_mac_path(mac)
|
||||
bm_utils.unlink_without_raise(mac_path)
|
||||
bm_utils.create_link_without_raise(pxe_config_file_path, mac_path)
|
||||
|
||||
def deactivate_bootloader(self, context, node, instance):
|
||||
"""Delete PXE bootloader images and config."""
|
||||
try:
|
||||
db.bm_node_update(context, node['id'],
|
||||
{'deploy_key': None,
|
||||
'image_path': None,
|
||||
'pxe_config_path': None,
|
||||
'root_mb': 0,
|
||||
'swap_mb': 0})
|
||||
except exception.NodeNotFound:
|
||||
pass
|
||||
|
||||
# NOTE(danms): the flavor extra_specs do not need to be
|
||||
# present/correct at deactivate time, so pass something empty
|
||||
# to avoid an extra lookup
|
||||
flavor = dict(extra_specs={
|
||||
'baremetal:deploy_ramdisk_id': 'ignore',
|
||||
'baremetal:deploy_kernel_id': 'ignore'})
|
||||
try:
|
||||
image_info = get_tftp_image_info(instance, flavor)
|
||||
except exception.NovaException:
|
||||
pass
|
||||
else:
|
||||
for label in image_info.keys():
|
||||
(uuid, path) = image_info[label]
|
||||
bm_utils.unlink_without_raise(path)
|
||||
|
||||
bm_utils.unlink_without_raise(get_pxe_config_file_path(instance))
|
||||
try:
|
||||
macs = self._collect_mac_addresses(context, node)
|
||||
except db_exc.DBError:
|
||||
pass
|
||||
else:
|
||||
for mac in macs:
|
||||
bm_utils.unlink_without_raise(get_pxe_mac_path(mac))
|
||||
|
||||
bm_utils.rmtree_without_raise(
|
||||
os.path.join(CONF.baremetal.tftp_root, instance['uuid']))
|
||||
|
||||
def activate_node(self, context, node, instance):
|
||||
"""Wait for PXE deployment to complete."""
|
||||
|
||||
locals = {'error': '', 'started': False}
|
||||
|
||||
def _wait_for_deploy():
|
||||
"""Called at an interval until the deployment completes."""
|
||||
try:
|
||||
row = db.bm_node_get(context, node['id'])
|
||||
if instance['uuid'] != row.get('instance_uuid'):
|
||||
locals['error'] = _("Node associated with another instance"
|
||||
" while waiting for deploy of %s")
|
||||
raise loopingcall.LoopingCallDone()
|
||||
|
||||
status = row.get('task_state')
|
||||
if (status == baremetal_states.DEPLOYING
|
||||
and locals['started'] is False):
|
||||
LOG.info(_("PXE deploy started for instance %s")
|
||||
% instance['uuid'])
|
||||
locals['started'] = True
|
||||
elif status in (baremetal_states.DEPLOYDONE,
|
||||
baremetal_states.ACTIVE):
|
||||
LOG.info(_("PXE deploy completed for instance %s")
|
||||
% instance['uuid'])
|
||||
raise loopingcall.LoopingCallDone()
|
||||
elif status == baremetal_states.DEPLOYFAIL:
|
||||
locals['error'] = _("PXE deploy failed for instance %s")
|
||||
except exception.NodeNotFound:
|
||||
locals['error'] = _("Baremetal node deleted while waiting "
|
||||
"for deployment of instance %s")
|
||||
|
||||
if (CONF.baremetal.pxe_deploy_timeout and
|
||||
timeutils.utcnow() > expiration):
|
||||
locals['error'] = _("Timeout reached while waiting for "
|
||||
"PXE deploy of instance %s")
|
||||
if locals['error']:
|
||||
raise loopingcall.LoopingCallDone()
|
||||
|
||||
expiration = timeutils.utcnow() + datetime.timedelta(
|
||||
seconds=CONF.baremetal.pxe_deploy_timeout)
|
||||
timer = loopingcall.FixedIntervalLoopingCall(_wait_for_deploy)
|
||||
timer.start(interval=1).wait()
|
||||
|
||||
if locals['error']:
|
||||
raise exception.InstanceDeployFailure(
|
||||
locals['error'] % instance['uuid'])
|
||||
|
||||
def deactivate_node(self, context, node, instance):
|
||||
pass
|
||||
@@ -1,11 +0,0 @@
|
||||
default deploy
|
||||
|
||||
label deploy
|
||||
kernel {{ pxe_options.deployment_aki_path }}
|
||||
append initrd={{ pxe_options.deployment_ari_path }} selinux=0 disk=cciss/c0d0,sda,hda,vda iscsi_target_iqn={{ pxe_options.deployment_iscsi_iqn }} deployment_id={{ pxe_options.deployment_id }} deployment_key={{ pxe_options.deployment_key }} troubleshoot=0 {{ pxe_options.pxe_append_params|default("", true) }}
|
||||
ipappend 3
|
||||
|
||||
|
||||
label boot
|
||||
kernel {{ pxe_options.aki_path }}
|
||||
append initrd={{ pxe_options.ari_path }} root={{ ROOT }} ro {{ pxe_options.pxe_append_params|default("", true) }} {{ pxe_options.pxe_network_config|default("", true) }}
|
||||
@@ -1,351 +0,0 @@
|
||||
# Copyright (c) 2011-2013 University of Southern California / ISI
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Class for Tilera bare-metal nodes.
|
||||
"""
|
||||
|
||||
import base64
|
||||
import os
|
||||
|
||||
import jinja2
|
||||
from oslo.config import cfg
|
||||
from oslo.db import exception as db_exc
|
||||
|
||||
from nova.compute import flavors
|
||||
from nova import exception
|
||||
from nova.i18n import _
|
||||
from nova.openstack.common import fileutils
|
||||
from nova.openstack.common import log as logging
|
||||
from nova import utils
|
||||
from nova.virt.baremetal import baremetal_states
|
||||
from nova.virt.baremetal import base
|
||||
from nova.virt.baremetal import db
|
||||
from nova.virt.baremetal import utils as bm_utils
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.import_opt('use_ipv6', 'nova.netconf')
|
||||
CONF.import_opt('net_config_template', 'nova.virt.baremetal.pxe',
|
||||
group='baremetal')
|
||||
|
||||
|
||||
def build_network_config(network_info):
|
||||
interfaces = bm_utils.map_network_interfaces(network_info, CONF.use_ipv6)
|
||||
tmpl_path, tmpl_file = os.path.split(CONF.baremetal.net_config_template)
|
||||
env = jinja2.Environment(loader=jinja2.FileSystemLoader(tmpl_path))
|
||||
template = env.get_template(tmpl_file)
|
||||
return template.render({'interfaces': interfaces,
|
||||
'use_ipv6': CONF.use_ipv6})
|
||||
|
||||
|
||||
def get_image_dir_path(instance):
|
||||
"""Generate the dir for an instances disk."""
|
||||
return os.path.join(CONF.instances_path, instance['name'])
|
||||
|
||||
|
||||
def get_image_file_path(instance):
|
||||
"""Generate the full path for an instances disk."""
|
||||
return os.path.join(CONF.instances_path, instance['name'], 'disk')
|
||||
|
||||
|
||||
def get_tilera_nfs_path(node_id):
|
||||
"""Generate the path for an instances Tilera nfs."""
|
||||
tilera_nfs_dir = "fs_" + str(node_id)
|
||||
return os.path.join(CONF.baremetal.tftp_root, tilera_nfs_dir)
|
||||
|
||||
|
||||
def get_partition_sizes(instance):
|
||||
flavor = flavors.extract_flavor(instance)
|
||||
root_mb = flavor['root_gb'] * 1024
|
||||
swap_mb = flavor['swap']
|
||||
|
||||
if swap_mb < 1:
|
||||
swap_mb = 1
|
||||
|
||||
return (root_mb, swap_mb)
|
||||
|
||||
|
||||
def get_tftp_image_info(instance):
|
||||
"""Generate the paths for tftp files for this instance.
|
||||
|
||||
Raises NovaException if
|
||||
- instance does not contain kernel_id
|
||||
"""
|
||||
image_info = {
|
||||
'kernel': [None, None],
|
||||
}
|
||||
try:
|
||||
image_info['kernel'][0] = str(instance['kernel_id'])
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
missing_labels = []
|
||||
for label in image_info.keys():
|
||||
(uuid, path) = image_info[label]
|
||||
if not uuid:
|
||||
missing_labels.append(label)
|
||||
else:
|
||||
image_info[label][1] = os.path.join(CONF.baremetal.tftp_root,
|
||||
instance['uuid'], label)
|
||||
if missing_labels:
|
||||
raise exception.NovaException(_(
|
||||
"Can not activate Tilera bootloader. "
|
||||
"The following boot parameters "
|
||||
"were not passed to baremetal driver: %s") % missing_labels)
|
||||
return image_info
|
||||
|
||||
|
||||
class Tilera(base.NodeDriver):
|
||||
"""Tilera bare metal driver."""
|
||||
|
||||
def __init__(self, virtapi):
|
||||
super(Tilera, self).__init__(virtapi)
|
||||
|
||||
def _collect_mac_addresses(self, context, node):
|
||||
macs = set()
|
||||
for nic in db.bm_interface_get_all_by_bm_node_id(context, node['id']):
|
||||
if nic['address']:
|
||||
macs.add(nic['address'])
|
||||
return sorted(macs)
|
||||
|
||||
def _cache_tftp_images(self, context, instance, image_info):
|
||||
"""Fetch the necessary kernels and ramdisks for the instance."""
|
||||
fileutils.ensure_tree(
|
||||
os.path.join(CONF.baremetal.tftp_root, instance['uuid']))
|
||||
|
||||
LOG.debug("Fetching kernel and ramdisk for instance %s",
|
||||
instance['name'])
|
||||
for label in image_info.keys():
|
||||
(uuid, path) = image_info[label]
|
||||
bm_utils.cache_image(
|
||||
context=context,
|
||||
target=path,
|
||||
image_id=uuid,
|
||||
user_id=instance['user_id'],
|
||||
project_id=instance['project_id'],
|
||||
)
|
||||
|
||||
def _cache_image(self, context, instance, image_meta):
|
||||
"""Fetch the instance's image from Glance
|
||||
|
||||
This method pulls the relevant AMI and associated kernel and ramdisk,
|
||||
and the deploy kernel and ramdisk from Glance, and writes them
|
||||
to the appropriate places on local disk.
|
||||
|
||||
Both sets of kernel and ramdisk are needed for Tilera booting, so these
|
||||
are stored under CONF.baremetal.tftp_root.
|
||||
|
||||
At present, the AMI is cached and certain files are injected.
|
||||
Debian/ubuntu-specific assumptions are made regarding the injected
|
||||
files. In a future revision, this functionality will be replaced by a
|
||||
more scalable and os-agnostic approach: the deployment ramdisk will
|
||||
fetch from Glance directly, and write its own last-mile configuration.
|
||||
"""
|
||||
fileutils.ensure_tree(get_image_dir_path(instance))
|
||||
image_path = get_image_file_path(instance)
|
||||
|
||||
LOG.debug("Fetching image %(ami)s for instance %(name)s",
|
||||
{'ami': image_meta['id'], 'name': instance['name']})
|
||||
bm_utils.cache_image(context=context,
|
||||
target=image_path,
|
||||
image_id=image_meta['id'],
|
||||
user_id=instance['user_id'],
|
||||
project_id=instance['project_id'],
|
||||
clean=True,
|
||||
)
|
||||
|
||||
return [image_meta['id'], image_path]
|
||||
|
||||
def _inject_into_image(self, context, node, instance, network_info,
|
||||
injected_files=None, admin_password=None):
|
||||
"""Inject last-mile configuration into instances image
|
||||
|
||||
Much of this method is a hack around DHCP and cloud-init
|
||||
not working together with baremetal provisioning yet.
|
||||
"""
|
||||
partition = None
|
||||
if not instance['kernel_id']:
|
||||
partition = "1"
|
||||
|
||||
ssh_key = None
|
||||
if 'key_data' in instance and instance['key_data']:
|
||||
ssh_key = str(instance['key_data'])
|
||||
|
||||
if injected_files is None:
|
||||
injected_files = []
|
||||
else:
|
||||
injected_files = list(injected_files)
|
||||
|
||||
net_config = build_network_config(network_info)
|
||||
|
||||
if instance['hostname']:
|
||||
injected_files.append(('/etc/hostname', instance['hostname']))
|
||||
|
||||
LOG.debug("Injecting files into image for instance %(name)s",
|
||||
{'name': instance['name']})
|
||||
|
||||
bm_utils.inject_into_image(
|
||||
image=get_image_file_path(instance),
|
||||
key=ssh_key,
|
||||
net=net_config,
|
||||
metadata=utils.instance_meta(instance),
|
||||
admin_password=admin_password,
|
||||
files=injected_files,
|
||||
partition=partition,
|
||||
)
|
||||
|
||||
def cache_images(self, context, node, instance,
|
||||
admin_password, image_meta, injected_files, network_info):
|
||||
"""Prepare all the images for this instance."""
|
||||
tftp_image_info = get_tftp_image_info(instance)
|
||||
self._cache_tftp_images(context, instance, tftp_image_info)
|
||||
|
||||
self._cache_image(context, instance, image_meta)
|
||||
self._inject_into_image(context, node, instance, network_info,
|
||||
injected_files, admin_password)
|
||||
|
||||
def destroy_images(self, context, node, instance):
|
||||
"""Delete instance's image file."""
|
||||
bm_utils.unlink_without_raise(get_image_file_path(instance))
|
||||
bm_utils.rmtree_without_raise(get_image_dir_path(instance))
|
||||
|
||||
def activate_bootloader(self, context, node, instance, network_info):
|
||||
"""Configure Tilera boot loader for an instance
|
||||
|
||||
Kernel and ramdisk images are downloaded by cache_tftp_images,
|
||||
and stored in /tftpboot/{uuid}/
|
||||
|
||||
This method writes the instances config file, and then creates
|
||||
symlinks for each MAC address in the instance.
|
||||
|
||||
By default, the complete layout looks like this::
|
||||
|
||||
/tftpboot/
|
||||
./{uuid}/
|
||||
kernel
|
||||
./fs_node_id/
|
||||
|
||||
"""
|
||||
get_tftp_image_info(instance)
|
||||
(root_mb, swap_mb) = get_partition_sizes(instance)
|
||||
tilera_nfs_path = get_tilera_nfs_path(node['id'])
|
||||
image_file_path = get_image_file_path(instance)
|
||||
|
||||
deployment_key = bm_utils.random_alnum(32)
|
||||
db.bm_node_update(context, node['id'],
|
||||
{'deploy_key': deployment_key,
|
||||
'image_path': image_file_path,
|
||||
'pxe_config_path': tilera_nfs_path,
|
||||
'root_mb': root_mb,
|
||||
'swap_mb': swap_mb})
|
||||
|
||||
if os.path.exists(image_file_path) and \
|
||||
os.path.exists(tilera_nfs_path):
|
||||
utils.execute('mount', '-o', 'loop', image_file_path,
|
||||
tilera_nfs_path, run_as_root=True)
|
||||
|
||||
def deactivate_bootloader(self, context, node, instance):
|
||||
"""Delete Tilera bootloader images and config."""
|
||||
try:
|
||||
db.bm_node_update(context, node['id'],
|
||||
{'deploy_key': None,
|
||||
'image_path': None,
|
||||
'pxe_config_path': None,
|
||||
'root_mb': 0,
|
||||
'swap_mb': 0})
|
||||
except exception.NodeNotFound:
|
||||
pass
|
||||
|
||||
tilera_nfs_path = get_tilera_nfs_path(node['id'])
|
||||
|
||||
if os.path.ismount(tilera_nfs_path):
|
||||
utils.execute('rpc.mountd', run_as_root=True)
|
||||
utils.execute('umount', '-f', tilera_nfs_path, run_as_root=True)
|
||||
|
||||
try:
|
||||
image_info = get_tftp_image_info(instance)
|
||||
except exception.NovaException:
|
||||
pass
|
||||
else:
|
||||
for label in image_info.keys():
|
||||
(uuid, path) = image_info[label]
|
||||
bm_utils.unlink_without_raise(path)
|
||||
|
||||
try:
|
||||
self._collect_mac_addresses(context, node)
|
||||
except db_exc.DBError:
|
||||
pass
|
||||
|
||||
if os.path.exists(os.path.join(CONF.baremetal.tftp_root,
|
||||
instance['uuid'])):
|
||||
bm_utils.rmtree_without_raise(
|
||||
os.path.join(CONF.baremetal.tftp_root, instance['uuid']))
|
||||
|
||||
def _iptables_set(self, node_ip, user_data):
|
||||
"""Sets security setting (iptables:port) if needed.
|
||||
|
||||
iptables -A INPUT -p tcp ! -s $IP --dport $PORT -j DROP
|
||||
/tftpboot/iptables_rule script sets iptables rule on the given node.
|
||||
"""
|
||||
rule_path = CONF.baremetal.tftp_root + "/iptables_rule"
|
||||
if user_data is not None:
|
||||
open_ip = base64.b64decode(user_data)
|
||||
utils.execute(rule_path, node_ip, open_ip)
|
||||
|
||||
def activate_node(self, context, node, instance):
|
||||
"""Wait for Tilera deployment to complete."""
|
||||
|
||||
locals = {'error': '', 'started': False}
|
||||
|
||||
try:
|
||||
row = db.bm_node_get(context, node['id'])
|
||||
if instance['uuid'] != row.get('instance_uuid'):
|
||||
locals['error'] = _("Node associated with another instance"
|
||||
" while waiting for deploy of %s")
|
||||
|
||||
status = row.get('task_state')
|
||||
if (status == baremetal_states.DEPLOYING and
|
||||
locals['started'] is False):
|
||||
LOG.info(_('Tilera deploy started for instance %s')
|
||||
% instance['uuid'])
|
||||
locals['started'] = True
|
||||
elif status in (baremetal_states.DEPLOYDONE,
|
||||
baremetal_states.BUILDING,
|
||||
baremetal_states.ACTIVE):
|
||||
LOG.info(_("Tilera deploy completed for instance %s")
|
||||
% instance['uuid'])
|
||||
node_ip = node['pm_address']
|
||||
user_data = instance['user_data']
|
||||
try:
|
||||
self._iptables_set(node_ip, user_data)
|
||||
except Exception:
|
||||
self.deactivate_bootloader(context, node, instance)
|
||||
raise exception.NovaException(_("Node is "
|
||||
"unknown error state."))
|
||||
elif status == baremetal_states.DEPLOYFAIL:
|
||||
locals['error'] = _("Tilera deploy failed for instance %s")
|
||||
except exception.NodeNotFound:
|
||||
locals['error'] = _("Baremetal node deleted while waiting "
|
||||
"for deployment of instance %s")
|
||||
|
||||
if locals['error']:
|
||||
raise exception.InstanceDeployFailure(
|
||||
locals['error'] % instance['uuid'])
|
||||
|
||||
def deactivate_node(self, context, node, instance):
|
||||
pass
|
||||
@@ -1,169 +0,0 @@
|
||||
# coding=utf-8
|
||||
|
||||
# Copyright (c) 2011-2013 University of Southern California / ISI
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Baremetal PDU power manager.
|
||||
"""
|
||||
|
||||
import time
|
||||
|
||||
from oslo.config import cfg
|
||||
|
||||
from nova import exception
|
||||
from nova.i18n import _
|
||||
from nova.openstack.common import log as logging
|
||||
from nova.openstack.common import processutils
|
||||
from nova import utils
|
||||
from nova.virt.baremetal import baremetal_states
|
||||
from nova.virt.baremetal import base
|
||||
|
||||
opts = [
|
||||
cfg.StrOpt('tile_pdu_ip',
|
||||
default='10.0.100.1',
|
||||
help='IP address of tilera pdu'),
|
||||
cfg.StrOpt('tile_pdu_mgr',
|
||||
default='/tftpboot/pdu_mgr',
|
||||
help='Management script for tilera pdu'),
|
||||
cfg.IntOpt('tile_pdu_off',
|
||||
default=2,
|
||||
help='Power status of tilera PDU is OFF'),
|
||||
cfg.IntOpt('tile_pdu_on',
|
||||
default=1,
|
||||
help='Power status of tilera PDU is ON'),
|
||||
cfg.IntOpt('tile_pdu_status',
|
||||
default=9,
|
||||
help='Power status of tilera PDU'),
|
||||
cfg.IntOpt('tile_power_wait',
|
||||
default=9,
|
||||
help='Wait time in seconds until check the result '
|
||||
'after tilera power operations'),
|
||||
]
|
||||
|
||||
baremetal_group = cfg.OptGroup(name='baremetal',
|
||||
title='Baremetal Options')
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.register_group(baremetal_group)
|
||||
CONF.register_opts(opts, baremetal_group)
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Pdu(base.PowerManager):
|
||||
"""PDU Power Driver for Baremetal Nova Compute
|
||||
|
||||
This PowerManager class provides mechanism for controlling the power state
|
||||
of physical hardware via PDU calls.
|
||||
"""
|
||||
|
||||
def __init__(self, node, **kwargs):
|
||||
self.state = None
|
||||
self.retries = None
|
||||
self.node_id = node['id']
|
||||
self.address = node['pm_address']
|
||||
self.user = node['pm_user']
|
||||
self.password = node['pm_password']
|
||||
self.port = node['terminal_port']
|
||||
|
||||
if self.node_id is None:
|
||||
raise exception.InvalidParameterValue(_("Node id not supplied "
|
||||
"to PDU"))
|
||||
if self.address is None:
|
||||
raise exception.InvalidParameterValue(_("Address not supplied "
|
||||
"to PDU"))
|
||||
if self.user is None:
|
||||
raise exception.InvalidParameterValue(_("User not supplied "
|
||||
"to PDU"))
|
||||
if self.password is None:
|
||||
raise exception.InvalidParameterValue(_("Password not supplied "
|
||||
"to PDU"))
|
||||
|
||||
def _exec_pdutool(self, mode):
|
||||
"""Changes power state of the given node.
|
||||
|
||||
According to the mode (1-ON, 2-OFF, 3-REBOOT), power state can be
|
||||
changed. /tftpboot/pdu_mgr script handles power management of
|
||||
PDU (Power Distribution Unit).
|
||||
"""
|
||||
if mode == CONF.baremetal.tile_pdu_status:
|
||||
try:
|
||||
utils.execute('ping', '-c1', self.address,
|
||||
check_exit_code=True)
|
||||
return CONF.baremetal.tile_pdu_on
|
||||
except processutils.ProcessExecutionError:
|
||||
return CONF.baremetal.tile_pdu_off
|
||||
else:
|
||||
try:
|
||||
utils.execute(CONF.baremetal.tile_pdu_mgr,
|
||||
CONF.baremetal.tile_pdu_ip, mode)
|
||||
time.sleep(CONF.baremetal.tile_power_wait)
|
||||
return mode
|
||||
except processutils.ProcessExecutionError:
|
||||
LOG.exception(_("PDU failed"))
|
||||
|
||||
def _is_power(self, state):
|
||||
out_err = self._exec_pdutool(CONF.baremetal.tile_pdu_status)
|
||||
return out_err == state
|
||||
|
||||
def _power_on(self):
|
||||
"""Turn the power to this node ON."""
|
||||
|
||||
try:
|
||||
self._exec_pdutool(CONF.baremetal.tile_pdu_on)
|
||||
if self._is_power(CONF.baremetal.tile_pdu_on):
|
||||
self.state = baremetal_states.ACTIVE
|
||||
else:
|
||||
self.state = baremetal_states.ERROR
|
||||
except Exception:
|
||||
self.state = baremetal_states.ERROR
|
||||
LOG.exception(_("PDU power on failed"))
|
||||
|
||||
def _power_off(self):
|
||||
"""Turn the power to this node OFF."""
|
||||
|
||||
try:
|
||||
self._exec_pdutool(CONF.baremetal.tile_pdu_off)
|
||||
if self._is_power(CONF.baremetal.tile_pdu_off):
|
||||
self.state = baremetal_states.DELETED
|
||||
else:
|
||||
self.state = baremetal_states.ERROR
|
||||
except Exception:
|
||||
self.state = baremetal_states.ERROR
|
||||
LOG.exception(_("PDU power off failed"))
|
||||
|
||||
def activate_node(self):
|
||||
"""Turns the power to node ON."""
|
||||
if (self._is_power(CONF.baremetal.tile_pdu_on)
|
||||
and self.state == baremetal_states.ACTIVE):
|
||||
LOG.warning(_("Activate node called, but node %s "
|
||||
"is already active") % self.address)
|
||||
self._power_on()
|
||||
return self.state
|
||||
|
||||
def reboot_node(self):
|
||||
"""Cycles the power to a node."""
|
||||
self._power_off()
|
||||
self._power_on()
|
||||
return self.state
|
||||
|
||||
def deactivate_node(self):
|
||||
"""Turns the power to node OFF, regardless of current state."""
|
||||
self._power_off()
|
||||
return self.state
|
||||
|
||||
def is_power_on(self):
|
||||
return self._is_power(CONF.baremetal.tile_pdu_on)
|
||||
@@ -1,132 +0,0 @@
|
||||
# Copyright (c) 2012 NTT DOCOMO, INC.
|
||||
# Copyright 2014 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.
|
||||
|
||||
import errno
|
||||
import os
|
||||
import shutil
|
||||
|
||||
from nova.i18n import _
|
||||
from nova.openstack.common import log as logging
|
||||
from nova.virt.disk import api as disk_api
|
||||
from nova.virt import images
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def cache_image(context, target, image_id, user_id, project_id, clean=False):
|
||||
if clean and os.path.exists(target):
|
||||
os.unlink(target)
|
||||
if not os.path.exists(target):
|
||||
images.fetch_to_raw(context, image_id, target, user_id, project_id)
|
||||
|
||||
|
||||
def inject_into_image(image, key, net, metadata, admin_password, files,
|
||||
partition, use_cow=False):
|
||||
try:
|
||||
if os.path.exists(image):
|
||||
disk_api.inject_data(image, key, net, metadata, admin_password,
|
||||
files, partition, use_cow)
|
||||
else:
|
||||
LOG.warning(_('Image %s not found on disk storage. '
|
||||
'Continue without injecting data'), image)
|
||||
except Exception as e:
|
||||
LOG.warn(_("Failed to inject data into image %(image)s. "
|
||||
"Error: %(e)s"), {'image': image, 'e': e})
|
||||
|
||||
|
||||
def unlink_without_raise(path):
|
||||
try:
|
||||
os.unlink(path)
|
||||
except OSError as e:
|
||||
if e.errno == errno.ENOENT:
|
||||
return
|
||||
else:
|
||||
LOG.warn(_("Failed to unlink %(path)s, error: %(e)s"),
|
||||
{'path': path, 'e': e})
|
||||
|
||||
|
||||
def rmtree_without_raise(path):
|
||||
try:
|
||||
if os.path.isdir(path):
|
||||
shutil.rmtree(path)
|
||||
except OSError as e:
|
||||
LOG.warn(_("Failed to remove dir %(path)s, error: %(e)s"),
|
||||
{'path': path, 'e': e})
|
||||
|
||||
|
||||
def write_to_file(path, contents):
|
||||
with open(path, 'w') as f:
|
||||
f.write(contents)
|
||||
|
||||
|
||||
def create_link_without_raise(source, link):
|
||||
try:
|
||||
os.symlink(source, link)
|
||||
except OSError as e:
|
||||
if e.errno == errno.EEXIST:
|
||||
return
|
||||
else:
|
||||
LOG.warn(_("Failed to create symlink from %(source)s to %(link)s"
|
||||
", error: %(e)s"),
|
||||
{'source': source, 'link': link, 'e': e})
|
||||
|
||||
|
||||
def random_alnum(count):
|
||||
import random
|
||||
import string
|
||||
chars = string.ascii_uppercase + string.digits
|
||||
return "".join(random.choice(chars) for _ in range(count))
|
||||
|
||||
|
||||
def map_network_interfaces(network_info, use_ipv6=False):
|
||||
# TODO(deva): fix assumption that device names begin with "eth"
|
||||
# and fix assumption about ordering
|
||||
if not isinstance(network_info, list):
|
||||
network_info = [network_info]
|
||||
|
||||
interfaces = []
|
||||
for id, vif in enumerate(network_info):
|
||||
address_v6 = gateway_v6 = netmask_v6 = None
|
||||
address_v4 = gateway_v4 = netmask_v4 = dns_v4 = None
|
||||
|
||||
if use_ipv6:
|
||||
subnets_v6 = [s for s in vif['network']['subnets']
|
||||
if s['version'] == 6]
|
||||
if len(subnets_v6):
|
||||
address_v6 = subnets_v6[0]['ips'][0]['address']
|
||||
netmask_v6 = subnets_v6[0].as_netaddr()._prefixlen
|
||||
gateway_v6 = subnets_v6[0]['gateway']['address']
|
||||
|
||||
subnets_v4 = [s for s in vif['network']['subnets']
|
||||
if s['version'] == 4]
|
||||
if len(subnets_v4):
|
||||
address_v4 = subnets_v4[0]['ips'][0]['address']
|
||||
netmask_v4 = subnets_v4[0].as_netaddr().netmask
|
||||
gateway_v4 = subnets_v4[0]['gateway']['address']
|
||||
dns_v4 = ' '.join([x['address'] for x in subnets_v4[0]['dns']])
|
||||
|
||||
interface = {
|
||||
'name': 'eth%d' % id,
|
||||
'address': address_v4,
|
||||
'gateway': gateway_v4,
|
||||
'netmask': netmask_v4,
|
||||
'dns': dns_v4,
|
||||
'address_v6': address_v6,
|
||||
'gateway_v6': gateway_v6,
|
||||
'netmask_v6': netmask_v6,
|
||||
}
|
||||
interfaces.append(interface)
|
||||
return interfaces
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user