Skip to content

Commit 970e6e6

Browse files
authored
Merge pull request ceph#57138 from guits/node-proxy-improvements
node-proxy: make the daemon discover endpoints
2 parents 441dd6f + ce360a4 commit 970e6e6

File tree

9 files changed

+353
-174
lines changed

9 files changed

+353
-174
lines changed

src/ceph-node-proxy/ceph_node_proxy/baseredfishsystem.py

Lines changed: 131 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,134 @@
33
from ceph_node_proxy.basesystem import BaseSystem
44
from ceph_node_proxy.redfish_client import RedFishClient
55
from time import sleep
6-
from ceph_node_proxy.util import get_logger
6+
from ceph_node_proxy.util import get_logger, to_snake_case
77
from typing import Dict, Any, List, Callable, Union
88
from urllib.error import HTTPError, URLError
99

1010

11+
class EndpointMgr:
12+
NAME: str = 'EndpointMgr'
13+
14+
def __init__(self,
15+
client: RedFishClient,
16+
prefix: str = RedFishClient.PREFIX) -> None:
17+
self.log = get_logger(f'{__name__}:{EndpointMgr.NAME}')
18+
self.prefix: str = prefix
19+
self.client: RedFishClient = client
20+
21+
def __getitem__(self, index: str) -> Any:
22+
if index in self.__dict__:
23+
return self.__dict__[index]
24+
else:
25+
raise RuntimeError(f'{index} is not a valid endpoint.')
26+
27+
def init(self) -> None:
28+
_error_msg: str = "Can't discover entrypoint(s)"
29+
try:
30+
_, _data, _ = self.client.query(endpoint=self.prefix)
31+
json_data: Dict[str, Any] = json.loads(_data)
32+
for k, v in json_data.items():
33+
if '@odata.id' in v:
34+
self.log.debug(f'entrypoint found: {to_snake_case(k)} = {v["@odata.id"]}')
35+
_name: str = to_snake_case(k)
36+
_url: str = v['@odata.id']
37+
e = Endpoint(self, _url, self.client)
38+
setattr(self, _name, e)
39+
setattr(self, 'session', json_data['Links']['Sessions']['@odata.id']) # TODO(guits): needs to be fixed
40+
except (URLError, KeyError) as e:
41+
msg = f'{_error_msg}: {e}'
42+
self.log.error(msg)
43+
raise RuntimeError
44+
45+
46+
class Endpoint:
47+
NAME: str = 'Endpoint'
48+
49+
def __init__(self, url: str, client: RedFishClient) -> None:
50+
self.log = get_logger(f'{__name__}:{Endpoint.NAME}')
51+
self.url: str = url
52+
self.client: RedFishClient = client
53+
self.data: Dict[str, Any] = self.get_data()
54+
self.id: str = ''
55+
self.members_names: List[str] = []
56+
57+
if self.has_members:
58+
self.members_names = self.get_members_names()
59+
60+
if self.data:
61+
try:
62+
self.id = self.data['Id']
63+
except KeyError:
64+
self.id = self.data['@odata.id'].split('/')[-1:]
65+
else:
66+
self.log.warning(f'No data could be loaded for {self.url}')
67+
68+
def __getitem__(self, index: str) -> Any:
69+
if not getattr(self, index, False):
70+
_url: str = f'{self.url}/{index}'
71+
setattr(self, index, Endpoint(_url, self.client))
72+
return self.__dict__[index]
73+
74+
def query(self, url: str) -> Dict[str, Any]:
75+
data: Dict[str, Any] = {}
76+
try:
77+
self.log.debug(f'Querying {url}')
78+
_, _data, _ = self.client.query(endpoint=url)
79+
data = json.loads(_data)
80+
except KeyError as e:
81+
self.log.error(f'Error while querying {self.url}: {e}')
82+
return data
83+
84+
def get_data(self) -> Dict[str, Any]:
85+
return self.query(self.url)
86+
87+
def get_members_names(self) -> List[str]:
88+
result: List[str] = []
89+
if self.has_members:
90+
for member in self.data['Members']:
91+
name: str = member['@odata.id'].split('/')[-1:][0]
92+
result.append(name)
93+
return result
94+
95+
def get_name(self, endpoint: str) -> str:
96+
return endpoint.split('/')[-1:][0]
97+
98+
def get_members_endpoints(self) -> Dict[str, str]:
99+
members: Dict[str, str] = {}
100+
name: str = ''
101+
if self.has_members:
102+
for member in self.data['Members']:
103+
name = self.get_name(member['@odata.id'])
104+
members[name] = member['@odata.id']
105+
else:
106+
name = self.get_name(self.data['@odata.id'])
107+
members[name] = self.data['@odata.id']
108+
109+
return members
110+
111+
def get_members_data(self) -> Dict[str, Any]:
112+
result: Dict[str, Any] = {}
113+
if self.has_members:
114+
for member, endpoint in self.get_members_endpoints().items():
115+
result[member] = self.query(endpoint)
116+
return result
117+
118+
@property
119+
def has_members(self) -> bool:
120+
return 'Members' in self.data.keys()
121+
122+
11123
class BaseRedfishSystem(BaseSystem):
12124
def __init__(self, **kw: Any) -> None:
13125
super().__init__(**kw)
14-
self.common_endpoints: List[str] = kw.get('common_endpoints', ['/Systems/System.Embedded.1',
15-
'/UpdateService'])
16-
self.chassis_endpoint: str = kw.get('chassis_endpoint', '/Chassis/System.Embedded.1')
17126
self.log = get_logger(__name__)
18127
self.host: str = kw['host']
19128
self.port: str = kw['port']
20129
self.username: str = kw['username']
21130
self.password: str = kw['password']
22131
# move the following line (class attribute?)
23132
self.client: RedFishClient = RedFishClient(host=self.host, port=self.port, username=self.username, password=self.password)
133+
self.endpoints: EndpointMgr = EndpointMgr(self.client)
24134
self.log.info(f'redfish system initialization, host: {self.host}, user: {self.username}')
25135
self.data_ready: bool = False
26136
self.previous_data: Dict = {}
@@ -48,6 +158,8 @@ def __init__(self, **kw: Any) -> None:
48158
def main(self) -> None:
49159
self.stop = False
50160
self.client.login()
161+
self.endpoints.init()
162+
51163
while not self.stop:
52164
self.log.debug('waiting for a lock in the update loop.')
53165
with self.lock:
@@ -100,9 +212,7 @@ def _get_path(self, path: str) -> Dict:
100212
return result
101213

102214
def get_members(self, data: Dict[str, Any], path: str) -> List:
103-
_path = data[path]['@odata.id']
104-
_data = self._get_path(_path)
105-
return [self._get_path(member['@odata.id']) for member in _data['Members']]
215+
return [self._get_path(member['@odata.id']) for member in data['Members']]
106216

107217
def get_system(self) -> Dict[str, Any]:
108218
result = {
@@ -117,15 +227,18 @@ def get_system(self) -> Dict[str, Any]:
117227
'fans': self.get_fans()
118228
},
119229
'firmwares': self.get_firmwares(),
120-
'chassis': {'redfish_endpoint': f'/redfish/v1{self.chassis_endpoint}'} # TODO(guits): not ideal
121230
}
122231
return result
123232

124233
def _update_system(self) -> None:
125-
for endpoint in self.common_endpoints:
126-
result = self.client.get_path(endpoint)
127-
_endpoint = endpoint.strip('/').split('/')[0]
128-
self._system[_endpoint] = result
234+
system_members: Dict[str, Any] = self.endpoints['systems'].get_members_data()
235+
update_service_members: Endpoint = self.endpoints['update_service']
236+
237+
for member, data in system_members.items():
238+
self._system[member] = data
239+
self._sys[member] = dict()
240+
241+
self._system[update_service_members.id] = update_service_members.data
129242

130243
def _update_sn(self) -> None:
131244
raise NotImplementedError()
@@ -196,7 +309,7 @@ def get_device_led(self, device: str) -> Dict[str, Any]:
196309

197310
def set_device_led(self, device: str, data: Dict[str, bool]) -> int:
198311
try:
199-
_, response, status = self.client.query(
312+
_, _, status = self.client.query(
200313
data=json.dumps(data),
201314
method='PATCH',
202315
endpoint=self._sys['storage'][device]['redfish_endpoint']
@@ -207,7 +320,7 @@ def set_device_led(self, device: str, data: Dict[str, bool]) -> int:
207320
return status
208321

209322
def get_chassis_led(self) -> Dict[str, Any]:
210-
endpoint = f'/redfish/v1/{self.chassis_endpoint}'
323+
endpoint = list(self.endpoints['chassis'].get_members_endpoints().values())[0]
211324
try:
212325
result = self.client.query(method='GET',
213326
endpoint=endpoint,
@@ -227,10 +340,10 @@ def set_chassis_led(self, data: Dict[str, str]) -> int:
227340
# '{"IndicatorLED": "Lit"}' -> LocationIndicatorActive = false
228341
# '{"IndicatorLED": "Blinking"}' -> LocationIndicatorActive = true
229342
try:
230-
_, response, status = self.client.query(
343+
_, _, status = self.client.query(
231344
data=json.dumps(data),
232345
method='PATCH',
233-
endpoint=f'/redfish/v1{self.chassis_endpoint}'
346+
endpoint=list(self.endpoints['chassis'].get_members_endpoints().values())[0]
234347
)
235348
except HTTPError as e:
236349
self.log.error(f"Couldn't set the ident chassis LED: {e}")
@@ -260,7 +373,7 @@ def powercycle(self) -> int:
260373
def create_reboot_job(self, reboot_type: str) -> str:
261374
data: Dict[str, str] = dict(RebootJobType=reboot_type)
262375
try:
263-
headers, response, status = self.client.query(
376+
headers, _, _ = self.client.query(
264377
data=json.dumps(data),
265378
endpoint=self.create_reboot_job_endpoint
266379
)
@@ -273,7 +386,7 @@ def create_reboot_job(self, reboot_type: str) -> str:
273386
def schedule_reboot_job(self, job_id: str) -> int:
274387
data: Dict[str, Union[List[str], str]] = dict(JobArray=[job_id], StartTimeInterval='TIME_NOW')
275388
try:
276-
headers, response, status = self.client.query(
389+
_, _, status = self.client.query(
277390
data=json.dumps(data),
278391
endpoint=self.setup_job_queue_endpoint
279392
)

src/ceph-node-proxy/ceph_node_proxy/redfish_client.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,24 @@ def __init__(self,
2222
self.url: str = f'https://{self.host}:{self.port}'
2323
self.token: str = ''
2424
self.location: str = ''
25+
self.session_service: str = ''
26+
27+
def sessionservice_discover(self) -> None:
28+
_error_msg: str = "Can't discover SessionService url"
29+
try:
30+
_headers, _data, _status_code = self.query(endpoint=RedFishClient.PREFIX)
31+
json_data: Dict[str, Any] = json.loads(_data)
32+
self.session_service = json_data['Links']['Sessions']['@odata.id']
33+
except (URLError, KeyError) as e:
34+
msg = f'{_error_msg}: {e}'
35+
self.log.error(msg)
36+
raise RuntimeError
2537

2638
def login(self) -> None:
2739
if not self.is_logged_in():
40+
self.log.debug('Discovering SessionService url...')
41+
self.sessionservice_discover()
42+
self.log.debug(f'SessionService url is {self.session_service}')
2843
self.log.info('Logging in to '
2944
f"{self.url} as '{self.username}'")
3045
oob_credentials = json.dumps({'UserName': self.username,
@@ -35,7 +50,7 @@ def login(self) -> None:
3550
try:
3651
_headers, _data, _status_code = self.query(data=oob_credentials,
3752
headers=headers,
38-
endpoint='/redfish/v1/SessionService/Sessions/')
53+
endpoint=self.session_service)
3954
if _status_code != 201:
4055
self.log.error(f"Can't log in to {self.url} as '{self.username}': {_status_code}")
4156
raise RuntimeError
@@ -119,5 +134,5 @@ def query(self,
119134

120135
return response_headers, response_str, response_status
121136
except (HTTPError, URLError) as e:
122-
self.log.debug(f'{e}')
137+
self.log.debug(f'endpoint={endpoint} err={e}')
123138
raise

0 commit comments

Comments
 (0)