Merge "Add a noop deploy interface"
This commit is contained in:
@@ -303,3 +303,51 @@ Limitations
|
||||
``ironic-conductor`` host is not available. If you need the contaitainer
|
||||
content localized to the conductor, consider utilizing your own container
|
||||
registry.
|
||||
|
||||
.. _noop-deploy:
|
||||
|
||||
Noop deploy
|
||||
===========
|
||||
|
||||
The ``noop`` deploy interface is a minimal deployment mechanism that performs
|
||||
no actual deployment operations. It is designed for scenarios where operators
|
||||
want to allocate nodes and mark them as ``active`` in Ironic without deploying
|
||||
an operating system.
|
||||
|
||||
This is useful when nodes are being managed by external systems but tracked in
|
||||
Ironic's inventory.
|
||||
|
||||
You can specify this deploy interface when creating or updating a node::
|
||||
|
||||
baremetal node create --driver manual-management --deploy-interface noop
|
||||
baremetal node set <NODE> --deploy-interface noop
|
||||
|
||||
Usage with Nova
|
||||
---------------
|
||||
|
||||
For deployments using Ironic in conjunction with Nova, the ``noop`` deploy
|
||||
interface provides an alternative approach to adding production nodes that are
|
||||
already running workloads. Unlike the :doc:`node adoption </admin/adoption>`
|
||||
feature, which uses the ``adopt`` verb and is not suitable for Nova
|
||||
deployments, the ``noop`` deploy interface allows you to use the normal
|
||||
``deploy`` verb instead.
|
||||
|
||||
With the ``noop`` deploy interface, nodes can be moved through the normal
|
||||
deployment workflow (from ``manageable`` to ``available`` to ``deploying`` to
|
||||
``active``) without any actual deployment operations taking place. This enables
|
||||
Nova to properly track the instances while Ironic skips all conductor-side
|
||||
deployment actions.
|
||||
|
||||
.. note::
|
||||
The ``noop`` deploy interface completely bypasses cleaning operations.
|
||||
Even if a user cannot update ``Node.automated_clean``, as long as they set
|
||||
``Node.instance_info[deploy_interface]`` to ``noop``, cleaning will be
|
||||
skipped.
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
|
||||
Enable the interface in ``ironic.conf``::
|
||||
|
||||
[DEFAULT]
|
||||
enabled_deploy_interfaces = direct,ramdisk,noop
|
||||
|
||||
@@ -20,6 +20,7 @@ exceptions for user-accessible actions.
|
||||
"""
|
||||
|
||||
from ironic.common import exception
|
||||
from ironic.common import states
|
||||
from ironic.drivers import base
|
||||
|
||||
|
||||
@@ -91,3 +92,32 @@ class NoFirmware(FailMixin, base.FirmwareInterface):
|
||||
|
||||
def cache_firmware_components(self, task):
|
||||
pass
|
||||
|
||||
|
||||
class NoDeploy(base.DeployInterface):
|
||||
"""Deploy interface that does nothing and succeeds."""
|
||||
|
||||
def get_properties(self):
|
||||
return {}
|
||||
|
||||
def validate(self, task):
|
||||
# Intentionally accept any node config for noop deploy.
|
||||
pass
|
||||
|
||||
@base.deploy_step(priority=100)
|
||||
def deploy(self, task):
|
||||
# Synchronous success (mirrors FakeDeploy behavior).
|
||||
return None
|
||||
|
||||
def tear_down(self, task):
|
||||
# Indicate the node is torn down.
|
||||
return states.DELETED
|
||||
|
||||
def prepare(self, task):
|
||||
pass
|
||||
|
||||
def clean_up(self, task):
|
||||
pass
|
||||
|
||||
def take_over(self, task):
|
||||
pass
|
||||
|
||||
@@ -73,3 +73,45 @@ class NoInterfacesTestCase(base.TestCase):
|
||||
self.assertEqual({}, inst.get_properties())
|
||||
self.assertRaises(exception.UnsupportedDriverExtension,
|
||||
inst.validate, self.task)
|
||||
|
||||
|
||||
class NoDeployTestCase(base.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(NoDeployTestCase, self).setUp()
|
||||
self.task = mock.Mock(node=mock.Mock(driver='fake', spec=['driver']),
|
||||
spec=['node'])
|
||||
self.deploy = noop.NoDeploy()
|
||||
|
||||
def test_get_properties(self):
|
||||
self.assertEqual({}, self.deploy.get_properties())
|
||||
|
||||
def test_validate(self):
|
||||
# Should not raise
|
||||
self.assertIsNone(self.deploy.validate(self.task))
|
||||
|
||||
def test_deploy(self):
|
||||
# Should return None for synchronous completion
|
||||
self.assertIsNone(self.deploy.deploy(self.task))
|
||||
|
||||
def test_tear_down(self):
|
||||
from ironic.common import states
|
||||
# Should return DELETED state
|
||||
self.assertEqual(states.DELETED, self.deploy.tear_down(self.task))
|
||||
|
||||
def test_prepare(self):
|
||||
# Should not raise
|
||||
self.assertIsNone(self.deploy.prepare(self.task))
|
||||
|
||||
def test_clean_up(self):
|
||||
# Should not raise
|
||||
self.assertIsNone(self.deploy.clean_up(self.task))
|
||||
|
||||
def test_take_over(self):
|
||||
# Should not raise
|
||||
self.assertIsNone(self.deploy.take_over(self.task))
|
||||
|
||||
def test_load_by_name(self):
|
||||
inst = noop.NoDeploy()
|
||||
self.assertIsInstance(inst, noop.NoDeploy)
|
||||
self.assertEqual({}, inst.get_properties())
|
||||
|
||||
@@ -85,6 +85,7 @@ bootc = "ironic.drivers.modules.agent:BootcAgentDeploy"
|
||||
custom-agent = "ironic.drivers.modules.agent:CustomAgentDeploy"
|
||||
direct = "ironic.drivers.modules.agent:AgentDeploy"
|
||||
fake = "ironic.drivers.modules.fake:FakeDeploy"
|
||||
noop = "ironic.drivers.modules.noop:NoDeploy"
|
||||
ramdisk = "ironic.drivers.modules.ramdisk:RamdiskDeploy"
|
||||
|
||||
[project.entry-points."ironic.hardware.interfaces.firmware"]
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Adds a ``noop`` deploy interface that performs no actual deployment
|
||||
operations. This allows operators to allocate nodes and mark them as
|
||||
``active`` in Ironic without deploying an operating system. This provides
|
||||
an alternative approach to node adoption for Nova deployments, where the
|
||||
normal deploy verb can be used instead of the adopt verb.
|
||||
It is also useful when nodes are managed by external systems but tracked
|
||||
in Ironic's inventory.
|
||||
Reference in New Issue
Block a user