Skip to content

Commit 9068db0

Browse files
committed
Add nova-manage ironic-compute-node-move
When people transition from three ironic nova-compute processes down to one process, we need a way to move the ironic nodes, and any associcated instances, between nova-compute processes. For saftey, a nova-compute process must first be forced_down via the API, similar to when using evacaute, before moving the associated ironic nodes to another nova-compute process. The destination nova-compute process should ideally not be running, but not forced down. blueprint ironic-shards Change-Id: I7ef25e27bf8c47f994e28c59858cf3df30975b05
1 parent f5a12f5 commit 9068db0

File tree

4 files changed

+141
-1
lines changed

4 files changed

+141
-1
lines changed

doc/source/cli/nova-manage.rst

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -465,6 +465,47 @@ command completed successfully with exit code 0.
465465
* - 127
466466
- Invalid input was provided.
467467

468+
db ironic_compute_node_move
469+
---------------------------
470+
471+
.. program:: nova-manage db ironic_compute_node_move
472+
473+
.. code-block:: shell
474+
475+
nova-manage db ironic_compute_node_move --ironic-node-uuid <uuid> --destination-host <host>
476+
477+
Move ironic nodes, along with any associated instances,
478+
between nova-compute services.
479+
480+
This is useful when migrating away from using peer_list and multiple
481+
hash ring balanced nova-compute servers to the new ironic shard system.
482+
483+
First you must turn off the nova-compute service that currently manages
484+
the Ironic host. Second you mark that nova-compute service as forced down
485+
via the Nova API. Third, you ensure the new nova-compute service is
486+
correctly configured to target the appropriate shard (and optionally
487+
also a conductor group). Finally, most Ironic nodes should now move to
488+
the new service, but any Ironic nodes with instances on them
489+
will need to be manually moved to their new Ironic service
490+
by using this nova-manage command.
491+
492+
.. versionadded:: 28.0.0 (2023.2 Bobcat)
493+
494+
.. rubric:: Options
495+
496+
.. option:: --ironic-node-uuid <uuid>
497+
498+
Ironic node uuid to be moved (which is also the Nova compute node uuid
499+
and the uuid of corresponding resource provider in Placement).
500+
501+
The Nova compute service that currently manages this Ironic node
502+
must first be marked a "forced down" via the Nova API, in a similar
503+
way to a down hypervisor that is about to have its VMs evacuated to
504+
a replacement hypervisor.
505+
506+
.. option:: --destination-host <host>
507+
508+
Destination ironic nova-compute service CONF.host.
468509

469510
API Database Commands
470511
=====================

nova/cmd/manage.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -620,6 +620,47 @@ def online_data_migrations(self, max_count=None):
620620
# "there are more migrations, but not completable right now"
621621
return ran and 1 or 0
622622

623+
@args('--ironic-node-uuid', metavar='<uuid>', dest='compute_node_uuid',
624+
help='UUID of Ironic node to be moved between services')
625+
@args('--destination-host', metavar='<host>',
626+
dest='destination_service_host',
627+
help='Destination ironic nova-compute service CONF.host')
628+
def ironic_compute_node_move(self, compute_node_uuid,
629+
destination_service_host):
630+
ctxt = context.get_admin_context()
631+
632+
destination_service = objects.Service.get_by_compute_host(
633+
ctxt, destination_service_host)
634+
if destination_service.forced_down:
635+
raise exception.NovaException(
636+
"Destination compute is forced down!")
637+
638+
target_compute_node = objects.ComputeNode.get_by_uuid(
639+
ctxt, compute_node_uuid)
640+
source_service = objects.Service.get_by_id(
641+
ctxt, target_compute_node.service_id)
642+
if not source_service.forced_down:
643+
raise exception.NovaException(
644+
"Source service is not yet forced down!")
645+
646+
instances = objects.InstanceList.get_by_host_and_node(
647+
ctxt, target_compute_node.host,
648+
target_compute_node.hypervisor_hostname)
649+
if len(instances) > 1:
650+
raise exception.NovaException(
651+
"Found an ironic host with more than one instance! "
652+
"Please delete all Nova instances that do not match "
653+
"the instance uuid recorded on the Ironic node.")
654+
655+
target_compute_node.service_id = destination_service.id
656+
target_compute_node.host = destination_service.host
657+
target_compute_node.save()
658+
659+
for instance in instances:
660+
# this is a bit like evacuate, except no need to rebuild
661+
instance.host = destination_service.host
662+
instance.save()
663+
623664

624665
class ApiDbCommands(object):
625666
"""Class for managing the api database."""

nova/tests/functional/test_nova_manage.py

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,9 @@ def setUp(self):
162162
user_id=self.context.user_id,
163163
project_id=self.context.project_id,
164164
flavor=flavor,
165-
node=cn.hypervisor_hostname)
165+
node=cn.hypervisor_hostname,
166+
host=cn.host,
167+
compute_id=cn.id)
166168
inst.create()
167169
self.insts.append(inst)
168170

@@ -172,6 +174,57 @@ def setUp(self):
172174
if i.node == self.cn4.hypervisor_hostname]
173175

174176

177+
class TestIronicComputeNodeMove(NovaManageDBIronicTest):
178+
"""Functional tests for "nova-manage db ironic_compute_node_move" CLI."""
179+
api_major_version = 'v2.1'
180+
181+
def setUp(self):
182+
super(TestIronicComputeNodeMove, self).setUp()
183+
self.enforce_fk_constraints()
184+
self.cli = manage.DbCommands()
185+
self.output = StringIO()
186+
self.useFixture(fixtures.MonkeyPatch('sys.stdout', self.output))
187+
188+
def test_ironic_compute_node_move_success(self):
189+
self.service1.forced_down = True
190+
self.service1.save()
191+
self.assertEqual(self.service1.id, self.cn1.service_id)
192+
# move cn1 on service1 to service2
193+
node_uuid = self.cn1.uuid
194+
dest_host = self.service2.host
195+
196+
self.commands.ironic_compute_node_move(node_uuid, dest_host)
197+
198+
# check the compute node got moved to service 2
199+
updated_cn1 = objects.ComputeNode.get_by_id(self.context, self.cn1.id)
200+
self.assertEqual(self.service2.id, updated_cn1.service_id)
201+
self.assertEqual(self.service2.host, updated_cn1.host)
202+
# check the instance got moved too
203+
updated_instance = objects.Instance.get_by_id(
204+
self.context, self.insts[0].id)
205+
self.assertEqual(self.service2.host, updated_instance.host)
206+
207+
def test_ironic_compute_node_move_raise_not_forced_down(self):
208+
node_uuid = self.cn1.uuid
209+
dest_host = self.service2.host
210+
211+
self.assertRaises(exception.NovaException,
212+
self.commands.ironic_compute_node_move,
213+
node_uuid, dest_host)
214+
215+
def test_ironic_compute_node_move_raise_forced_down(self):
216+
self.service1.forced_down = True
217+
self.service1.save()
218+
self.service2.forced_down = True
219+
self.service2.save()
220+
node_uuid = self.cn1.uuid
221+
dest_host = self.service2.host
222+
223+
self.assertRaises(exception.NovaException,
224+
self.commands.ironic_compute_node_move,
225+
node_uuid, dest_host)
226+
227+
175228
class NovaManageCellV2Test(test.TestCase):
176229
def setUp(self):
177230
super(NovaManageCellV2Test, self).setUp()

releasenotes/notes/ironic-shards-5641e4b1ab5bb7aa.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,8 @@ features:
1010
which ironic nodes are managed by each nova-compute service.
1111
Note that when you use ``[ironic]shard`` the ``[ironic]peer_list``
1212
is hard coded to a single nova-compute service.
13+
14+
There is a new nova-manage command ``db ironic_compute_node_move`` that
15+
can be used to move ironic nodes, and the associated instances, between
16+
nova-compute services. This is useful when migrating from the legacy
17+
hash ring based HA towards the new sharding approach.

0 commit comments

Comments
 (0)