# 2주차 과제 - 호세헌

**URL:** <https://forum.openinfra-kr.org/t/topic/140>\
**Category:** 컨트리뷰션\
**Tags:** 컨트리뷰션, 2025컨트리뷰션\
**Created:** [7월 26, 2025, 6:39오전 UTC](https://forum.openinfra-kr.org/t/topic/140 "2025-07-26T06:39:14Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

**Author:** ![seheon](https://forum.openinfra-kr.org/letter_avatar_proxy/v4/letter/s/a9a28c/32.png) [@seheon](https://forum.openinfra-kr.org/u/seheon)\
**Post date:** [7월 26, 2025, 6:39오전 UTC](https://forum.openinfra-kr.org/t/topic/140/1 "2025-07-26T06:39:15Z")

</div>

# 필수 과제

> server list는 아래와 같이 한정된 값만 출력을 해주고 있습니다.  
> 결과값에 project name , user name을 포함시켜서 출력되게 해주세요.

## 초기

```python
data = list(compute_client.servers(**search_opts))
for s in data:
		print(s)

```

`search_opts` 조건으로 서버 목록을 조회합니다.  
`openstack.compute.v2.server.Server` 객체들이 출력되는 것을 확인할 수 있습니다.  
이 출력에는 `tenant_id`, `user_id`, `location.project.name` 등의 정보가 포함되어 있습니다.

```bash
openstack.compute.v2.server.Server(id=182eef16-2d78-41c0-a962-c2e75cfc0699, name=cirros-instance, status=ACTIVE, tenant_id=5bbf3e275f984ec88409ee3169110457, user_id=54c9c618ceff42478c721eab531051df, metadata={}, hostId=878c6089e0f2518b4921f22d4c2aa8bbd90b3ba516dc43db2558ba97, image=, flavor={'vcpus': 1, 'ram': 256, 'disk': 1, 'ephemeral': 0, 'swap': 0, 'original_name': 'cirros256', 'extra_specs': {'hw_rng:allowed': 'True'}}, created=2025-07-16T07:12:06Z, updated=2025-07-16T07:12:35Z, addresses={'shared': [{'version': 4, 'addr': '192.168.233.187', 'OS-EXT-IPS:type': 'fixed', 'OS-EXT-IPS-MAC:mac_addr': 'fa:16:3e:0b:2e:c9'}, {'version': 4, 'addr': '192.168.100.59', 'OS-EXT-IPS:type': 'floating', 'OS-EXT-IPS-MAC:mac_addr': 'fa:16:3e:0b:2e:c9'}]}, accessIPv4=, accessIPv6=, links=[{'rel': 'self', 'href': 'http://133.186.203.38/compute/v2.1/servers/182eef16-2d78-41c0-a962-c2e75cfc0699'}, {'rel': 'bookmark', 'href': 'http://133.186.203.38/compute/servers/182eef16-2d78-41c0-a962-c2e75cfc0699'}], OS-DCF:diskConfig=AUTO, progress=0, OS-EXT-AZ:availability_zone=nova, pinned_availability_zone=nova, OS-SCH-HNT:scheduler_hints={}, config_drive=, key_name=None, OS-SRV-USG:launched_at=2025-07-16T07:12:34.000000, OS-SRV-USG:terminated_at=None, OS-EXT-SRV-ATTR:host=devstack, OS-EXT-SRV-ATTR:instance_name=instance-00000001, OS-EXT-SRV-ATTR:hypervisor_hostname=devstack.novalocal, OS-EXT-SRV-ATTR:reservation_id=r-nrmh6cpa, OS-EXT-SRV-ATTR:launch_index=0, OS-EXT-SRV-ATTR:hostname=cirros-instance, OS-EXT-SRV-ATTR:kernel_id=, OS-EXT-SRV-ATTR:ramdisk_id=, OS-EXT-SRV-ATTR:root_device_name=/dev/vda, OS-EXT-SRV-ATTR:user_data=None, OS-EXT-STS:task_state=None, OS-EXT-STS:vm_state=active, OS-EXT-STS:power_state=1, os-extended-volumes:volumes_attached=[{'id': '9ef4368c-4fd4-464c-b737-f079be71374b', 'delete_on_termination': True}], locked=False, locked_reason=None, description=None, tags=[], trusted_image_certificates=None, host_status=UP, security_groups=[{'name': 'default'}], location=Munch({'cloud': 'envvars', 'region_name': 'RegionOne', 'zone': 'nova', 'project': Munch({'id': '5bbf3e275f984ec88409ee3169110457', `'name': 'admin'`, 'domain_id': 'default', 'domain_name': None})}))

```

```python
# project_name, user_name
data = list(compute_client.servers(**search_opts))
for s in data:
    s.project_name = s.location.project.name
    s.user_name = identity_client.users.get(s.user_id).name if s.user_id else 'N/A'

```

**문제점:** `identity_client.users.get()`는 서버 개수만큼 API 호출이 발생합니다.

## 개선

```python
projects_map = {p.id: p.name for p in identity_client.projects.list()}
users_map = {u.id: u.name for u in identity_client.users.list()}

```

`projects_map`, `users_map` 로 미리 수집합니다.

```python
...

# project_name, user_name
data = list(compute_client.servers(**search_opts))
for s in data:
    s.project_name = projects_map.get(getattr(s, "project_id", getattr(s, "tenant_id", None)), "")
    s.user_name = users_map.get(getattr(s, "user_id", None), "")
        
images = {}
flavors = {}
        
...

```

```python
...

columns: tuple[str, ...] = (
    'id',
    'name',
    'status',
    'project_name',
    'user_name',
)
column_headers: tuple[str, ...] = (
    'ID',
    'Name',
    'Status',
    'Project Name',
    'User Name',
)

...

```

 ![image](https://forum.openinfra-kr.org/uploads/default/original/1X/d1a80f8b3eaf5d842e36b5f1518614df0dff1148.png)

# 선택 과제

> openstack server list를 입력했을 때 take\_action()함수를 찾아가는 과정 분석해보기

```json
// launch.json
{
  "version": "0.2.0",
  "configurations": [
    {
					... 
      "args": [
        "server",
        "list"
      ],
      "env": {
					...
      }
    }
  ]
}

```

## 1. 시작점: `main()` 함수

```python
# ./python-openstackclient/openstackclient/shell.py
def main(argv=None):
    if argv is None:
        argv = sys.argv[1:]

    return OpenStackShell().run(argv)

if __name__ == " __main__":
    sys.exit(main())

```

`argv = ['server', 'list']` 가 전달되어 `OpenStackShell.run(argv)` 을 호출합니다.

## 2. `OpenStackShell.run()` → `App.run(argv)` 호출

```python
# python-openstackclient/openstackclient
class OpenStackShell(app.App):
    ...
    def __init__ (..):
        ...

    def configure_logging(self):
				...
				
    def run(self, argv: list[str]) -> int:
        ret_val = 1
        self.command_options = argv
        try:
            ret_val = super().run(argv) # 부모 클래스 호출
            return ret_val
        ...

```

## 3. App.run(argv) 클래스

```python

class App:
		...
		def run(self, argv: list[str]) -> int:
        ...
        self.options, remainder = self.parser.parse_known_args(argv) # remainder = ['server', 'list']
				...
				self.initialize_app(remainder)
				...
				self.run_subcommand(remainder)

```

이때 `server`는 **command group** , `list`는 **command name** 입니다.

## 4. `App.run_subcommand()` 내부에서 해당 command를 로드

이 함수는 cliff의 핵심 기능 중 하나로, `'server list'` 명령에 해당하는 Command 클래스를 찾아서 해당 클래스의 `take_action()`을 실행합니다.

```python
def run_subcommand(self, argv: list[str]) -> int:
    ...
    subcommand = self.command_manager.find_command(argv)
    # ['server', 'list'] → ('ListServer', 'server list', ['list'])

    ...
    cmd_factory, cmd_name, sub_argv = subcommand
    ..
    cmd = cmd_factory(self, self.options, **kwargs) # → ListServer 인스턴스 생성
    ...
    result = cmd.run(parsed_args) # → 내부에서 take_action(parsed_args) 호출
    # ListServer의 take_action() 실행

```
