Skip to content

Commit 0736979

Browse files
committed
refactor: Extract configuration, exceptions, and HTTP client into separate modules
1 parent 299dc1f commit 0736979

22 files changed

+502
-395
lines changed

src/badfish/config.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
RETRIES = 15

src/badfish/helpers/exceptions.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
class BadfishException(Exception):
2+
pass

src/badfish/helpers/http_client.py

Lines changed: 198 additions & 201 deletions
Large diffs are not rendered by default.

src/badfish/helpers/parser.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
#!/usr/bin/env python3
21
import argparse
32

3+
from badfish.config import RETRIES
4+
45

56
def create_parser():
6-
"""Create and configure the argument parser for badfish CLI."""
77
parser = argparse.ArgumentParser(
88
prog="badfish",
99
description="Tool for managing server hardware via the Redfish API.",
@@ -235,7 +235,7 @@ def create_parser():
235235
"-r",
236236
"--retries",
237237
help="Number of retries for executing actions.",
238-
default=15, # RETRIES constant value
238+
default=RETRIES,
239239
)
240240
parser.add_argument(
241241
"--get-scp-targets",

src/badfish/main.py

Lines changed: 72 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
from badfish.helpers.logger import (
2121
BadfishLogger,
2222
)
23-
from badfish.helpers.http_client import BadfishHTTPClient
23+
from badfish.helpers.http_client import HTTPClient
2424

2525
from logging import (
2626
DEBUG,
@@ -61,7 +61,7 @@ def __init__(self, _host, _username, _password, _logger, _retries, _loop=None):
6161
self.loop = _loop
6262
if not self.loop:
6363
self.loop = asyncio.get_event_loop()
64-
self.http_client = BadfishHTTPClient(_host, _username, _password, _logger, _retries)
64+
self.http_client = HTTPClient(_host, _username, _password, _logger, _retries)
6565
self.system_resource = None
6666
self.manager_resource = None
6767
self.bios_uri = None
@@ -188,7 +188,7 @@ async def get_sriov_mode(self):
188188
async def get_bios_attributes_registry(self):
189189
self.logger.debug("Getting BIOS attribute registry.")
190190
_uri = "%s%s/Bios/BiosRegistry" % (self.host_uri, self.system_resource)
191-
data = await self.http_client.get_request(_uri)
191+
data = await self.http_client.get_json(_uri)
192192

193193
if not data:
194194
self.logger.error("Operation not supported by vendor.")
@@ -213,7 +213,7 @@ async def get_bios_attribute_registry(self, attribute):
213213
async def get_bios_attributes(self):
214214
self.logger.debug("Getting BIOS attributes.")
215215
_uri = "%s%s/Bios" % (self.host_uri, self.system_resource)
216-
data = await self.http_client.get_request(_uri)
216+
data = await self.http_client.get_json(_uri)
217217

218218
if not data:
219219
self.logger.error("Operation not supported by vendor.")
@@ -280,8 +280,11 @@ async def get_boot_devices(self):
280280
_uri = "%s%s/BootSources" % (self.host_uri, self.system_resource)
281281
_response = await self.get_request(_uri)
282282

283-
if _response.status == 404:
284-
self.logger.debug(_response.text)
283+
if _response and _response.status == 404:
284+
self.logger.debug(await _response.text())
285+
raise BadfishException("Boot order modification is not supported by this host.")
286+
287+
if not _response:
285288
raise BadfishException("Boot order modification is not supported by this host.")
286289

287290
raw = await _response.text("utf-8", "ignore")
@@ -380,11 +383,13 @@ async def get_host_type(self, _interfaces_path):
380383
return None
381384

382385
async def find_session_uri(self):
383-
data = await self.http_client.get_request(self.root_uri, _get_token=True)
386+
response = await self.http_client.get_request(self.root_uri, _get_token=True)
384387

385-
if not data:
388+
if not response:
386389
raise BadfishException(f"Failed to communicate with {self.host}")
387390

391+
raw = await response.text("utf-8", "ignore")
392+
data = json.loads(raw.strip())
388393
redfish_version = int(data["RedfishVersion"].replace(".", ""))
389394
session_uri = None
390395
if redfish_version >= 160:
@@ -393,8 +398,8 @@ async def find_session_uri(self):
393398
session_uri = "/redfish/v1/Sessions"
394399

395400
_uri = "%s%s" % (self.host_uri, session_uri)
396-
check_data = await self.http_client.get_request(_uri, _continue=True, _get_token=True)
397-
if check_data is None:
401+
check_response = await self.http_client.get_request(_uri, _continue=True, _get_token=True)
402+
if check_response is None:
398403
session_uri = "/redfish/v1/SessionService/Sessions"
399404

400405
return session_uri
@@ -404,26 +409,29 @@ async def validate_credentials(self):
404409
headers = {"content-type": "application/json"}
405410
_uri = "%s%s" % (self.host_uri, self.session_uri)
406411

407-
response_data, status, response_headers = await self.http_client.post_request(_uri, payload, headers, _get_token=True)
408-
412+
_response = await self.http_client.post_request(_uri, payload, headers, _get_token=True)
413+
414+
await _response.text("utf-8", "ignore")
415+
416+
status = _response.status
409417
if status == 401:
410418
raise BadfishException(f"Failed to authenticate. Verify your credentials for {self.host}")
411419
if status not in [200, 201]:
412420
raise BadfishException(f"Failed to communicate with {self.host}")
413-
421+
414422
# Extract token from response headers
415-
token = response_headers.get("X-Auth-Token") if response_headers else None
423+
token = _response.headers.get("X-Auth-Token")
416424
if token:
417425
self.token = token
418426
self.http_client.token = token
419427
# Store session info for cleanup
420-
self.session_id = response_headers.get("Location")
428+
self.session_id = _response.headers.get("Location")
421429

422430
return token
423431

424432
async def get_interfaces_endpoints(self):
425433
_uri = "%s%s/EthernetInterfaces" % (self.host_uri, self.system_resource)
426-
data = await self.http_client.get_request(_uri)
434+
data = await self.http_client.get_json(_uri)
427435

428436
if not data:
429437
raise BadfishException("EthernetInterfaces entry point not supported by this host.")
@@ -439,25 +447,30 @@ async def get_interfaces_endpoints(self):
439447

440448
async def get_interface(self, endpoint):
441449
_uri = "%s%s" % (self.host_uri, endpoint)
442-
data = await self.http_client.get_request(_uri)
450+
data = await self.http_client.get_json(_uri)
443451

444452
if not data:
445453
raise BadfishException("EthernetInterface entry point not supported by this host.")
446454

447455
return data
448456

449457
async def find_systems_resource(self):
450-
data = await self.http_client.get_request(self.root_uri)
451-
if not data:
458+
response = await self.http_client.get_request(self.root_uri)
459+
if not response:
452460
raise BadfishException("Failed to communicate with server.")
453461

462+
raw = await response.text("utf-8", "ignore")
463+
data = json.loads(raw.strip())
454464
if "Systems" not in data:
455465
raise BadfishException("Systems resource not found")
456466

457467
systems = data["Systems"]["@odata.id"]
458-
systems_data = await self.http_client.get_request(self.host_uri + systems)
459-
if not systems_data:
468+
systems_response = await self.http_client.get_request(self.host_uri + systems)
469+
if not systems_response:
460470
raise BadfishException("Authorization Error: verify credentials.")
471+
472+
raw = await systems_response.text("utf-8", "ignore")
473+
systems_data = json.loads(raw.strip())
461474

462475
if systems_data.get("Members"):
463476
for member in systems_data["Members"]:
@@ -468,17 +481,23 @@ async def find_systems_resource(self):
468481
raise BadfishException("ComputerSystem's Members array is either empty or missing")
469482

470483
async def find_managers_resource(self):
471-
data = await self.http_client.get_request(self.root_uri)
472-
if not data:
484+
response = await self.http_client.get_request(self.root_uri)
485+
if not response:
473486
raise BadfishException("Failed to communicate with server.")
474487

488+
raw = await response.text("utf-8", "ignore")
489+
data = json.loads(raw.strip())
475490
self.vendor = "Dell" if "Dell" in data["Oem"] else "Supermicro"
476491

477492
if "Managers" not in data:
478493
raise BadfishException("Managers resource not found")
479494

480495
managers = data["Managers"]["@odata.id"]
481-
managers_data = await self.http_client.get_request(self.host_uri + managers)
496+
managers_response = await self.http_client.get_request(self.host_uri + managers)
497+
managers_data = None
498+
if managers_response:
499+
raw = await managers_response.text("utf-8", "ignore")
500+
managers_data = json.loads(raw.strip())
482501
if managers_data and managers_data.get("Members"):
483502
for member in managers_data["Members"]:
484503
managers_service = member["@odata.id"]
@@ -487,11 +506,24 @@ async def find_managers_resource(self):
487506
else:
488507
raise BadfishException("Manager's Members array is either empty or missing")
489508

509+
# HTTP client wrapper methods
510+
async def get_request(self, uri, _continue=False, _get_token=False):
511+
return await self.http_client.get_request(uri, _continue, _get_token)
512+
513+
async def post_request(self, uri, payload, headers, _get_token=False):
514+
return await self.http_client.post_request(uri, payload, headers, _get_token)
515+
516+
async def patch_request(self, uri, payload, headers, _continue=False):
517+
return await self.http_client.patch_request(uri, payload, headers, _continue)
518+
519+
async def delete_request(self, uri, headers):
520+
return await self.http_client.delete_request(uri, headers)
521+
490522
async def get_power_state(self):
491523
_uri = "%s%s" % (self.host_uri, self.system_resource)
492524
self.logger.debug("url: %s" % _uri)
493525

494-
data = await self.http_client.get_request(_uri, _continue=True)
526+
data = await self.http_client.get_json(_uri, _continue=True)
495527
if not data:
496528
self.logger.debug("Couldn't get power state. Retrying.")
497529
return "Down"
@@ -796,34 +828,38 @@ async def check_schedule_job_status(self, job_id):
796828

797829
if status_code == 200:
798830
await asyncio.sleep(10)
831+
# Only try to access job data if we got a successful response
832+
if isinstance(data, dict) and 'Id' in data:
833+
self.logger.info(f"JobID: {data[u'Id']}")
834+
self.logger.info(f"Name: {data[u'Name']}")
835+
self.logger.info(f"Message: {data[u'Message']}")
836+
self.logger.info(f"PercentComplete: {str(data[u'PercentComplete'])}")
799837
else:
800838
self.logger.error(f"Command failed to check job status, return code is {status_code}")
801839
self.logger.debug(f"Extended Info Message: {data}")
802840
return False
803-
804-
self.logger.info(f"JobID: {data[u'Id']}")
805-
self.logger.info(f"Name: {data[u'Name']}")
806-
self.logger.info(f"Message: {data[u'Message']}")
807-
self.logger.info(f"PercentComplete: {str(data[u'PercentComplete'])}")
808841
else:
809842
self.logger.error("Command failed to check job status")
810843
return False
811844

812845
async def check_job_status(self, job_id):
813846
for count in range(self.retries):
814847
_url = f"{self.host_uri}{self.manager_resource}/Jobs/{job_id}"
815-
self.get_request.cache_clear()
848+
self.http_client.get_request.cache_clear()
816849
_response = await self.get_request(_url)
817850

818851
status_code = _response.status
819852
raw = await _response.text("utf-8", "ignore")
820853
data = json.loads(raw.strip())
821-
if status_code == 200:
822-
pass
823-
else:
854+
if status_code != 200:
824855
self.logger.error(f"Command failed to check job status, return code is {status_code}")
825856
self.logger.debug(f"Extended Info Message: {data}")
826857
return False
858+
859+
if "Message" not in data:
860+
self.logger.warning("Job status response missing Message field")
861+
return False
862+
827863
if "Fail" in data["Message"] or "fail" in data["Message"]:
828864
self.logger.debug(f"\n{job_id} job failed.")
829865
return False
@@ -2096,7 +2132,7 @@ async def export_scp(self, file_path, targets="ALL", include_read_only=False):
20962132
while True:
20972133
ct = get_now() - start_time
20982134
uri = "%s/redfish/v1/TaskService/Tasks/%s" % (self.host_uri, job_id)
2099-
response = await self.get_raw(uri)
2135+
response = await self.get_request(uri)
21002136
raw = await response.text("utf-8", "ignore")
21012137
data = json.loads(raw.strip())
21022138
if "SystemConfiguration" in data:
@@ -2164,7 +2200,7 @@ async def import_scp(self, file_path, targets="ALL"):
21642200
while True:
21652201
ct = get_now() - start_time
21662202
uri = "%s/redfish/v1/TaskService/Tasks/%s" % (self.host_uri, job_id)
2167-
response = await self.get_raw(uri)
2203+
response = await self.get_request(uri)
21682204
raw = await response.text("utf-8", "ignore")
21692205
data = json.loads(raw.strip())
21702206
if response.status in [200, 202]:

0 commit comments

Comments
 (0)