2011年1月29日 星期六

unittest2.case.assertRaises 的實作方式

New and Improved Coming changes to unittest in Python 2.7 & 3.2 有詳細的 unittest2 介紹, 其中我覺得最漂亮的改進是 assertRaises, 用法變成:
with self.assertRaises(TypeError) as cm:
    do_something()

exception = cm.exception
self.assertEqual(exception.error_code, 3)
我覺得新介面用 with 執行, 既易懂又能彈性地執行不同行數的程式。想不到它怎麼做出這樣的行為。好奇之下, 看了一下原始碼, 才發覺作法很簡單:
def assertRaises(self, excClass, callableObj=None, *args, **kwargs):
        # ...
        if callableObj is None:
            return _AssertRaisesContext(excClass, self)
        # ...
class _AssertRaisesContext(object):
    """A context manager used to implement TestCase.assertRaises* methods."""

    def __init__(self, expected, test_case, expected_regexp=None):
        self.expected = expected
        self.failureException = test_case.failureException
        self.expected_regexp = expected_regexp

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, tb):
        if exc_type is None:
            try:
                exc_name = self.expected.__name__
            except AttributeError:
                exc_name = str(self.expected)
            raise self.failureException(
                "%s not raised" % (exc_name,))
        # ...

就是傳回一個物件 _AssertRaisesContext, 讓該物件實作 with 的 __enter__ 和 __exit__, __exit__ 的參數含有過程中產生的 exception 相關資訊, 離開時檢查它就知道有沒有產生 exception 了。

另外 unittest2 為了和 unittest 相容, 介面寫得挺漂亮的, unittest2 的 assertRaises 會先依參數數量判斷是舊的呼叫方式還是新的使用 with 的呼叫方式, 再做對應的處理。需要維護的程式變多後, 對於向下相容的藝術, 才有進一步的體會。

Django middleware 基本應用: 過濾黑名單

參考了幾份程式, 寫法大同小異, 這裡有簡單的 code snippet, 另外 django-ban 大小適中, 不會太複雜而不想看, 又附 sample project 說明怎麼設定, 若沒用過 middleware 的話, 可以參考看看。

概念就是自己寫個 middleware, 加入 settings 裡。middleware 有實作 process_request, 在函式裡檢查 REMOTE_ADDR (我看了好幾份程式都先優先用 HTTP_X_FORWARDED_FOR, 但這樣並不安全), 若在黑名單內, 就傳回 http.HttpResponseForbidden()。感覺這種寫法滿乾淨的。

剛開始一直在找類似 pre-processor 之類的語法, 因為學生時期玩 Rails 1.8 時, 好像有這樣的東西, 就「直覺」地找類似的東西。後來才想到, 這種要擋在全部連線之前的程式, 似乎正好適合寫在 middleware 裡。找對方向後, 就找到一堆東西了。

備註: 找的過程中看到有人用 ipcalc, 計算 ip subnet 的好東西, 之後需要再裝來用。

在 Django 發生 exception 時記錄相關訊息

官網文件可知, views 裡發生任何 exception 時, Django 會執行 handler500, 所以在 urls.py 裡設自己的 handler500 就可以攔截所有 500 server error, 接著就是如何取得 exception 內容。後來看到 ticket #4007 才知道可以從 sys.exc_info 裡取得相關資訊。後來想想, 這表示 logging.exception() 應該已包好對應的操作, 所以做法相當簡單:
  • 改 urls.py 加入自己的 handler500
  • 在 handler500 裡先呼叫 logging.exception(MESSAGE)
  • 再讀入自己的 500.html, 回傳結果
附帶一提, 404 的行為也值得一看, 可以參考內建的 404 handler 自己寫一份, 方便記錄查不到的頁面。官網建議改 template 就好, 不要自己寫 handler404, 不過我覺得複制程式碼, 指定到自己的 handler, 日後方便加東西。

其餘 views 在找不到頁面時, 直接 raise http.Http404(), 就會進入 handler404。當 settings.DEBUG = False 時, url routes 找不到對應的 url 時也會呼叫 handler404。

    用 apache2 轉址

    原本想用 virtualhost + script 轉址, 查了一下相關作法, 才發現用 mod_rewrite 簡單多了。懶得看 mod_rewrite 超詳細說明, 可以直接看 URL Rewriting Guide, 有常見問題的設法, 第二則就是轉址 (Canonical Hostnames)。附帶一提, RewriteCond 預設是和之後條件做 AND 運算, 可以下參數改用 OR。對照一下 Guide 再查查相關 directive 大概說明, 就知道怎麼用在自己的情況了。

    2015/01/04 更新

    注意設定放在 Directory 下會比較複雜, 直接放在最上層或 VirtualHost 可避免「未知」的錯誤, 見 Per-directory Rewrites

    mercurial-server 運作流程

    執行新版 1.1  後, 在 push 時會出現以下的錯誤訊息:
    remote: error: changegroup.aaaaa_servelog hook raised an exception: 'str' object has no attribute 'format'
    弄清楚 mercurial-server 執行過程後, 發現這是因為 1.1 版用 python 2.6 的語法 (str.format) 造成的,  我是用 python 2.5。修改 /usr/local/share/mercurial-server/mercurialserver/servelog.py 去掉 format 和 json 相關的程式就好了。若不修正的話, /var/lib/mercurial-server/repos/REPO/.hg/mercurial-server.log 不會存有操作記錄。

    解決問題後, 順便記一下 mercurial-server 的運作流程:
    1. ssh -> /var/lib/mercurial-server/.ssh/authorized_keys
    2. authorized_keys 裡記錄:
      no-pty,no-port-forwarding,no-X11-forwarding,no-agent-forwarding,command="/usr/local/share/mercurial-server/hg-ssh KEY_FILE SSH_PUBLIC_KEY"
      藉由這裡的設定會啟動 hg-ssh 處理透過 ssh 來的指令。 
    3. hg-ssh 是個 python script, 會載入 package /usr/local/share/mercurial-server/mercurialserver/ 下的 modules 來完成指令
    4. mercurialserver.config 載入 /var/lib/mercurial-server/.mercurial-server
    5. /var/lib/mercurial-server/.mercurial-server 讀入權限設定、hg hooks 等
    從 hg hooks 設定 (/etc/mercurial-server/remote-hgrc.d/logging.rc) 找到這行
    changegroup.aaaaa_servelog = python:mercurialserver.servelog.hook
    再由此配合 hg-ssh 找到 /usr/local/share/mercurial-server/mercurialserver/servelog.py, 就解決問題啦。

    2011年1月27日 星期四

    顯示網路流量的指令 vnstat -l

    上次要用時想不起來, 找了一陣子, 先記在這裡吧。

    • vnstat: 顯示歷史記錄
    • vnstat -l: 顯示目前收發的流量
    • vnstat -l -i eth1: 同上, 改顯示 eth1 的流量

    REMOTE_ADDR vs. HTTP_X_FORWARDED_FOR

    REMOTE_ADDR 是 apache2 設的變數, client 很難欺騙 apache, 但 HTTP_X_FORWARDED_FOR 是由 HTTP header 的 X-Forwarded-For 來的, client 可以自己設任意值。所以若要用 IP 做認證, 要用 REMOTE_ADDR。缺點就是使用者透過 proxy 連線就會將同一 proxy IP 的人一視同仁, 挺多再用 cookie 做區別。

    幾篇參考資料:

    安裝 mercurial server

    筆記一下 mercurial server 安裝過程。

    照著 README 所言打 sudo make setup-adduser, 結果出現錯誤, 看起來是缺一些 package 無法產生文件。反正用不到文件, 看了一下 Makefile, 找出必要部份, 一步步自己 make, 跳過產生文件那步 即可:
    1. sudo make installetc
    2. sudo make pythoninstall
    3. sudo make adduser
    4. sudo make inituser
    mercurial server 認 public key 管權限, 看一下官網文件就會懂怎麼用。先將第一個使用者加入  root 權限:
    1. sudo cp ~/.ssh/id_rsa.pub /etc/mercurial-server/keys/root/USER
    2. sudo -u hg /usr/local/share/mercurial-server/refresh-auth
    第二步會將 key 加入 hg 帳號的 authorized_keys 裡。用 USER 執行 ssh hg@localhost 後看到以下的訊息表示 key 有設好:
    mercurial-server: direct logins on the hg account prohibited
    Connection to localhost closed.
    有 root 後, 再來透過 repository hgadmin 來管比較方便, 也可以留 commit log:
    1. hg clone ssh://hg@localhost/hgadmin ~/hgadmin
    2. cd ~/hgadmin/
    3. mkdir -p keys/root/
    4. mkdir keys/root/USER
    5. cp PUBLIC_KEY_OF_USER_AT_HOST keys/root/USER/HOST
    6. hg add keys/root/USER/HOST
    7. hg ci -m 'Add USER@HOST with root permission'
    8. hg push
    push 後就會更新結果。若需要做細部的設定, 參考官網文件修改 /etc/mercurial-server/access.conf。

    2011年1月24日 星期一

    xargs + mv

    之前一直很弱的只會用 xargs 加 rm、echo 這種不需另加參數的用法。剛需要用到 mv 就查了一下, 找到這篇, -n 和 -I 看起來不錯用。xargs + mv 的寫法:
    $ ... | xargs -I @ mv @ DEST_DIR

    另外附上一個用到 -n 的情境, 查詢 log 裡所有 IP 的 host name, 方便依 domain name 做些後處理:
    $ awk '{print $1}' /var/log/apache2/access.log | sort -nu | xargs -n 1 host
    

    2011年1月22日 星期六

    找出目錄下非特定使用者擁有的檔案

    find -uid 可以找目錄下特定使用者有的檔案, 反過來不知怎麼找。

    今天靈機一動, 想到可以這麼搞, 不夠直接, 至少能用就是了:
    ls -lR DIR | grep "^[-rw]\{10\} " | grep -v USER

    2011-01-22 更新

    DK 留言說加 "!" 即可, man page 沒看清楚, 唉呀呀:
    find /path ! -uid 1234

    2011年1月20日 星期四

    在 Ubuntu 上編原始碼安裝的方法

    官網有超詳細的說明, 看完這兩篇後就有基本戰鬥力了:
    編原始碼的三個步驟是
    1. ./configure
    2. make
    3. make install
    最難的是第一步, 這要看 tarball 裡附的 INSTALL 或 README 或 ./configure --help 了解相關設定, 要有耐心。編的過程遇到問題就用 apt-file 或 auto-apt 找出相關套件, 裝好後再繼續, 缺 header 的話, 通常是要裝 X-dev  這類 package。
    第二步沒什麼好說的, 第三步最好改用 checkinstall, 可以包成 package 再裝, 日後可用 dpkg -r PKG 移除, 還有用 dpkg -l 查版本, 用 dpkg -L PKG 了解裝了那些檔案。以前用 make install 都會怕怕的, 不知道到底裝了什麼, 裝爛後要怎麼移除。有 checkinstall 實在太方便啦!! 若有多台同 OS 的機器, 編一次之後, 剩下的機器可以用 package 安裝, 省下不少工夫。
    但是 Ubuntu 上用 checkinstall 安裝時會出現 "No such file or directory" 的錯誤, 從 console 的訊息來看, checkinstall 嘗試一些 mkdir 的操作, 卻沒有真的建出那些目錄。查了一下, 官網的 bug report 有提到解法, 要加 --fstrans=no, 也就是用 sudo checkinstall -D --fstrans=no 來安裝。另外可用 –install=no 只包 package 不安裝, 還有用 --requires 自己建 package dependency。這裡有些介紹, 超簡單的。

    用 apt-file、auto-apt、wajig 從檔案找 package

    Find which package contains a file in Debian Linux 提到各種從檔案找 package 的作法。試了幾個例子, 覺得 apt-file 找的東西最多, 找出來的雜訊自然也多。比較之後, 我還是會用 apt-file 吧。wajig 滿有趣的, 將相關工具包成一個 python script, 改天來讀讀原始碼, 應該可以學到一些東西。

    dpkg 常用參數

    記一下常用的幾個參數

    • dpkg -l : 列出所有裝過的 package 和版號, 裝過再移除也會留有記錄
    • dpkg -L PKG: 列出 PKG 裝入的檔案
    • dpkg -i PKG 或 dpkg -r PKG: 安裝或移除 PKG, 很少會用到這個, 大多用 aptitude

    Ubuntu 上編 apache2 + mod_wsgi 原始碼以及 module、設定的簡介

    基本安裝流程
    • sudo aptitude install auto-apt checkinstall
    • sudo chown YOUR_ACCOUNT /usr/local/src
    • cd /usr/local/src
    • wget http://ftp.stut.edu.tw/var/ftp/pub/OpenSource/apache//httpd/httpd-2.2.17.tar.gz
    • tar zxvf httpd-2.2.17.tar.gz
    • cd httpd-2.2.17
    • auto-apt run ./configure --enable-mods-shared=most --enable-so --enable-rewrite --enable-expires --enable-cache --enable-usertrack
    • make
    • sudo checkinstall -D --fstrans=no
    • ln -s /usr/local/apache2/bin/apachectl /etc/init.d/apachectl
    • 將 /etc/init.d/apachectl 加到 /etc/rc.local 或用 udpate-rc.d 裝到 /etc/rc*.d/ 裡
    安裝和設定 mod_wsgi
    啟動、停止等操作:
    • 透過 apachectl 操作, 它是一個 shell script, 最新版的很精簡, 幫忙傳參數給 httpd
    • 附帶一提, 裝 Ubuntu package 的話, 操作方式變成 /etc/init.d/apache2 -> apache2ctl -> apache2, 多一層 /etc/init.d/apache2 另外包一些操作。
    • httpd 或 apache2 只是編出來的名稱, 可用 --with-program-name 設定
    安裝、設定的相關資訊
    • ./configure --help:看有那些參數可選, 都不選也還 OK 啦
    • apache2 有兩種 MPM (Multi-Processing Modules) 模式: prefork (multiple process) 或 worker (multiple thread), 預設是 prefork。Python thread 有頂頂大名的 GIL, 所以我先用 prefork 減少日後擔心的項目
    • auto-apt 幫忙檢查 package dependency, 不用它也 OK
    • checkinstall: 用來打包 package 檔, 之後方便管理。上述的流程會產生 httpd 的 package 檔, 並裝進去
    • dpkg -L httpd 觀看裝了那些東西
    • dpkg -r httpd 移除 apache2
    • 用 httpd -l 觀看執行檔內編進了那些 modules, 若沒用 --enable-mods-shared=most 的話, 會編進一大堆
    • httpd -V 觀看預設參數, 像是 config 檔位置 (-D SERVER_CONFIG_FILE), 知道這個才有辦法開始設定
    apache2 與 modules 的關係
    • 官網的 DSO (dynamic shared object) 和 apxs 說明得很清楚, apache2 採用 dynamic loading 的方式載入 module, 可以讓 apache2 daemon 動態載入 modules, 不需重編 apache2 執行檔 。若 module 版號差不多, 應該是不用太在意 apache2 版本和 module 的版本對應
    • 一但從原始碼編 apache2 後, 之後就要用原始碼編 module。不然裝 module package 時, 由於 package dependency 的關係, 會要求裝 apache2 的一些核心 package。雖說也可以照裝 apache2 的 package (記得要手動移掉 /etc/init.d/apache2 避免開機時啟動到舊的 apache2), 然後複製 /usr/lib/apache2/modules/ 下的東西到 /usr/local/apache2/modules/, 或直接改設定檔載入 /usr/lib/apache2/modules/。只是會編 apache2 後, 編 module 就沒那麼難了
    • 可從官網 module 文件的 status 欄位了解如何取得該 module。base 表示編完 apache2 後就會放在 /usr/local/apache2/modules/ 下, 若 ./configure 時有加上 --enable (如 --enable-usertrack), 則會產生對應的設定在產生的設定檔裡。 ./configure 時沒加 enable, 之後想用 base 的 module, 就要自己找一下載入和設定的方式
    • 若 status 為 extension (如 mod_ssl), apache2 官方的 module 似乎要透過重新 configure 重編的方式取得, 不確定有沒有更簡單的作法。
    • third-party module (如 mod_wsgi) 會有文件說明如何用 apxs 編出 module。
    apache2 的設定檔和 Debian (Ubuntu) 的 apache2 配置
    • Managing Apache2 modules the Debian way 清楚的說明 Debian 配置 apache2 設定檔的方式。基本運作方式是透過預設的 /etc/apache2/apache2.conf 讀入分配好的目錄: /etc/apache2/conf.d、/etc/apache2/mods-enabled/、/etc/apache2/sites-enabled/, 來將 module 的設定和 virtualhost 的設定分散出去, 並保留 conf.d 提供其它客置需求。我覺得配得挺漂亮的。
    • 為了讓整個設定保有彈性, /etc/apache2 還有下不少工夫, 像是設好 apache2ctl  和 config 檔, 將共同變數 USER、GROUP、PID_FILE 放在 /etc/apache2/envvars。雖然架構很漂亮, 當自己編 apache2 時, 硬要將原本的設定套到 Debian 配置, 只會事倍功半。另一方面我編出來的 apachectl 和原本 package 內的 apache2ctl 參數有些差異, 搞了一陣子就決定自己切 /usr/local/apache2/conf/httpd.conf 和 /usr/local/apache2/conf/conf.d/ 即可
    • 這篇提到可用 ./configure --with-program-name=apache2 --enable-layout=Debian 編出類似的配置, 我試的結果的確滿像的, 不過還是湊不起來, 試一下失敗後就沒繼續弄了
    php5
    • 我有順便編看看 php5, 去除一堆延伸功能, 至少照網路上的教學編原始碼沒問題, 挺多遇到 mysql 錯誤: "Cannot find MySQL header files under", 照這篇說的, 裝好 libmysqlclient-dev 再編就 OK 了
    • 用 checkinstall 安裝 package 時遇到 "dpkg error: trying to overwrite ...", 它嘗試覆蓋 /usr/local/apache2/conf/httpd.conf。找了一下只看到「用力覆蓋過去」的解法。也就是出現錯誤後再手動用 dpkg -i --force-overwrite php 裝。不確定之後 dpkg -r php 時, 是不是也會移除 httpd.conf。不過我的設定檔都有存在 VCS 裡, 出事的話也很容易復原
    其它參考資料

                      2011年1月10日 星期一

                      rsync 壓縮參數的大概測試心得

                      昨天意外發現 rsync 有 -z 的參數, 可以壓縮再傳。好奇它的效果就試了一下, 結果得到出人意外的結論: 有時候直接全部重新複製還比較快........, 雖然是很明顯的事實, 用慣 rsync 後到沒想到這點。

                      簡易的測試環境如下:
                      • 原始檔案: 一堆目錄合起來 7G 左右放在 A 機器
                      • 目標檔案: 另一台機器放著很久以前的版本, 6G 左右, 至少有 1G 的差異
                      • gigabyte 網路
                      簡易的測試結果如下:

                      用 rsync -av --delete 跨機器傳8m
                      加上 -z超過 13m, 我等不下去, 就停了
                      用 rsync 在目的機器產生新目錄, 不 diff2m54s
                      用 rsync -z 在目的機器產生新目錄, 不 diff超過 6m, 我等不下去, 就停了
                      scp2m47s

                      懶得測傳說中最快的 nc + compression, 有興趣的人可以看看《High performance MySQL 2e》 Appendix A, 有多種不同組合的測試數據。

                      在傳輸過程中用 iostat -dx 觀察兩台機器的 utility rate 和用 htop 看 rsync 的 CPU 使用狀態, 就可以明白加 compression 應該不會有好下場。不加前就是 IO rate 低, CPU 100%; 加 -z 後變成 IO rate 更低, CPU 當然還是 100%。

                      2011年1月8日 星期六

                      Zombie process 的說明

                      發完上篇後, 看到 freedom 和 command 回說 Zombie 反而是最常見 process 殺不掉的情況。於是又複習了一下 Zombie 相關說明, 摘要一下:
                      • process 結束後要等 parent process 讀取它的 exit status 才能結束, 在那之前, 會進入 Zombie 狀態 (ps 顯示 Z)。
                      • Zombie 不會占用 memory, 只是在 process table 裡留筆記錄, 所以挺多會用光 process id 而無法新增 process。
                      • Zombie 不是 process, 自然也不能接受 signal, 所以無法 kill zombie (geek 的雙關笑話嗎...)。
                      避免 Zombie 的解法:
                      • double fork, 也就是不直接將工作交給 child process, 而是先 fork child process, 讓它再 fork 出 "child child process"。接著 parent process 會 wait child process, 而 child process 不等待 "child child process" 直接結束, 所以 parent process 也不會等多久就能繼續做事。依 Unix 的設計, 因為 "child child process" 的 parent process 掛了, 它會自動被 init 接管, 而 init 會定期讀取 child process 的 exit status。
                      • 另外就是 Wikipedia 上寫的, 我沒試過這招: 
                        if the parent explicitly ignores SIGCHLD by setting its handler to SIG_IGN (rather than simply ignoring the signal by default) or has the SA_NOCLDWAIT flag set, all child exit status information will be discarded and no zombie processes will be left.
                      Btw, 用 Google 搜過去寫的 plurk, 還是比 plurk 內建搜尋方便啊, 剛搜 "fcamel double fork" 就找出一年多前寫的心得

                      NFS 造成殺不掉的 process

                      原因詳見這篇, 當一個 process 進入 uninterruptible sleep 時 (ps 的狀態顯示 D), 表示這個 process 不能被中斷, 所以即使用 kill -9 也沒用。一個 process 進入 uninterruptible sleep 時, 表示它正在等 I/O。所以, 唯一的解法是讓它等到 I/O 或是放棄等 I/O。若使用的裝置是外接式硬碟、光碟機之類的, 可以試著重接讓 process 離開狀態 D。若等待的裝置是 NFS 的話, 需要另外的解法。

                      NFS 官網的 D12 描述的解法如下:
                      Sometimes it can at a couple of interations of the "kill processes" then "umount -f" cycle until the filesystem is unmounted, but it usually works.
                      If all else fails, you can still unmount the partition on which the processes are hanging using the "umount -l" command. This causes the stuck mount to become detached from the file system name space hierarchy on your client, and will thus no longer be visible to other processes
                      結論是: 重覆 kill、umount -f、mount 多次應該會有效。再不行的話, 改試看看 umount -l。我自己試的時候是 「umount -l, mount, kill (失敗), umount -f」 結果就好了。

                      2011年1月4日 星期二

                      強迫 mysql 照指定的順序 join

                      語法:
                      SELECT STRAIGHT_JOIN ... FROM A, B, C ...
                      
                      這樣就會照 table 出現的順序 (即 A, B, C) join。若你很清楚怎麼 join 最省事, 用 STRAIGHT_JOIN 可以避免 mysql 猜錯順序造成超大 join。

                      我遇到的情況是需要用 primary key 分批取出 A 部份 rows 和 B, C, D 做 join, 再做些計算。這樣資料不會過大而無法處理。但交給 mysql 自行判斷時, 因為資料分佈的變化, mysql 有時選擇先 join B, C, D 最後才 join A, 但 join B, C, D 會造成大量 IO, 速度極慢。反之, 先取 A 部份的 rows 再往後 join, 取得資料不多。

                      2011年1月3日 星期一

                      Ubuntu security update

                      找了一會兒, 只看官網的 AutomaticSecurityUpdates, 從 Ubuntu 討論區也只看到有人說執行 package update, 和 Stephon 確認也是這麼說, 沒想到 update package 就是 security update 啊。

                      官網連結附的指令可以比較「安全」地更新套件。更新完後記得檢查有無產生 /var/run/reboot-required, 有的話表示需要重開機。

                      C++ 能否用 memcpy 複製 class / struct 的資料?

                      答案是: POD (plain old data) type 可以。POD type 可和 C 互通, CPP Reference POD Type 的介紹: Specifies that the type is POD (Plain Old Data) type. Thi...