Command 개발: openstack image task show
미리 보는 결론
명령어를 구현하는 것으로 Openstack의 기능 하나하나가 어떤 방식으로 구현되어 있는지 체득할 수 있었습니다.
결과적으로는 관련 코드도 수천 줄을 읽었다. 어느 때보다 많이 코드를 읽었고, 기여에 필요한 프로세스도 모두 문서를 보고 실습하며 공부했습니다.
기여 활동이라고 여겼던 오픈소스 활동이 개발자 본인에게도 이렇게 크게 도움이 될 지는 몰랐습니다.
좋은 멘토님들과 멘티님들, 메인테이너들과 소통을 통해, 정말 배울 수 있었습니다.
앞으로도 개발을 하며 살아가면서 오픈 소스를 지속하면서 살아가고 싶습니다.
배경
Openstack은 Python-openstackclient와 OpenstackSDK를 중심으로 각각의 컴포넌트의 Client로 구성된 명령어들을 Opnestack으로 통합하고 있습니다.
그 중 우리는 명령어 아직 통합된 OpenstackSDK에 추가되지 않았던 명령어를 구현하는 데에 주안점을 두고 진행했습니다.
그 중 Glance 라는 OS 이미지를 관리하는 컴포넌트의 task-create, task-list, task-show 3개의 명령어를 3명의 멘티가 각각 맡아 구현하였습니다.
멘토님과 3명의 멘티가 따로 미팅을 수차례 함께 하면서, 끝내 구현에 성공했습니다.
기능 설명
image task show 라는 명령어는 그 중 Glance 라는 OS 이미지를 관리하는 컴포넌트의 커맨드 중 하나입니다.
glance에서 잘못된 형식의 OS image가 업로드되거나 만들어졌을 때 잘못된 이미지로 플랫폼에서 문제가 될 수 있습니다. task는 glance에서 새로운 image를 등록하기 전에 OS-Image의 상태와 등록 결과를 저장합니다.
그 중 내가 구현한 명령어는 만들어진 task를 지정해서 세부 사항을 확인할 수 있는 “task show”입니다.
기능 동작
명렁어 사용법
Usage: image task show <Task ID>
기능 동작 예시
$ openstack image task show 53326486-2089-4679-8d24-0b09192ad2d8 +------------+--------------------------------------------+ | Field | Value | +------------+--------------------------------------------+ | created_at | 2021-10-14T05:56:20Z | | expires_at | 2021-10-16T05:56:20Z | | id | 53326486-2089-4679-8d24-0b09192ad2d8 | | input | {} | | message | Input does not contain 'import_from' field | | owner | 1565f71789534d35b188781d7ec3e30e | | properties | schema='/v2/schemas/task' | | status | failure | | type | import | | updated_at | 2021-10-14T05:56:20Z | +------------+--------------------------------------------+
기능 구현 방법
Image task show 작성
image_client의 get_task를 호출하고 그 결과로 Task 객체를 반환합니다. Task 객체를 view로 변환하기 위해 format 함수로 처리하고 sort한 결과를 return 합니다.
크게 3개의 구현체로 이루어져 있습니다.
image_client의 get_task 함수
Task 객체
_format_task 함수
image_client는 client_manager가 keystone에서 openstacksdk의 구현체를 인스턴스화해서 가져오는 객체입니다.
이 부분은 꼭 해당 명령어를 디버깅해가면서 확인해 보셔야 찾을 수 있습니다. 과제 2 참고
openstacksdk 프로젝트의 openstack/image/v2/_proxy.py 의 get_task의 부분
def get_task(self, task): """Get task details :param task: The value can be the ID of a task or a :class:`~openstack.image.v2.task.Task` instance. :returns: One :class:`~openstack.image.v2.task.Task` :raises: :class:`~openstack.exceptions.ResourceNotFound` when no resource can be found. """ return self._get(_task.Task, task)
Task 객체
_format_task 역시 Task 객체의 member 변수를 dictionary 형태로 반환합니다.
Task 객체로 보여줄 수 있는 멤버 변수는 created_at, expires_at, input, message, owner_id, result, schema, status, type 가 있습니다.
class Task(resource.Resource): resources_key = 'tasks' base_path = '/tasks' # capabilities allow_create = True allow_fetch = True allow_list = True _query_mapping = resource.QueryParameters( 'type', 'status', 'sort_dir', 'sort_key' ) created_at = resource.Body('created_at') expires_at = resource.Body('expires_at') input = resource.Body('input') message = resource.Body('message') owner_id = resource.Body('owner') result = resource.Body('result') schema = resource.Body('schema') status = resource.Body('status') type = resource.Body('type') updated_at = resource.Body('updated_at')
Image 객체와 image_client의 find_image 함수는 각각 openstacksdk로부터 호출된 결과입니다. (디버깅으로 객체를 인스턴스화한 상태로 보아야 openstackSDK로 연결됨을 확인할 수 있습니다.)
Task의 유닛 테스트를 위한 Test와 Fake 구현
Python-openstackclient에서는 가져온 API의 기능이 해석되어 Openstack shell commmand로 잘 호출 되는 지 가 테스트의 핵심입니다.
이를 위해 유닛 테스트이 API 호출 부분은 Fake Mock 객체를 사용하여 처리하고 있고 기존에 없던 Fake_task를 구현하였습니다.
등록된 Image의 상태와 종류, 이미지를 등록할 owner 프로젝트가 Task를 나타냅니다. 따라서 Task에서 핵심적으로 일치 여부를 테스트 해야하는 부분은 id, type, owner, status 로 지정했습니다.
Fake_task 코드
class FakeTaskv2Client(object): def __init__(self, **kwargs): self.get_task = mock.Mock() self.get_task.resource_class = fakes.FakeResource(None, {}) self.auth_token = kwargs['token'] self.management_url = kwargs['endpoint'] self.version = 2.0 class TestTaskv2(utils.TestCommand): def setUp(self): super(TestTaskv2, self).setUp() self.app.client_manager.image = FakeTaskv2Client( endpoint=fakes.AUTH_URL, token=fakes.AUTH_TOKEN, ) self.app.client_manager.identity = identity_fakes.FakeIdentityv3Client( endpoint=fakes.AUTH_URL, token=fakes.AUTH_TOKEN, ) class FakeTask(object): """Fake one or more tasks. """ @staticmethod def create_one_task(attrs=None): """Create a fake task. :param Dictionary attrs: A dictionary with all attributes of task :return: A FakeResource object with id, type and owner attrs """ attrs = attrs or {} # Set default attribute task_info = { 'id': str(uuid.uuid4()), 'type': 'image-name' + uuid.uuid4().hex, 'owner': 'image-owner' + uuid.uuid4().hex, 'status': 'image-status' + uuid.uuid4().hex, } # Overwrite default attributes if there are some attributes set task_info.update(attrs) return task.Task(**task_info)
python-openstackclient의 unit test를 위한 test_image.py 코드
class TestTask(task_fakes.TestTaskv2): def setUp(self): super(TestTask, self).setUp() # SDK proxy mock self.app.client_manager.image = mock.Mock() self.client = self.app.client_manager.image self.client.show_task = mock.Mock() class TestTaskShow(TestTask): _data = task_fakes.FakeTask.create_one_task() columns = ( 'id', 'owner', 'status', 'type' ) data = ( _data.id, _data.owner_id, _data.status, _data.type ) def setUp(self): super(TestTaskShow, self).setUp() self.client.get_task = mock.Mock(return_value=self._data) # Get the command object to test self.cmd = image.ShowTask(self.app, None) def test_task_show(self): arglist = [ task_fakes.task_id ] verifylist = [ ('task', task_fakes.task_id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) # In base command class ShowOne in cliff, abstract method take_action() # returns a two-part tuple with a tuple of column names and a tuple of # data to be shown. columns, data = self.cmd.take_action(parsed_args) self.client.get_task.assert_called_with( task_fakes.task_id ) self.assertEqual(self.columns, columns) self.assertCountEqual(self.data, data)
가장 어려웠던 것은 하나도 내가 계획하지 않았던 거대한 코드를 수정하기 시작하는 것이었습니다.
저 또한 유사한 형태의 코드를 프로젝트 내에서 참고하면서 시작하였습니다.
그 코드의 목적성에 맞게 고쳐나가고 모르면 동료들과 커뮤니티에 물어가며 진행하였습니다.
이 글이 새로이 오픈스택에서 기능 구현하는 분들께 자그마한 도움이 되신다면 좋겠습니다.
혹시 질문 있으시면 lsmman07@gmai.com로 메일 주세요.
