4주차 과제 - 권순석 (1)

volume_message

  • openstack volume message 명령어에 나타나는 메시지들은 블록 스토리지 서비스인 cinder 에서 볼륨과 관련된 작업(생성, 삭제, 확장, 연결 등)을 수행하다가 실패했을 때 기록되는 오류 로그들입니다.

  • 사용자가 API를 통해 내부 작업을 진행하다가, cinder 내부에서 오류가 발생한 경우 이를 알 수 있도록 사용자에게 메시지를 남기는 기능이라고 볼 수 있습니다.

  • 보통 가용 공간 부족, 적합한 스토리지 호스트 없음, 이미지로부터 볼륨 생성 실패, 볼륨 삭제 실패(사용중), 볼륨 확장 실패(사용중), 볼륨 연결 및 해제 실패 등의 시나리오에서 에러 메시지가 발생합니다.

  • 따라서 volume_message 에 대해 삭제, 목록 확인, 상세 정보 확인 기능을 제공하는 코드입니다.


volume_message docs 분석

https://docs.openstack.org/python-openstackclient/latest/cli/command-objects/volume-message.html

volume message delete

openstack volume message delete <message-id> [<message-id> ...]

  • volume 실패 메시지의 id를 인자로 입력하여, 해당 메시지를 삭제합니다.

volume message list

openstack volume message list
    [--sort-column SORT_COLUMN]
    [--sort-ascending | --sort-descending]
    [--project <project>]
    [--project-domain <project-domain>]
    [--limit <limit>]
    [--marker <marker>]

  • volume 실패 메시지 리스트를 확인합니다.

    • --sort-column SORT_COLUMN 데이터를 정렬할 특정 컬럼값을 지정합니다. 먼저 지정된 컬럼이 우선순위를 가지며, 존재하지 않는 컬럼은 무시됩니다. (반복 가능)

    • --sort-ascending 컬럼들을 오름차순으로 정렬합니다.

    • --sort-descending 컬럼들을 내림차순으로 정렬합니다.

    • --project <project> 결과값을 프로젝트 기준(이름, ID값)으로 필터링합니다. (관리자 기능)

    • --project-domain <project-domain> 프로젝트 도메인 기준(이름, ID값)으로 결과를 필터링합니다. 프로젝트 이름이 충돌하는 경우 사용이 가능합니다.

    • --limit <limit> 반환할 엔트리의 개수를 제한합니다. 서버 지정 최댓값을 초과하는 경우, 최댓값이 사용됩니다.

    • --marker <marker> 결과를 반환할 컬렉션의 첫 번째 위치를 지정합니다. 이전 요청에서 반환된 값이어야 합니다.

volume message show

openstack volume message show <message-id>

  • volume 실패 메시지의 id를 인자로 입력하여, 해당 메시지를 확인합니다.

volume_message 코드 분석

  • volume message delete (DeleteMessage(…))

    def get_parser(self, prog_name):
        parser = super().get_parser(prog_name)
        parser.add_argument(
            'message_ids',
            metavar='<message-id>',
            nargs='+',
            help=_('Message(s) to delete (ID)'),
        )
    
        return parser
    
    
    • get_parser 부분에서는 삭제하려는 message_id를 인자로 받습니다.
    def take_action(self, parsed_args):
        volume_client = self.app.client_manager.volume
    
        if volume_client.api_version < api_versions.APIVersion('3.3'):
            msg = _(
                "--os-volume-api-version 3.3 or greater is required to "
                "support the 'volume message delete' command"
            )
            raise exceptions.CommandError(msg)
    
        # ...
    
    
    • take_action 부분에서는 제일 먼저 volume 관련 기능을 사용하기 위해 volume_client 를 받아옵니다.

    • 현재 사용하고 있는 volume_client의 api 버전이 3.3 미만이라면, 명령어를 지원하지 않습니다.

      • openstack --os-volume-api-version 3.3 volume message ...

        • 실행할 때 명시적으로 api 버전을 3.3으로 지정하면 임시 해결이 가능합니다.
    def take_action(self, parsed_args):
        # ...
    
        errors = 0
        for message_id in parsed_args.message_ids:
            try:
                volume_client.messages.delete(message_id)
            except Exception:
                LOG.error(_('Failed to delete message: %s'), message_id)
                errors += 1
    
        # ...
    
    
    • 이후 errors = 0 으로 지정해두고, 입력한 전체 message_id 값들에 대해 반복하여 실제 메시지 삭제를 진행합니다.

    • 만약 도중에 메시지 삭제를 실패한 경우, errors 값을 증가시켜 에러 수를 기록합니다.

        def take_action(self, parsed_args):
            # ...
    
            if errors > 0:
                total = len(parsed_args.message_ids)
                msg = _('Failed to delete %(errors)s of %(total)s messages.') % {
                    'errors': errors,
                    'total': total,
                }
                raise exceptions.CommandError(msg)
    
    
    • 이후 메시지 삭제를 완료하면, 에러 발생 횟수를 체크하여 입력한 전체 message_id 중 몇 개나 에러가 발생했는지 보여줍니다.
  • volume message list (ListMessages(…))

    def get_parser(self, prog_name):
        parser = super().get_parser(prog_name)
    
        parser.add_argument(
            '--project',
            metavar='<project>',
            help=_('Filter results by project (name or ID) (admin only)'),
        )
        identity_common.add_project_domain_option_to_parser(parser)
        pagination.add_marker_pagination_option_to_parser(parser)
    
        return parser
    
    
    • get_parser에서는 프로젝트 이름, ID를 통해 데이터를 필터링하는 --project에 대한 부분만 해당 함수에 작성되어 있습니다.

    • 나머지 옵션들은 identity_common.add_project_domain_option_to_parser(parser) pagination.add_marker_pagination_option_to_parser(parser) 이 두 부분에 구현된 것을 가져와서 현재 parser에 적용시킵니다.

    def take_action(self, parsed_args):
        volume_client = self.app.client_manager.volume
        identity_client = self.app.client_manager.identity
    
        if volume_client.api_version < api_versions.APIVersion('3.3'):
            msg = _(
                "--os-volume-api-version 3.3 or greater is required to "
                "support the 'volume message list' command"
            )
            raise exceptions.CommandError(msg)
    
        column_headers = (
            'ID',
            'Event ID',
            'Resource Type',
            'Resource UUID',
            'Message Level',
            'User Message',
            'Request ID',
            'Created At',
            'Guaranteed Until',
        )
    
    	  # ...
    
    
    • volume_clientidentity_client 를 동시에 가져옵니다.

    • API 버전 3.3 검사는 동일하게 진행합니다.

    • 이 함수 내부에서 column_headers 목록을 직접 작성해둔 것을 확인할 수 있습니다.

    def take_action(self, parsed_args):
        # ...
    
        project_id = None
        if parsed_args.project:
            project_id = identity_common.find_project(
                identity_client,
                parsed_args.project,
                parsed_args.project_domain,
            ).id
    
        search_opts = {
            'project_id': project_id,
        }
        data = volume_client.messages.list(
            search_opts=search_opts,
            marker=parsed_args.marker,
            limit=parsed_args.limit,
        )
    
        return (
            column_headers,
            (utils.get_item_properties(s, column_headers) for s in data),
        )
    
    
    • 이후 만약 --project 옵션을 사용했다면, 그 부분을 반영하기 위한 코드가 구현되어 있습니다.

      • 이 때, 해당 옵션이 관리자 전용이라 identity_client 를 통해 권한을 검사하는 것도 확인할 수 있습니다.
    • data 에 커맨드라인에서 입력했던 옵션들을 통해 필터링한 메시지 목록만 가져와 할당합니다.

    • 마지막 return 구문에서는 표 형태로 결과를 반환합니다.

      • 앞에서 정의했던 column_headers 값들과, 그에 대응되는 실제 데이터를 data 에서 뽑아 함께 반환합니다.
  • volume message show (ShowMessage(…))

    def get_parser(self, prog_name):
            parser = super().get_parser(prog_name)
            parser.add_argument(
                'message_id',
                metavar='<message-id>',
                help=_('Message to show (ID).'),
            )
    
            return parser
    
    
    • get_parser 부분에서는 확인하려는 message_id를 인자로 받습니다.
    def take_action(self, parsed_args):
        volume_client = self.app.client_manager.volume
    
        if volume_client.api_version < api_versions.APIVersion('3.3'):
            msg = _(
                "--os-volume-api-version 3.3 or greater is required to "
                "support the 'volume message show' command"
            )
            raise exceptions.CommandError(msg)
    
        message = volume_client.messages.get(parsed_args.message_id)
    
        return zip(*sorted(message._info.items()))
    
    
    • volume_client 를 받아오고, api 3.3 이상 버전을 검사하는 부분은 동일합니다.

    • 인자로 입력했던 message_id를 통해 실제 메시지 데이터를 message에 받아옵니다.

    • 메시지 정보 값(key-value 형태)들을 key 값 기준으로 정렬한 후, zip 명령어로 key 값끼리, value 값끼리 각각 묶어서 튜플을 만들고 반환합니다.


volume_message 기능 분석

  • 기능 실제 테스트를 위해 cinder 사용이 필요합니다.

  • 우선 NHN 클라우드에서 전용 인스턴스를 생성하여 devstack 을 구성합니다.

  • volume 에러 메시지를 생성하려고 사용중인 볼륨의 삭제를 시도했습니다.

    • 이렇게 에러가 발생한 것을 확인할 수 있지만,

    • openstack --os-volume-api-version 3.3 volume message list 해당 명령어로 확인할 수 없어서 다음 과제 진행시 함께 추가로 작성하도록 하겠습니다.