From 56fe4c7620ae358bcbebf2a50dc8bce955334660 Mon Sep 17 00:00:00 2001 From: Mikyung Kang Date: Wed, 7 Nov 2012 19:10:56 +0900 Subject: Added separate bare-metal MySQL DB. Part 2 of 6: blueprint general-bare-metal-provisioning-framework In baremetal provisioning, one nova-compute manages multiple bare-metal machines. A bare-metal machine does not run openstack at all. Previously, bare-metal provisioning used text files to store information of bare-metal machines. In this patch, a MySQL database is used to store the information. We target only MySQL database. The DB is designed to support PXE/non-PXE booting methods, heterogeneous hypervisor types, and architectures. Using a MySQL database makes maintenance and upgrades easier than using text files. The DB for bare-metal machines is implemented as a separate DB from the main Nova DB. The DB can be on any machines/places. The location of the DB and its server needs to be specified as a flag in the nova.conf file (as in the case of glance). There are a couple of reasons for this approach. First, the information needed for bare-metal machines is different from that for non-bare-metal machines. With a separate database for bare-metal machines, the database can be customized without affecting the main Nova DB. Second, fault tolerance can be embedded in nova-compute. Since one nova-compute manages multiple bare-metal machines, fault tolerance of a nova-compute node is very important. With a separate DB for bare-metal machines, fault-tolerance can be achieved independently from the main Nova DB. Replication of the bare-metal DB and implementation of fault-tolerance are not part of this patch. The implementation models nova and its DB as much as possible. The bare-metal driver must be upgraded to use this DB. Change-Id: I7b7ba1903a672a50c567f95fc6554d119463b0c5 Co-authored-by: Mikyung Kang Co-authored-by: David Kang Co-authored-by: Ken Igarashi Co-authored-by: Arata Notsu --- nova/tests/baremetal/db/__init__.py | 14 +++ nova/tests/baremetal/db/base.py | 51 ++++++++++ nova/tests/baremetal/db/test_bm_interface.py | 47 +++++++++ nova/tests/baremetal/db/test_bm_node.py | 140 +++++++++++++++++++++++++++ nova/tests/baremetal/db/test_bm_pxe_ip.py | 93 ++++++++++++++++++ nova/tests/baremetal/db/utils.py | 81 ++++++++++++++++ 6 files changed, 426 insertions(+) create mode 100644 nova/tests/baremetal/db/__init__.py create mode 100644 nova/tests/baremetal/db/base.py create mode 100644 nova/tests/baremetal/db/test_bm_interface.py create mode 100644 nova/tests/baremetal/db/test_bm_node.py create mode 100644 nova/tests/baremetal/db/test_bm_pxe_ip.py create mode 100644 nova/tests/baremetal/db/utils.py (limited to 'nova/tests') diff --git a/nova/tests/baremetal/db/__init__.py b/nova/tests/baremetal/db/__init__.py new file mode 100644 index 000000000..19071662c --- /dev/null +++ b/nova/tests/baremetal/db/__init__.py @@ -0,0 +1,14 @@ +# 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. diff --git a/nova/tests/baremetal/db/base.py b/nova/tests/baremetal/db/base.py new file mode 100644 index 000000000..83abcb58e --- /dev/null +++ b/nova/tests/baremetal/db/base.py @@ -0,0 +1,51 @@ +# 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 nova import config +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 = None + +CONF = config.CONF +CONF.import_opt('baremetal_sql_connection', + 'nova.virt.baremetal.db.sqlalchemy.session') + + +def _reset_bmdb(): + global _DB + engine = bm_session.get_engine() + engine.dispose() + conn = engine.connect() + if _DB is None: + if bm_migration.db_version() > bm_migration.INIT_VERSION: + return + bm_migration.db_sync() + _DB = "".join(line for line in conn.connection.iterdump()) + else: + conn.connection.executescript(_DB) + + +class BMDBTestCase(test.TestCase): + + def setUp(self): + super(BMDBTestCase, self).setUp() + self.flags(baremetal_sql_connection='sqlite:///:memory:') + _reset_bmdb() + self.context = nova_context.get_admin_context() diff --git a/nova/tests/baremetal/db/test_bm_interface.py b/nova/tests/baremetal/db/test_bm_interface.py new file mode 100644 index 000000000..6aef437c1 --- /dev/null +++ b/nova/tests/baremetal/db/test_bm_interface.py @@ -0,0 +1,47 @@ +# 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 nova import exception +from nova.tests.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(exception.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.assertTrue(pif2_id is not None) + + 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.DBError, + db.bm_interface_set_vif_uuid, + self.context, pif2_id, 'AAAA') diff --git a/nova/tests/baremetal/db/test_bm_node.py b/nova/tests/baremetal/db/test_bm_node.py new file mode 100644 index 000000000..062b209a6 --- /dev/null +++ b/nova/tests/baremetal/db/test_bm_node.py @@ -0,0 +1,140 @@ +# 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.tests.baremetal.db import base +from nova.tests.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_all0(self): + r = db.bm_node_get_all(self.context) + self.assertEquals(r, []) + + def test_get_all(self): + r = db.bm_node_get_all(self.context) + self.assertEquals(r, []) + + self._create_nodes() + + r = db.bm_node_get_all(self.context) + self.assertEquals(len(r), 6) + + def test_get(self): + self._create_nodes() + + r = db.bm_node_get(self.context, self.ids[0]) + self.assertEquals(r['pm_address'], '0') + + r = db.bm_node_get(self.context, self.ids[1]) + self.assertEquals(r['pm_address'], '1') + + r = db.bm_node_get(self.context, -1) + self.assertTrue(r is None) + + def test_get_by_service_host(self): + self._create_nodes() + + r = db.bm_node_get_all(self.context, service_host=None) + self.assertEquals(len(r), 6) + + r = db.bm_node_get_all(self.context, service_host="host1") + self.assertEquals(len(r), 1) + self.assertEquals(r[0]['pm_address'], '0') + + r = db.bm_node_get_all(self.context, service_host="host2") + self.assertEquals(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.assertEquals(r, []) + + def test_destroy(self): + self._create_nodes() + + db.bm_node_destroy(self.context, self.ids[0]) + + r = db.bm_node_get(self.context, self.ids[0]) + self.assertTrue(r is None) + + r = db.bm_node_get_all(self.context) + self.assertEquals(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.assertTrue(fn is None) + + # check cpus + fn = db.bm_node_find_free(self.context, 'host2', + memory_mb=2000, cpus=3, local_gb=1000) + self.assertTrue(fn is None) + + # check local_gb + fn = db.bm_node_find_free(self.context, 'host2', + memory_mb=2000, cpus=2, local_gb=1001) + self.assertTrue(fn is None) diff --git a/nova/tests/baremetal/db/test_bm_pxe_ip.py b/nova/tests/baremetal/db/test_bm_pxe_ip.py new file mode 100644 index 000000000..9a93b46ad --- /dev/null +++ b/nova/tests/baremetal/db/test_bm_pxe_ip.py @@ -0,0 +1,93 @@ +# 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 BareMetalPxeIp +""" + +from nova import exception +from nova.tests.baremetal.db import base +from nova.tests.baremetal.db import utils +from nova.virt.baremetal import db + + +class BareMetalPxeIpTestCase(base.BMDBTestCase): + + def _create_pxe_ip(self): + i1 = utils.new_bm_pxe_ip(address='10.1.1.1', + server_address='10.1.1.101') + i2 = utils.new_bm_pxe_ip(address='10.1.1.2', + server_address='10.1.1.102') + + i1_ref = db.bm_pxe_ip_create_direct(self.context, i1) + self.assertTrue(i1_ref['id'] is not None) + self.assertEqual(i1_ref['address'], '10.1.1.1') + self.assertEqual(i1_ref['server_address'], '10.1.1.101') + + i2_ref = db.bm_pxe_ip_create_direct(self.context, i2) + self.assertTrue(i2_ref['id'] is not None) + self.assertEqual(i2_ref['address'], '10.1.1.2') + self.assertEqual(i2_ref['server_address'], '10.1.1.102') + + self.i1 = i1_ref + self.i2 = i2_ref + + def test_unuque_address(self): + self._create_pxe_ip() + + # address duplicates + i = utils.new_bm_pxe_ip(address='10.1.1.1', + server_address='10.1.1.201') + self.assertRaises(exception.DBError, + db.bm_pxe_ip_create_direct, + self.context, i) + + # server_address duplicates + i = utils.new_bm_pxe_ip(address='10.1.1.3', + server_address='10.1.1.101') + self.assertRaises(exception.DBError, + db.bm_pxe_ip_create_direct, + self.context, i) + + db.bm_pxe_ip_destroy(self.context, self.i1['id']) + i = utils.new_bm_pxe_ip(address='10.1.1.1', + server_address='10.1.1.101') + ref = db.bm_pxe_ip_create_direct(self.context, i) + self.assertTrue(ref is not None) + + def test_bm_pxe_ip_associate(self): + self._create_pxe_ip() + node = db.bm_node_create(self.context, utils.new_bm_node()) + ip_id = db.bm_pxe_ip_associate(self.context, node['id']) + ref = db.bm_pxe_ip_get(self.context, ip_id) + self.assertEqual(ref['bm_node_id'], node['id']) + + def test_bm_pxe_ip_associate_raise(self): + self._create_pxe_ip() + node_id = 123 + self.assertRaises(exception.NovaException, + db.bm_pxe_ip_associate, + self.context, node_id) + + def test_delete_by_address(self): + self._create_pxe_ip() + db.bm_pxe_ip_destroy_by_address(self.context, '10.1.1.1') + del_ref = db.bm_pxe_ip_get(self.context, self.i1['id']) + self.assertTrue(del_ref is None) + + def test_delete_by_address_not_exist(self): + self._create_pxe_ip() + del_ref = db.bm_pxe_ip_destroy_by_address(self.context, '10.11.12.13') + self.assertTrue(del_ref is None) diff --git a/nova/tests/baremetal/db/utils.py b/nova/tests/baremetal/db/utils.py new file mode 100644 index 000000000..800305402 --- /dev/null +++ b/nova/tests/baremetal/db/utils.py @@ -0,0 +1,81 @@ +# 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.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.prov_mac_address = kwargs.pop('prov_mac_address', '12:34:56:78:90:ab') + h.registration_status = kwargs.pop('registration_status', 'done') + h.task_state = kwargs.pop('task_state', None) + h.prov_vlan_id = kwargs.pop('prov_vlan_id', 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_pxe_ip(**kwargs): + x = bm_models.BareMetalPxeIp() + x.id = kwargs.pop('id', None) + x.address = kwargs.pop('address', None) + x.server_address = kwargs.pop('server_address', None) + x.bm_node_id = kwargs.pop('bm_node_id', None) + if len(kwargs) > 0: + raise test.TestingException("unknown field: %s" + % ','.join(kwargs.keys())) + return x + + +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 + + +def new_bm_deployment(**kwargs): + x = bm_models.BareMetalDeployment() + x.id = kwargs.pop('id', None) + x.key = kwargs.pop('key', None) + x.image_path = kwargs.pop('image_path', None) + x.pxe_config_path = kwargs.pop('pxe_config_path', None) + x.root_mb = kwargs.pop('root_mb', None) + x.swap_mb = kwargs.pop('swap_mb', None) + if len(kwargs) > 0: + raise test.TestingException("unknown field: %s" + % ','.join(kwargs.keys())) + return x -- cgit