summaryrefslogtreecommitdiffstats
path: root/nova
diff options
context:
space:
mode:
authorMikyung Kang <mkkang@isi.edu>2012-11-07 19:10:56 +0900
committerArata Notsu <notsu@virtualtech.jp>2012-11-13 20:56:20 +0900
commit56fe4c7620ae358bcbebf2a50dc8bce955334660 (patch)
tree77ff677f5683232fb471b3a53fe93f911ff594e2 /nova
parent3c9e48d1119b77428346680c2a009c44ff9bf2ce (diff)
downloadnova-56fe4c7620ae358bcbebf2a50dc8bce955334660.tar.gz
nova-56fe4c7620ae358bcbebf2a50dc8bce955334660.tar.xz
nova-56fe4c7620ae358bcbebf2a50dc8bce955334660.zip
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 <mkkang@isi.edu> Co-authored-by: David Kang <dkang@isi.edu> Co-authored-by: Ken Igarashi <igarashik@nttdocomo.co.jp> Co-authored-by: Arata Notsu <notsu@virtualtech.jp>
Diffstat (limited to 'nova')
-rw-r--r--nova/tests/baremetal/db/__init__.py14
-rw-r--r--nova/tests/baremetal/db/base.py51
-rw-r--r--nova/tests/baremetal/db/test_bm_interface.py47
-rw-r--r--nova/tests/baremetal/db/test_bm_node.py140
-rw-r--r--nova/tests/baremetal/db/test_bm_pxe_ip.py93
-rw-r--r--nova/tests/baremetal/db/utils.py81
-rw-r--r--nova/virt/baremetal/db/__init__.py16
-rw-r--r--nova/virt/baremetal/db/api.py175
-rw-r--r--nova/virt/baremetal/db/migration.py38
-rw-r--r--nova/virt/baremetal/db/sqlalchemy/__init__.py14
-rw-r--r--nova/virt/baremetal/db/sqlalchemy/api.py351
-rw-r--r--nova/virt/baremetal/db/sqlalchemy/migrate_repo/__init__.py14
-rw-r--r--nova/virt/baremetal/db/sqlalchemy/migrate_repo/migrate.cfg20
-rw-r--r--nova/virt/baremetal/db/sqlalchemy/migrate_repo/versions/001_init.py124
-rw-r--r--nova/virt/baremetal/db/sqlalchemy/migrate_repo/versions/__init__.py14
-rw-r--r--nova/virt/baremetal/db/sqlalchemy/migration.py115
-rw-r--r--nova/virt/baremetal/db/sqlalchemy/models.py80
-rw-r--r--nova/virt/baremetal/db/sqlalchemy/session.py58
-rw-r--r--nova/virt/baremetal/doc/README.rst69
19 files changed, 1514 insertions, 0 deletions
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 <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}
+
+ # 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
+