一 用户命令到的HTTP请求
一般的 Openstack 用户和管理员能够通过执行简易的 Openstack Commands 来管理和使用 Openstack 。
但需要注意的是,Openstack Services 的 API 并不会识别这类指令,所以在 API 的外层还需要一重转化机制 —— novaclient。
如果是使用 Devstack 进行部署的话,novaclient 会被默认安装在本地,安装目录为 /usr/local/lib/python2.7/dist-packages/novaclient。
我们可以通过设置该目录下的 shell.py 文件中的DEFAULT_OS_COMPUTE_API_VERSION选项来修改 novaclient 的版本。
目前novaclient默认使用的是v2 API,如果希望使用V3 API,可以修改配置。
DEFAULT_OS_COMPUTE_API_VERSION = '3'
执行 Openstack Commands 都发生了什么?
以 nova list 为例进行说明
1. 执行 nova list指令
2. 该指令被 shell.py 捕获
3. shell.py 将所需要的 Header/Context 等信息和 nova list 的操作请求内容进行组合封装,形成一个标准的 HTTP 请求
4. HTTP 请求被 nova-api 监听捕获
5. nova-api 按照 WSGI 规范的流程来完成HTTP请求的分发、路由、执行、响应的操作。
现在以子命令"list"为例,执行"nova list"获取当前所有活跃状态的虚拟机,附加"--debug"选项,可以打印一些中间信息。
[root@localhost ~(keystone_demo)]# nova --debug list
DEBUG (extension:180) found extension EntryPoint.parse('v2token = keystoneauth1.loading._plugins.identity.v2:Token')
DEBUG (extension:180) found extension EntryPoint.parse('v3oauth1 = keystoneauth1.extras.oauth1._loading:V3OAuth1')
DEBUG (extension:180) found extension EntryPoint.parse('admin_token = keystoneauth1.loading._plugins.admin_token:AdminToken')
DEBUG (extension:180) found extension EntryPoint.parse('v3oidcauthcode = keystoneauth1.loading._plugins.identity.v3:OpenIDConnectAuthorizationCode')
DEBUG (extension:180) found extension EntryPoint.parse('v2password = keystoneauth1.loading._plugins.identity.v2:Password')
DEBUG (extension:180) found extension EntryPoint.parse('v3samlpassword = keystoneauth1.extras._saml2._loading:Saml2Password')
DEBUG (extension:180) found extension EntryPoint.parse('v3password = keystoneauth1.loading._plugins.identity.v3:Password')
DEBUG (extension:180) found extension EntryPoint.parse('v3oidcaccesstoken = keystoneauth1.loading._plugins.identity.v3:OpenIDConnectAccessToken')
DEBUG (extension:180) found extension EntryPoint.parse('v3oidcpassword = keystoneauth1.loading._plugins.identity.v3:OpenIDConnectPassword')
DEBUG (extension:180) found extension EntryPoint.parse('v3kerberos = keystoneauth1.extras.kerberos._loading:Kerberos')
DEBUG (extension:180) found extension EntryPoint.parse('token = keystoneauth1.loading._plugins.identity.generic:Token')
DEBUG (extension:180) found extension EntryPoint.parse('v3oidcclientcredentials = keystoneauth1.loading._plugins.identity.v3:OpenIDConnectClientCredentials')
DEBUG (extension:180) found extension EntryPoint.parse('v3tokenlessauth = keystoneauth1.loading._plugins.identity.v3:TokenlessAuth')
DEBUG (extension:180) found extension EntryPoint.parse('v3token = keystoneauth1.loading._plugins.identity.v3:Token')
DEBUG (extension:180) found extension EntryPoint.parse('v3totp = keystoneauth1.loading._plugins.identity.v3:TOTP')
DEBUG (extension:180) found extension EntryPoint.parse('password = keystoneauth1.loading._plugins.identity.generic:Password')
DEBUG (extension:180) found extension EntryPoint.parse('v3fedkerb = keystoneauth1.extras.kerberos._loading:MappedKerberos')
DEBUG (extension:180) found extension EntryPoint.parse('v1password = swiftclient.authv1:PasswordLoader')
DEBUG (extension:180) found extension EntryPoint.parse('token_endpoint = openstackclient.api.auth_plugin:TokenEndpoint')
DEBUG (extension:180) found extension EntryPoint.parse('gnocchi-basic = gnocchiclient.auth:GnocchiBasicLoader')
DEBUG (extension:180) found extension EntryPoint.parse('gnocchi-noauth = gnocchiclient.auth:GnocchiNoAuthLoader')
DEBUG (extension:180) found extension EntryPoint.parse('aodh-noauth = aodhclient.noauth:AodhNoAuthLoader')
DEBUG (session:347) REQ: curl -g -i -X GET http://192.168.0.120:5000/v3 -H "Accept: application/json" -H "User-Agent: nova keystoneauth1/2.18.0 python-requests/2.11.1 CPython/2.7.5"
INFO (connectionpool:214) Starting new HTTP connection (1): 192.168.0.120
DEBUG (connectionpool:401) "GET /v3 HTTP/1.1" 200 196
DEBUG (session:395) RESP: [200] Date: Sat, 24 Mar 2018 10:19:04 GMT Server: Apache/2.4.6 (CentOS) Vary: X-Auth-Token,Accept-Encoding x-openstack-request-id: req-8f183e1c-9817-49e4-b541-e1e0fdb0cb5d Content-Encoding: gzip Content-Length: 196 Connection: close Content-Type: application/json
RESP BODY: {"version": {"status": "stable", "updated": "2017-02-22T00:00:00Z", "media-types": [{"base": "application/json", "type": "application/vnd.openstack.identity-v3+json"}], "id": "v3.8", "links": [{"href": "http://192.168.0.120:5000/v3/";, "rel": "self"}]}}
DEBUG (session:640) GET call to None for http://192.168.0.120:5000/v3 used request id req-8f183e1c-9817-49e4-b541-e1e0fdb0cb5d
DEBUG (base:165) Making authentication request to http://192.168.0.120:5000/v3/auth/tokens
INFO (connectionpool:249) Resetting dropped connection: 192.168.0.120
DEBUG (connectionpool:401) "POST /v3/auth/tokens HTTP/1.1" 201 7793
DEBUG (base:170) {"token": {"is_domain": false, "methods": ["password"], "roles": [{"id": "9fe2ff9ee4384b1894a90878d3e92bab", "name": "_member_"}], "expires_at": "2018-03-24T11:19:04.000000Z", "project": {"domain": {"id": "default", "name": "Default"}, "id": "b38c09cb0b5a4d46820c66052bb0ee94", "name": "demo"}, "catalog": [{"endpoints": [{"url": "http://192.168.0.120:8778/placement";, "interface": "admin", "region": "RegionOne", "region_id": "RegionOne", "id": "17c4fd23e6994597b6f2905c71957b51"}, {"url": "http://192.168.0.120:8778/placement";, "interface": "internal", "region": "RegionOne", "region_id": "RegionOne", "id": "f2041ca3559d41c58598155d777af732"}, {"url": "http://192.168.0.120:8778/placement";, "interface": "public", "region": "RegionOne", "region_id": "RegionOne", "id": "fbf96657d51941a5b1627b14b5069d11"}], "type": "placement", "id": "126f23393d2c4db98f19ff93b8444d35", "name": "placement"}, {"endpoints": [{"url": "http://192.168.0.120:8777";, "interface": "admin", "region": "RegionOne", "region_id": "RegionOne", "id": "1c2fe371656a49afa2d05b693620dddc"}, {"url": "http://192.168.0.120:8777";, "interface": "public", "region": "RegionOne", "region_id": "RegionOne", "id": "66c1ac9c0305402ca06dc97379a8f5b0"}, {"url": "http://192.168.0.120:8777";, "interface": "internal", "region": "RegionOne", "region_id": "RegionOne", "id": "fd6418c3d0314af4b93788d7f2469842"}], "type": "metering", "id": "134bde6449bd43239bea70ca9a179b2a", "name": "ceilometer"}, {"endpoints": [{"url": "http://192.168.0.120:8776/v2/b38c09cb0b5a4d46820c66052bb0ee94";, "interface": "admin", "region": "RegionOne", "region_id": "RegionOne", "id": "194f3c15afd4415093c7124bc9a264f9"}, {"url": "http://192.168.0.120:8776/v2/b38c09cb0b5a4d46820c66052bb0ee94";, "interface": "internal", "region": "RegionOne", "region_id": "RegionOne", "id": "40d754a2e89a4baaba65db46a32ed646"}, {"url": "http://192.168.0.120:8776/v2/b38c09cb0b5a4d46820c66052bb0ee94";, "interface": "public", "region": "RegionOne", "region_id": "RegionOne", "id": "bcf8c85d409e4a119753d6c7530c4365"}], "type": "volumev2", "id": "3e22b4e4252b43639b41da61aef0ad03", "name": "cinderv2"}, {"endpoints": [{"url": "http://192.168.0.120:8042";, "interface": "internal", "region": "RegionOne", "region_id": "RegionOne", "id": "2a68ff1e21d24f1281cdbc2425c767ee"}, {"url": "http://192.168.0.120:8042";, "interface": "public", "region": "RegionOne", "region_id": "RegionOne", "id": "c7fb7d968dbf4683b4a4a464d2492ed6"}, {"url": "http://192.168.0.120:8042";, "interface": "admin", "region": "RegionOne", "region_id": "RegionOne", "id": "d5e2af9cf3194c67879a2428259c62f6"}], "type": "alarming", "id": "4aa8df57593d495092630f2e85ce6174", "name": "aodh"}, {"endpoints": [{"url": "http://192.168.0.120:8776/v1/b38c09cb0b5a4d46820c66052bb0ee94";, "interface": "internal", "region": "RegionOne", "region_id": "RegionOne", "id": "5c62e261854d40e5b90de3f7a948960a"}, {"url": "http://192.168.0.120:8776/v1/b38c09cb0b5a4d46820c66052bb0ee94";, "interface": "public", "region": "RegionOne", "region_id": "RegionOne", "id": "92111e55ef4941b4a485ea61af7d2c6c"}, {"url": "http://192.168.0.120:8776/v1/b38c09cb0b5a4d46820c66052bb0ee94";, "interface": "admin", "region": "RegionOne", "region_id": "RegionOne", "id": "d30bc30757f849c79b68e16cd2b9f31f"}], "type": "volume", "id": "5d3d752e84ef4c989fcc917de9042324", "name": "cinder"}, {"endpoints": [{"url": "http://192.168.0.120:5000/v3";, "interface": "internal", "region": "RegionOne", "region_id": "RegionOne", "id": "63ab2cf4942a45b7933eb8b7cde297dc"}, {"url": "http://192.168.0.120:35357/v3";, "interface": "admin", "region": "RegionOne", "region_id": "RegionOne", "id": "b4738b39a020433d9203662dfee96d30"}, {"url": "http://192.168.0.120:5000/v3";, "interface": "public", "region": "RegionOne", "region_id": "RegionOne", "id": "bef9f544713a47939b0e68848e627956"}], "type": "identity", "id": "5f0e253c80854443be5cab7ed3a77cae", "name": "keystone"}, {"endpoints": [{"url": "http://192.168.0.120:9292";, "interface": "internal", "region": "RegionOne", "region_id": "RegionOne", "id": "5ac11bad61f84a3ebae97c421a067c80"}, {"url": "http://192.168.0.120:9292";, "interface": "public", "region": "RegionOne", "region_id": "RegionOne", "id": "c59d9756e8b044e289bb4aca6665b16f"}, {"url": "http://192.168.0.120:9292";, "interface": "admin", "region": "RegionOne", "region_id": "RegionOne", "id": "ff19aeecce074395a6e3f053d53129cd"}], "type": "image", "id": "605c3d6c06424606854166792671bdb4", "name": "glance"}, {"endpoints": [{"url": "http://192.168.0.120:8776/v3/b38c09cb0b5a4d46820c66052bb0ee94";, "interface": "internal", "region": "RegionOne", "region_id": "RegionOne", "id": "1abdbe731ad641b2b20d505e6180687f"}, {"url": "http://192.168.0.120:8776/v3/b38c09cb0b5a4d46820c66052bb0ee94";, "interface": "admin", "region": "RegionOne", "region_id": "RegionOne", "id": "4711c98645a44653809ee1edd6ad7e6f"}, {"url": "http://192.168.0.120:8776/v3/b38c09cb0b5a4d46820c66052bb0ee94";, "interface": "public", "region": "RegionOne", "region_id": "RegionOne", "id": "d48a986e4c17401c97bbbad431e29e30"}], "type": "volumev3", "id": "73cd56899898444d9909f692221f9f18", "name": "cinderv3"}, {"endpoints": [{"url": "http://192.168.0.120:8041";, "interface": "public", "region": "RegionOne", "region_id": "RegionOne", "id": "2a654c0ff8944a9683ebc498007eeb2e"}, {"url": "http://192.168.0.120:8041";, "interface": "admin", "region": "RegionOne", "region_id": "RegionOne", "id": "65e4c4754ac04447934f5010f29c6aef"}, {"url": "http://192.168.0.120:8041";, "interface": "internal", "region": "RegionOne", "region_id": "RegionOne", "id": "7bc10dd694594a679a8ab75556ab46f2"}], "type": "metric", "id": "7605431b7a2b4ed296d6279d4e3b736d", "name": "gnocchi"}, {"endpoints": [{"url": "http://192.168.0.120:8080/v1/AUTH_b38c09cb0b5a4d46820c66052bb0ee94";, "interface": "public", "region": "RegionOne", "region_id": "RegionOne", "id": "90bbfbb898c54fecb1ecb542b7252087"}, {"url": "http://192.168.0.120:8080/v1/AUTH_b38c09cb0b5a4d46820c66052bb0ee94";, "interface": "internal", "region": "RegionOne", "region_id": "RegionOne", "id": "c93c87dfe7d7497a85e240000429f3dc"}, {"url": "http://192.168.0.120:8080/v1/AUTH_b38c09cb0b5a4d46820c66052bb0ee94";, "interface": "admin", "region": "RegionOne", "region_id": "RegionOne", "id": "f92aae7ceb9642a98cbb63a2931e16a4"}], "type": "object-store", "id": "8c718458160a404eb3ddbf1f302fbc1b", "name": "swift"}, {"endpoints": [{"url": "http://192.168.0.120:8774/v2.1/b38c09cb0b5a4d46820c66052bb0ee94";, "interface": "admin", "region": "RegionOne", "region_id": "RegionOne", "id": "3a44a54d5f4e4c249863e1d87cb4def3"}, {"url": "http://192.168.0.120:8774/v2.1/b38c09cb0b5a4d46820c66052bb0ee94";, "interface": "public", "region": "RegionOne", "region_id": "RegionOne", "id": "535998df2dac489f88ed535fd28ff93e"}, {"url": "http://192.168.0.120:8774/v2.1/b38c09cb0b5a4d46820c66052bb0ee94";, "interface": "internal", "region": "RegionOne", "region_id": "RegionOne", "id": "69565e2a72a542e3b291fa8ea1a8d89c"}], "type": "compute", "id": "e8d57e5cec094ab9ada6df9f53b15cf2", "name": "nova"}, {"endpoints": [{"url": "http://192.168.0.120:9696";, "interface": "public", "region": "RegionOne", "region_id": "RegionOne", "id": "1a12521b0c3b429485e401eb56e204c9"}, {"url": "http://192.168.0.120:9696";, "interface": "admin", "region": "RegionOne", "region_id": "RegionOne", "id": "2820d2534c2f44fcab59ecc20165a2f0"}, {"url": "http://192.168.0.120:9696";, "interface": "internal", "region": "RegionOne", "region_id": "RegionOne", "id": "a2a0b2633eec45d3b3d3863118b8da14"}], "type": "network", "id": "eed91bf85e1246cdbed5f10ed36b4bd0", "name": "neutron"}], "user": {"domain": {"id": "default", "name": "Default"}, "password_expires_at": null, "name": "demo", "id": "7ed4ce59626941478efb08c37cad526e"}, "audit_ids": ["7GlloCWJTRKmZhEXtrhTMA"], "issued_at": "2018-03-24T10:19:04.000000Z"}}
REQ: curl -g -i -X GET http://192.168.0.120:8774/v2.1/b38c09cb0b5a4d46820c66052bb0ee94 -H "User-Agent: python-novaclient" -H "Accept: application/json" -H "X-Auth-Token: {SHA1}a061ff6ccfe550621f348287929f8c6e64541eb8"
DEBUG (session:347) REQ: curl -g -i -X GET http://192.168.0.120:8774/v2.1/b38c09cb0b5a4d46820c66052bb0ee94 -H "User-Agent: python-novaclient" -H "Accept: application/json" -H "X-Auth-Token: {SHA1}a061ff6ccfe550621f348287929f8c6e64541eb8"
INFO (connectionpool:214) Starting new HTTP connection (1): 192.168.0.120
DEBUG (connectionpool:401) "GET /v2.1/b38c09cb0b5a4d46820c66052bb0ee94 HTTP/1.1" 404 112
RESP: [404] Content-Length: 112 Content-Type: application/json; charset=UTF-8 X-Compute-Request-Id: req-f071ad6e-a5a0-4453-9b99-529d9ebf9733 Date: Sat, 24 Mar 2018 10:19:05 GMT Connection: keep-alive
RESP BODY: {"message": "The resource could not be found.<br /><br />\n\n\n", "code": "404 Not Found", "title": "Not Found"}
DEBUG (session:395) RESP: [404] Content-Length: 112 Content-Type: application/json; charset=UTF-8 X-Compute-Request-Id: req-f071ad6e-a5a0-4453-9b99-529d9ebf9733 Date: Sat, 24 Mar 2018 10:19:05 GMT Connection: keep-alive
RESP BODY: {"message": "The resource could not be found.<br /><br />\n\n\n", "code": "404 Not Found", "title": "Not Found"}
GET call to compute for http://192.168.0.120:8774/v2.1/b38c09cb0b5a4d46820c66052bb0ee94 used request id req-f071ad6e-a5a0-4453-9b99-529d9ebf9733
DEBUG (session:640) GET call to compute for http://192.168.0.120:8774/v2.1/b38c09cb0b5a4d46820c66052bb0ee94 used request id req-f071ad6e-a5a0-4453-9b99-529d9ebf9733
REQ: curl -g -i -X GET http://192.168.0.120:8774/v2.1/ -H "User-Agent: python-novaclient" -H "Accept: application/json" -H "X-Auth-Token: {SHA1}a061ff6ccfe550621f348287929f8c6e64541eb8"
DEBUG (session:347) REQ: curl -g -i -X GET http://192.168.0.120:8774/v2.1/ -H "User-Agent: python-novaclient" -H "Accept: application/json" -H "X-Auth-Token: {SHA1}a061ff6ccfe550621f348287929f8c6e64541eb8"
DEBUG (connectionpool:401) "GET /v2.1/ HTTP/1.1" 200 387
RESP: [200] Content-Length: 387 Content-Type: application/json Openstack-Api-Version: compute 2.1 X-Openstack-Nova-Api-Version: 2.1 Vary: OpenStack-API-Version, X-OpenStack-Nova-API-Version X-Compute-Request-Id: req-e2d4e47d-f24f-4013-a06a-52a35c243b5c Date: Sat, 24 Mar 2018 10:19:05 GMT Connection: keep-alive
RESP BODY: {"version": {"status": "CURRENT", "updated": "2013-07-23T11:33:21Z", "links": [{"href": "http://192.168.0.120:8774/v2.1/";, "rel": "self"}, {"href": "http://docs.openstack.org/";, "type": "text/html", "rel": "describedby"}], "min_version": "2.1", "version": "2.42", "media-types": [{"base": "application/json", "type": "application/vnd.openstack.compute+json;version=2.1"}], "id": "v2.1"}}
DEBUG (session:395) RESP: [200] Content-Length: 387 Content-Type: application/json Openstack-Api-Version: compute 2.1 X-Openstack-Nova-Api-Version: 2.1 Vary: OpenStack-API-Version, X-OpenStack-Nova-API-Version X-Compute-Request-Id: req-e2d4e47d-f24f-4013-a06a-52a35c243b5c Date: Sat, 24 Mar 2018 10:19:05 GMT Connection: keep-alive
RESP BODY: {"version": {"status": "CURRENT", "updated": "2013-07-23T11:33:21Z", "links": [{"href": "http://192.168.0.120:8774/v2.1/";, "rel": "self"}, {"href": "http://docs.openstack.org/";, "type": "text/html", "rel": "describedby"}], "min_version": "2.1", "version": "2.42", "media-types": [{"base": "application/json", "type": "application/vnd.openstack.compute+json;version=2.1"}], "id": "v2.1"}}
GET call to compute for http://192.168.0.120:8774/v2.1/ used request id req-e2d4e47d-f24f-4013-a06a-52a35c243b5c
DEBUG (session:640) GET call to compute for http://192.168.0.120:8774/v2.1/ used request id req-e2d4e47d-f24f-4013-a06a-52a35c243b5c
DEBUG (extension:180) found extension EntryPoint.parse('v2token = keystoneauth1.loading._plugins.identity.v2:Token')
DEBUG (extension:180) found extension EntryPoint.parse('v3oauth1 = keystoneauth1.extras.oauth1._loading:V3OAuth1')
DEBUG (extension:180) found extension EntryPoint.parse('admin_token = keystoneauth1.loading._plugins.admin_token:AdminToken')
DEBUG (extension:180) found extension EntryPoint.parse('v3oidcauthcode = keystoneauth1.loading._plugins.identity.v3:OpenIDConnectAuthorizationCode')
DEBUG (extension:180) found extension EntryPoint.parse('v2password = keystoneauth1.loading._plugins.identity.v2:Password')
DEBUG (extension:180) found extension EntryPoint.parse('v3samlpassword = keystoneauth1.extras._saml2._loading:Saml2Password')
DEBUG (extension:180) found extension EntryPoint.parse('v3password = keystoneauth1.loading._plugins.identity.v3:Password')
DEBUG (extension:180) found extension EntryPoint.parse('v3oidcaccesstoken = keystoneauth1.loading._plugins.identity.v3:OpenIDConnectAccessToken')
DEBUG (extension:180) found extension EntryPoint.parse('v3oidcpassword = keystoneauth1.loading._plugins.identity.v3:OpenIDConnectPassword')
DEBUG (extension:180) found extension EntryPoint.parse('v3kerberos = keystoneauth1.extras.kerberos._loading:Kerberos')
DEBUG (extension:180) found extension EntryPoint.parse('token = keystoneauth1.loading._plugins.identity.generic:Token')
DEBUG (extension:180) found extension EntryPoint.parse('v3oidcclientcredentials = keystoneauth1.loading._plugins.identity.v3:OpenIDConnectClientCredentials')
DEBUG (extension:180) found extension EntryPoint.parse('v3tokenlessauth = keystoneauth1.loading._plugins.identity.v3:TokenlessAuth')
DEBUG (extension:180) found extension EntryPoint.parse('v3token = keystoneauth1.loading._plugins.identity.v3:Token')
DEBUG (extension:180) found extension EntryPoint.parse('v3totp = keystoneauth1.loading._plugins.identity.v3:TOTP')
DEBUG (extension:180) found extension EntryPoint.parse('password = keystoneauth1.loading._plugins.identity.generic:Password')
DEBUG (extension:180) found extension EntryPoint.parse('v3fedkerb = keystoneauth1.extras.kerberos._loading:MappedKerberos')
DEBUG (extension:180) found extension EntryPoint.parse('v1password = swiftclient.authv1:PasswordLoader')
DEBUG (extension:180) found extension EntryPoint.parse('token_endpoint = openstackclient.api.auth_plugin:TokenEndpoint')
DEBUG (extension:180) found extension EntryPoint.parse('gnocchi-basic = gnocchiclient.auth:GnocchiBasicLoader')
DEBUG (extension:180) found extension EntryPoint.parse('gnocchi-noauth = gnocchiclient.auth:GnocchiNoAuthLoader')
DEBUG (extension:180) found extension EntryPoint.parse('aodh-noauth = aodhclient.noauth:AodhNoAuthLoader')
DEBUG (session:347) REQ: curl -g -i -X GET http://192.168.0.120:8774/v2.1/b38c09cb0b5a4d46820c66052bb0ee94/servers/detail -H "OpenStack-API-Version: compute 2.41" -H "User-Agent: python-novaclient" -H "Accept: application/json" -H "X-OpenStack-Nova-API-Version: 2.41" -H "X-Auth-Token: {SHA1}a061ff6ccfe550621f348287929f8c6e64541eb8"
DEBUG (connectionpool:401) "GET /v2.1/b38c09cb0b5a4d46820c66052bb0ee94/servers/detail HTTP/1.1" 200 1544
DEBUG (session:395) RESP: [200] Content-Length: 1544 Content-Type: application/json Openstack-Api-Version: compute 2.41 X-Openstack-Nova-Api-Version: 2.41 Vary: OpenStack-API-Version, X-OpenStack-Nova-API-Version X-Compute-Request-Id: req-219e6e99-6065-4ac0-a4bb-a8181059af2b Date: Sat, 24 Mar 2018 10:19:05 GMT Connection: keep-alive
RESP BODY: {"servers": [{"OS-EXT-STS:task_state": null, "addresses": {"private": [{"OS-EXT-IPS-MAC:mac_addr": "fa:16:3e:5f:26:45", "version": 4, "addr": "10.0.0.3", "OS-EXT-IPS:type": "fixed"}]}, "links": [{"href": "http://192.168.0.120:8774/v2.1/b38c09cb0b5a4d46820c66052bb0ee94/servers/57835459-1d60-40ae-b0c7-381bcbaba738";, "rel": "self"}, {"href": "http://192.168.0.120:8774/b38c09cb0b5a4d46820c66052bb0ee94/servers/57835459-1d60-40ae-b0c7-381bcbaba738";, "rel": "bookmark"}], "image": "", "OS-EXT-STS:vm_state": "stopped", "OS-SRV-USG:launched_at": "2018-03-18T08:48:43.000000", "flavor": {"id": "1", "links": [{"href": "http://192.168.0.120:8774/b38c09cb0b5a4d46820c66052bb0ee94/flavors/1";, "rel": "bookmark"}]}, "id": "57835459-1d60-40ae-b0c7-381bcbaba738", "security_groups": [{"name": "default"}], "OS-SRV-USG:terminated_at": null, "user_id": "7ed4ce59626941478efb08c37cad526e", "OS-DCF:diskConfig": "AUTO", "accessIPv4": "", "accessIPv6": "", "OS-EXT-STS:power_state": 4, "OS-EXT-AZ:availability_zone": "nova", "metadata": {}, "status": "SHUTOFF", "updated": "2018-03-24T10:07:23Z", "hostId": "7d96721294a7a29a9c48879e04f370eb2e7dd70a36aa0d96ca225f30", "description": "test", "tags": [], "key_name": null, "locked": false, "name": "test", "created": "2018-03-18T08:48:06Z", "tenant_id": "b38c09cb0b5a4d46820c66052bb0ee94", "os-extended-volumes:volumes_attached": [{"id": "b64fedef-d528-435f-9256-414b44aa9bc5", "delete_on_termination": false}, {"id": "7236ed4b-bf0a-423f-bc7a-bb213c0e9853", "delete_on_termination": false}], "config_drive": ""}]}
DEBUG (session:640) GET call to compute for http://192.168.0.120:8774/v2.1/b38c09cb0b5a4d46820c66052bb0ee94/servers/detail used request id req-219e6e99-6065-4ac0-a4bb-a8181059af2b
+--------------------------------------+------+---------+------------+-------------+------------------+
| ID | Name | Status | Task State | Power State | Networks |
+--------------------------------------+------+---------+------------+-------------+------------------+
| 57835459-1d60-40ae-b0c7-381bcbaba738 | test | SHUTOFF | - | Shutdown | private=10.0.0.3 |
+--------------------------------------+------+---------+------------+-------------+------------------+
"nova list"命令发送有两个重要的HTTP请求,第一个请求发给keystone获取授权。Openstack中的各种服务都需要经过授权才能够使用,因此对Nova API的调用都要首先从keystone拿到一个授权token,然后将其填入随后API请求中"X-Auth-Token"字段。
对于上面的例子,获取token,然后基于这个授权发送第二个请求给Nova获取虚拟机列表。
向Nova API服务发送请求有多种方式,使用novaclient提供的命令只是其中的一种,我们还可以使用Dashboard提供的图形界面,比如Horizon,它本质上也基于novaclient对API的封装。此外还可以使用curl命令直接发送HTTP请求。
二 PasteDeploy 将 HTTP 请求路由到具体的 WSGI Application
首先,Openstack 在启动 nova-api service 时,会根据 Nova 配置文件 nova.conf 中的配置项 enabled_apis = ec2,osapi_compute,metadata 来创建一个或多个 WSGI Server 。
每一个 WSGI Server 负责处理一种类型的 Nova API 请求。
上述的选项值表示了三种类型的 Nova API 。
在创建 WSGI Server 的同时会根据 Paste 的配置文件 nova/etc/nova/api-paste.ini 加载 WSGI Application 。
在加载 WSGI Application 之后,WSGI Application 就处在等待和监听请求的状态。
加载的动作由nova/nova/service.py实现。
# nova/nova/service.py
class WSGIService(service.Service):
"""Provides ability to launch API from a 'paste' configuration."""
def __init__(self, name, loader=None, use_ssl=False, max_url_len=None):
self.name = name
self.manager = self._get_manager()
self.loader = loader or wsgi.Loader()
#从api-paste.ini配置文件加载 Nova API 对应的 WSGI Application,由 HTTP 请求的 URL 来决定
self.app = self.loader.load_app(name)
# inherit all compute_api worker counts from osapi_compute
if name.startswith('openstack_compute_api'):
wname = 'osapi_compute'
else:
wname = name
self.host = getattr(CONF, '%s_listen' % name, "0.0.0.0")
self.port = getattr(CONF, '%s_listen_port' % name, 0)
self.workers = (getattr(CONF, '%s_workers' % wname, None) or
processutils.get_worker_count())
if self.workers and self.workers < 1:
worker_name = '%s_workers' % name
msg = (_("%(worker_name)s value of %(workers)s is invalid, "
"must be greater than 0") %
{'worker_name': worker_name,
'workers': str(self.workers)})
raise exception.InvalidInput(msg)
self.use_ssl = use_ssl
#使用指定的 HostIP 和 Port 创建用于监听HTTP请求的 Socket,这个Socket只会捕获与 host 和 port 对应的 HTTP 请求。
self.server = wsgi.Server(name,
self.app,
host=self.host,
port=self.port,
use_ssl=self.use_ssl,
max_url_len=max_url_len)
#nova/etc/nova/api-paste.ini
############
# Metadata #
############
[composite:metadata]
use = egg:Paste#urlmap
/: meta
[pipeline:meta]
pipeline = ec2faultwrap logrequest metaapp
[app:metaapp]
paste.app_factory = nova.api.metadata.handler:MetadataRequestHandler.factory
#######
# EC2 #
#######
[composite:ec2]
use = egg:Paste#urlmap
/services/Cloud: ec2cloud
[composite:ec2cloud]
use = call:nova.api.auth:pipeline_factory
noauth = ec2faultwrap logrequest ec2noauth cloudrequest validator ec2executor
keystone = ec2faultwrap logrequest ec2keystoneauth cloudrequest validator ec2executor
[filter:ec2faultwrap]
paste.filter_factory = nova.api.ec2:FaultWrapper.factory
[filter:logrequest]
paste.filter_factory = nova.api.ec2:RequestLogging.factory
[filter:ec2lockout]
paste.filter_factory = nova.api.ec2:Lockout.factory
[filter:ec2keystoneauth]
paste.filter_factory = nova.api.ec2:EC2KeystoneAuth.factory
[filter:ec2noauth]
paste.filter_factory = nova.api.ec2:NoAuth.factory
[filter:cloudrequest]
controller = nova.api.ec2.cloud.CloudController
paste.filter_factory = nova.api.ec2:Requestify.factory
[filter:authorizer]
paste.filter_factory = nova.api.ec2:Authorizer.factory
[filter:validator]
paste.filter_factory = nova.api.ec2:Validator.factory
[app:ec2executor]
paste.app_factory = nova.api.ec2:Executor.factory
#############
# OpenStack #
#############
[composite:osapi_compute]
use = call:nova.api.openstack.urlmap:urlmap_factory
/: oscomputeversions
/v1.1: openstack_compute_api_v2
/v2: openstack_compute_api_v2
/v2.1: openstack_compute_api_v21
/v3: openstack_compute_api_v3
[composite:openstack_compute_api_v2]
use = call:nova.api.auth:pipeline_factory
noauth = compute_req_id faultwrap sizelimit noauth ratelimit osapi_compute_app_v2
keystone = compute_req_id faultwrap sizelimit authtoken keystonecontext ratelimit osapi_compute_app_v2
keystone_nolimit = compute_req_id faultwrap sizelimit authtoken keystonecontext osapi_compute_app_v2
[composite:openstack_compute_api_v21]
use = call:nova.api.auth:pipeline_factory_v21
noauth = request_id faultwrap sizelimit noauth osapi_compute_app_v21
keystone = request_id faultwrap sizelimit authtoken keystonecontext osapi_compute_app_v21
[composite:openstack_compute_api_v3]
use = call:nova.api.auth:pipeline_factory_v21
noauth = request_id faultwrap sizelimit noauth_v3 osapi_compute_app_v3
keystone = request_id faultwrap sizelimit authtoken keystonecontext osapi_compute_app_v3
[filter:request_id]
paste.filter_factory = nova.openstack.common.middleware.request_id:RequestIdMiddleware.factory
[filter:compute_req_id]
paste.filter_factory = nova.api.compute_req_id:ComputeReqIdMiddleware.factory
[filter:faultwrap]
paste.filter_factory = nova.api.openstack:FaultWrapper.factory
[filter:noauth]
paste.filter_factory = nova.api.openstack.auth:NoAuthMiddleware.factory
[filter:noauth_v3]
paste.filter_factory = nova.api.openstack.auth:NoAuthMiddlewareV3.factory
[filter:ratelimit]
paste.filter_factory = nova.api.openstack.compute.limits:RateLimitingMiddleware.factory
[filter:sizelimit]
paste.filter_factory = nova.api.sizelimit:RequestBodySizeLimiter.factory
[app:osapi_compute_app_v2]
paste.app_factory = nova.api.openstack.compute:APIRouter.factory
[app:osapi_compute_app_v21]
paste.app_factory = nova.api.openstack.compute:APIRouterV21.factory
[app:osapi_compute_app_v3]
paste.app_factory = nova.api.openstack.compute:APIRouterV3.factory
[pipeline:oscomputeversions]
pipeline = faultwrap oscomputeversionapp
[app:oscomputeversionapp]
paste.app_factory = nova.api.openstack.compute.versions:Versions.factory
##########
# Shared #
##########
[filter:keystonecontext]
paste.filter_factory = nova.api.auth:NovaKeystoneContext.factory
[filter:authtoken]
paste.filter_factory = keystonemiddleware.auth_token:filter_factory
api-paste.ini 配置文件定义了不同类型的 composite section。
[composite:metadata] / [composite:ec2] / [composite:osapi_compute] / [composite:openstack_compute_api_v2] /[composite:openstack_compute_api_v21]、[composite:openstack_compute_api_v3] 等。
当 Socket 监听到 HTTP 请求: http://192.168.0.120:8774/v2.1/b38c09cb0b5a4d46820c66052bb0ee94/servers/detail时,composite section 是该请求进入到 nova-api 后第一个通过的 Section。
下面是该请求通过 api-paste.ini 配置映射到一个 WSGI Application 的步骤:
第一步:因为这个请求是 OpenstackAPI 类型,所以会被 [composite:osapi_compute] 监听到,并使用 nova.api.openstack.urlmap 模块下的 urlmap_factory 函数将请求分发到 [composite:openstack_compute_api_v21] 。
[composite:osapi_compute]
use = call:nova.api.openstack.urlmap:urlmap_factory
/: oscomputeversions
/v1.1: openstack_compute_api_v21_legacy_v2_compatible
/v2: openstack_compute_api_v21_legacy_v2_compatible
/v2.1: openstack_compute_api_v21
第二步:[composite:openstack_compute_api_v21] 再使用 nova.api.auth模块下的 pipeline_factory_v21 函数对请求进一步分发。
而且这里需要根据 nova.conf 配置文件中的认证策略选项 auth_strategy 来确定使用那一种认证方式 (keystone/noauth),在 nova/api/auth.py 文件定义了默认为认证方式为 keystone 。
[composite:openstack_compute_api_v21]
use = call:nova.api.auth:pipeline_factory_v21
noauth = request_id faultwrap sizelimit noauth osapi_compute_app_v21
keystone = request_id faultwrap sizelimit authtoken keystonecontext osapi_compute_app_v21
第三步:keystone/noauth 这两个 Pipeline 的参数列表中除了最后一个参数之外的都是 filter section,最后一个参数对应了一个 app section( Application) 。
等这个 pipeline 里所有的 filter 都执行完了之后,最终再调用 application osapi_compute_app_v21 。
[app:osapi_compute_app_v21]
paste.app_factory = nova.api.openstack.compute:APIRouterV21.factory
所以说,将 HTTP 请求路由到哪一个 WSGI Application 是由配置文件 api-paste.ini 来决定的。
而且需要注意的是:Openstack Project 拥有着各自不同的 Paste 配置文件,即拥有各自不同的 WSGI Serever 和 WSGI Application 。
三 WSGI Application到具体的执行函数
在 Openstack 中,每个资源都被封装成为一个 nova.api.openstack.wsgi.Resource 对象,且对应着一个 WSGI Application ,这样就保证了资源的独立性。
上面,我们已经确定了处理 HTTP 请求的 Application ,
除此之外,我们还需要继续的去确定这个 HTTP 请求希望执行 Application 中具体的哪一个操作函数。
这需要 Routes Module 的 Mapper 对象来支撑。
Routes Module:主要用于把接收到的 HTTP URL 请求的后缀 Path(/servers/detail)映射到具体的操作函数。
其实现原理是创建一个 mapper 对象,然后调用该对象的 connect() 方法把后缀 Path 和 HTTP 内建方法映射到一个 Controller 的某个 Action 上。
Controller 是一个自定义的类实例对象,每一个Openstack 资源都会对应一个 Controller,同时 Controller 也是资源操作函数的集合。
Action 表示 Controller 对象提供的操作函数(index/show/delect/update/create)。
一般调用这些 Action 之前会把这个 Action 映射到 HTTP 的内置方法。GET/POST/DELETE/PUT 。
HTTP 请求的 URL Path 和 HTTP 内置方法能够确定对 Openstack 中唯一的一个资源执行何种操作。
class Router(object):
"""WSGI middleware that maps incoming requests to WSGI apps."""
def __init__(self, mapper):
"""Create a router for the given routes.Mapper.
Each route in `mapper` must specify a 'controller', which is a
WSGI app to call. You'll probably want to specify an 'action' as
well and have your controller be an object that can route
the request to the action-specific method.
Examples:
mapper = routes.Mapper()
sc = ServerController()
# Explicit mapping of one route to a controller+action
mapper.connect(None, '/svrlist', controller=sc, action='list')
# Actions are all implicitly defined
mapper.resource('server', 'servers', controller=sc)
# Pointing to an arbitrary WSGI app. You can specify the
# {path_info:.*} parameter so the target app can be handed just that
# section of the URL.
mapper.connect(None, '/v1.0/{path_info:.*}', controller=BlogApp())
"""
self.map = mapper
# 使用 routers 模块将 mapper 和 _dispatch 关联起来
# routes.middleware.RoutesMiddleware 会调用mapper.routematch()函数
#来获取url的controller等参数,保存在match中,并设置environ变量供_dispatch()使用
#environ['wsgiorg.routing_args']=((url),match)
#environ['routes.url']=url
self._router = routes.middleware.RoutesMiddleware(self._dispatch,
self.map)
@webob.dec.wsgify(RequestClass=Request)
def __call__(self, req):
"""Route the incoming request to a controller based on self.map.
If no match, return a 404.
"""
# 根据 mapper 将HTTP请求路由到适当的 WSGI Application,即资源上。再根据 HTTP 请求的 URL 将其路由到对应的 Controller 中的 Action。
return self._router
@staticmethod
@webob.dec.wsgify(RequestClass=Request)
def _dispatch(req):
"""Dispatch the request to the appropriate controller.
Called by self._router after matching the incoming request to a route
and putting the information into req.environ. Either returns 404
or the routed WSGI app's response.
"""
# 根据 HTTP 请求的 environ 信息并根据上述设置的 environ 来找到 URL 对应的 Controller 。
match = req.environ['wsgiorg.routing_args'][1]
if not match:
return webob.exc.HTTPNotFound()
app = match['controller']
return app
通过 Route 模块,一个 HTTP 请求 的 URL 能够映射到对应的资源的 Action(对资源操作的方法) 。
四 参考