Add action for generating fencing parameters.

We can now automatically generate environment parameters for fencing,
post-deployment.

Partial-Bug: #1649695

Depends-On: I4f22c84fb65c7bba6904c174ff6819c6eaee0c32
Change-Id: I0ff173b6daf770329b25f61b76fcba0e28b2550a
This commit is contained in:
Chris Jones 2017-01-18 23:26:32 +00:00
parent 75dfaa9ee4
commit 6dcef35ab8
5 changed files with 195 additions and 0 deletions

View File

@ -8,6 +8,8 @@ features:
- Add a new Workflow which can be used to wait for Heat stacks finish with
COMPLETE or FAILED.
- CephMdsKey is now a generated Heat parameter.
- Add an new Action which generates environment parameters for configuring
fencing.
fixes:
- Fixes `bug 1644756 <https://bugs.launchpad.net/tripleo/+bug/1644756>`__ so
that flavour matching works as expected with the object-storage role.

View File

@ -78,6 +78,7 @@ mistral.actions =
tripleo.parameters.update_role = tripleo_common.actions.parameters:UpdateRoleParametersAction
tripleo.parameters.generate_passwords = tripleo_common.actions.parameters:GeneratePasswordsAction
tripleo.parameters.get_passwords = tripleo_common.actions.parameters:GetPasswordsAction
tripleo.parameters.generate_fencing = tripleo_common.actions.parameters:GenerateFencingParametersAction
tripleo.plan.create = tripleo_common.actions.plan:CreatePlanAction
tripleo.plan.update = tripleo_common.actions.plan:UpdatePlanAction
tripleo.plan.create_container = tripleo_common.actions.plan:CreateContainerAction

View File

@ -34,6 +34,7 @@ from mistral.workflow import utils as mistral_workflow_utils
from tripleo_common.actions import base
from tripleo_common.actions import templates
from tripleo_common import constants
from tripleo_common.utils import nodes
from tripleo_common.utils import parameters
from tripleo_common.utils import passwords as password_utils
@ -210,3 +211,87 @@ class GetPasswordsAction(base.TripleOAction):
passwords[name] = parameter_defaults[name]
return passwords
class GenerateFencingParametersAction(base.TripleOAction):
"""Generates fencing configuration for a deployment.
:param nodes_json: list of nodes & attributes in json format
:param os_auth: dictionary of OS client auth data (if using pxe_ssh)
:param fence_action: action to take when fencing nodes
:param delay: time to wait before taking fencing action
:param ipmi_level: IPMI user level to use
:param ipmi_cipher: IPMI cipher suite to use
:param ipmi_lanplus: whether to use IPMIv2.0
"""
def __init__(self, nodes_json, os_auth, fence_action, delay,
ipmi_level, ipmi_cipher, ipmi_lanplus):
super(GenerateFencingParametersAction, self).__init__()
self.nodes_json = nodes_json
self.os_auth = os_auth
self.fence_action = fence_action
self.delay = delay
self.ipmi_level = ipmi_level
self.ipmi_cipher = ipmi_cipher
self.ipmi_lanplus = ipmi_lanplus
def run(self):
"""Returns the parameters for fencing controller nodes"""
hostmap = nodes.generate_hostmap(self.get_baremetal_client(),
self.get_compute_client())
fence_params = {"EnableFencing": True, "FencingConfig": {}}
devices = []
for node in self.nodes_json:
node_data = {}
params = {}
if "mac" in node:
# Not all Ironic drivers present a MAC address, so we only
# capture it if it's present
mac_addr = node["mac"][0]
node_data["host_mac"] = mac_addr
# Build up fencing parameters based on which Ironic driver this
# node is using
if node["pm_type"] == "pxe_ssh":
# Ironic fencing driver
node_data["agent"] = "fence_ironic"
params["action"] = self.fence_action
params["auth_url"] = self.os_auth["auth_url"]
params["login"] = self.os_auth["login"]
params["passwd"] = self.os_auth["passwd"]
params["tenant_name"] = self.os_auth["tenant_name"]
params["pcmk_host_map"] = "%(compute_name)s:%(bm_name)s" % (
{"compute_name": hostmap[mac_addr]["compute_name"],
"bm_name": hostmap[mac_addr]["baremetal_name"]})
if self.delay:
params["delay"] = self.delay
elif node["pm_type"].split('_')[1] in ("ipmitool", "ilo", "drac"):
# IPMI fencing driver
node_data["agent"] = "fence_ipmilan"
params["action"] = self.fence_action
params["ipaddr"] = node["pm_addr"]
params["passwd"] = node["pm_password"]
params["login"] = node["pm_user"]
params["pcmk_host_list"] = hostmap[mac_addr]["compute_name"]
if "pm_port" in node:
params["ipport"] = node["pm_port"]
if self.ipmi_lanplus:
params["lanplus"] = self.ipmi_lanplus
if self.delay:
params["delay"] = self.delay
if self.ipmi_cipher:
params["cipher"] = self.ipmi_cipher
if self.ipmi_level:
params["privlvl"] = self.ipmi_level
else:
error = ("Unable to generate fencing parameters for %s" %
node["pm_type"])
raise ValueError(error)
node_data["params"] = params
devices.append(node_data)
fence_params["FencingConfig"]["devices"] = devices
return {"parameter_defaults": fence_params}

View File

@ -495,3 +495,99 @@ class GetPasswordsActionTest(base.TestCase):
# ensure old passwords used and no new generation
self.assertEqual(_EXISTING_PASSWORDS, result)
class GenerateFencingParametersActionTestCase(base.TestCase):
@mock.patch('tripleo_common.utils.nodes.'
'generate_hostmap')
@mock.patch('tripleo_common.actions.base.TripleOAction.'
'get_compute_client')
@mock.patch('tripleo_common.actions.base.TripleOAction.'
'get_baremetal_client')
@mock.patch('tripleo_common.actions.base.TripleOAction.'
'get_workflow_client')
@mock.patch('tripleo_common.actions.base.TripleOAction.'
'get_orchestration_client')
@mock.patch('mistral.context.ctx')
def test_no_success(self, mock_ctx, mock_get_orchestration,
mock_get_workflow, mock_get_baremetal,
mock_get_compute, mock_generate_hostmap):
test_hostmap = {
"00:11:22:33:44:55": {
"compute_name": "compute_name_0",
"baremetal_name": "baremetal_name_0"
},
"11:22:33:44:55:66": {
"compute_name": "compute_name_1",
"baremetal_name": "baremetal_name_1"
}
}
mock_generate_hostmap.return_value = test_hostmap
test_envjson = [{
"name": "control-0",
"pm_password": "control-0-password",
"pm_type": "pxe_ipmitool",
"pm_user": "control-0-admin",
"pm_addr": "0.1.2.3",
"pm_port": "0123",
"mac": [
"00:11:22:33:44:55"
]
}, {
"name": "control-1",
"pm_password": "control-1-password",
"pm_type": "pxe_ssh",
"pm_user": "control-1-admin",
"pm_addr": "1.2.3.4",
"mac": [
"11:22:33:44:55:66"
]
}]
test_osauth = {
"auth_url": "test://auth.url",
"login": "test_os_username",
"passwd": "test_os_password",
"tenant_name": "test_os_tenant_name",
}
action = parameters.GenerateFencingParametersAction(test_envjson,
test_osauth,
"test_action",
28,
5,
0,
True)
result = action.run()["parameter_defaults"]
self.assertTrue(result["EnableFencing"])
self.assertEqual(result["FencingConfig"]["devices"][0], {
"agent": "fence_ipmilan",
"host_mac": "00:11:22:33:44:55",
"params": {
"action": "test_action",
"delay": 28,
"ipaddr": "0.1.2.3",
"ipport": "0123",
"lanplus": True,
"privlvl": 5,
"login": "control-0-admin",
"passwd": "control-0-password",
"pcmk_host_list": "compute_name_0"
}
})
self.assertEqual(result["FencingConfig"]["devices"][1], {
"agent": "fence_ironic",
"host_mac": "11:22:33:44:55:66",
"params": {
"auth_url": "test://auth.url",
"delay": 28,
"action": "test_action",
"login": "test_os_username",
"passwd": "test_os_password",
"tenant_name": "test_os_tenant_name",
"pcmk_host_map": "compute_name_1:baremetal_name_1"
}
})

View File

@ -432,3 +432,14 @@ def update_node_capability(node_uuid, capability, value, client):
node = client.node.get(node_uuid)
patch = _get_capability_patch(node, capability, value)
return client.node.update(node_uuid, patch)
def generate_hostmap(baremetal_client, compute_client):
"""Create a map between Compute nodes and Baremetal nodes"""
hostmap = {}
for node in compute_client.servers.list():
bm_node = baremetal_client.node.get_by_instance_uuid(node.id)
for port in baremetal_client.port.list(node=bm_node.uuid):
hostmap[port.address] = {"compute_name": node.name,
"baremetal_name": bm_node.name}
return hostmap