Switch from subprocess to ironicclient

As one large improvement to register-nodes, stop using subprocess to
fork off the ironic CLI, and instead use python-ironicclient and its
Python API to register nodes.

Change-Id: I2fd51873b892026c21045e6b912ef35b0be66fc5
This commit is contained in:
Steve Kowalik 2014-05-16 13:30:06 -04:00
parent 5e19886f82
commit 99e7f8f2fd
3 changed files with 74 additions and 58 deletions

View File

@ -13,8 +13,11 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
import os
import subprocess import subprocess
from ironicclient import client as ironicclient
def _get_id_line(lines, id_description, position=3): def _get_id_line(lines, id_description, position=3):
for line in lines.split('\n'): for line in lines.split('\n'):
@ -35,7 +38,7 @@ def _check_output(command):
return output return output
def register_nova_bm_node(service_host, node): def register_nova_bm_node(service_host, node, client=None):
if not service_host: if not service_host:
raise ValueError("Nova-baremetal requires a service host.") raise ValueError("Nova-baremetal requires a service host.")
out = _check_output(["nova", "baremetal-node-create", out = _check_output(["nova", "baremetal-node-create",
@ -49,45 +52,51 @@ def register_nova_bm_node(service_host, node):
subprocess.check_call(["nova", "baremetal-interface-add", bm_id, mac]) subprocess.check_call(["nova", "baremetal-interface-add", bm_id, mac])
def register_ironic_node(service_host, node): def register_ironic_node(service_host, node, client=None):
out = _check_output(["ironic", "node-create", "-d", node["pm_type"]]) properties = {"cpus": node["cpu"],
node_id = _get_id_line(out, "uuid") "memory_mb": node["memory"],
node_properties = ["properties/cpus=%s" % node["cpu"], "local_gb": node["disk"],
"properties/memory_mb=%s" % node["memory"], "cpu_arch": node["arch"]}
"properties/local_gb=%s" % node["disk"],
"properties/cpu_arch=%s" % node["arch"]]
if "ipmi" in node["pm_type"]: if "ipmi" in node["pm_type"]:
pm_password = node["pm_password"] driver_info = {"ipmi_address": node["pm_addr"],
ipmi_properties = ["driver_info/ipmi_address=%s" % node["pm_addr"], "ipmi_username": node["pm_user"],
"driver_info/ipmi_username=%s" % node["pm_user"], "ipmi_password": node["pm_password"]}
"driver_info/ipmi_password=%s" % pm_password]
node_properties.extend(ipmi_properties)
elif node["pm_type"] == "pxe_ssh": elif node["pm_type"] == "pxe_ssh":
ssh = ("driver_info/ssh_key_filename=/mnt/state/var/lib/ironic/" ssh_key_filename = "/mnt/state/var/lib/ironic/virtual-power-key"
"virtual-power-key") driver_info = {"ssh_address": node["pm_addr"],
ssh_properties = ["driver_info/ssh_address=%s" % node["pm_addr"], "ssh_username": node["pm_user"],
"driver_info/ssh_username=%s" % node["pm_user"], "ssh_key_filename": ssh_key_filename,
ssh, "driver_info/ssh_virt_type=virsh"] "ssh_virt_type": "virsh"}
node_properties.extend(ssh_properties)
else: else:
raise Exception("Unknown pm_type: %s" % node["pm_type"]) raise Exception("Unknown pm_type: %s" % node["pm_type"])
subprocess.check_call(["ironic", "node-update", node_id, "add"] ironic_node = client.node.create(driver=node["pm_type"],
+ node_properties) driver_info=driver_info,
# Ironic should do this directly, see bug 1315225. properties=properties)
subprocess.check_call(["ironic", "node-set-power-state", node_id, "off"])
for mac in node["mac"]: for mac in node["mac"]:
subprocess.check_call(["ironic", "port-create", "-a", mac, "-n", client.port.create(address=mac, node_uuid=ironic_node.uuid)
node_id]) # Ironic should do this directly, see bug 1315225.
client.node.set_power_state(ironic_node.uuid, 'off')
def register_all_nodes(service_host, nodes_list): def _get_ironic_client():
kwargs = {'os_username': os.environ['OS_USERNAME'],
'os_password': os.environ['OS_PASSWORD'],
'os_auth_url': os.environ['OS_AUTH_URL'],
'os_tenant_name': os.environ['OS_TENANT_NAME']}
return ironicclient.get_client(1, **kwargs)
def register_all_nodes(service_host, nodes_list, client=None):
if using_ironic(): if using_ironic():
if client is None:
client = _get_ironic_client()
register_func = register_ironic_node register_func = register_ironic_node
else: else:
register_func = register_nova_bm_node register_func = register_nova_bm_node
for node in nodes_list: for node in nodes_list:
register_func(service_host, node) register_func(service_host, node, client=client)
# TODO(StevenK): Perhaps this should spin over the first node until it is # TODO(StevenK): Perhaps this should spin over the first node until it is

View File

@ -41,38 +41,44 @@ class NodesTest(base.TestCase):
"2048", "30", "aaa"]) "2048", "30", "aaa"])
check_mock.has_calls([nova_bm_call, nova_bm_call]) check_mock.has_calls([nova_bm_call, nova_bm_call])
@mock.patch('os_cloud_config.nodes._check_output') @mock.patch('os.environ')
@mock.patch('ironicclient.client.get_client')
def test_get_ironic_client(self, client_mock, environ):
nodes._get_ironic_client()
client_mock.assert_called_once_with(
1, os_username=environ["OS_USERNAME"],
os_password=environ["OS_PASSWORD"],
os_auth_url=environ["OS_AUTH_URL"],
os_tenant_name=environ["OS_TENANT_NAME"])
@mock.patch('os_cloud_config.nodes.using_ironic', return_value=True) @mock.patch('os_cloud_config.nodes.using_ironic', return_value=True)
@mock.patch('os_cloud_config.nodes._get_id_line', return_value="1") def test_register_all_nodes_ironic(self, using_ironic):
@mock.patch('subprocess.check_call')
def test_register_all_nodes_ironic(self, check_call, id_mock, ironic_mock,
check_output_mock):
node_list = [self._get_node(), self._get_node()] node_list = [self._get_node(), self._get_node()]
node_list[1]["pm_type"] = "ipmi" node_list[1]["pm_type"] = "ipmi"
nodes.register_all_nodes('servicehost', node_list) node_properties = {"cpus": "1",
pxe_node_create = mock.call( "memory_mb": "2048",
["ironic", "node-create", "-d", "pxe_ssh"]) "local_gb": "30",
ipmi_node_create = mock.call( "cpu_arch": "amd64"}
["ironic", "node-create", "-d", "ipmi"]) ironic = mock.MagicMock()
check_output_mock.has_calls([pxe_node_create, ipmi_node_create]) nodes.register_all_nodes('servicehost', node_list, client=ironic)
ssh_key = "/mnt/state/var/lib/ironic/virtual-power-key" ssh_key = "/mnt/state/var/lib/ironic/virtual-power-key"
common_update_args = [ pxe_node_driver_info = {"ssh_address": "foo.bar",
"ironic", "node-update", "1", "add", "properties/cpus=1", "ssh_username": "test",
"properties/memory_mb=2048", "properties/local_gb=30", "ssh_key_filename": ssh_key,
"properties/cpu_arch=amd64"] "ssh_virt_type": "virsh"}
pxe_node_update_call = mock.call( ipmi_node_driver_info = {"ipmi_address": "foo.bar",
common_update_args + ["driver_info/ssh_address=foo.bar", "ipmi_username": "test",
"driver_info/ssh_username=test", "ipmi_password": "random"}
"driver_info/ssh_key_filename=%s" % ssh_key, pxe_node = mock.call(driver="pxe_ssh",
"driver_info/ssh_virt_type=virsh"]) driver_info=pxe_node_driver_info,
ipmi_node_update_call = mock.call( properties=node_properties)
common_update_args + ["driver_info/ipmi_address=foo.bar", port_call = mock.call(node_uuid=ironic.node.create.return_value.uuid,
"driver_info/ipmi_username=test", address='aaa')
"driver_info/ipmi_password=random"]) power_off_call = mock.call(ironic.node.create.return_value.uuid, 'off')
power_off_call = mock.call( ipmi_node = mock.call(driver="ipmi",
["ironic", "node-set-power-state", "1", "off"]) driver_info=ipmi_node_driver_info,
port_update_call = mock.call( properties=node_properties)
["ironic", "port-create", "-a", "aaa", "-n", "1"]) ironic.node.create.assert_has_calls([pxe_node, ipmi_node])
check_call.assert_has_calls( ironic.port.create.assert_has_calls([port_call, port_call])
[pxe_node_update_call, power_off_call, port_update_call, ironic.node.set_power_state.assert_has_calls(
ipmi_node_update_call, power_off_call, port_update_call]) [power_off_call, power_off_call])

View File

@ -2,6 +2,7 @@ pbr>=0.6,<1.0
argparse argparse
Babel>=1.3 Babel>=1.3
python-ironicclient
python-keystoneclient>=0.6.0 python-keystoneclient>=0.6.0
oslo.config>=1.2.0 oslo.config>=1.2.0
simplejson>=2.0.9 simplejson>=2.0.9