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 +++++ nova/virt/baremetal/db/__init__.py | 16 + nova/virt/baremetal/db/api.py | 175 ++++++++++ nova/virt/baremetal/db/migration.py | 38 +++ nova/virt/baremetal/db/sqlalchemy/__init__.py | 14 + nova/virt/baremetal/db/sqlalchemy/api.py | 351 +++++++++++++++++++++ .../db/sqlalchemy/migrate_repo/__init__.py | 14 + .../db/sqlalchemy/migrate_repo/migrate.cfg | 20 ++ .../sqlalchemy/migrate_repo/versions/001_init.py | 124 ++++++++ .../sqlalchemy/migrate_repo/versions/__init__.py | 14 + nova/virt/baremetal/db/sqlalchemy/migration.py | 115 +++++++ nova/virt/baremetal/db/sqlalchemy/models.py | 80 +++++ nova/virt/baremetal/db/sqlalchemy/session.py | 58 ++++ nova/virt/baremetal/doc/README.rst | 69 ++++ 19 files changed, 1514 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 create mode 100644 nova/virt/baremetal/db/__init__.py create mode 100644 nova/virt/baremetal/db/api.py create mode 100644 nova/virt/baremetal/db/migration.py create mode 100644 nova/virt/baremetal/db/sqlalchemy/__init__.py create mode 100644 nova/virt/baremetal/db/sqlalchemy/api.py create mode 100644 nova/virt/baremetal/db/sqlalchemy/migrate_repo/__init__.py create mode 100644 nova/virt/baremetal/db/sqlalchemy/migrate_repo/migrate.cfg create mode 100644 nova/virt/baremetal/db/sqlalchemy/migrate_repo/versions/001_init.py create mode 100644 nova/virt/baremetal/db/sqlalchemy/migrate_repo/versions/__init__.py create mode 100644 nova/virt/baremetal/db/sqlalchemy/migration.py create mode 100644 nova/virt/baremetal/db/sqlalchemy/models.py create mode 100644 nova/virt/baremetal/db/sqlalchemy/session.py create mode 100644 nova/virt/baremetal/doc/README.rst 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 diff --git a/nova/virt/baremetal/db/__init__.py b/nova/virt/baremetal/db/__init__.py new file mode 100644 index 000000000..ad883f505 --- /dev/null +++ b/nova/virt/baremetal/db/__init__.py @@ -0,0 +1,16 @@ +# 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 * diff --git a/nova/virt/baremetal/db/api.py b/nova/virt/baremetal/db/api.py new file mode 100644 index 000000000..a9b6b3fe2 --- /dev/null +++ b/nova/virt/baremetal/db/api.py @@ -0,0 +1,175 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# 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 nova import config +from nova.openstack.common import cfg +from nova import utils + + +db_opts = [ + cfg.StrOpt('baremetal_db_backend', + default='sqlalchemy', + help='The backend to use for db'), + ] + +CONF = config.CONF +CONF.register_opts(db_opts) + +IMPL = utils.LazyPluggable( + 'baremetal_db_backend', + 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_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_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_pxe_ip_create(context, address, server_address): + return IMPL.bm_pxe_ip_create(context, address, server_address) + + +def bm_pxe_ip_create_direct(context, bm_pxe_ip): + return IMPL.bm_pxe_ip_create_direct(context, bm_pxe_ip) + + +def bm_pxe_ip_destroy(context, ip_id): + return IMPL.bm_pxe_ip_destroy(context, ip_id) + + +def bm_pxe_ip_destroy_by_address(context, address): + return IMPL.bm_pxe_ip_destroy_by_address(context, address) + + +def bm_pxe_ip_get_all(context): + return IMPL.bm_pxe_ip_get_all(context) + + +def bm_pxe_ip_get(context, ip_id): + return IMPL.bm_pxe_ip_get(context, ip_id) + + +def bm_pxe_ip_get_by_bm_node_id(context, bm_node_id): + return IMPL.bm_pxe_ip_get_by_bm_node_id(context, bm_node_id) + + +def bm_pxe_ip_associate(context, bm_node_id): + return IMPL.bm_pxe_ip_associate(context, bm_node_id) + + +def bm_pxe_ip_disassociate(context, bm_node_id): + return IMPL.bm_pxe_ip_disassociate(context, bm_node_id) + + +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) + + +def bm_deployment_create(context, key, image_path, pxe_config_path, root_mb, + swap_mb): + return IMPL.bm_deployment_create(context, key, image_path, + pxe_config_path, root_mb, swap_mb) + + +def bm_deployment_get(context, dep_id): + return IMPL.bm_deployment_get(context, dep_id) + + +def bm_deployment_destroy(context, dep_id): + return IMPL.bm_deployment_destroy(context, dep_id) diff --git a/nova/virt/baremetal/db/migration.py b/nova/virt/baremetal/db/migration.py new file mode 100644 index 000000000..40631bf45 --- /dev/null +++ b/nova/virt/baremetal/db/migration.py @@ -0,0 +1,38 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# 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( + 'baremetal_db_backend', + sqlalchemy='nova.virt.baremetal.db.sqlalchemy.migration') + +INIT_VERSION = 0 + + +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() diff --git a/nova/virt/baremetal/db/sqlalchemy/__init__.py b/nova/virt/baremetal/db/sqlalchemy/__init__.py new file mode 100644 index 000000000..19071662c --- /dev/null +++ b/nova/virt/baremetal/db/sqlalchemy/__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/virt/baremetal/db/sqlalchemy/api.py b/nova/virt/baremetal/db/sqlalchemy/api.py new file mode 100644 index 000000000..1127d77e8 --- /dev/null +++ b/nova/virt/baremetal/db/sqlalchemy/api.py @@ -0,0 +1,351 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# 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.""" + +from sqlalchemy import and_ +from sqlalchemy.exc import IntegrityError +from sqlalchemy import or_ +from sqlalchemy.orm import joinedload +from sqlalchemy.orm import joinedload_all +from sqlalchemy.sql.expression import asc +from sqlalchemy.sql.expression import desc +from sqlalchemy.sql.expression import literal_column +from sqlalchemy.sql import func + +from nova.db.sqlalchemy.api import is_user_context +from nova.db.sqlalchemy.api import require_admin_context +from nova import exception +from nova.openstack.common import log as logging +from nova.openstack.common import timeutils +from nova.virt.baremetal.db.sqlalchemy import models +from nova.virt.baremetal.db.sqlalchemy.session import get_session + +LOG = logging.getLogger(__name__) + + +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 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 is_user_context(context): + query = query.filter_by(project_id=context.project_id) + + return query + + +def _save(ref, session=None): + if not session: + 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 + + +@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() + + +@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 == None) + 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() + + +@require_admin_context +def bm_node_get(context, bm_node_id): + result = model_query(context, models.BareMetalNode, read_deleted="no").\ + filter_by(id=bm_node_id).\ + first() + return result + + +@require_admin_context +def bm_node_get_by_instance_uuid(context, instance_uuid): + result = model_query(context, models.BareMetalNode, read_deleted="no").\ + filter_by(instance_uuid=instance_uuid).\ + first() + return result + + +@require_admin_context +def bm_node_create(context, values): + bm_node_ref = models.BareMetalNode() + bm_node_ref.update(values) + _save(bm_node_ref) + return bm_node_ref + + +@require_admin_context +def bm_node_update(context, bm_node_id, values, ): + model_query(context, models.BareMetalNode, read_deleted="no").\ + filter_by(id=bm_node_id).\ + update(values) + + +@require_admin_context +def bm_node_destroy(context, bm_node_id): + model_query(context, models.BareMetalNode).\ + filter_by(id=bm_node_id).\ + update({'deleted': True, + 'deleted_at': timeutils.utcnow(), + 'updated_at': literal_column('updated_at')}) + + +@require_admin_context +def bm_pxe_ip_get_all(context, session=None): + query = model_query(context, models.BareMetalPxeIp, read_deleted="no") + return query.all() + + +@require_admin_context +def bm_pxe_ip_create(context, address, server_address): + ref = models.BareMetalPxeIp() + ref.address = address + ref.server_address = server_address + _save(ref) + return ref + + +@require_admin_context +def bm_pxe_ip_create_direct(context, bm_pxe_ip): + ref = bm_pxe_ip_create(context, + address=bm_pxe_ip['address'], + server_address=bm_pxe_ip['server_address']) + return ref + + +@require_admin_context +def bm_pxe_ip_destroy(context, ip_id): + # Delete physically since it has unique columns + model_query(context, models.BareMetalPxeIp, read_deleted="no").\ + filter_by(id=ip_id).\ + delete() + + +@require_admin_context +def bm_pxe_ip_destroy_by_address(context, address): + # Delete physically since it has unique columns + model_query(context, models.BareMetalPxeIp, read_deleted="no").\ + filter_by(address=address).\ + delete() + + +@require_admin_context +def bm_pxe_ip_get(context, ip_id): + ref = model_query(context, models.BareMetalPxeIp, read_deleted="no").\ + filter_by(id=ip_id).\ + first() + return ref + + +@require_admin_context +def bm_pxe_ip_get_by_bm_node_id(context, bm_node_id): + ref = model_query(context, models.BareMetalPxeIp, read_deleted="no").\ + filter_by(bm_node_id=bm_node_id).\ + first() + return ref + + +@require_admin_context +def bm_pxe_ip_associate(context, bm_node_id): + session = get_session() + with session.begin(): + # Check if the node really exists + node_ref = model_query(context, models.BareMetalNode, + read_deleted="no", session=session).\ + filter_by(id=bm_node_id).\ + first() + if not node_ref: + raise exception.NovaException("bm_node %s not found" % bm_node_id) + # Check if the node already has a pxe_ip + ip_ref = model_query(context, models.BareMetalPxeIp, + read_deleted="no", session=session).\ + filter_by(bm_node_id=bm_node_id).\ + first() + if ip_ref: + return ip_ref.id + # with_lockmode('update') and filter_by(bm_node_id=None) will lock all + # records. It may cause a performance problem in high-concurrency + # environment. + ip_ref = model_query(context, models.BareMetalPxeIp, + read_deleted="no", session=session).\ + filter_by(bm_node_id=None).\ + with_lockmode('update').\ + first() + if not ip_ref: + raise exception.NovaException("free bm_pxe_ip not found") + ip_ref.bm_node_id = bm_node_id + session.add(ip_ref) + return ip_ref.id + + +@require_admin_context +def bm_pxe_ip_disassociate(context, bm_node_id): + model_query(context, models.BareMetalPxeIp, read_deleted="no").\ + filter_by(bm_node_id=bm_node_id).\ + update({'bm_node_id': None}) + + +@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() + return result + + +def bm_interface_get_all(context): + query = model_query(context, models.BareMetalInterface, + read_deleted="no") + return query.all() + + +@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() + + +@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 + + +@require_admin_context +def bm_interface_set_vif_uuid(context, if_id, vif_uuid): + session = get_session() + with session.begin(): + ref = model_query(context, models.BareMetalInterface, + read_deleted="no", session=session).\ + filter_by(id=if_id).\ + with_lockmode('update').\ + first() + if not ref: + raise exception.NovaException('interface id=%s is not found' % + if_id) + ref.vif_uuid = vif_uuid + try: + session.add(ref) + session.flush() + except IntegrityError: + raise exception.NovaException('vif_uuid %s is already assigned' % + vif_uuid) + + +@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() + return result + + +@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() + return result + + +@require_admin_context +def bm_deployment_create(context, key, image_path, pxe_config_path, root_mb, + swap_mb): + ref = models.BareMetalDeployment() + ref.key = key + ref.image_path = image_path + ref.pxe_config_path = pxe_config_path + ref.root_mb = root_mb + ref.swap_mb = swap_mb + _save(ref) + return ref.id + + +@require_admin_context +def bm_deployment_get(context, dep_id): + result = model_query(context, models.BareMetalDeployment, + read_deleted="no").\ + filter_by(id=dep_id).\ + first() + return result + + +@require_admin_context +def bm_deployment_destroy(context, dep_id): + model_query(context, models.BareMetalDeployment).\ + filter_by(id=dep_id).\ + update({'deleted': True, + 'deleted_at': timeutils.utcnow(), + 'updated_at': literal_column('updated_at')}) diff --git a/nova/virt/baremetal/db/sqlalchemy/migrate_repo/__init__.py b/nova/virt/baremetal/db/sqlalchemy/migrate_repo/__init__.py new file mode 100644 index 000000000..19071662c --- /dev/null +++ b/nova/virt/baremetal/db/sqlalchemy/migrate_repo/__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/virt/baremetal/db/sqlalchemy/migrate_repo/migrate.cfg b/nova/virt/baremetal/db/sqlalchemy/migrate_repo/migrate.cfg new file mode 100644 index 000000000..368e93a52 --- /dev/null +++ b/nova/virt/baremetal/db/sqlalchemy/migrate_repo/migrate.cfg @@ -0,0 +1,20 @@ +[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=[] diff --git a/nova/virt/baremetal/db/sqlalchemy/migrate_repo/versions/001_init.py b/nova/virt/baremetal/db/sqlalchemy/migrate_repo/versions/001_init.py new file mode 100644 index 000000000..d945755fc --- /dev/null +++ b/nova/virt/baremetal/db/sqlalchemy/migrate_repo/versions/001_init.py @@ -0,0 +1,124 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# 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 migrate import ForeignKeyConstraint +from sqlalchemy import Boolean, BigInteger, Column, DateTime, Float, ForeignKey +from sqlalchemy import Index, Integer, MetaData, String, Table, Text + +from nova.openstack.common import log as logging + +LOG = logging.getLogger(__name__) + + +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', + #mysql_charset='utf8' + ) + + 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', + #mysql_charset='utf8' + ) + + 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', + #mysql_charset='utf8' + ) + + 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', + #mysql_charset='utf8' + ) + + 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): + pass diff --git a/nova/virt/baremetal/db/sqlalchemy/migrate_repo/versions/__init__.py b/nova/virt/baremetal/db/sqlalchemy/migrate_repo/versions/__init__.py new file mode 100644 index 000000000..19071662c --- /dev/null +++ b/nova/virt/baremetal/db/sqlalchemy/migrate_repo/versions/__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/virt/baremetal/db/sqlalchemy/migration.py b/nova/virt/baremetal/db/sqlalchemy/migration.py new file mode 100644 index 000000000..473f6e6f6 --- /dev/null +++ b/nova/virt/baremetal/db/sqlalchemy/migration.py @@ -0,0 +1,115 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# 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 distutils.version as dist_version +import migrate +from migrate.versioning import util as migrate_util +import os +import sqlalchemy + +from nova import exception +from nova import flags +from nova.openstack.common import log as logging +from nova.virt.baremetal.db import migration +from nova.virt.baremetal.db.sqlalchemy.session import get_engine + +LOG = logging.getLogger(__name__) + + +@migrate_util.decorator +def patched_with_engine(f, *a, **kw): + url = a[0] + engine = migrate_util.construct_engine(url, **kw) + + try: + kw['engine'] = engine + return f(*a, **kw) + finally: + if isinstance(engine, migrate_util.Engine) and engine is not url: + migrate_util.log.debug('Disposing SQLAlchemy engine %s', engine) + engine.dispose() + + +# TODO(jkoelker) When migrate 0.7.3 is released and nova depends +# on that version or higher, this can be removed +MIN_PKG_VERSION = dist_version.StrictVersion('0.7.3') +if (not hasattr(migrate, '__version__') or + dist_version.StrictVersion(migrate.__version__) < MIN_PKG_VERSION): + migrate_util.with_engine = patched_with_engine + + +# NOTE(jkoelker) Delay importing migrate until we are patched +from migrate import exceptions as versioning_exceptions +from migrate.versioning import api as versioning_api +from migrate.versioning.repository import Repository + +FLAGS = flags.FLAGS + +_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(get_engine(), repository, version) + else: + return versioning_api.downgrade(get_engine(), repository, + version) + + +def db_version(): + repository = _find_migrate_repo() + try: + return versioning_api.db_version(get_engine(), repository) + except versioning_exceptions.DatabaseNotControlledError: + meta = sqlalchemy.MetaData() + engine = get_engine() + meta.reflect(bind=engine) + tables = meta.tables + if len(tables) == 0: + db_version_control(migration.INIT_VERSION) + return versioning_api.db_version(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_version_control(version=None): + repository = _find_migrate_repo() + versioning_api.version_control(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 diff --git a/nova/virt/baremetal/db/sqlalchemy/models.py b/nova/virt/baremetal/db/sqlalchemy/models.py new file mode 100644 index 000000000..c1ab191d0 --- /dev/null +++ b/nova/virt/baremetal/db/sqlalchemy/models.py @@ -0,0 +1,80 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# 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.orm import relationship, backref, object_mapper +from sqlalchemy import Column, Integer, BigInteger, String, schema +from sqlalchemy import ForeignKey, DateTime, Boolean, Text, Float, Index +from sqlalchemy.exc import IntegrityError +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.schema import ForeignKeyConstraint + +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) + service_host = Column(String(255)) + instance_uuid = Column(String(36), nullable=True) + cpus = Column(Integer) + memory_mb = Column(Integer) + local_gb = Column(Integer) + pm_address = Column(Text) + pm_user = Column(Text) + pm_password = Column(Text) + prov_mac_address = Column(Text) + registration_status = Column(String(16)) + task_state = Column(String(255)) + prov_vlan_id = Column(Integer) + terminal_port = Column(Integer) + + +class BareMetalPxeIp(BASE, models.NovaBase): + __tablename__ = 'bm_pxe_ips' + id = Column(Integer, primary_key=True) + address = Column(String(255), unique=True) + server_address = Column(String(255), unique=True) + bm_node_id = Column(Integer, ForeignKey('bm_nodes.id'), nullable=True) + + +class BareMetalInterface(BASE, models.NovaBase): + __tablename__ = 'bm_interfaces' + id = Column(Integer, primary_key=True) + bm_node_id = Column(Integer, ForeignKey('bm_nodes.id'), nullable=True) + address = Column(String(255), unique=True) + datapath_id = Column(String(255)) + port_no = Column(Integer) + vif_uuid = Column(String(36), unique=True) + + +class BareMetalDeployment(BASE, models.NovaBase): + __tablename__ = 'bm_deployments' + id = Column(Integer, primary_key=True) + key = Column(String(255)) + image_path = Column(String(255)) + pxe_config_path = Column(String(255)) + root_mb = Column(Integer) + swap_mb = Column(Integer) diff --git a/nova/virt/baremetal/db/sqlalchemy/session.py b/nova/virt/baremetal/db/sqlalchemy/session.py new file mode 100644 index 000000000..2cae17f18 --- /dev/null +++ b/nova/virt/baremetal/db/sqlalchemy/session.py @@ -0,0 +1,58 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# 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 nova import config +from nova.db.sqlalchemy import session as nova_session +from nova.openstack.common import cfg + +opts = [ + cfg.StrOpt('baremetal_sql_connection', + default='sqlite:///$state_path/baremetal_$sqlite_db', + help='The SQLAlchemy connection string used to connect to the ' + 'bare-metal database'), + ] + +CONF = config.CONF +CONF.register_opts(opts) + +_ENGINE = None +_MAKER = None + + +def get_session(autocommit=True, expire_on_commit=False): + """Return a SQLAlchemy session.""" + global _MAKER + + if _MAKER is None: + engine = get_engine() + _MAKER = nova_session.get_maker(engine, autocommit, expire_on_commit) + + session = _MAKER() + session = nova_session.wrap_session(session) + return session + + +def get_engine(): + """Return a SQLAlchemy engine.""" + global _ENGINE + if _ENGINE is None: + _ENGINE = nova_session.create_engine(CONF.baremetal_sql_connection) + return _ENGINE diff --git a/nova/virt/baremetal/doc/README.rst b/nova/virt/baremetal/doc/README.rst new file mode 100644 index 000000000..6d5cfd466 --- /dev/null +++ b/nova/virt/baremetal/doc/README.rst @@ -0,0 +1,69 @@ +General Bare-metal Provisioning README +====================================== + +:Authors: + [USC/ISI] Mikyung Kang , David Kang + + [NTT DOCOMO] Ken Igarashi + + [VirtualTech Japan Inc.] Arata Notsu +: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} + + # instance_type_extra_specs this baremetal compute + instanse_type_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 + -- cgit