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
# limitations under the License.
import os
import subprocess
from ironicclient import client as ironicclient
def _get_id_line(lines, id_description, position=3):
for line in lines.split('\n'):
@ -35,7 +38,7 @@ def _check_output(command):
return output
def register_nova_bm_node(service_host, node):
def register_nova_bm_node(service_host, node, client=None):
if not service_host:
raise ValueError("Nova-baremetal requires a service host.")
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])
def register_ironic_node(service_host, node):
out = _check_output(["ironic", "node-create", "-d", node["pm_type"]])
node_id = _get_id_line(out, "uuid")
node_properties = ["properties/cpus=%s" % node["cpu"],
"properties/memory_mb=%s" % node["memory"],
"properties/local_gb=%s" % node["disk"],
"properties/cpu_arch=%s" % node["arch"]]
def register_ironic_node(service_host, node, client=None):
properties = {"cpus": node["cpu"],
"memory_mb": node["memory"],
"local_gb": node["disk"],
"cpu_arch": node["arch"]}
if "ipmi" in node["pm_type"]:
pm_password = node["pm_password"]
ipmi_properties = ["driver_info/ipmi_address=%s" % node["pm_addr"],
"driver_info/ipmi_username=%s" % node["pm_user"],
"driver_info/ipmi_password=%s" % pm_password]
node_properties.extend(ipmi_properties)
driver_info = {"ipmi_address": node["pm_addr"],
"ipmi_username": node["pm_user"],
"ipmi_password": node["pm_password"]}
elif node["pm_type"] == "pxe_ssh":
ssh = ("driver_info/ssh_key_filename=/mnt/state/var/lib/ironic/"
"virtual-power-key")
ssh_properties = ["driver_info/ssh_address=%s" % node["pm_addr"],
"driver_info/ssh_username=%s" % node["pm_user"],
ssh, "driver_info/ssh_virt_type=virsh"]
node_properties.extend(ssh_properties)
ssh_key_filename = "/mnt/state/var/lib/ironic/virtual-power-key"
driver_info = {"ssh_address": node["pm_addr"],
"ssh_username": node["pm_user"],
"ssh_key_filename": ssh_key_filename,
"ssh_virt_type": "virsh"}
else:
raise Exception("Unknown pm_type: %s" % node["pm_type"])
subprocess.check_call(["ironic", "node-update", node_id, "add"]
+ node_properties)
# Ironic should do this directly, see bug 1315225.
subprocess.check_call(["ironic", "node-set-power-state", node_id, "off"])
ironic_node = client.node.create(driver=node["pm_type"],
driver_info=driver_info,
properties=properties)
for mac in node["mac"]:
subprocess.check_call(["ironic", "port-create", "-a", mac, "-n",
node_id])
client.port.create(address=mac, node_uuid=ironic_node.uuid)
# 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 client is None:
client = _get_ironic_client()
register_func = register_ironic_node
else:
register_func = register_nova_bm_node
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

View File

@ -41,38 +41,44 @@ class NodesTest(base.TestCase):
"2048", "30", "aaa"])
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._get_id_line', return_value="1")
@mock.patch('subprocess.check_call')
def test_register_all_nodes_ironic(self, check_call, id_mock, ironic_mock,
check_output_mock):
def test_register_all_nodes_ironic(self, using_ironic):
node_list = [self._get_node(), self._get_node()]
node_list[1]["pm_type"] = "ipmi"
nodes.register_all_nodes('servicehost', node_list)
pxe_node_create = mock.call(
["ironic", "node-create", "-d", "pxe_ssh"])
ipmi_node_create = mock.call(
["ironic", "node-create", "-d", "ipmi"])
check_output_mock.has_calls([pxe_node_create, ipmi_node_create])
node_properties = {"cpus": "1",
"memory_mb": "2048",
"local_gb": "30",
"cpu_arch": "amd64"}
ironic = mock.MagicMock()
nodes.register_all_nodes('servicehost', node_list, client=ironic)
ssh_key = "/mnt/state/var/lib/ironic/virtual-power-key"
common_update_args = [
"ironic", "node-update", "1", "add", "properties/cpus=1",
"properties/memory_mb=2048", "properties/local_gb=30",
"properties/cpu_arch=amd64"]
pxe_node_update_call = mock.call(
common_update_args + ["driver_info/ssh_address=foo.bar",
"driver_info/ssh_username=test",
"driver_info/ssh_key_filename=%s" % ssh_key,
"driver_info/ssh_virt_type=virsh"])
ipmi_node_update_call = mock.call(
common_update_args + ["driver_info/ipmi_address=foo.bar",
"driver_info/ipmi_username=test",
"driver_info/ipmi_password=random"])
power_off_call = mock.call(
["ironic", "node-set-power-state", "1", "off"])
port_update_call = mock.call(
["ironic", "port-create", "-a", "aaa", "-n", "1"])
check_call.assert_has_calls(
[pxe_node_update_call, power_off_call, port_update_call,
ipmi_node_update_call, power_off_call, port_update_call])
pxe_node_driver_info = {"ssh_address": "foo.bar",
"ssh_username": "test",
"ssh_key_filename": ssh_key,
"ssh_virt_type": "virsh"}
ipmi_node_driver_info = {"ipmi_address": "foo.bar",
"ipmi_username": "test",
"ipmi_password": "random"}
pxe_node = mock.call(driver="pxe_ssh",
driver_info=pxe_node_driver_info,
properties=node_properties)
port_call = mock.call(node_uuid=ironic.node.create.return_value.uuid,
address='aaa')
power_off_call = mock.call(ironic.node.create.return_value.uuid, 'off')
ipmi_node = mock.call(driver="ipmi",
driver_info=ipmi_node_driver_info,
properties=node_properties)
ironic.node.create.assert_has_calls([pxe_node, ipmi_node])
ironic.port.create.assert_has_calls([port_call, port_call])
ironic.node.set_power_state.assert_has_calls(
[power_off_call, power_off_call])

View File

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