Hello World, OpenNebula Cloud API 编程

先报告一下我们云计算项目的进度。去年休假前订购的服务器和部件已经陆续到货了,计算节点采用的是 Dell PowerEdge M710HD 刀片服务器,特别为数据中心级虚拟应用设计,海量内存、密集 IO 吞吐等优势,特别适合云计算、虚拟机等应用。现在正在等 Dell 的售后技术人员过来安装服务器和存储阵列,有些电源和机柜问题需要解决,顺利的话下周服务器可以上线。

dell poweredge m710hd

OpenNebula 提供了 XML-RPC 的方式访问 OpenNebula Cloud Api (OCA),这样就允许不同操作系统、不同语言编写的客户端程序可以通过 XML-RPC 远程调用的方式来访问 OpenNebula 服务。下面通过两个不同语言编写的最简单例子抛砖引玉一下,来看看如何是如何与 OCA 打交道的。

OpenNebula 绝大部分是由 Ruby 编写的,其提供的 Ruby OCA API 实现当然是最丰富和完整的。先安装 Ruby OCA Bindings:

$ sudo gem install oca

用 Ruby 编写一小段代码试验一下,以下代码用来打印当前云里每个计算结点的 hostname:

#!/usr/bin/ruby

require 'rubygems'
require 'oca'

include OpenNebula

# OpenNebula credentials
CREDENTIALS = "oneadmin:vpsee"

# XML_RPC endpoint where OpenNebula is listening
ENDPOINT    = "http://localhost:2633/RPC2"

client = Client.new(CREDENTIALS, ENDPOINT)
host_pool = HostPool.new(client)
rc = host_pool.info

# Print all the hostname from the host pool
host_pool.each do |host|
     puts host.name
end

再来看看用 Python 如何编写上面类似功能的代码。安装 Python OCA Bindings:

$ sudo easy_install oca

用 Python 编写一小段代码看一下:

#!/usr/bin/python

import oca

# OpenNebula credentials
CREDENTIALS = "oneadmin:vpsee"

# XML_RPC endpoint where OpenNebula is listening
ENDPOINT    = "http://localhost:2633/RPC2"

client = oca.Client(CREDENTIALS, ENDPOINT)
host_pool = oca.HostPool(client)
host_pool.info()

# Print all the hostname from the host pool
for host in host_pool:
    print host.name

应该没人会想在这种情况下用 Java 或 C++ 吧,Programming Examples 里面提供的 Java OCA 和 C++ 例子比 Ruby, Python 复杂得多。

随机生成 Xen 虚拟机的 MAC 地址

如果不在创建 Xen 虚拟机(domU)的时候指定 MAC 地址的话,Xen 就会随机生成一个 MAC 地址给虚拟机,这样管理员就没办法知道虚拟机的 MAC 地址了,不利于以后带宽的统计和虚拟机的管理,所以最好就在 Xen 虚拟机配置文件中直接指明虚拟机的 MAC 地址,比如:

# vi /etc/xen/vpsuser1
vif = [ "mac=00:16:3e:0c:11:53,ip=172.16.16.200,bridge=xenbr0" ]

IEEE OUI 为 Xen domU 保留了一段 MAC 地址,前3段是 “00-16-3e”,后3段是随机的,其中第1个随机段的第1个 bit 是0(IEEE OUI 给 VMware 保留的前3段地址是 “00-0c-29”,后3段随机)。我们在为用户分配 MAC 地址时候不想有规律的分配(虽然也不会有什么安全问题),所以 VPSee 写了一个简单的 Python 脚本用来随机生成符合 Xen 虚拟机标准的 MAC 地址:

#!/usr/bin/python
# generates a MAC address for Xen domU
# http://www.vpsee.com
#

import random

mac = [ 0x00, 0x16, 0x3e, random.randint(0x00, 0x7f),
random.randint(0x00, 0xff), random.randint(0x00, 0xff) ]
s = []
for item in mac:
        s.append(str("%02x" % item))
print ':'.join(s)

简单调试 Python 程序

在 Python 中也可以像 gcc/gdb 那样调试程序,只要在运行 Python 程序时引入 pdb 模块(假设要调试的程序名为 d.py):

$ vi d.py
#!/usr/bin/python

def main():
        i, sum = 1, 0
        for i in xrange(100):
                sum = sum + i
        print sum

if __name__ == '__main__':
        main()

$ python -m pdb d.py

运行上面的命令后进入以下界面,可以输入类似 gdb 的命令来改变程序的执行流程:

$ python -m pdb 1.py
> d.py(3)()
-> def main():
(Pdb)

list 显示程序的最近代码段:

(Pdb) list
  1  	#!/usr/bin/python
  2
  3  ->	def main():
  4  		i, sum = 1, 0
  5  		for i in xrange(100):
  6  			sum = sum + i
  7  		print sum
  8
  9  	if __name__ == '__main__':
 10  		main()
[EOF]

next 或者 n 执行下一行代码:

(Pdb) next
> d.py(9)()
-> if __name__ == '__main__':

用 break 在第6行设置一个断点:

(Pdb) break d.py:6
Breakpoint 1 at d.py:6

(Pdb) list
  1  	#!/usr/bin/python
  2
  3  	def main():
  4  		i, sum = 1, 0
  5  ->		for i in xrange(100):
  6 B			sum = sum + i
  7  		print sum
  8
  9  	if __name__ == '__main__':
 10  		main()
[EOF]

如果想在函数处设置断点:

(Pdb) break d.main
d.py:3

(Pdb) list
  1  	#!/usr/bin/python
  2
  3 B	def main():
  4  ->		i, sum = 1, 0
  5  		for i in xrange(100):
  6  			sum = sum + i
  7  		print sum
  8
  9  	if __name__ == '__main__':
 10  		main()
[EOF]

还可以给断点加条件,比如设置条件只有当 sum > 50 的时候才 break:

(Pdb) break d.py:6, sum > 50
Breakpoint 1 at d.py:6

如果想查看某个变量的值,可以用 pp 命令打印出来:

(Pdb) step
> d.py(5)main()
-> for i in xrange(100):
(Pdb) pp sum
0

可以直接在程序里使用 pdb 模块,import pdb 后 pdb.set_trace():

#!/usr/bin/python
import pdb

def main():
        i, sum = 1, 0
        for i in xrange(100):
                sum = sum + i
        pdb.set_trace()
        print sum

if __name__ == '__main__':
        main()

这样只要运行程序 ./d.py 就可以直接运行到 print sum 处:

$ ./d.py
> d.py(9)main()
-> print sum
(Pdb)

总结

命令 用途
break 或 b 设置断点
continue 或 c 继续执行程序
list 或 l 查看当前行的代码段
step 或 s 进入函数
return 或 r 执行代码直到从当前函数返回
exit 或 q 中止并退出
next 或 n 执行下一行
pp 打印变量的值
help 帮助

用 Python 做单词拼写检查

这几天在翻旧代码时发现以前写的注释部分有很多单词拼写错误,这些单词错得不算离谱,应该可以用工具自动纠错绝大部分。用 Python 写个拼写检查脚本很容易,如果能很好利用 aspell/ispell 这些现成的小工具就更简单了。

要点

1、输入一个拼写错误的单词,调用 aspell -a 后得到一些候选正确单词,然后用距离编辑进一步嗮选出更精确的词。比如运行 aspell -a,输入 ‘hella’ 后得到如下结果:
hell, Helli, hello, heal, Heall, he’ll, hells, Heller, Ella, Hall, Hill, Hull, hall, heel, hill, hula, hull, Helga, Helsa, Bella, Della, Mella, Sella, fella, Halli, Hally, Hilly, Holli, Holly, hallo, hilly, holly, hullo, Hell’s, hell’s

2、什么是距离编辑(Edit-Distance,也叫 Levenshtein algorithm)呢?就是说给定一个单词,通过多次插入、删除、交换、替换单字符的操作后枚举出所有可能的正确拼写,比如输入 ‘hella’,经过多次插入、删除、交换、替换单字符的操作后变成:
‘helkla’, ‘hjlla’, ‘hylla’, ‘hellma’, ‘khella’, ‘iella’, ‘helhla’, ‘hellag’, ‘hela’, ‘vhella’, ‘hhella’, ‘hell’, ‘heglla’, ‘hvlla’, ‘hellaa’, ‘ghella’, ‘hellar’, ‘heslla’, ‘lhella’, ‘helpa’, ‘hello’, …

3、综合上面2个集合的结果,并且考虑到一些理论知识可以提高拼写检查的准确度,比如一般来说写错单词都是无意的或者误打,完全错的单词可能性很小,而且单词的第一个字母一般不会拼错。所以可以在上面集合里去掉第一个字母不符合的单词,比如:’Sella’, ‘Mella’, khella’, ‘iella’ 等,这里 VPSee 不删除单词,而把这些单词从队列里取出来放到队列最后(优先级降低),所以实在匹配不了以 h 开头的单词才去匹配那些以其他字母开头的单词。

4、程序中用到了外部工具 aspell,如何在 Python 里捕捉外部程序的输入和输出以便在 Python 程序里处理这些输入和输出呢?Python 2.4 以后引入了 subprocess 模块,可以用 subprocess.Popen 来处理。

5、Google 大牛 Peter Norvig 写了一篇 How to Write a Spelling Corrector 很值得一看,大牛就是大牛,21行 Python 就解决拼写问题,而且还不用外部工具,只需要事先读入一个词典文件。本文程序的 edits1 函数就是从牛人家那里 copy 的。

代码

#!/usr/bin/python
# A simple spell checker
# written by http://www.vpsee.com 

import os, sys, subprocess, signal

alphabet = 'abcdefghijklmnopqrstuvwxyz'

def found(word, args, cwd = None, shell = True):
    child = subprocess.Popen(args,
        shell = shell,
        stdin = subprocess.PIPE,
        stdout = subprocess.PIPE,
        cwd = cwd,
        universal_newlines = True)
    child.stdout.readline()
    (stdout, stderr) = child.communicate(word)
    if ": " in stdout:
        # remove \n\n
        stdout = stdout.rstrip("\n")
        # remove left part until :
        left, candidates = stdout.split(": ", 1)
        candidates = candidates.split(", ")
        # making an error on the first letter of a word is less
        # probable, so we remove those candidates and append them
        # to the tail of queue, make them less priority
        for item in candidates:
            if item[0] != word[0]:
                candidates.remove(item)
                candidates.append(item)
        return candidates
    else:
        return None

# copy from http://norvig.com/spell-correct.html
def edits1(word):
    n = len(word)
    return set([word[0:i]+word[i+1:] for i in range(n)] +
        [word[0:i]+word[i+1]+word[i]+word[i+2:] for i in range(n-1)] +
        [word[0:i]+c+word[i+1:] for i in range(n) for c in alphabet] +
        [word[0:i]+c+word[i:] for i in range(n+1) for c in alphabet])

def correct(word):
    candidates1 = found(word, 'aspell -a')
    if not candidates1:
        print "no suggestion"
        return  

    candidates2  = edits1(word)
    candidates  = []
    for word in candidates1:
        if word in candidates2:
            candidates.append(word)
    if not candidates:
        print "suggestion: %s" % candidates1[0]
    else:
        print "suggestion: %s" % max(candidates)

def signal_handler(signal, frame):
    sys.exit(0)

if __name__ == '__main__':
    signal.signal(signal.SIGINT, signal_handler)
    while True:
        input = raw_input()
        correct(input)

更简单的方法

当然直接在程序里调用相关模块最简单了,有个叫做 PyEnchant 的库支持拼写检查,安装 PyEnchant 和 Enchant 后就可以直接在 Python 程序里 import 了:

>>> import enchant
>>> d = enchant.Dict("en_US")
>>> d.check("Hello")
True
>>> d.check("Helo")
False
>>> d.suggest("Helo")
['He lo', 'He-lo', 'Hello', 'Helot', 'Help', 'Halo', 'Hell', 'Held', 'Helm', 'Hero', "He'll"]
>>>

如何查看进程 IO 读写情况?

Linux Kernel 2.6.20 以上的内核支持进程 IO 统计,可以用类似 iotop 这样的工具来监测每个进程对 IO 操作的情况,就像用 top 来实时查看进程内存、CPU 等占用情况那样。但是对于 2.6.20 以下的 Linux 内核版本就没那么幸运了,根据 Stack Overflow 的这篇回帖 给出的方法,VPSee 写了一个简单的 Python 脚本用来在 linux kernel < 2.6.20 下打印进程 IO 状况。

Kernel < 2.6.20

这个脚本的想法很简单,把 dmesg 的结果重定向到一个文件后再解析出来,每隔1秒钟打印一次进程 IO 读写的统计信息,执行这个脚本需要 root:

#!/usr/bin/python
# Monitoring per-process disk I/O activity
# written by http://www.vpsee.com 

import sys, os, time, signal, re

class DiskIO:
    def __init__(self, pname=None, pid=None, reads=0, writes=0):
        self.pname = pname
        self.pid = pid
        self.reads = 0
        self.writes = 0

def main():
    argc = len(sys.argv)
    if argc != 1:
        print "usage: ./iotop"
        sys.exit(0)

    if os.getuid() != 0:
        print "must be run as root"
        sys.exit(0)

    signal.signal(signal.SIGINT, signal_handler)
    os.system('echo 1 > /proc/sys/vm/block_dump')
    print "TASK              PID       READ      WRITE"
    while True:
        os.system('dmesg -c > /tmp/diskio.log')
        l = []
        f = open('/tmp/diskio.log', 'r')
        line = f.readline()
        while line:
            m = re.match(\
                '^(\S+)\((\d+)\): (READ|WRITE) block (\d+) on (\S+)', line)
            if m != None:
                if not l:
                    l.append(DiskIO(m.group(1), m.group(2)))
                    line = f.readline()
                    continue
                found = False
                for item in l:
                    if item.pid == m.group(2):
                        found = True
                        if m.group(3) == "READ":
                            item.reads = item.reads + 1
                        elif m.group(3) == "WRITE":
                            item.writes = item.writes + 1
                if not found:
                    l.append(DiskIO(m.group(1), m.group(2)))
            line = f.readline()
        time.sleep(1)
        for item in l:
            print "%-10s %10s %10d %10d" % \
                (item.pname, item.pid, item.reads, item.writes)

def signal_handler(signal, frame):
    os.system('echo 0 > /proc/sys/vm/block_dump')
    sys.exit(0)

if __name__=="__main__":
    main()


继续阅读 »

自动监测和重启 FastCGI 服务

昨天有个服务器出了点小问题,PHP FastCGI 进程无缘无故就死在那里了,造成 Nginx 不能和 FastCGI 通信,不能解析 PHP 页面,只能看到 Nginx 的 默认 HTML 页面。登录到服务器检查日志也没有找到原因,重启 FastCGI 后恢复正常。服务器上装了 monit,如果服务关闭的话会自动重启,但是这里 FastCGI 服务并没有关闭,只不过由于某种原因不能和 Nginx 通信,所以 monit 误认为 FastCGI 运行正常没有执行重启 FastCGI 的命令。

今天写了一个 Python 脚本用来后台监测 nginx 的日志,如果在日志里发现 111: Connection refused 或者 104: Connection reset by peer 等错误就 kill 掉所有 php-cgi 进程后重启服务。程序有几个地方需要说明:

1、如果不先 kill 直接运行 /etc/init.d/php-cgi restart 重启服务有时候不管用,因为这时候 php-cgi 只是驻留在内存里的死进程而而已不能执行任何命令,所以需要 kill 全部 php-cgi 进程后再启动;
2、读 nginx 的 log 文件的时候要小心,log 文件通常很大,如果用 python 的普通读文件函数会把整个文件读出来后再处理,速度很慢,也会占用大量内存,所以最好用 tail 截取文件,我们只需要分析最后部分(也是最近)的记录即可;
3、程序通过 /var/run/php_cgi.pid 来判断 PHP FastCGI 是否正在运行,通过 /var/log/nginx/error.log 来判断 Nginx/FastCGI 是否工作正常,每600秒检查一次,如果工作不正常 sleep 2秒后重启 php-cgi;
4、本程序可扩展到重启其他服务,比如通过 /var/run/lighttpd.pid 来判断 lighttpd 是否需要重启;
5、程序中 daemonize 函数来自 Python Cookbook(O’Reilly ) 一书。

程序使用

拷贝下面代码做一定修改以后保存为 checkphpcgi,增加文件可执行权限后用 root 运行程序:

# chmod +x checkphpcgi

# ./checkphpcgi
usage: ./checkphpcgi start|stop|restart

# ./checkphpcgi start

如果想要停止程序:

继续阅读 »

在 Python 中使用 difflib 比较字符串

用 Shell 写程序还是不方便,今天用 Python 把昨天写的 Bash 脚本重写了,遇到两个关于字符串的个小问题:

1、做个类似 diff 工具的效果,大致指出两个字符串的不同之处,这个可以用 difflib 模块解决。

!/usr/bin/python
import difflib

text1 = """http://www.vpsee.com is a website which is dedicated for
building scalable websites on cloud platforms. The keywords are: Linux, Mac,
Cloud Computing, C, Python, MySQL, Nginx, VPS, Performance, Scalability,
Architecture, ..., etc. Have fun!"""
text1_lines = text1.splitlines()

text2 = """http://VPSee.com is a website which is dedicated for
building scalable websites on cloud platforms. The keywords are: Linux, Mac,
Cloud Computing, C, Python, MySQL, Nginx, VPS, Performance, Scalability,
Programming, Optimisation, Architecture, ... , etc. Have fun !"""
text2_lines = text2.splitlines()

d = difflib.Differ()
diff = d.compare(text1_lines, text2_lines)
print '\n'.join(list(diff))

程序运行结果如下:

- http://www.vpsee.com is a website which is dedicated for
?        ^^^^^^^

+ http://VPSee.com is a website which is dedicated for
?        ^^^

  building scalable websites on cloud platforms. The keywords are: Linux, Mac,
  Cloud Computing, C, Python, MySQL, Nginx, VPS, Performance, Scalability,
- Architecture, ..., etc. Have fun!
+ Programming, Optimisation, Architecture, ... , etc. Have fun !

2、如何比较两个字符串,并且忽略大小写、空白字符、TAB 制表符、换行等。这个很容易解决,把字符串转换成小写后 split,然后以空格为分隔符 join 在一起。

继续阅读 »

用 Shell 脚本访问 MySQL 数据库

下午写了一个简单的 bash 脚本,用来测试程序,输入一个测试用例文件,输出没有通过测试的用例和结果,然后把结果保存到数据库里。如何在 bash 脚本里直接访问数据库呢?既然在 shell 里可以直接用 mysql 命令操作数据库,那么在 shell script 里也应该可以通过调用 mysql 来操作数据库。比如用下面的 bash shell 脚本查询数据库:

Bash

#!/bin/bash

mysql -uvpsee -ppassword test < < EOFMYSQL
select * from test_mark;
EOFMYSQL

如果需要复杂的数据库操作的话不建议用 shell 脚本,用 Perl/Python/PHP 操作数据库很方便,分别通过 Perl DBI/Python MySQLdb/PHP MySQL Module 接口来操作数据库。这里再给出这三种不同语言连接、查询数据库的简单例子(为了简单和减少篇幅删除一些不必要的代码):

Perl

#!/usr/bin/perl
use DBI;

$db = DBI->connect('dbi:mysql:test', 'vpsee', 'password');
$query = "select * from test_mark";
$cursor = $db->prepare($query);
$cursor->execute;
while (@row = $cursor->fetchrow_array) {
        print "@row\n";
}

Python


继续阅读 »

修正 Django Step by Step 的一些例子

周末看了 limodouDjango Step by Step 入门教程,写得很棒,通俗易懂,谢谢先。Django 发展太快,现在已是 1.0.2 版本,可惜 Django Step by Step 上面的例子还是 0.9x 版本,所以编译运行上面的例子时会出现一些问题,主要是一些兼容问题,列出来以供参考:

第六讲

3、编辑 wiki/models.py


pagename = models.CharField(maxlength=20, unique=True)

在 Django 1.0.2 中,上面的 maxlength 应改为:

max_length

6、修改 wiki/views.py


c = Context({‘pagename’:page.pagename, ‘content’:content})

上面代码输出 content 时会把 “< >” 等字符转义输出成 “< &gt”,这个时候需要关闭自动转义,以便 content 输出 HTML 代码。改成如下:

c = Context({'pagename':page.pagename,'content':content},autoescape=False)

还有一种方法就是在 wiki/page.html 里改成:

{% autoescape off %}

{{ content }}

{% endautoescape %}

第七讲

3、修改 address/models.py


gender = models.CharField(‘性别’, choices=((‘M’, ‘男’), (‘F’, ‘女’)),
maxlength=1, radio_admin=True)

注意上面的 radio_admin=True 已经在 Django 1.0.2 中不适用了,不能写在 field 里,admin 和 model分离了,把 models.py 的全部内容用以下代码替换:

from django.db import models

# Create your models here.
class Address(models.Model):
        name = models.CharField('Name', max_length=6, unique=True)
        gender = models.CharField('Sex', choices=(('M', 'Male'), ('F', 'Female')), max_length=1)
        telphone = models.CharField('Telphone', max_length=20)
        mobile = models.CharField('Cellphone', max_length=11)
        room = models.CharField('Room', max_length=10)

from django.contrib import admin

class AddressAdmin(admin.ModelAdmin):
        model=Address
        radio_fields = {'gender':admin.VERTICAL}

admin.site.register(Address, AddressAdmin)

6、修改 urls.py

from django.conf.urls.defaults import *

urlpatterns = patterns(”,

# Uncomment this for admin:
(r’^admin/’, include(‘django.contrib.admin.urls’)),
)

新的变化已经在 Django 1.0.2 生成的默认 url.py 里了,只需要把 urls.py 里面的 comment 前面的 # 去掉就可以了。

# Uncomment the next two lines to enable the admin:
from django.contrib import admin
admin.autodiscover()

# Uncomment the next line to enable the admin:
     (r'^admin/(.*)', admin.site.root),

7、增加超级用户

manage.py shell
>>> from django.contrib.auth.create_superuser import createsuperuser
>>> createsuperuser()

只需要改成下面一条命令:

./manage.py createsuperuser

以上修正在 Django 1.0.2 + Python 2.5.1 + Mac OS X 10.5.7 上调试通过。