作者:Maxwell Li
日期:2017/01/23
未經(jīng)作者允許,禁止轉(zhuǎn)載本文任何內(nèi)容。如需轉(zhuǎn)載請留言。
Ansible 版本升級之后更新了 OpenStack 相關(guān)的模塊,主要是引用了 Shade。Shade 的開發(fā)主要是為了讓用戶能夠自己編寫程序或者利用 Ansible 對 OpenStack 進(jìn)行一些操作。關(guān)于 Shade 的介紹,這里就不展開了,詳見 shade’s documentation。
然而,Ansible 作為運維工具,所開發(fā)的 OpenStack 模塊主要是為了對 OpenStack 進(jìn)行訪問和操作,例如創(chuàng)建網(wǎng)絡(luò),配置安全組,創(chuàng)建用戶等等。但是對于 endpoint,Ansible 并沒有相關(guān)模塊,因為這是在 OpenStack 部署環(huán)節(jié)才需要的操作。追求代碼潔癖,以及提高創(chuàng)建 endpoint 的穩(wěn)定性,特編寫 keystone_endpoint 模塊以供使用。
查看源碼
查看 ansible os_keystone_service 模塊源碼:
cloud = shade.operator_cloud(**module.params)
services = cloud.search_services(name_or_id=name,
filters=dict(type=service_type))
if len(services) > 1:
module.fail_json(msg='Service name %s and type %s are not unique' %
(name, service_type))
elif len(services) == 1:
service = services[0]
else:
service = None
if module.check_mode:
module.exit_json(changed=_system_state_change(module, service))
if state == 'present':
if service is None:
service = cloud.create_service(name=name,
description=description, type=service_type, enabled=True)
changed = True
else:
if _needs_update(module, service):
service = cloud.update_service(
service.id, name=name, type=service_type, enabled=enabled,
description=description)
changed = True
else:
changed = False
module.exit_json(changed=changed, service=service, id=service.id)
elif state == 'absent':
if service is None:
changed=False
else:
cloud.delete_service(service.id)
changed=True
module.exit_json(changed=changed)
可知 os_keystone_service 模塊調(diào)用 shade 中 search_service create_service update_service delete_service 等函數(shù)。在 shade 目錄下的 operatorcloud.py 文件中找到了這些函數(shù):
@_utils.valid_kwargs('type', 'service_type', 'description')
def create_service(self, name, enabled=True, **kwargs):
"""Create a service.
:param name: Service name.
:param type: Service type. (type or service_type required.)
:param service_type: Service type. (type or service_type required.)
:param description: Service description (optional).
:param enabled: Whether the service is enabled (v3 only)
:returns: a ``munch.Munch`` containing the services description,
i.e. the following attributes::
- id: <service id>
- name: <service name>
- type: <service type>
- service_type: <service type>
- description: <service description>
:raises: ``OpenStackCloudException`` if something goes wrong during the
openstack API call.
"""
...
def search_services(self, name_or_id=None, filters=None):
"""Search Keystone services.
:param name_or_id: Name or id of the desired service.
:param filters: a dict containing additional filters to use. e.g.
{'type': 'network'}.
:returns: a list of ``munch.Munch`` containing the services description
:raises: ``OpenStackCloudException`` if something goes wrong during the
openstack API call.
"""
...
def delete_service(self, name_or_id):
"""Delete a Keystone service.
:param name_or_id: Service name or id.
:returns: True if delete succeeded, False otherwise.
:raises: ``OpenStackCloudException`` if something goes wrong during
the openstack API call
"""
...
這些函數(shù)的注釋中詳細(xì)解釋了函數(shù)的使用方法。考慮到 shade 是 OpenStack 開發(fā)的,在這份文件中搜索 endpoint,找到了 endpoint 的相關(guān)函數(shù)。
編寫模塊
參照 os_keystone_service,開始編寫 keystone_endpoint 模塊。為了測試可行性,先完成 list 功能。
argument_spec = openstack_full_argument_spec( # noqa: F405
enabled=dict(default=True, type='bool'),
name=dict(required=True),
service_type=dict(required=True),
state=dict(default='present', choices=['absent', 'present']),
region=dict(default=None, required=False),
interface=dict(default=None,
choices=['admin', 'internal', 'public']),
url=dict(default=None, required=False),
)
...
enabled = module.params['enabled'] # noqa: F841
name = module.params['name']
service_type = module.params['service_type']
state = module.params['state']
region = module.params['region']
interface = module.params['interface']
url = module.params['url']
try:
cloud = shade.operator_cloud(**module.params)
endpoints = cloud.list_endpoints()
module.exit_json(ansible_facts=dict(openstack=dict(endpoints=endpoints)))
測試后輸出:
[root@compass openstack_newton-opnfv2]# ansible-playbook -i inventories/inventory.yml HA-ansible-multinodes.yml -t keystone_create -vvvv
...
"endpoints": [
{
"HUMAN_ID": false,
"NAME_ATTR": "name",
"enabled": true,
"human_id": null,
"id": "009a973b4ccc4c2dbe2264edb6ae411c",
"interface": "public",
"links": {
"self": "http://172.16.1.222:35357/v3/endpoints/009a973b4ccc4c2dbe2264edb6ae411c"
},
"region": "RegionOne",
"region_id": "RegionOne",
"service_id": "9f07f419738c4acf8e0f18915169c08e",
"url": "http://192.168.116.222:8774/v2.1/%(tenant_id)s"
},
...
...
...
{
"HUMAN_ID": false,
"NAME_ATTR": "name",
"enabled": true,
"human_id": null,
"id": "fe53c627afdd488ab9af245bd928dd41",
"interface": "public",
"links": {
"self": "http://172.16.1.222:35357/v3/endpoints/fe53c627afdd488ab9af245bd928dd41"
},
"region": "RegionOne",
"region_id": "RegionOne",
"service_id": "a740b9524a60401196b5a06a4fd4ba79",
"url": "http://192.168.116.222:8776/v2/%(tenant_id)s"
}
]
...
可以看到,模塊輸出了所有的 endpoints,并沒有進(jìn)行任何篩選。os_keystone_service 模塊中為了獲取指定 service,調(diào)用了 shade 中的 search_service 函數(shù),直接傳入 service 的 id 或者 name 就可以返回 service 相關(guān)信息。原本打算調(diào)用 search_endpoint 函數(shù)來完成篩選工作,但是由于相同名字的 endpoint 會有 3 個:admin,internal,public。因此 search_endpoint 函數(shù)只能夠通過傳入 endpoint id 來獲取相關(guān)信息。無奈之下只能利用 service id 和 interface 進(jìn)行篩選。
try:
cloud = shade.operator_cloud(**module.params)
services = cloud.search_services(name_or_id=name,
filters=dict(type=service_type))
if len(services) > 1:
module.fail_json(msg='Service name %s and type %s are not unique' %
(name, service_type))
elif len(services) == 0:
module.fail_json(msg="No services with name %s" % name)
else:
service = services[0]
endpoints = [x for x in cloud.list_endpoints()
if (x.service_id == service.id and
x.interface == interface)]
module.exit_json(ansible_facts=dict(openstack=dict(endpoints=endpoints)))
測試后,成功輸出篩選結(jié)果:
[root@compass openstack_newton-opnfv2]# ansible-playbook -i inventories/inventory.yml HA-ansible-multinodes.yml -t keystone_create -vvvv
...
{
"HUMAN_ID": false,
"NAME_ATTR": "name",
"enabled": true,
"human_id": null,
"id": "8d8ac7c39aea42528605d304b547b403",
"interface": "public",
"links": {
"self": "http://172.16.1.222:35357/v3/endpoints/8d8ac7c39aea42528605d304b547b403"
},
"region": "RegionOne",
"region_id": "RegionOne",
"service_id": "b23733515212450f8d04c7b4a41c3585",
"url": "http://192.168.116.222:9292"
}
...
驗證了模塊的可用性之后,就可以加入創(chuàng)建、刪除、更新等功能。
count = len(endpoints)
if count > 1:
module.fail_json(msg='%d endpoints with service name %s' %
(count, name))
elif count == 0:
endpoint = None
else:
endpoint = endpoints[0]
if module.check_mode:
module.exit_json(changed=_system_state_change(module, endpoint))
if state == 'present':
if endpoint is None:
endpoint = cloud.create_endpoint(
service_name_or_id=service.id, enabled=enabled,
region=region, interface=interface, url=url)
changed = True
else:
if _needs_update(module, endpoint):
endpoint = cloud.update_endpoint(
endpoint_id=endpoint.id, enabled=enabled,
service_name_or_id=service.id, region=region,
interface=interface, url=url)
changed = True
else:
changed = False
module.exit_json(changed=changed, endpoint=endpoint)
elif state == 'absent':
if endpoint is None:
changed = False
else:
cloud.delete_endpoint(endpoint.id)
changed = True
module.exit_json(changed=changed)
并進(jìn)行測試和驗證。
Patch 地址:Ansible Module substitute for Shell Commands
總結(jié)
利用 Shade 提供的 OpenStack 相關(guān)函數(shù),顯著降低了開發(fā)程序的困難度,尤其是對于 Ansible 用戶。雖然 Ansible 已經(jīng)為用戶編寫好了大部分模塊,但任有部分功能缺失。此次模塊編寫,提高了自己對 Ansible 的掌握程度,進(jìn)一步了解 Keystone 的鑒權(quán)機制。