필수 과제
server list는 아래와 같이 한정된 값만 출력을 해주고 있습니다.
결과값에 project name , user name을 포함시켜서 출력되게 해주세요.
초기
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
등의 정보가 포함되어 있습니다.
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})}))
# 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 호출이 발생합니다.
개선
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
로 미리 수집합니다.
...
# 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 = {}
...
...
columns: tuple[str, ...] = (
'id',
'name',
'status',
'project_name',
'user_name',
)
column_headers: tuple[str, ...] = (
'ID',
'Name',
'Status',
'Project Name',
'User Name',
)
...
선택 과제
openstack server list를 입력했을 때 take_action()함수를 찾아가는 과정 분석해보기
// launch.json
{
"version": "0.2.0",
"configurations": [
{
...
"args": [
"server",
"list"
],
"env": {
...
}
}
]
}
1. 시작점: main()
함수
# ./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-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) 클래스
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()
을 실행합니다.
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() 실행