天天看点

分析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,如需转载请自行联系原作者