描述
冪等性是在實際應用中經常需要考慮的概念,尤其是運維中。相較於將冪等性理解為各種異常情況的綜合處理,將其理解為執行時需要考慮到在前次執行產生的影響的情況下能夠正常執行則會更加容易接近業務需求。
ansible包含眾多的模組,大部分內建模組都能夠保證操作的冪等性,即相關操作的多次執行能夠達到相同結果這一特性,不會出現多次執行帶來副作用的影響。但是也有不滿足冪等原則的,比如shell模組、raw模組、command模組。
冪等操作和非冪等操作的對比
場景說明:
比如實現刪除一個臨時性的檔案/root/testfile的操作,如果希望其在相同的條件下,多次執行能夠保持相同的結果和不會帶來其它副作用,至少需要保證此操作在/root/testfile檔案存在和不存在的情況下都能正常動作。
# 當採用raw模組執行shell命令刪除檔案,第一次刪除是成功,當執行第二次刪除也是成功的,但是在生產環境這結果是不理想的,比如重啟一個服務,你會隨便重啟服務嗎?
[root@Server-1~]# touch /root/testfile
[root@Server-1~]# ansible localhost -m shell -a "rm -rf testfile"
localhost | CHANGED | rc=0 >>
[root@Server-1~]# ansible localhost -m shell -a "rm -rf testfile"
localhost | CHANGED | rc=0 >>
# 當採用file 模組執行刪除檔案,第一次執行刪除檔案成功changed: true
,多次執行刪除檔案都是同一樣的結果,不會帶來副作用的影響changed: Fasle
。
[root@Server-1~]# touch /root/testfile
[root@Server-1~]# ansible localhost -m file -a "path=/root/testfile state=absent"
localhost | CHANGED => {
"changed": true,
"path": "/root/testfile",
"state": "absent"
}
[root@Server-1~]# ansible localhost -m file -a "path=/root/testfile state=absent"
localhost | SUCCESS => {
"changed": false,
"path": "/root/testfile",
"state": "absent"
}
貼上file模組部份程式碼:state=absent實現的冪等(中文註釋)
vim /usr/lib/python2.7/site-packages/ansible/modules/files/file.py
.....
def get_state(path):
''' Find out current state '''
b_path = to_bytes(path, errors='surrogate_or_strict')
try:
if os.path.lexists(b_path): # 如果檔案存在返回file,檔案不存在返回absent
if os.path.islink(b_path):
return 'link'
elif os.path.isdir(b_path):
return 'directory'
elif os.stat(b_path).st_nlink > 1:
return 'hard'
# could be many other things, but defaulting to file
return 'file'
return 'absent'
except OSError as e:
if e.errno == errno.ENOENT: # It may already have been removed
return 'absent'
else:
raise
def ensure_absent(path):
b_path = to_bytes(path, errors='surrogate_or_strict')
prev_state = get_state(b_path) # 獲取檔案的狀態
result = {}
if prev_state != 'absent': # 當prev_state='directory' or 'file' 為真
diff = initial_diff(path, 'absent', prev_state)
if not module.check_mode:
if prev_state == 'directory': # 如果prev_state='directory', 則刪除目錄
try:
shutil.rmtree(b_path, ignore_errors=False)
except Exception as e:
raise AnsibleModuleError(results={'msg': "rmtree failed: %s" % to_native(e)})
else:
try:
os.unlink(b_path) # 如果prev_state='file', 則刪除檔案
except OSError as e:
if e.errno != errno.ENOENT: # It may already have been removed
raise AnsibleModuleError(results={'msg': "unlinking failed: %s " % to_native(e),
'path': path})
result.update({'path': path, 'changed': True, 'diff': diff, 'state': 'absent'}) # 刪除檔案成功,動作有改變,changed=True
else:
result.update({'path': path, 'changed': False, 'state': 'absent'}) # 如果prev_state='absent', 動作沒有改變,changed=False, 實現多次操作執行不會有任何改變。
return result
def main():
global module
module = AnsibleModule(
argument_spec=dict(
state=dict(type='str', choices=['absent', 'directory', 'file', 'hard', 'link', 'touch']),
path=dict(type='path', required=True, aliases=['dest', 'name']),
_original_basename=dict(type='str'), # Internal use only, for recursive ops
recurse=dict(type='bool', default=False),
force=dict(type='bool', default=False), # Note: Should not be in file_common_args in future
follow=dict(type='bool', default=True), # Note: Different default than file_common_args
_diff_peek=dict(type='bool'), # Internal use only, for internal checks in the action plugins
src=dict(type='path'), # Note: Should not be in file_common_args in future
modification_time=dict(type='str'),
modification_time_format=dict(type='str', default='%Y%m%d%H%M.%S'),
access_time=dict(type='str'),
access_time_format=dict(type='str', default='%Y%m%d%H%M.%S'),
),
add_file_common_args=True,
supports_check_mode=True,
)
# When we rewrite basic.py, we will do something similar to this on instantiating an AnsibleModule
sys.excepthook = _ansible_excepthook
additional_parameter_handling(module.params)
params = module.params
state = params['state']
recurse = params['recurse']
force = params['force']
follow = params['follow']
path = params['path']
src = params['src']
timestamps = {}
timestamps['modification_time'] = keep_backward_compatibility_on_timestamps(params['modification_time'], state)
timestamps['modification_time_format'] = params['modification_time_format']
timestamps['access_time'] = keep_backward_compatibility_on_timestamps(params['access_time'], state)
timestamps['access_time_format'] = params['access_time_format']
# short-circuit for diff_peek
if params['_diff_peek'] is not None:
appears_binary = execute_diff_peek(to_bytes(path, errors='surrogate_or_strict'))
module.exit_json(path=path, changed=False, appears_binary=appears_binary)
if state == 'file':
result = ensure_file_attributes(path, follow, timestamps)
elif state == 'directory':
result = ensure_directory(path, follow, recurse, timestamps)
elif state == 'link':
result = ensure_symlink(path, src, follow, force, timestamps)
elif state == 'hard':
result = ensure_hardlink(path, src, follow, force, timestamps)
elif state == 'touch':
result = execute_touch(path, follow, timestamps)
elif state == 'absent':
result = ensure_absent(path) # 執行刪除檔案時,呼叫方法 def ensure_absent
module.exit_json(**result)
if __name__ == '__main__':
main()