Skip to content

Commit

Permalink
CRAYSAT-1896: Add CFSClient methods to get configurations, sessions
Browse files Browse the repository at this point in the history
Add methods to the `CFSV2Client` and `CFSV3Client` classes to get a list
of all the CFS configurations and sessions. The CFS V3 version handles
paging in the same way paging is handled for the `components` resource.

This change is needed for the `sat bootprep` command to be updated to
support CFS v2 and v3 because it looks through all the existing CFS
configurations to determine if there are any existing CFS configurations
of the same name.

Test Description:
Unit tests only so far. This will be tested by pulling it into the
corresponding branch in the sat repository that adds CFS v2/v3 support
to `sat bootprep`.
  • Loading branch information
haasken-hpe committed Nov 18, 2024
1 parent fd76876 commit 3996ca0
Show file tree
Hide file tree
Showing 3 changed files with 185 additions and 12 deletions.
10 changes: 8 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,17 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [2.3.0] - 2024-11-18

### Added
- Added methods to the `CFSV2Client` and `CFSV3Client` classes to get a list of
all the configurations and sessions, handling paging for the CFS v3 API.

## [2.2.4] - 2024-10-23

### Updated
- Updated `poetry.lock` file to fetch latest version of `cray product catalog`
to which hash value remains consistent.
- Updated `poetry.lock` file to fetch latest version of `cray product catalog`
to which hash value remains consistent.

## [2.2.3] - 2024-10-14

Expand Down
77 changes: 67 additions & 10 deletions csm_api_client/service/cfs.py
Original file line number Diff line number Diff line change
Expand Up @@ -1474,6 +1474,34 @@ def get_components(self, params: Dict = None) -> Generator[Dict, None, None]:
"""
pass

@abstractmethod
def get_configurations(self, params: Dict = None) -> Generator[Dict, None, None]:
"""Get all the CFS configurations.
This method must handle paging if necessary.
Args:
params: the parameters to pass to the GET on configurations
Yields:
The CFS configurations.
"""
pass

@abstractmethod
def get_sessions(self, params: Dict = None) -> Generator[Dict, None, None]:
"""Get all the CFS sessions.
This method must handle paging if necessary.
Args:
params: the parameters to pass to the GET on sessions
Yields:
The CFS sessions.
"""
pass

def get_component_ids_using_config(self, config_name: str) -> List[str]:
"""Get a list of CFS components using the given CFS configuration.
Expand Down Expand Up @@ -1564,14 +1592,29 @@ class CFSV2Client(CFSClientBase):
def join_words(*words: str) -> str:
return ''.join([words[0].lower()] + [word.capitalize() for word in words[1:]])

def get_components(self, params: Dict = None) -> Generator[Dict, None, None]:
def get_resource(self, resource: str, params: Dict = None) -> Generator[Dict, None, None]:
"""Get a resource from the CFS API.
Args:
resource: the name of the resource to get (e.g. 'components')
params: the parameters to pass to the GET on the resource
"""
try:
yield from self.get('components', params=params).json()
yield from self.get(resource, params=params).json()
except APIError as err:
raise APIError(f'Failed to get CFS components: {err}')
raise APIError(f'Failed to get CFS {resource}: {err}')
except ValueError as err:
raise APIError(f'Failed to parse JSON in response from CFS when getting '
f'components: {err}')
f'{resource}: {err}')

def get_components(self, params: Dict = None) -> Generator[Dict, None, None]:
yield from self.get_resource('components', params=params)

def get_configurations(self, params: Dict = None) -> Generator[Dict, None, None]:
yield from self.get_resource('configurations', params=params)

def get_sessions(self, params: Dict = None) -> Generator[Dict, None, None]:
yield from self.get_resource('sessions', params=params)


# Create an alias for CFSClient that points at CFSV2Client to preserve backwards compatibility
Expand All @@ -1586,19 +1629,33 @@ class CFSV3Client(CFSClientBase):
def join_words(*words: str) -> str:
return '_'.join([word.lower() for word in words])

def get_components(self, params: Dict = None) -> Generator[Dict, None, None]:
def get_paged_resource(self, resource: str, params: Dict = None) -> Generator[Dict, None, None]:
"""Get a paged resource from the CFS API.
Args:
resource: the name of the resource to get (e.g. 'components')
params: the parameters to pass to the GET on the resource
"""
# On the first request, pass in user-specified parameters
next_params = params
try:
while True:
response = self.get('components', params=next_params).json()
yield from response['components']
# The CFS API preserves user-specified parameters and adds pagination parameters
response = self.get(resource, params=next_params).json()
yield from response[resource]
next_params = response.get('next')
if not next_params:
break
except APIError as err:
raise APIError(f'Failed to get CFS components: {err}')
raise APIError(f'Failed to get CFS {resource}: {err}')
except ValueError as err:
raise APIError(f'Failed to parse JSON in response from CFS when getting '
f'components: {err}')
f'{resource}: {err}')

def get_components(self, params: Dict = None) -> Generator[Dict, None, None]:
yield from self.get_paged_resource('components', params=params)

def get_configurations(self, params: Dict = None) -> Generator[Dict, None, None]:
yield from self.get_paged_resource('configurations', params=params)

def get_sessions(self, params: Dict = None) -> Generator[Dict, None, None]:
yield from self.get_paged_resource('sessions', params=params)
110 changes: 110 additions & 0 deletions tests/service/test_cfs.py
Original file line number Diff line number Diff line change
Expand Up @@ -1885,4 +1885,114 @@ def test_get_components_unpaged(self):
mock_get.assert_has_calls([
call('components', params=None),
call().json()
])

def test_get_configurations_paged(self):
"""Test get_configurations method of CFSV3Client with paged results"""
cfs_client = CFSV3Client(Mock())
configurations = [
{'name': 'config-1'},
{'name': 'config-2'},
{'name': 'config-3'},
{'name': 'config-4'},
{'name': 'config-5'},
]

base_params = {'limit': 2}
with patch.object(cfs_client, 'get') as mock_get:
mock_get.return_value.json.side_effect = [
{'configurations': configurations[:2], 'next': {'limit': 2, 'after': 'config-2'}},
{'configurations': configurations[2:4], 'next': {'limit': 2, 'after': 'config-4'}},
{'configurations': [configurations[4]], 'next': None}
]

result = list(cfs_client.get_configurations(params=base_params))

self.assertEqual(configurations, result)
mock_get.assert_has_calls([
call('configurations', params=base_params),
call().json(),
call('configurations', params={'limit': 2, 'after': 'config-2'}),
call().json(),
call('configurations', params={'limit': 2, 'after': 'config-4'}),
call().json()
])

def test_get_configurations_unpaged(self):
"""Test get_configurations method of CFSV3Client when results are not paged"""
cfs_client = CFSV3Client(Mock())
configurations = [
{'name': 'config-1'},
{'name': 'config-2'},
{'name': 'config-3'},
{'name': 'config-4'},
{'name': 'config-5'},
]

with patch.object(cfs_client, 'get') as mock_get:
mock_get.return_value.json.side_effect = [
{'configurations': configurations, 'next': None}
]

result = list(cfs_client.get_configurations())

self.assertEqual(configurations, result)
mock_get.assert_has_calls([
call('configurations', params=None),
call().json()
])

def test_get_sessions_paged(self):
"""Test get_sessions method of CFSV3Client with paged results"""
cfs_client = CFSV3Client(Mock())
sessions = [
{'name': 'session-1'},
{'name': 'session-2'},
{'name': 'session-3'},
{'name': 'session-4'},
{'name': 'session-5'},
]

base_params = {'limit': 2}
with patch.object(cfs_client, 'get') as mock_get:
mock_get.return_value.json.side_effect = [
{'sessions': sessions[:2], 'next': {'limit': 2, 'after': 'session-2'}},
{'sessions': sessions[2:4], 'next': {'limit': 2, 'after': 'session-4'}},
{'sessions': [sessions[4]], 'next': None}
]

result = list(cfs_client.get_sessions(params=base_params))

self.assertEqual(sessions, result)
mock_get.assert_has_calls([
call('sessions', params=base_params),
call().json(),
call('sessions', params={'limit': 2, 'after': 'session-2'}),
call().json(),
call('sessions', params={'limit': 2, 'after': 'session-4'}),
call().json()
])

def test_get_sessions_unpaged(self):
"""Test get_sessions method of CFSV3Client when results are not paged"""
cfs_client = CFSV3Client(Mock())
sessions = [
{'name': 'session-1'},
{'name': 'session-2'},
{'name': 'session-3'},
{'name': 'session-4'},
{'name': 'session-5'},
]

with patch.object(cfs_client, 'get') as mock_get:
mock_get.return_value.json.side_effect = [
{'sessions': sessions, 'next': None}
]

result = list(cfs_client.get_sessions())

self.assertEqual(sessions, result)
mock_get.assert_has_calls([
call('sessions', params=None),
call().json()
])

0 comments on commit 3996ca0

Please sign in to comment.