2020. 8. 21. 02:00ㆍCloud
1. Remote Debugging with VSCode
References
https://github.com/openstack-kr/contributon-2020/blob/master/Remote%20Debugging%20VSCode.md
먼저 실습을 진행하기 위한 VSCode 환경설정을 진행합니다.
Putty로 OpenStackClient를 Tracing 하기에는 많이 불편한 감이 있었습니다.
위의 포스팅을 참조하여 진행했으며, 따라하였으나 원격 접속 진입이 되지 않아 재작성합니다.
로컬설정
1. VSCode로 들어가서 Remote-SSH를 설치합니다.
2. VSCode 좌측 하단에 있는 초록색 버튼을 클릭한 후 Remote-SSH: Connet to Host...를 클릭합니다.
3. Configure SSH Host를 클릭합니다
4. 아래와 같이 Config file을 작성해줍니다.
Host : VSCode Remote - SSH 에서 보이는 이름 명
HostName : Remote Debugging 할 Target 의 IP Address
User : Remote SSH 접속할 Target의 login ID
IdentityFile : SSH 접속시 필요한 pem(개인키) 파일
* 개인키 파일은 잘 복사해서 .ssh 경로에 붙여넣기 하도록 합니다
설정후에 VSCode를 껏다가 다시 켜주시고요,
아래 보이는 Host 명을 클릭하면 접속이 됩니다.
좌측 하단의 SSH: 초록색 버튼을 통해 정상 접속이 된것을 알 수 있습니다.
저는 참조한 링크처럼 시도를 했을 때 아래와 같은 에러가 발생했습니다.
원격지 설정
root 계정이 아닐 시 Debugging을 할 수 없는 문제가 있었습니다.
Root 계정으로 접속하는 방법입니다. 먼저 일반 계정으로 접속한뒤 root 계정으로 switch user 하여 .ssh 내용을 변경토록 합니다.
# root 계정으로 전환
$ sudo su - root
# .ssh 내용 변경
root@:~# vim ~/.ssh/authorized_keys
#ssh-rsa 앞 까지 주석을 지워주면 됨
Local의 Remote - SSH Config File도 수정해줍니다.
이후 접속하여, 아래와 같이 설정해줍니다.
1. Open Folder를 클릭하고 Path를 입력합니다. 저는 Search로 찾을 거라 대충 / 경로로 넣었습니다.
[Python Extension 설치]
$ /usr/bin/python3.6 -m pip install --upgrade pip
$ /usr/bin/python3.6 /home/ubuntu/.vscode-server/extensions/ms-python.python-2020.8.101144/pythonFiles/pyvsc-run-isolated.py pip install -U pylint --user
[Debugg launch.json 설정]
Python file을 하나 연뒤 F5 (Debug)를 하게 되면 Add Configuration을 통해 만들 수 있습니다.
생성된 파일에 아래와 같이 설정해줍니다.
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Python: Current File",
"type": "python",
"request": "launch",
// 이곳은 시작할 프로그램 명을 넣습니다.
"program": "/usr/local/bin/openstack",
// 이곳은 arguments들을 입력합니다
"args": [
"server list"
],
"console": "integratedTerminal",
"env": {
"OS_PROJECT_NAME": "demo",
"OS_TENANT_NAME": "demo",
"OS_USERNAME": "demo",
"OS_PASSWORD": "secret",
"OS_REGION_NAME": "RegionOne",
"OS_IDENTITY_API_VERSION": "3",
"OS_AUTH_TYPE": "password",
"OS_AUTH_URL": "http://127.0.0.1/identity",
"OS_VOLUME_API_VERSION": "3",
"OS_USER_DOMAIN_ID": "default",
"OS_PROJECT_DOMAIN_ID": "default",
"CINDER_VERSION": "3"
},
"justMyCode": false,
"gevent": true,
"redirectOutput": true
}
]
}
{
"python.pythonPath": "/usr/bin/python3"
}
2. [실습] Openstack server list가 출력되는 과정 Tracing 하기
꼭 source openrc admin 을 해준 뒤, openstack을 실행시켜야 실행이 된다.
$sudo su- stack
$pwd
/opt/stack
stack@devstack-master:~$ cd devstack/
stack@devstack-master:~/devstack$ source openrc admin
WARNING: setting legacy OS_TENANT_NAME to support cli tools.
stack@devstack-ussuri:~/devstack$ openstack server list
+--------------------------------------+------+--------+---------------------------------------------------------------------+-------+---------+
| ID | Name | Status | Networks | Image | Flavor |
+--------------------------------------+------+--------+---------------------------------------------------------------------+-------+---------+
| baad8346-1541-4f12-a7f3-a41681398679 | test | ACTIVE | private=fd82:98dc:7575:0:f816:3eff:fec7:61d8, 10.0.0.22, 172.24.4.3 | | m1.nano |
+--------------------------------------+------+--------+---------------------------------------------------------------------+-------+---------+
VSCode Remote - SSH 를 사용하기 전에는 아래와 같이 Tracing 하였습니다.
stack@devstack-master:~/devstack$ whereis openstack
openstack: /etc/openstack /usr/local/bin/openstack
stack@devstack-ussuri:~/devstack$ cat /usr/local/bin/openstack
#!/usr/bin/python3.6
# -*- coding: utf-8 -*-
import re
import sys
from openstackclient.shell import main
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
sys.exit(main())
stack@devstack-ussuri:~/devstack$ python3
>>> import openstackclient
>>> openstackclient
<module 'openstackclient' from '/usr/local/lib/python3.6/dist-packages/openstackclient/__init__.py'>
stack@devstack-ussuri:~/devstack$ cat /usr/local/lib/python3.6/dist-packages/openstackclient/shell.py
1. whereis 로 Python file을 찾고
2. cat 으로 file 내용을 보고
3. import 한 module의 경로를 직접 찾았음
눈물없이 볼 수 없는 광경
이제는 VSCode를 사용하여 Tracing 하도록 합니다.
Tracing을 위해 자주 사용한 단축키는 아래와 같습니다.
- F12 : 함수가 선언된 곳으로 이동
- Ctrl + E (or Ctrl+P) : Python File 명으로 검색
- Ctrl + Shift + F : 전체 디렉토리에서 검색
- Alt + ← : 이전 결과로 돌아가기
- Alt + → : 다음 결과로 다시가기
- F9 : Break Point
- F5 : Debug Mode
- F11 : Step into
- F10 : Step
1.출발 : openstack 명령어
stack@devstack-ussuri:~/devstack$ openstack server list
+--------------------------------------+------+--------+---------------------------------------------------------------------+-------+---------+
| ID | Name | Status | Networks | Image | Flavor |
+--------------------------------------+------+--------+---------------------------------------------------------------------+-------+---------+
| baad8346-1541-4f12-a7f3-a41681398679 | test | ACTIVE | private=fd82:98dc:7575:0:f816:3eff:fec7:61d8, 10.0.0.22, 172.24.4.3 | | m1.nano |
+--------------------------------------+------+--------+---------------------------------------------------------------------+-------+---------+
openstack 이라는 명령어와 Argument는 어떻게 넘어가서 출력되는지 찾으러 떠납니다.
stack@devstack-master:~/devstack$ whereis openstack
openstack: /etc/openstack /usr/local/bin/openstack
Ctrl + Shift + F 를 사용하여 openstack 명령어가 실행된 파일을 클릭합니다.
/usr/local/bin/openstack
#!/usr/bin/python3.6
# -*- coding: utf-8 -*-
import re
import sys
from openstackclient.shell import main
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
sys.exit(main())
- line 1 : argument를 받습니다. 출력과 무관할 것이라 예상됩니다.
- line 2 : exit(main())으로 어딘가의 main으로 빠집니다. main() 위에 커서를 두고 F12를 클릭합니다
/usr/local/lib/python3.6/dist-packages/openstackclient/shell.py
def main(argv=None):
if argv is None:
argv = sys.argv[1:]
if six.PY2:
# Emulate Py3, decode argv into Unicode based on locale so that
# commands always see arguments as text instead of binary data
encoding = locale.getpreferredencoding()
if encoding:
argv = map(lambda arg: arg.decode(encoding), argv)
return OpenStackShell().run(argv)
- return 값으로 무엇인가를 넘겨주는 것을 볼 수 있습니다. 이 구문으로부터 출력이 나올것이라 예상하고 run이라는 function 위에서 F12를 눌러줍니다.
/usr/local/lib/python3.6/dist-packages/osc_lib/shell.py
class OpenStackShell(app.App):
def run(self, argv):
ret_val = 1
self.command_options = argv
try:
ret_val = super(OpenStackShell, self).run(argv)
return ret_val
except Exception as e:
if not logging.getLogger('').handlers:
logging.basicConfig()
if self.dump_stack_trace:
self.log.error(traceback.format_exc())
else:
self.log.error('Exception raised: ' + str(e))
return ret_val
finally:
self.log.info("END return value: %s", ret_val)
- run 이라는 function은 try~except 구문을 사용하고 있습니다.
- 이 구문을 자세히 보면 return ret_val을 넘겨주는 것을 알 수 있습니다.
- 따라서, ret_val에 전달되는 값인 super(OpenStackShell, self).run(argv)의 run 함수에 커서를 두고 F12를 누릅니다.
Super : Python의 상속의 개념에서 나오며, 자식클래스가 부모 클래스를 사용할 경우 사용합니다.
class className(Parent) : 괄호 안에 들어있는 것은 상속받는 부모 입니다.
/usr/local/lib/python3.6/dist-packages/cliff/app.py
class App(object):
def run(self, argv):
"""Equivalent to the main program for the application.
:param argv: input arguments and options
:paramtype argv: list of str
"""
try:
self.options, remainder = self.parser.parse_known_args(argv)
self.configure_logging()
self.interactive_mode = not remainder
if self.deferred_help and self.options.deferred_help and remainder:
# When help is requested and `remainder` has any values disable
# `deferred_help` and instead allow the help subcommand to
# handle the request during run_subcommand(). This turns
# "app foo bar --help" into "app help foo bar". However, when
# `remainder` is empty use print_help_if_requested() to allow
# for an early exit.
# Disabling `deferred_help` here also ensures that
# print_help_if_requested will not fire if called by a subclass
# during its initialize_app().
self.options.deferred_help = False
remainder.insert(0, "help")
self.initialize_app(remainder)
self.print_help_if_requested()
except Exception as err:
if hasattr(self, 'options'):
debug = self.options.debug
else:
debug = True
if debug:
self.LOG.exception(err)
raise
else:
self.LOG.error(err)
return 1
result = 1
if self.interactive_mode:
result = self.interact()
else:
result = self.run_subcommand(remainder)
return result
- try ~ except 구문을 사용하고 있습니다. 눈여겨 봐야할 것은 어떤 값이 반환되는지보는 return 값 입니다.
- return result를 사용하고 있으며, result에는 if ~ else 구문으로 각기 다른 값이 들어가고 있습니다.
- `remainder` is empty use print_help_if_requested() to allow
- 이 문장에서, arguments가 empty일 때 remainder가 0이 되고 -> interactive_mode=True가 됩니다.
- 따라서, run_subcommand()로 진입합니다.
/usr/local/lib/python3.6/dist-packages/cliff/app.py
class App(object):
def run_subcommand(self, argv):
try:
subcommand = self.command_manager.find_command(argv)
except ValueError as err:
# If there was no exact match, try to find a fuzzy match
the_cmd = argv[0]
fuzzy_matches = self.get_fuzzy_matches(the_cmd)
if fuzzy_matches:
article = 'a'
if self.NAME[0] in 'aeiou':
article = 'an'
self.stdout.write('%s: \'%s\' is not %s %s command. '
'See \'%s --help\'.\n'
% (self.NAME, ' '.join(argv), article,
self.NAME, self.NAME))
self.stdout.write('Did you mean one of these?\n')
for match in fuzzy_matches:
self.stdout.write(' %s\n' % match)
else:
if self.options.debug:
raise
else:
self.LOG.error(err)
return 2
cmd_factory, cmd_name, sub_argv = subcommand
kwargs = {}
if 'cmd_name' in utils.getargspec(cmd_factory.__init__).args:
kwargs['cmd_name'] = cmd_name
cmd = cmd_factory(self, self.options, **kwargs)
result = 1
try:
self.prepare_to_run_command(cmd)
full_name = (cmd_name
if self.interactive_mode
else ' '.join([self.NAME, cmd_name])
)
cmd_parser = cmd.get_parser(full_name)
parsed_args = cmd_parser.parse_args(sub_argv)
result = cmd.run(parsed_args)
except Exception as err:
if self.options.debug:
self.LOG.exception(err)
else:
self.LOG.error(err)
try:
self.clean_up(cmd, result, err)
except Exception as err2:
if self.options.debug:
self.LOG.exception(err2)
else:
self.LOG.error('Could not clean up: %s', err2)
if self.options.debug:
# 'raise' here gets caught and does not exit like we want
return result
else:
try:
self.clean_up(cmd, result, None)
except Exception as err3:
if self.options.debug:
self.LOG.exception(err3)
else:
self.LOG.error('Could not clean up: %s', err3)
return result
코드가 기니까 중요하지 않은 부분은 설명과 함께 주석으로 처리해서 핵심만 바라봅시다.
try:
subcommand = self.command_manager.find_command(argv)
# Exception 발생에 대한 처리입니다. 넘기도록 합니다.
# except ValueError as err:
# return 2
cmd_factory, cmd_name, sub_argv = subcommand
kwargs = {}
① if 'cmd_name' in utils.getargspec(cmd_factory.__init__).args:
kwargs['cmd_name'] = cmd_name
cmd = cmd_factory(self, self.options, **kwargs)
result = 1
try:
self.prepare_to_run_command(cmd)
full_name = (cmd_name
if self.interactive_mode
else ' '.join([self.NAME, cmd_name])
)
cmd_parser = cmd.get_parser(full_name)
parsed_args = cmd_parser.parse_args(sub_argv)
result = cmd.run(parsed_args)
# Exception 발생에 대한 처리입니다. 넘기도록 합니다.
# except Exception as err:
# return result
② else:
try:
self.clean_up(cmd, result, None)
# Exception 발생에 대한 처리입니다. 넘기도록 합니다.
except Exception as err3:
return result
- 가장 먼저 맨 마지막의 return result를 바라봅니다. 함수를 추적하는데 있어서 제일 먼저 바라봐야 할 것은 함수의 Entry와 Exit 입니다.
- result의 값이 유의미하게 쓰여지는 곳은 result = cmd.run(parsed_args) 입니다.
- 느낌상 cmd.run이 print 해줄 것 같습니다. 일단 따라갑니다.
/usr/local/lib/python3.6/dist-packages/osc_lib/command/command.py
class Command(command.Command, metaclass=CommandMeta):
def run(self, parsed_args):
self.log.debug('run(%s)', parsed_args)
return super(Command, self).run(parsed_args)
여기서부터는 답을 찾지 못했는데, 아시는 분은 알려주시면 감사할 것 같습니다.
20.08.30
F12를 눌르면 아래의 경로로 갑니다.
/usr/local/lib/python3.6/dist-packages/osc_lib/command/command.py
class Command(command.Command, metaclass=CommandMeta):
def run(self, parsed_args):
"""Invoked by the application when the command is run.
Developers implementing commands should override
:meth:`take_action`.
Developers creating new command base classes (such as
:class:`Lister` and :class:`ShowOne`) should override this
method to wrap :meth:`take_action`.
Return the value returned by :meth:`take_action` or 0.
"""
parsed_args = self._run_before_hooks(parsed_args)
return_code = self.take_action(parsed_args) or 0
return_code = self._run_after_hooks(parsed_args, return_code)
return return_code
그러나 실제로 디버깅 진입시에는 아래와 같은 경로로 진입합니다.
/usr/local/lib/python3.6/dist-packages/cliff/display.py
@six.add_metaclass(abc.ABCMeta)
class DisplayCommandBase(command.Command):
def run(self, parsed_args):
parsed_args = self._run_before_hooks(parsed_args)
self.formatter = self._formatter_plugins[parsed_args.formatter].obj
column_names, data = self.take_action(parsed_args)
column_names, data = self._run_after_hooks(parsed_args,
(column_names, data))
self.produce_output(parsed_args, column_names, data)
return 0
참조한 Site
[Python에서 자식 클래스 확인]
Foo.__subclasses__()
[<class '__main__.Bar'>, <class '__main__.Baz'>]
[Python에서 부모 클래스 확인]
cls.__bases__
(<class '__main__.A'>, <class '__main__.B'>)
[Python에서 Instance 확인]
simclass = Csimple()
isinstance(simclass,Csimple)
# simclass가 Csimple 클래스인지 확인. 결과는 True
마저 진행해보면
/usr/local/lib/python3.6/dist-packages/cliff/display.py
@six.add_metaclass(abc.ABCMeta)
class DisplayCommandBase(command.Command):
def run(self, parsed_args):
parsed_args = self._run_before_hooks(parsed_args)
self.formatter = self._formatter_plugins[parsed_args.formatter].obj
column_names, data = self.take_action(parsed_args)
column_names, data = self._run_after_hooks(parsed_args,
(column_names, data))
self.produce_output(parsed_args, column_names, data)
return 0
- self.produce_output() 함수를 통해 출력이 됨을 확인할 수 있습니다.
+--------------------------------------+---------+--------+----------------------------------------------------------------------+-------+---------+
| ID | Name | Status | Networks | Image | Flavor |
+--------------------------------------+---------+--------+----------------------------------------------------------------------+-------+---------+
| 6f91eb30-8687-49d7-859f-7284887d57f8 | 1st_Ins | ACTIVE | private=10.0.0.53, fd30:b09a:8b83:0:f816:3eff:fe79:1bec, 172.24.4.59 | | m1.nano |
+--------------------------------------+---------+--------+----------------------------------------------------------------------+-------+---------+
- 함수명에 output 이 있는걸로 보아 출력 된다는 것을 예상 할 수 있지만, 표준출력함수(stdout)이 나올 때 까지 좀 더 Tracing 해보도록 합니다.
/usr/local/lib/python3.6/dist-packages/cliff/display.py
@six.add_metaclass(abc.ABCMeta)
class DisplayCommandBase(command.Command):
def run(self, parsed_args):
self.produce_output(parsed_args, column_names, data)
return 0
@abc.abstractmethod
def produce_output(self, parsed_args, column_names, data):
"""Use the formatter to generate the output.
:param parsed_args: argparse.Namespace instance with argument values
:param column_names: sequence of strings containing names
of output columns
:param data: iterable with values matching the column names
"""
- DisplayCommandBase Class의 produce_output() function 에는 내용이 없습니다.
- F11을 누르면 상속받은 Class의 produce_output() function으로 갑니다.
DisplayCommandBase.__subclasses__()
[<class 'cliff.lister.Lister'>, <class 'cliff.lister.Lister'>, <class 'cliff.show.ShowOne'>]
class variables
0:<class 'cliff.lister.Lister'>
1:<class 'cliff.lister.Lister'>
2:<class 'cliff.show.ShowOne'>
/usr/local/lib/python3.6/dist-packages/cliff/lister.py
@six.add_metaclass(abc.ABCMeta)
class Lister(display.DisplayCommandBase):
def produce_output(self, parsed_args, column_names, data):
if parsed_args.sort_columns and self.need_sort_by_cliff:
indexes = [column_names.index(c) for c in parsed_args.sort_columns
if c in column_names]
if indexes:
data = sorted(data, key=operator.itemgetter(*indexes))
(columns_to_include, selector) = self._generate_columns_and_selector(
parsed_args, column_names)
if selector:
# Generator expression to only return the parts of a row
# of data that the user has expressed interest in
# seeing. We have to convert the compress() output to a
# list so the table formatter can ask for its length.
data = (list(self._compress_iterable(row, selector))
for row in data)
self.formatter.emit_list(columns_to_include,
data,
self.app.stdout,
parsed_args,
)
return 0
- self.formatter.emit_list 이후 출력이 됩니다.
- F11을 눌러 emit_list 함수를 따라갑니다.
- F12를 눌러 선언을 확인했을 때는 이동하지 않았었습니다. ㅠㅠ.
+--------------------------------------+---------+--------+----------------------------------------------------------------------+-------+---------+
| ID | Name | Status | Networks | Image | Flavor |
+--------------------------------------+---------+--------+----------------------------------------------------------------------+-------+---------+
| 6f91eb30-8687-49d7-859f-7284887d57f8 | 1st_Ins | ACTIVE | private=10.0.0.53, fd30:b09a:8b83:0:f816:3eff:fe79:1bec, 172.24.4.59 | | m1.nano |
+--------------------------------------+---------+--------+----------------------------------------------------------------------+-------+---------+
/usr/local/lib/python3.6/dist-packages/cliff/formatters/table.py
class TableFormatter(base.ListFormatter, base.SingleFormatter):
def emit_list(self, column_names, data, stdout, parsed_args):
x = prettytable.PrettyTable(
column_names,
print_empty=parsed_args.print_empty,
)
x.padding_width = 1
# Add rows if data is provided
if data:
self.add_rows(x, column_names, data)
# Choose a reasonable min_width to better handle many columns on a
# narrow console. The table will overflow the console width in
# preference to wrapping columns smaller than 8 characters.
min_width = 8
self._assign_max_widths(
stdout, x, int(parsed_args.max_width), min_width,
parsed_args.fit_width)
formatted = x.get_string()
stdout.write(formatted)
stdout.write('\n')
return
- stdou.wrtie(formatted) 이후 출력이 나옵니다.
- 이를 통해서 formatted 라는 곳에 문자열(string)이 들어가 있음을 알 수 있겠습니다.
+--------------------------------------+---------+--------+----------------------------------------------------------------------+-------+---------+
| ID | Name | Status | Networks | Image | Flavor |
+--------------------------------------+---------+--------+----------------------------------------------------------------------+-------+---------+
| 6f91eb30-8687-49d7-859f-7284887d57f8 | 1st_Ins | ACTIVE | private=10.0.0.53, fd30:b09a:8b83:0:f816:3eff:fe79:1bec, 172.24.4.59 | | m1.nano |
+--------------------------------------+---------+--------+----------------------------------------------------------------------+-------+---------+
이제 출력이 되는 것을 확인하였으니 변경을 시도해 봅니다.
3. [실습] Openstack server list Table 변경하여 출력하기
/usr/local/lib/python3.6/dist-packages/cliff/formatters/table.py
class TableFormatter(base.ListFormatter, base.SingleFormatter):
def emit_list(self, column_names, data, stdout, parsed_args):
x = prettytable.PrettyTable(
column_names,
print_empty=parsed_args.print_empty,
)
x.padding_width = 1
=>
# Add rows if data is provided
if data:
self.add_rows(x, column_names, data)
# Choose a reasonable min_width to better handle many columns on a
# narrow console. The table will overflow the console width in
# preference to wrapping columns smaller than 8 characters.
min_width = 8
self._assign_max_widths(
stdout, x, int(parsed_args.max_width), min_width,
parsed_args.fit_width)
formatted = x.get_string()
stdout.write(formatted)
stdout.write('\n')
return
- x=prettytable.PrettyTable() 에서 +---+ 가 들어가게 이쁘게 테이블을 생성해주는 것 같습니다.
- 이 때 들어가는 column들은 column_names 라는 argument가 될 것 같네요. 한번 찍어봅시다.
#DEBUG CONSOLE
column_names
('ID', 'Name', 'Status', 'Networks', 'Image', 'Flavor')
- self.add_rows 에서 Data를 추가해줄 것 같네요. F11을 눌러 들어가 봅니다
/usr/local/lib/python3.6/dist-packages/cliff/formatters/table.py
class TableFormatter(base.ListFormatter, base.SingleFormatter):
def add_rows(self, table, column_names, data):
# Figure out the types of the columns in the
# first row and set the alignment of the
# output accordingly.
data_iter = iter(data)
try:
first_row = next(data_iter)
except StopIteration:
pass
else:
for value, name in zip(first_row, column_names):
alignment = self.ALIGNMENTS.get(type(value), 'l')
table.align[name] = alignment
# Now iterate over the data and add the rows.
table.add_row(_format_row(first_row))
for row in data_iter:
table.add_row(_format_row(row))
- fisrt_row 라는 곳에 저희가 원하는 데이터가 들어가 있습니다.
first_row
('6f91eb30-8687-49d7-8...84887d57f8', '1st_Ins', 'ACTIVE', 'private=10.0.0.53, f...72.24.4.59', '', 'm1.nano')
special variables
function variables
0:'6f91eb30-8687-49d7-859f-7284887d57f8'
1:'1st_Ins'
2:'ACTIVE'
3:'private=10.0.0.53, fd30:b09a:8b83:0:f816:3eff:fe79:1bec, 172.24.4.59'
4:''
5:'m1.nano'
len():6
type(first_row)
<class 'tuple'>
타입은 Tuple이군요
저는 여기서 fisrt_row와 column_names에 item을 추가해서 출력시켜 보겠습니다.
first_row += ('Test:)',)
first_row
('6f91eb30-8687-49d7-8...84887d57f8', '1st_Ins', 'ACTIVE', 'private=10.0.0.53, f...72.24.4.59', '', 'm1.nano', 'Test:)')
/usr/local/lib/python3.6/dist-packages/cliff/display.py
column_names += ("TestL:)",)
column_names
('ID', 'Name', 'Status', 'Networks', 'Image', 'Flavor', 'TestL:)')
- 일단 출력 되었습니다 !
- 이제 이런 주먹구구식 말고, 좀 더 general 하게 접근할 수 있는 방법을 찾아보겠습니다.
1. 가장 먼저 추측해본 곳은 column_names에 정보가 담기는 곳입니다.
최종적으로 stdout.write 될 때, 사용 되는 것이 column_names 였기 때문입니다.
따라서 Display.py 파일부터 tracing Entry로 잡았습니다.
/usr/local/lib/python3.6/dist-packages/cliff/display.py
@six.add_metaclass(abc.ABCMeta)
class DisplayCommandBase(command.Command):
def run(self, parsed_args):
parsed_args = self._run_before_hooks(parsed_args)
self.formatter = self._formatter_plugins[parsed_args.formatter].obj
=> column_names, data = self.take_action(parsed_args)
column_names, data = self._run_after_hooks(parsed_args, (column_names, data))
self.produce_output(parsed_args, column_names, data)
return 0
- => 표시된 Break point를 걸고 F11로 진입하였습니다.
/usr/local/lib/python3.6/dist-packages/openstackclient/compute/v2/server.py
class ListServer(command.Lister):
def take_action(self, parsed_args):
compute_client = self.app.client_manager.compute
identity_client = self.app.client_manager.identity
image_client = self.app.client_manager.image
project_id = None
# None 이어서 실행되지 않는 문장 제거
## 명령어 입력시 --long을 해야함. 아래 참고한 페이지 서술
if parsed_args.long:
else:
if: parsed_args.no_name_lookup:
else:
columns = (
'ID',
'Name',
'Status',
'Networks',
'Image Name',
'Flavor Name',
)
column_headers = (
'ID',
'Name',
'Status',
'Networks',
'Image',
'Flavor',
)
mixed_case_fields = []
marker_id = None
=> data = compute_client.servers.list(search_opts=search_opts,marker=marker_id,limit=parsed_args.limit)
- 중간에 data=compute_client.servers.list() 를 담는 곳이 있습니다.
- 저희가 입력한 명령어는 server list 이기 때문에, 이부분이 실제로 list 자료를 get 하는 부분이 아닐까 의심합니다.
- => 표시된 Break point를 걸고 F11로 진입하였습니다.
[Debug Console로 확인한 parsed_args 의 값들]
parsed_args
all_projects:False
changes_before:None
changes_since:None
columns:[]
deleted:False
fit_width:False
flavor:None
formatter:'table'
no_name_lookup:False
noindent:False
print_empty:False
project:None
project_domain:None
quote_mode:'nonnumeric'
reservation_id:None
sort_columns:[]
status:None
unlocked:False
user:None
user_domain:None
/usr/local/lib/python3.6/dist-packages/novaclient/v2/servers.py
class ServerManager(base.BootingManagerWithFind):
def list(self, detailed=True, search_opts=None, marker=None, limit=None,
sort_keys=None, sort_dirs=None):
"""
Get a list of servers.
:param detailed: Whether to return detailed server info (optional).
:param search_opts: Search options to filter out servers which don't
match the search_opts (optional). The search opts format is a
dictionary of key / value pairs that will be appended to the query
string. For a complete list of keys see:
https://docs.openstack.org/api-ref/compute/#list-servers
:param marker: Begin returning servers that appear later in the server
list than that represented by this server id (optional).
:param limit: Maximum number of servers to return (optional).
Note the API server has a configurable default limit.
If no limit is specified here or limit is larger than
default, the default limit will be used.
If limit == -1, all servers will be returned.
:param sort_keys: List of sort keys
:param sort_dirs: List of sort directions
:rtype: list of :class:`Server`
Examples:
client.servers.list() - returns detailed list of servers
client.servers.list(search_opts={'status': 'ERROR'}) -
returns list of servers in error state.
client.servers.list(limit=10) - returns only 10 servers
"""
if search_opts is None:
search_opts = {}
....
=> servers = self._list("/servers%s%s" % (detail, query_string),
"servers")
result.extend(servers)
result.append_request_ids(servers.request_ids)
return result
- 함수 내용이 길지만, 주석에 대놓고 "Get a list of server" 라고 써져 있습니다.
- servers=self._list를 통해서 서버 명이 전달됩니다
result
[<Server: 1st_Ins>]
0:<Server: 1st_Ins>
special variables
function variables
HUMAN_ID:True
NAME_ATTR:'name'
OS-DCF:diskConfig:'AUTO'
OS-EXT-AZ:availability_zone:'nova'
OS-EXT-STS:power_state:1
OS-EXT-STS:task_state:None
OS-EXT-STS:vm_state:'active'
OS-SRV-USG:launched_at:'2020-08-06T13:21:45.000000'
OS-SRV-USG:terminated_at:None
accessIPv4:''
accessIPv6:''
addresses:{'private': [{...}, {...}, {...}]}
api_version:<APIVersion: 2.1>
config_drive:''
created:'2020-08-06T13:21:28Z'
flavor:{'id': '42', 'links': [{...}]}
hostId:'f0b0f1375254bb6ee4a83c7f4e2ad6331d94f497bb3a9b1848af817a'
human_id:'1st_ins'
id:'6f91eb30-8687-49d7-859f-7284887d57f8'
image:''
key_name:'keyy'
links:[{'href': 'http://192.168.1.10/...84887d57f8', 'rel': 'self'}, {'href': 'http://192.168.1.10/...84887d57f8', 'rel': 'bookmark'}]
manager:<novaclient.v2.servers.ServerManager object at 0x7f65b323eeb8>
metadata:{}
name:'1st_Ins'
networks:OrderedDict([('private', ['10.0.0.53', 'fd30:b09a:8b83:0:f81...:fe79:1bec', '172.24.4.59'])])
os-extended-volumes:volumes_attached:[{'id': '05e026ea-96ed-4499-9...ba1cb63897'}]
progress:0
request_ids:[]
security_groups:[{'name': 'default'}]
status:'ACTIVE'
tenant_id:'4e2abe72f99d484a82473b851a792f8a'
updated:'2020-08-06T13:21:46Z'
user_id:'b1f15ecfa456447c8d69ffaca15a2c69'
x_openstack_request_ids:[]
_add_details:<bound method Resource._add_details of <Server: 1st_Ins>>
_append_request_id:<bound method RequestIdMixin._append_request_id of <Server: 1st_Ins>>
_info:{'OS-DCF:diskConfig': 'AUTO', 'OS-EXT-AZ:availability_zone': 'nova', 'OS-EXT-STS:power_state': 1, 'OS-EXT-STS:task_state': None, 'OS-EXT-STS:vm_state': 'active', 'OS-SRV-USG:launched_at': '2020-08-06T13:21:45.000000', 'OS-SRV-USG:terminated_at': None, 'accessIPv4': '', 'accessIPv6': '', 'addresses': {'private': [...]}, 'config_drive': '', 'created': '2020-08-06T13:21:28Z', 'flavor': {'id': '42', 'links': [...]}, 'hostId': 'f0b0f1375254bb6ee4a8...1848af817a', ...}
_loaded:True
len():1
- 쫙 긁혀서 나옵니다.
- 그리고 _info를 보시면 위의 것이 한번 더 정리되어서 아래와 같습니다.
'hostId':'f0b0f1375254bb6ee4a83c7f4e2ad6331d94f497bb3a9b1848af817a'
'image':''
'flavor':{'id': '42', 'links': [{...}]}
'created':'2020-08-06T13:21:28Z'
'updated':'2020-08-06T13:21:46Z'
'addresses':{'private': [{...}, {...}, {...}]}
'accessIPv4':''
'accessIPv6':''
'links':[{'href': 'http://192.168.1.10/...84887d57f8', 'rel': 'self'}, {'href': 'http://192.168.1.10/...84887d57f8', 'rel': 'bookmark'}]
'OS-DCF:diskConfig':'AUTO'
'progress':0
'OS-EXT-AZ:availability_zone':'nova'
'config_drive':''
다시 돌아와서 이후 코드를 진행합니다.
/usr/local/lib/python3.6/dist-packages/openstackclient/compute/v2/server.py
class ListServer(command.Lister):
def take_action(self, parsed_args):
data = compute_client.servers.list(search_opts=search_opts,marker=marker_id,limit=parsed_args.limit)
#있는 list를 싹 다 긁어옴
# 싹다 긁어온거랑, server 에서 가져온거랑 matching 시킴
table = (column_headers,
(utils.get_item_properties(
s, columns,
mixed_case_fields=mixed_case_fields,
formatters={
'OS-EXT-STS:power_state':
_format_servers_list_power_state,
'Networks': _format_servers_list_networks,
'Metadata': utils.format_dict,
},
) for s in data))
return table
table
0:('ID', 'Name', 'Status', 'Networks', 'Image', 'Flavor')
1:<generator object ListServer.take_action.<locals>.<genexpr> at 0x7f65b2e3c1a8>
len():2
- 이 Table이 반환되면서 column_names와 Data가 반환되게 되고
- 우리의 여정은 끝입니다.
■ References
'Cloud' 카테고리의 다른 글
[Azure] AZ-900 Study -2. 핵심 Azure 서비스에 대해 설명하기 (0) | 2021.02.21 |
---|---|
[Azure] AZ-900 Study -1. 핵심 Azure 개념에 관해 설명하기 (1) | 2021.02.16 |
[OSS] OpenStack RST 문서 작성법 (0) | 2020.09.08 |
[OSS] OpenStack Meet-up 2 (0) | 2020.08.13 |
[OSS] #OpenStack Meet-up 1 (0) | 2020.08.09 |