天天看點

分析ansible源碼子產品中test-module是如何實作自定義子產品測試的

1. 為什麼會有這篇文章的介紹呢?

2. 自定義子產品分為兩種情況

1

2

3

4

5

<code>1</code><code>&gt; 不傳參數的,如下</code>

<code># ansible -i hosts hostgroup -m ping -k </code>

<code>2</code><code>&gt; 傳遞參數的, 如下</code>

<code># ansible -i hsots hostgroup -m shell -a 'uptime' -k</code>

ansible的文檔上也給了兩個對應的自定義子產品的示例

6

7

8

9

10

<code>1</code><code>&gt; 不傳參數的</code>

<code>    </code><code>#!/usr/bin/python</code>

<code>    </code><code>import</code> <code>datetime</code>

<code>    </code><code>import</code> <code>json</code>

<code>    </code><code>date </code><code>=</code> <code>str</code><code>(datetime.datetime.now())</code>

<code>    </code><code>print</code> <code>json.dumps({</code>

<code>        </code><code>"time"</code> <code>: date</code>

<code>    </code><code>})</code>

2&gt; 傳遞參數的

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

<code>#!/usr/bin/python</code>

<code># import some python modules that we'll use.  These are all</code>

<code># available in Python's core</code>

<code>import</code> <code>datetime</code>

<code>import</code> <code>sys</code>

<code>import</code> <code>json</code>

<code>import</code> <code>os</code>

<code>import</code> <code>shlex</code>

<code># read the argument string from the arguments file</code>

<code>args_file </code><code>=</code> <code>sys.argv[</code><code>1</code><code>]</code>

<code>args_data </code><code>=</code> <code>file</code><code>(args_file).read()</code>

<code># for this module, we're going to do key=value style arguments</code>

<code># this is up to each module to decide what it wants, but all</code>

<code># core modules besides 'command' and 'shell' take key=value</code>

<code># so this is highly recommended</code>

<code>arguments </code><code>=</code> <code>shlex.split(args_data)</code>

<code>for</code> <code>arg </code><code>in</code> <code>arguments:</code>

<code>    </code><code># ignore any arguments without an equals in it</code>

<code>    </code><code>if</code> <code>"="</code> <code>in</code> <code>arg:</code>

<code>        </code><code>(key, value) </code><code>=</code> <code>arg.split(</code><code>"="</code><code>)</code>

<code>        </code><code># if setting the time, the key 'time'</code>

<code>        </code><code># will contain the value we want to set the time to</code>

<code>        </code><code>if</code> <code>key </code><code>=</code><code>=</code> <code>"time"</code><code>:</code>

<code>            </code><code># now we'll affect the change.  Many modules</code>

<code>            </code><code># will strive to be 'idempotent', meaning they</code>

<code>            </code><code># will only make changes when the desired state</code>

<code>            </code><code># expressed to the module does not match</code>

<code>            </code><code># the current state.  Look at 'service'</code>

<code>            </code><code># or 'yum' in the main git tree for an example</code>

<code>            </code><code># of how that might look.</code>

<code>            </code><code>rc </code><code>=</code> <code>os.system(</code><code>"date -s \"%s\""</code> <code>%</code> <code>value)</code>

<code>            </code><code># always handle all possible errors</code>

<code>            </code><code>#</code>

<code>            </code><code># when returning a failure, include 'failed'</code>

<code>            </code><code># in the return data, and explain the failure</code>

<code>            </code><code># in 'msg'.  Both of these conventions are</code>

<code>            </code><code># required however additional keys and values</code>

<code>            </code><code># can be added.</code>

<code>            </code><code>if</code> <code>rc !</code><code>=</code> <code>0</code><code>:</code>

<code>                </code><code>print</code> <code>json.dumps({</code>

<code>                    </code><code>"failed"</code> <code>: </code><code>True</code><code>,</code>

<code>                    </code><code>"msg"</code>    <code>: </code><code>"failed setting the time"</code>

<code>                </code><code>})</code>

<code>                </code><code>sys.exit(</code><code>1</code><code>)</code>

<code>            </code><code># when things do not fail, we do not</code>

<code>            </code><code># have any restrictions on what kinds of</code>

<code>            </code><code># data are returned, but it's always a</code>

<code>            </code><code># good idea to include whether or not</code>

<code>            </code><code># a change was made, as that will allow</code>

<code>            </code><code># notifiers to be used in playbooks.</code>

<code>            </code><code>date </code><code>=</code> <code>str</code><code>(datetime.datetime.now())</code>

<code>            </code><code>print</code> <code>json.dumps({</code>

<code>                </code><code>"time"</code> <code>: date,</code>

<code>                </code><code>"changed"</code> <code>: </code><code>True</code>

<code>            </code><code>})</code>

<code>            </code><code>sys.exit(</code><code>0</code><code>)</code>

<code># if no parameters are sent, the module may or</code>

<code># may not error out, this one will just</code>

<code># return the time</code>

<code>date </code><code>=</code> <code>str</code><code>(datetime.datetime.now())</code>

<code>print</code> <code>json.dumps({</code>

<code>    </code><code>"time"</code> <code>: date</code>

<code>})</code>

不論是帶參數的還是不帶參數的,子產品寫完之後該如何測試你寫的子產品是否正确呢?

ansible的文檔上給了一種檢測子產品的方式:

Testing Modules

There’s a useful test script in the source checkout for ansible

<code># 下載下傳測試自定義子產品的腳本</code>

<code>1.</code> <code>克隆ansible源碼到本地</code>

<code># git clone git://github.com/ansible/ansible.git --recursive</code>

<code>2.</code> <code>source腳本中設定的環境變量到目前會話</code>

<code># source ansible/hacking/env-setup</code>

<code>3.</code> <code>賦予腳本執行權限</code>

<code># chmod +x ansible/hacking/test-module</code>

<code>由于第一步在克隆的時候操作就失敗了 索性直接将源碼全部clone到本地 操作如下</code>

<code># git clone https://github.com/ansible/ansible.git</code>

3. 測試子產品

1&gt; 自定義子產品不帶參數傳遞 執行方式

比如你的腳本名字為timetest.py,那麼執行指令如下所示

# ansible/hacking/test-module -m ./timetest.py

<code>*</code> <code>including generated source, </code><code>if</code> <code>any</code><code>, saving to: </code><code>/</code><code>root</code><code>/</code><code>.ansible_module_generated</code>

<code>*</code> <code>this may offset </code><code>any</code> <code>line numbers </code><code>in</code> <code>tracebacks</code><code>/</code><code>debuggers!</code>

<code>*</code><code>*</code><code>*</code><code>*</code><code>*</code><code>*</code><code>*</code><code>*</code><code>*</code><code>*</code><code>*</code><code>*</code><code>*</code><code>*</code><code>*</code><code>*</code><code>*</code><code>*</code><code>*</code><code>*</code><code>*</code><code>*</code><code>*</code><code>*</code><code>*</code><code>*</code><code>*</code><code>*</code><code>*</code><code>*</code><code>*</code><code>*</code><code>*</code><code>*</code><code>*</code>

<code>RAW OUTPUT</code>

<code>{</code><code>"time"</code><code>: </code><code>"2016-04-03 02:09:41.516592"</code><code>}</code>

<code>PARSED OUTPUT</code>

<code>{</code>

<code>    </code><code>"time"</code><code>: </code><code>"2016-04-03 02:09:41.516592"</code>

<code>}</code>

2&gt; 自定義子產品帶參數傳遞 執行方式

比如你的腳本名字為timetest.py,傳遞的參數為time="March 14 22:10",那麼執行指令如下所示

# ansible/hacking/test-module -m ./timetest.py -a "time=\"March 14 12:23\""

帶參數的這個地方執行失敗 報錯如下

<code>[root@ManagerAnsible sourceCode_tmp]</code><code># ansible/hacking/test-module -m ../modules/timetest_params.py -a "time=\"March 14 12:23\""</code>

<code>Mon Mar </code><code>14</code> <code>12</code><code>:</code><code>23</code><code>:</code><code>00</code> <code>UTC </code><code>2016</code>

<code>{</code><code>"changed"</code><code>: true, </code><code>"time"</code><code>: </code><code>"2016-03-14 12:23:00.000262"</code><code>}</code>

<code>INVALID OUTPUT </code><code>FORMAT</code>

<code>Traceback (most recent call last):</code>

<code>  </code><code>File</code> <code>"ansible/hacking/test-module"</code><code>, line </code><code>167</code><code>, </code><code>in</code> <code>runtest</code>

<code>    </code><code>results </code><code>=</code> <code>json.loads(out)</code>

<code>  </code><code>File</code> <code>"/usr/local/python27/lib/python2.7/json/__init__.py"</code><code>, line </code><code>339</code><code>, </code><code>in</code> <code>loads</code>

<code>    </code><code>return</code> <code>_default_decoder.decode(s)</code>

<code>  </code><code>File</code> <code>"/usr/local/python27/lib/python2.7/json/decoder.py"</code><code>, line </code><code>364</code><code>, </code><code>in</code> <code>decode</code>

<code>    </code><code>obj, end </code><code>=</code> <code>self</code><code>.raw_decode(s, idx</code><code>=</code><code>_w(s, </code><code>0</code><code>).end())</code>

<code>  </code><code>File</code> <code>"/usr/local/python27/lib/python2.7/json/decoder.py"</code><code>, line </code><code>382</code><code>, </code><code>in</code> <code>raw_decode</code>

<code>    </code><code>raise</code> <code>ValueError(</code><code>"No JSON object could be decoded"</code><code>)</code>

<code>ValueError: No JSON </code><code>object</code> <code>could be decoded</code>

從上面的報錯可以追蹤到ansible/hacking/test-module腳本的167行在json.loads對象的時候失敗.

<code>    </code><code>try</code><code>:</code>

<code>        </code><code>print</code><code>(</code><code>"***********************************"</code><code>)</code>

<code>        </code><code>print</code><code>(</code><code>"RAW OUTPUT"</code><code>)</code>

<code>        </code><code>print</code><code>(out)</code>

<code>        </code><code>print</code><code>(err)</code>

<code>        </code><code>results </code><code>=</code> <code>json.loads(out)    </code><code># 第167行</code>

<code>    </code><code>except</code><code>:</code>

<code>        </code><code>print</code><code>(</code><code>"INVALID OUTPUT FORMAT"</code><code>)</code>

<code>        </code><code>traceback.print_exc()</code>

<code>        </code><code>sys.exit(</code><code>1</code><code>)</code>

<code>    </code><code>print</code><code>(</code><code>"***********************************"</code><code>)</code>

<code>    </code><code>print</code><code>(</code><code>"PARSED OUTPUT"</code><code>)</code>

<code>    </code><code>print</code><code>(jsonify(results,</code><code>format</code><code>=</code><code>True</code><code>))</code>

至于為什麼會出現這個問題,在文章的後面會有解決辦法......

首先看timetest.py檔案(注釋比較多 代碼量其實就幾行)

正文 前兩行沒怎麼看懂

args_file = sys.argv[1]

args_data = file(args_file).read()

<code># 接受一個參數</code>

<code># 打開這個參數 file &lt;&lt;&gt;&gt; open</code>

<code>args_data </code><code>=</code> <code>file</code><code>(args_file).read()  </code><code>/</code><code>/</code><code>開始納悶了開始納悶了開始納悶了</code>

于是又對這個檔案ansible/hacking/test-module進行追蹤

我對test-module添加了中文注釋 有興趣的朋友可以參考下 已經上傳文章末尾到附件中.

解決了兩個問題:

問題1:

ansible/hacking/test-module

有以下幾個函數

parse # 接受指令行參數.

write_argsfile # 将指令行傳遞的參數寫入到指定的檔案中.

boilerplate_module # 将./timetest.py檔案的内容全部寫入到指令行-o預設指定的子產品檔案

runtest # 執行腳并打開參數本檔案

總結下

boilerplate_module這個函數:将使用者自定義的子產品寫入到~/.ansible_module_generated這個檔案中

write_argsfile這個函數:将使用者傳遞的參數寫入到~/.ansible_test_module_arguments這個檔案中

runtest這個函數:執行腳本和傳遞的參數~/.ansible_module_generated ~/.ansible_test_module_arguments

問題2:

修改文檔中timetest.py代碼

<code>修改前</code>

<code>rc </code><code>=</code> <code>os.system(</code><code>"date -s \"%s\""</code> <code>%</code> <code>value)</code>

<code>修改後</code>

<code>import</code> <code>commands</code>

<code>rc, output </code><code>=</code> <code>commands.getstatusoutput(</code><code>'date -s \"%s\"'</code> <code>%</code> <code>value)</code>

其實有兩處才讓我想到是這裡的原因:

原因1:

首先看timetest.py代碼中 摘取一段

<code>if</code> <code>rc !</code><code>=</code> <code>0</code><code>:</code>

這個rc到底是擷取os.system的指令執行結果還是擷取os.system的傳回值呢?

想必第二行的if語句你就弄明白.

原因2:

ansible/hacking/test-module檔案

<code>def</code> <code>runtest( modfile, argspath):</code>

<code>    </code><code>"""Test run a module, piping it's output for reporting."""</code>

<code>    </code><code>os.system(</code><code>"chmod +x %s"</code> <code>%</code> <code>modfile)</code>

<code>    </code><code>invoke </code><code>=</code> <code>"%s"</code> <code>%</code> <code>(modfile)</code>

<code>    </code><code>if</code> <code>argspath </code><code>is</code> <code>not</code> <code>None</code><code>:</code>

<code>        </code><code>invoke </code><code>=</code> <code>"%s %s"</code> <code>%</code> <code>(modfile, argspath)</code>

<code>    </code><code>cmd </code><code>=</code> <code>subprocess.Popen(invoke, shell</code><code>=</code><code>True</code><code>, stdout</code><code>=</code><code>subprocess.PIPE, stderr</code><code>=</code><code>subprocess.PIPE)</code>

<code>    </code><code>(out, err) </code><code>=</code> <code>cmd.communicate()</code>

<code>        </code><code>results </code><code>=</code> <code>json.loads(out)</code>

這個函數的正确傳回結果肯定要是json格式的,而timetest.py檔案有兩處列印;第一處列印便是os.system的執行結果;第二處便是print json.dumps的結果,顯然這是兩行列印 無法進行json

那麼我就舉個列子來說明下吧

<code># 對timetest.py簡寫成如下格式</code>

<code>import</code> <code>os, datetime, json</code>

<code>os.system(</code><code>'date -s %s'</code> <code>%</code> <code>3</code><code>)</code>

<code>            </code> 

<code># 那麼test-module中的out就相當于上面執行結果的相加</code>

<code>"Mon Mar 14 03:00:00 UTC 2016"</code> <code>+</code> <code>"{"</code><code>changed</code><code>": true, "</code><code>time</code><code>": "</code><code>2016</code><code>-</code><code>03</code><code>-</code><code>14</code> <code>03</code><code>:</code><code>00</code><code>:</code><code>00.013835</code><code>"}"</code>

<code>以上這種格式你是無法進行json.loads成json對象的 是以也就是報錯的原因.</code>

在文章的末尾就是如何使用使用者自定義的子產品呢,前面介紹了那麼多都是如何測試子產品,接下來就是使用者如何正确的使用自定義完成的子產品.

(1)通過ansible --help |grep module

<code>  </code><code>-</code><code>m MODULE_NAME, </code><code>-</code><code>-</code><code>module</code><code>-</code><code>name</code><code>=</code><code>MODULE_NAME</code>

<code>                        </code><code>module name to execute (default</code><code>=</code><code>command)</code>

<code>  </code><code>-</code><code>M MODULE_PATH, </code><code>-</code><code>-</code><code>module</code><code>-</code><code>path</code><code>=</code><code>MODULE_PATH</code>

<code>                        </code><code>specify path(s) to module library (default</code><code>=</code><code>None</code><code>)</code>

通過-M的方式來指定自定義子產品的路徑.

(2)通過ANSIBLE_LIBRARY 變量來指定

&lt;&lt;不夠慶幸的是 前兩種方式我Google好多文章也都沒有解決,如果哪位朋友有解決的版本 也請告知&gt;&gt;

(3)我使用了最後一種比較笨的方式:

<code>目前python版本為源碼安裝方式 </code><code>2.7</code><code>版本</code>

<code>python的安裝路徑為</code><code>/</code><code>usr</code><code>/</code><code>local</code><code>/</code><code>python27</code>

<code>python子產品包路徑為</code><code>/</code><code>usr</code><code>/</code><code>local</code><code>/</code><code>python27</code><code>/</code><code>lib</code><code>/</code><code>python2.</code><code>7</code><code>/</code><code>site</code><code>-</code><code>packages</code><code>/</code><code>ansible</code><code>/</code><code>modules</code>

<code>其中有core(核心包)和extras(擴充包)兩個目錄</code>

<code>那麼自定義子產品 應該屬于擴充包吧 于是做如下操作</code>

<code># cd /usr/local/python27/lib/python2.7/site-packages/ansible/modules/extras</code>

<code># mkdir zhengys</code>

<code># cp ~/demo/ansible/modules/* ./</code>

<code>也就是把自定義的子產品都放置與extras</code><code>/</code><code>zhengys目錄下 就可以使用了</code>

     本文轉自zys467754239 51CTO部落格,原文連結:http://blog.51cto.com/467754239/1759873,如需轉載請自行聯系原作者