Skip to content

Commit

Permalink
Merge pull request #271 from rackerlabs/PUC-503
Browse files Browse the repository at this point in the history
chore: create additional test data for testing workflows
  • Loading branch information
cardoe authored Sep 12, 2024
2 parents 4cd5b9e + d50a10a commit 98f6d7c
Show file tree
Hide file tree
Showing 5 changed files with 279 additions and 21 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
{
"uuid": "5540e25c-71a2-4d40-bb9b-b680e0226937",
"created_at": "2024-07-12T20:21:02+00:00",
"updated_at": "2024-08-05T17:55:23+00:00",
"automated_clean": false,
"bios_interface": "redfish",
"boot_interface": "ipxe",
"boot_mode": "uefi",
"clean_step": {},
"conductor_group": "",
"console_enabled": false,
"console_interface": "no-console",
"deploy_interface": "direct",
"deploy_step": {},
"description": null,
"driver": "redfish",
"driver_info": {
"redfish_address": "https://10.46.96.153",
"redfish_verify_ca": false,
"redfish_username": "root",
"redfish_password": "******"
},
"driver_internal_info": {
"deploy_steps": null,
"last_power_state_change": "2024-07-17T17:23:37.750722",
"agent_version": "9.11.0.dev5",
"agent_last_heartbeat": "2024-07-17T17:23:16.904131",
"hardware_manager_version": {
"generic_hardware_manager": "1.2"
},
"agent_cached_deploy_steps_refreshed": "2024-07-17T17:23:01.931895",
"is_whole_disk_image": true,
"dnsmasq_tag": "19fa0a49-380c-4a8c-b1bd-9c47c5bcd2f9",
"deploy_boot_mode": "uefi",
"agent_secret_token": "******",
"agent_url": "https://192.168.200.5:9999",
"agent_verify_ca": "/var/lib/ironic/certificates/5540e25c-71a2-4d40-bb9b-b680e0226937.crt"
},
"extra": {},
"fault": null,
"inspection_finished_at": null,
"inspection_started_at": null,
"inspect_interface": "redfish",
"instance_info": {
"image_source": "http://10.4.204.2/keekz-ubuntu-extras.qcow2",
"image_os_hash_value": "45e8ec2a058b10adcdf130065f78b75fc708f9a5525f6fbf70d3134f1da2472c",
"image_os_hash_algo": "sha256",
"image_type": "whole-disk",
"root_gb": 400,
"configdrive": "******",
"image_url": "******"
},
"instance_uuid": null,
"last_error": "During sync_power_state, max retries exceeded for node 5540e25c-71a2-4d40-bb9b-b680e0226937, node state None does not match expected state 'power on'. Updating DB state to 'None' Switching node to maintenance mode. Error: Redfish connection failed for node 5540e25c-71a2-4d40-bb9b-b680e0226937: Unable to connect to https://10.46.96.153/redfish/v1/. Error: HTTPSConnectionPool(host='10.46.96.153', port=443): Max retries exceeded with url: /redfish/v1/ (Caused by ConnectTimeoutError(<urllib3.connection.HTTPSConnection object at 0x7f9658fec1f0>, 'Connection to 10.46.96.153 timed out. (connect timeout=60)'))",
"lessee": null,
"maintenance": false,
"maintenance_reason": null,
"management_interface": "redfish",
"name": "1327197-GP2S.2.undercloud.iad3",
"network_data": {},
"network_interface": "noop",
"owner": "ebc5b22e420d4dfc9e385a63b4583623",
"power_interface": "redfish",
"power_state": "power on",
"properties": {
"vendor": "Dell Inc."
},
"protected": false,
"protected_reason": null,
"provision_state": "active",
"provision_updated_at": "2024-07-17T17:24:08+00:00",
"raid_config": {},
"raid_interface": "redfish",
"rescue_interface": "no-rescue",
"reservation": null,
"resource_class": "unconfigured",
"retired": false,
"retired_reason": null,
"secure_boot": false,
"shard": null,
"storage_interface": "noop",
"target_power_state": null,
"target_provision_state": null,
"target_raid_config": {
"logical_disks": [
{
"size_gb": 400,
"raid_level": "1",
"is_root_volume": true,
"controller": "RAID.Integrated.1-1"
}
]
},
"traits": [],
"vendor_interface": "redfish",
"conductor": null,
"allocation_uuid": null,
"chassis_uuid": null
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
{
"uuid": "53517d16-2e20-489f-a557-981006aed783",
"created_at": "2024-07-15T11:25:24+00:00",
"updated_at": "2024-07-24T11:47:47+00:00",
"automated_clean": false,
"bios_interface": "redfish",
"boot_interface": "ipxe",
"boot_mode": null,
"clean_step": {},
"conductor_group": "",
"console_enabled": false,
"console_interface": "no-console",
"deploy_interface": "direct",
"deploy_step": {},
"description": null,
"driver": "redfish",
"driver_info": {
"redfish_address": "https://10.46.96.151",
"redfish_verify_ca": false,
"redfish_username": "root",
"redfish_password": "******"
},
"driver_internal_info": {},
"extra": {},
"fault": null,
"inspection_finished_at": null,
"inspection_started_at": null,
"inspect_interface": "redfish",
"instance_info": {},
"instance_uuid": null,
"last_error": null,
"lessee": null,
"maintenance": false,
"maintenance_reason": null,
"management_interface": "redfish",
"name": "1327188-hp16.undercloud.iad3",
"network_data": {},
"network_interface": "noop",
"owner": "ebc5b22e420d4dfc9e385a63b4583623",
"power_interface": "redfish",
"power_state": null,
"properties": {},
"protected": false,
"protected_reason": null,
"provision_state": "enroll",
"provision_updated_at": null,
"raid_config": {},
"raid_interface": "redfish",
"rescue_interface": "no-rescue",
"reservation": null,
"resource_class": "unconfigured",
"retired": false,
"retired_reason": null,
"secure_boot": null,
"shard": null,
"storage_interface": "noop",
"target_power_state": null,
"target_provision_state": null,
"target_raid_config": {},
"traits": [],
"vendor_interface": "redfish",
"conductor": null,
"allocation_uuid": null,
"chassis_uuid": null
}
75 changes: 75 additions & 0 deletions python/understack-workflows/tests/test_sync_server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import sys
import pytest
import pathlib
import json

from understack_workflows.main.sync_server import get_args, get_ironic_node, update_ironic_node
from understack_workflows.node_configuration import IronicNodeConfiguration


def read_json_samples(file_path):
here = pathlib.Path(__file__).parent
ref = here.joinpath(file_path)
with ref.open("r") as f:
return f.read()


@pytest.fixture(autouse=True)
def mock_args(monkeypatch):
monkeypatch.setattr(sys, "argv", ["pytest",
read_json_samples("json_samples/event-interface-update.json")])


@pytest.fixture
def fake_client(mocker):
return mocker.patch("understack_workflows.ironic.client.IronicClient")


def get_ironic_node_state(fake_client, node_data):
node = IronicNodeConfiguration.from_event(json.loads(read_json_samples("json_samples/event-interface-update.json")))

ironic_node = get_ironic_node(node, fake_client)
ironic_node.return_value = node_data

return ironic_node.return_value['provision_state']


def test_args():
var = get_args()
assert var['data']['ip_addresses'][0]['host'] == "10.46.96.156"


def test_ironic_node_allowing_states(fake_client):
ironic_node_state = get_ironic_node_state(fake_client,
json.loads(read_json_samples(
"json_samples/ironic-enroll-node-data.json")))
assert ironic_node_state in ["enroll", "manageable"]


def test_ironic_non_allowing_states(fake_client):
ironic_node_state = get_ironic_node_state(fake_client,
json.loads(read_json_samples(
"json_samples/ironic-active-node-data.json")))
assert ironic_node_state not in ["enroll", "manageable"]


def test_update_ironic_node(fake_client):
node = IronicNodeConfiguration.from_event(json.loads(read_json_samples("json_samples/event-interface-update.json")))
drac_ip = json.loads(read_json_samples("json_samples/event-interface-update.json"))['data']["ip_addresses"][0]["host"]

patches = [{'op': 'add', 'path': '/name', 'value': '1327198-GP2S.3.understack.iad3'},
{'op': 'add', 'path': '/driver', 'value': 'idrac'},
{'op': 'add',
'path': '/driver_info/redfish_address',
'value': 'https://10.46.96.156'},
{'op': 'add', 'path': '/driver_info/redfish_verify_ca', 'value': False},
{'op': 'remove', 'path': '/bios_interface'},
{'op': 'remove', 'path': '/boot_interface'},
{'op': 'remove', 'path': '/inspect_interface'},
{'op': 'remove', 'path': '/management_interface'},
{'op': 'remove', 'path': '/power_interface'},
{'op': 'remove', 'path': '/vendor_interface'},
{'op': 'remove', 'path': '/raid_interface'},
{'op': 'remove', 'path': '/network_interface'}]
update_ironic_node(node, drac_ip, fake_client)
fake_client.update_node.assert_called_once_with(node.uuid, patches)
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ def main():
ironic_node = None
sys.exit(1)

STATES_ALLOWING_UPDATES = ["enroll", "manage"]
STATES_ALLOWING_UPDATES = ["enroll", "manageable"]
if ironic_node.provision_state not in STATES_ALLOWING_UPDATES:
logger.info(
f"Device {node.uuid} is in a {ironic_node.provision_state} "
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,45 +17,38 @@ def write_ironic_state_to_file(state):
f.write(state)


def main():
def get_args():
if len(sys.argv) < 1:
raise ValueError(
"Please provide node configuration in JSON format as first argument."
)

logger.info("Pushing device new node to Ironic.")
client = IronicClient(
return json.loads(sys.argv[1])


def get_ironic_client():
return IronicClient(
svc_url=read_secret("IRONIC_SVC_URL"),
username=read_secret("IRONIC_USERNAME"),
password=read_secret("IRONIC_PASSWORD"),
auth_url=read_secret("IRONIC_AUTH_URL"),
tenant_name=read_secret("IRONIC_TENANT"),
)

interface_update_event = json.loads(sys.argv[1])
logger.debug(f"Received: {json.dumps(interface_update_event, indent=2)}")
update_data = interface_update_event["data"]

node = IronicNodeConfiguration.from_event(interface_update_event)
def get_ironic_node(node, ironic_client):
logger.debug(f"Checking if node UUID {node.uuid} exists in Ironic.")

try:
ironic_node = client.get_node(node.uuid)
ironic_node = ironic_client.get_node(node.uuid)
except ironicclient.common.apiclient.exceptions.NotFound:
logger.debug(f"Node: {node.uuid} not found in Ironic, creating")
ironic_node = node.create_node(client)
ironic_node = node.create_node(ironic_client)

logger.debug("Got Ironic node: %s", json.dumps(ironic_node.to_dict(), indent=2))
return ironic_node

STATES_ALLOWING_UPDATES = ["enroll", "manage"]
if ironic_node.provision_state not in STATES_ALLOWING_UPDATES:
logger.info(
f"Device {node.uuid} is in a {ironic_node.provision_state} "
f"provision_state, so the updates are not allowed."
)
sys.exit(0)

drac_ip = update_data["ip_addresses"][0]["host"]
def update_ironic_node(node, drac_ip, ironic_client):
expected_address = f"https://{drac_ip}"

updates = [
Expand All @@ -78,8 +71,34 @@ def main():
# using the behavior from the ironicclient code
patches = args_array_to_patch("add", updates)
patches.extend(args_array_to_patch("remove", resets))

response = client.update_node(node.uuid, patches)
logger.info(f"Patching: {patches}")

return ironic_client.update_node(node.uuid, patches)


def main():
interface_update_event = get_args()
logger.debug(f"Received: {json.dumps(interface_update_event, indent=2)}")
update_data = interface_update_event["data"]

logger.info("Pushing device new node to Ironic.")
ironic_client = get_ironic_client()

node = IronicNodeConfiguration.from_event(interface_update_event)
ironic_node = get_ironic_node(node, ironic_client)
logger.debug(f"Got Ironic node: {json.dumps(ironic_node.to_dict(), indent=2)}")

STATES_ALLOWING_UPDATES = ["enroll", "manageable"]
if ironic_node.provision_state not in STATES_ALLOWING_UPDATES:
logger.info(
f"Device {node.uuid} is in a {ironic_node.provision_state} "
f"provision_state, so the updates are not allowed."
)
sys.exit(0)

drac_ip = update_data["ip_addresses"][0]["host"]

response = update_ironic_node(node, drac_ip, ironic_client)
logger.info(f"Updated: {response}")

write_ironic_state_to_file(ironic_node.provision_state)

0 comments on commit 98f6d7c

Please sign in to comment.