8ad36d15e4
When we delete a host mapping in the api database, we need to mark the compute node record as unmapped. Otherwise we can never re-discover it if added back. Change-Id: I9348827c5fd0364727e49cb8fae786cb2a29472a Closes-Bug: #1735719
320 lines
14 KiB
Python
320 lines
14 KiB
Python
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
from nova.cmd import manage
|
|
from nova import context
|
|
from nova import objects
|
|
from nova import test
|
|
|
|
|
|
class NovaManageDBIronicTest(test.TestCase):
|
|
def setUp(self):
|
|
super(NovaManageDBIronicTest, self).setUp()
|
|
self.commands = manage.DbCommands()
|
|
self.context = context.RequestContext('fake-user', 'fake-project')
|
|
|
|
self.service1 = objects.Service(context=self.context,
|
|
host='fake-host1',
|
|
binary='nova-compute',
|
|
topic='fake-host1',
|
|
report_count=1,
|
|
disabled=False,
|
|
disabled_reason=None,
|
|
availability_zone='nova',
|
|
forced_down=False)
|
|
self.service1.create()
|
|
|
|
self.service2 = objects.Service(context=self.context,
|
|
host='fake-host2',
|
|
binary='nova-compute',
|
|
topic='fake-host2',
|
|
report_count=1,
|
|
disabled=False,
|
|
disabled_reason=None,
|
|
availability_zone='nova',
|
|
forced_down=False)
|
|
self.service2.create()
|
|
|
|
self.service3 = objects.Service(context=self.context,
|
|
host='fake-host3',
|
|
binary='nova-compute',
|
|
topic='fake-host3',
|
|
report_count=1,
|
|
disabled=False,
|
|
disabled_reason=None,
|
|
availability_zone='nova',
|
|
forced_down=False)
|
|
self.service3.create()
|
|
|
|
self.cn1 = objects.ComputeNode(context=self.context,
|
|
service_id=self.service1.id,
|
|
host='fake-host1',
|
|
hypervisor_type='ironic',
|
|
vcpus=1,
|
|
memory_mb=1024,
|
|
local_gb=10,
|
|
vcpus_used=1,
|
|
memory_mb_used=1024,
|
|
local_gb_used=10,
|
|
hypervisor_version=0,
|
|
hypervisor_hostname='fake-node1',
|
|
cpu_info='{}')
|
|
self.cn1.create()
|
|
|
|
self.cn2 = objects.ComputeNode(context=self.context,
|
|
service_id=self.service1.id,
|
|
host='fake-host1',
|
|
hypervisor_type='ironic',
|
|
vcpus=1,
|
|
memory_mb=1024,
|
|
local_gb=10,
|
|
vcpus_used=1,
|
|
memory_mb_used=1024,
|
|
local_gb_used=10,
|
|
hypervisor_version=0,
|
|
hypervisor_hostname='fake-node2',
|
|
cpu_info='{}')
|
|
self.cn2.create()
|
|
|
|
self.cn3 = objects.ComputeNode(context=self.context,
|
|
service_id=self.service2.id,
|
|
host='fake-host2',
|
|
hypervisor_type='ironic',
|
|
vcpus=1,
|
|
memory_mb=1024,
|
|
local_gb=10,
|
|
vcpus_used=1,
|
|
memory_mb_used=1024,
|
|
local_gb_used=10,
|
|
hypervisor_version=0,
|
|
hypervisor_hostname='fake-node3',
|
|
cpu_info='{}')
|
|
self.cn3.create()
|
|
|
|
self.cn4 = objects.ComputeNode(context=self.context,
|
|
service_id=self.service3.id,
|
|
host='fake-host3',
|
|
hypervisor_type='libvirt',
|
|
vcpus=1,
|
|
memory_mb=1024,
|
|
local_gb=10,
|
|
vcpus_used=1,
|
|
memory_mb_used=1024,
|
|
local_gb_used=10,
|
|
hypervisor_version=0,
|
|
hypervisor_hostname='fake-node4',
|
|
cpu_info='{}')
|
|
self.cn4.create()
|
|
|
|
self.cn5 = objects.ComputeNode(context=self.context,
|
|
service_id=self.service2.id,
|
|
host='fake-host2',
|
|
hypervisor_type='ironic',
|
|
vcpus=1,
|
|
memory_mb=1024,
|
|
local_gb=10,
|
|
vcpus_used=1,
|
|
memory_mb_used=1024,
|
|
local_gb_used=10,
|
|
hypervisor_version=0,
|
|
hypervisor_hostname='fake-node5',
|
|
cpu_info='{}')
|
|
self.cn5.create()
|
|
|
|
self.insts = []
|
|
for cn in (self.cn1, self.cn2, self.cn3, self.cn4, self.cn4, self.cn5):
|
|
flavor = objects.Flavor(extra_specs={})
|
|
inst = objects.Instance(context=self.context,
|
|
user_id=self.context.user_id,
|
|
project_id=self.context.project_id,
|
|
flavor=flavor,
|
|
node=cn.hypervisor_hostname)
|
|
inst.create()
|
|
self.insts.append(inst)
|
|
|
|
self.ironic_insts = [i for i in self.insts
|
|
if i.node != self.cn4.hypervisor_hostname]
|
|
self.virt_insts = [i for i in self.insts
|
|
if i.node == self.cn4.hypervisor_hostname]
|
|
|
|
def test_ironic_flavor_migration_by_host_and_node(self):
|
|
ret = self.commands.ironic_flavor_migration('test', 'fake-host1',
|
|
'fake-node2', False, False)
|
|
self.assertEqual(0, ret)
|
|
k = 'resources:CUSTOM_TEST'
|
|
|
|
for inst in self.ironic_insts:
|
|
inst.refresh()
|
|
if inst.node == 'fake-node2':
|
|
self.assertIn(k, inst.flavor.extra_specs)
|
|
self.assertEqual('1', inst.flavor.extra_specs[k])
|
|
else:
|
|
self.assertNotIn(k, inst.flavor.extra_specs)
|
|
|
|
for inst in self.virt_insts:
|
|
inst.refresh()
|
|
self.assertNotIn(k, inst.flavor.extra_specs)
|
|
|
|
def test_ironic_flavor_migration_by_host(self):
|
|
ret = self.commands.ironic_flavor_migration('test', 'fake-host1', None,
|
|
False, False)
|
|
self.assertEqual(0, ret)
|
|
k = 'resources:CUSTOM_TEST'
|
|
|
|
for inst in self.ironic_insts:
|
|
inst.refresh()
|
|
if inst.node in ('fake-node1', 'fake-node2'):
|
|
self.assertIn(k, inst.flavor.extra_specs)
|
|
self.assertEqual('1', inst.flavor.extra_specs[k])
|
|
else:
|
|
self.assertNotIn(k, inst.flavor.extra_specs)
|
|
|
|
for inst in self.virt_insts:
|
|
inst.refresh()
|
|
self.assertNotIn(k, inst.flavor.extra_specs)
|
|
|
|
def test_ironic_flavor_migration_by_host_not_ironic(self):
|
|
ret = self.commands.ironic_flavor_migration('test', 'fake-host3', None,
|
|
False, False)
|
|
self.assertEqual(1, ret)
|
|
k = 'resources:CUSTOM_TEST'
|
|
|
|
for inst in self.ironic_insts:
|
|
inst.refresh()
|
|
self.assertNotIn(k, inst.flavor.extra_specs)
|
|
|
|
for inst in self.virt_insts:
|
|
inst.refresh()
|
|
self.assertNotIn(k, inst.flavor.extra_specs)
|
|
|
|
def test_ironic_flavor_migration_all_hosts(self):
|
|
ret = self.commands.ironic_flavor_migration('test', None, None,
|
|
True, False)
|
|
self.assertEqual(0, ret)
|
|
k = 'resources:CUSTOM_TEST'
|
|
|
|
for inst in self.ironic_insts:
|
|
inst.refresh()
|
|
self.assertIn(k, inst.flavor.extra_specs)
|
|
self.assertEqual('1', inst.flavor.extra_specs[k])
|
|
|
|
for inst in self.virt_insts:
|
|
inst.refresh()
|
|
self.assertNotIn(k, inst.flavor.extra_specs)
|
|
|
|
def test_ironic_flavor_migration_invalid(self):
|
|
# No host or node and not "all"
|
|
ret = self.commands.ironic_flavor_migration('test', None, None,
|
|
False, False)
|
|
self.assertEqual(3, ret)
|
|
|
|
# No host, only node
|
|
ret = self.commands.ironic_flavor_migration('test', None, 'fake-node',
|
|
False, False)
|
|
self.assertEqual(3, ret)
|
|
|
|
# Asked for all but provided a node
|
|
ret = self.commands.ironic_flavor_migration('test', None, 'fake-node',
|
|
True, False)
|
|
self.assertEqual(3, ret)
|
|
|
|
# Asked for all but provided a host
|
|
ret = self.commands.ironic_flavor_migration('test', 'fake-host', None,
|
|
True, False)
|
|
self.assertEqual(3, ret)
|
|
|
|
# Asked for all but provided a host and node
|
|
ret = self.commands.ironic_flavor_migration('test', 'fake-host',
|
|
'fake-node', True, False)
|
|
self.assertEqual(3, ret)
|
|
|
|
# Did not provide a resource_class
|
|
ret = self.commands.ironic_flavor_migration(None, 'fake-host',
|
|
'fake-node', False, False)
|
|
self.assertEqual(3, ret)
|
|
|
|
def test_ironic_flavor_migration_no_match(self):
|
|
ret = self.commands.ironic_flavor_migration('test', 'fake-nonexist',
|
|
None, False, False)
|
|
self.assertEqual(1, ret)
|
|
|
|
ret = self.commands.ironic_flavor_migration('test', 'fake-nonexist',
|
|
'fake-node', False, False)
|
|
self.assertEqual(1, ret)
|
|
|
|
def test_ironic_two_instances(self):
|
|
# NOTE(danms): This shouldn't be possible, but simulate it like
|
|
# someone hacked the database, which should also cover any other
|
|
# way this could happen.
|
|
|
|
# Since we created two instances on cn4 in setUp() we can convert that
|
|
# to an ironic host and cause the two-instances-on-one-ironic paradox
|
|
# to happen.
|
|
self.cn4.hypervisor_type = 'ironic'
|
|
self.cn4.save()
|
|
ret = self.commands.ironic_flavor_migration('test', 'fake-host3',
|
|
'fake-node4', False, False)
|
|
self.assertEqual(2, ret)
|
|
|
|
|
|
class NovaManageCellV2Test(test.TestCase):
|
|
def setUp(self):
|
|
super(NovaManageCellV2Test, self).setUp()
|
|
self.commands = manage.CellV2Commands()
|
|
self.context = context.RequestContext('fake-user', 'fake-project')
|
|
|
|
self.service1 = objects.Service(context=self.context,
|
|
host='fake-host1',
|
|
binary='nova-compute',
|
|
topic='fake-host1',
|
|
report_count=1,
|
|
disabled=False,
|
|
disabled_reason=None,
|
|
availability_zone='nova',
|
|
forced_down=False)
|
|
self.service1.create()
|
|
|
|
self.cn1 = objects.ComputeNode(context=self.context,
|
|
service_id=self.service1.id,
|
|
host='fake-host1',
|
|
hypervisor_type='ironic',
|
|
vcpus=1,
|
|
memory_mb=1024,
|
|
local_gb=10,
|
|
vcpus_used=1,
|
|
memory_mb_used=1024,
|
|
local_gb_used=10,
|
|
hypervisor_version=0,
|
|
hypervisor_hostname='fake-node1',
|
|
cpu_info='{}')
|
|
self.cn1.create()
|
|
|
|
def test_delete_host(self):
|
|
cells = objects.CellMappingList.get_all(self.context)
|
|
|
|
self.commands.discover_hosts()
|
|
|
|
# We should have one mapped node
|
|
cns = objects.ComputeNodeList.get_all(self.context)
|
|
self.assertEqual(1, len(cns))
|
|
self.assertEqual(1, cns[0].mapped)
|
|
|
|
for cell in cells:
|
|
r = self.commands.delete_host(cell.uuid, 'fake-host1')
|
|
if r == 0:
|
|
break
|
|
|
|
# Our node should now be unmapped
|
|
cns = objects.ComputeNodeList.get_all(self.context)
|
|
self.assertEqual(1, len(cns))
|
|
self.assertEqual(0, cns[0].mapped)
|