ちゃんと$VIRTUAL_ENV/bin/以下のスクリプトを起動してね

まだまだneutronが続く...
余計なことをするとロクなことがない...戒めのために晒してメモしておく。

■quantum-dhcp-agentでdhcpサーバ起動時にエラーが発生

virtualenvで隔離している環境でquantum-dhcp-agentを起動させると以下のようなエラーが出力されてdnsmasqが起動しない。

2013-09-09 12:22:41    DEBUG [quantum.agent.linux.utils]
Command: ['sudo', '/usr/local/grizzly/bin/quantum-rootwrap', '/usr/local/grizzly/neutron/etc/rootwrap.conf', 'QUANTUM_RELAY_SOCKET_PATH=/usr/local/grizzly/neutron/dhcp/dhcp/lease_relay', 'QUANTUM_NETWORK_ID=4fb43ca1-a972-49cd-b7e8-632d246ca810', 'ip', 'netns', 'exec', 'qdhcp-4fb43ca1-a972-49cd-b7e8-632d246ca810', 'dnsmasq', '--no-hosts', '--no-resolv', '--strict-order', '--bind-interfaces', '--interface=tap31d3e188-28', '--except-interface=lo', '--pid-file=/usr/local/grizzly/neutron/dhcp/dhcp/4fb43ca1-a972-49cd-b7e8-632d246ca810/pid', '--dhcp-hostsfile=/usr/local/grizzly/neutron/dhcp/dhcp/4fb43ca1-a972-49cd-b7e8-632d246ca810/host', '--dhcp-optsfile=/usr/local/grizzly/neutron/dhcp/dhcp/4fb43ca1-a972-49cd-b7e8-632d246ca810/opts', '--dhcp-script=/usr/local/grizzly/neutron/bin/quantum-dhcp-agent-dnsmasq-lease-update', '--leasefile-ro', '--dhcp-range=set:tag0,10.0.0.0,static,120s', '--dhcp-range=set:tag1,10.0.1.0,static,120s', '--dhcp-range=set:tag2,10.0.2.0,static,120s', '--conf-file=', '--domain=openstacklocal']
Exit code: 11
Stdout: ''
Stderr: 'Traceback (most recent call last):\n  File "/usr/local/grizzly/neutron/bin/quantum-dhcp-agent-dnsmasq-lease-update", line 19, in <module>\n    from quantum.agent.linux import dhcp\nImportError: No module named quantum.agent.linux\n\ndnsmasq: lease-init script returned exit code 1\n'
2013-09-09 12:22:41    ERROR [quantum.agent.dhcp_agent] Unable to enable dhcp.
Traceback (most recent call last):
  File "/usr/local/grizzly/neutron/quantum/agent/dhcp_agent.py", line 131, in call_driver
    getattr(driver, action)()
  File "/usr/local/grizzly/neutron/quantum/agent/linux/dhcp.py", line 129, in enable
    self.spawn_process()
  File "/usr/local/grizzly/neutron/quantum/agent/linux/dhcp.py", line 322, in spawn_process
    ip_wrapper.netns.execute(cmd, addl_env=env)
  File "/usr/local/grizzly/neutron/quantum/agent/linux/ip_lib.py", line 414, in execute
    check_exit_code=check_exit_code)
  File "/usr/local/grizzly/neutron/quantum/agent/linux/utils.py", line 61, in execute
    raise RuntimeError(m)
RuntimeError:

■原因をしらべてみる

いろいろ調べた結果、起動スクリプトに問題がった。

原因

githubからチェックアウトしたソースをsetuptoolsでインストールしたあと、本来だと$VIRTUAL_ENV/bin/以下にインストールされたスクリプトを起動すべきだったが、チェックアウトしたソースの./bin/以下にあるスクリプトを実行していた。

  • ソースコードは/usr/local/grizzly/neutron/以下にgit cloneしている
  • $VIRTUAL_ENVは/usr/local/grizzly/以下に指定している
  • 実行していたスクリプトはソースコードのほう(/usr/local/grizzly/neutron/bin/quantum-dhcp-agent)

なんとなく起動してしまうので、この原因を見落としてた。

2つのquantum-dhcp-agentの違い

#!/usr/bin/env pythonになっている時点でvirtualenv環境にインストールしたモジュールのロードに失敗して起動しそうもないんだけれど、起動スクリプトのなかで virtualenv環境をactivateするようにしていたので、ソースコードのほう(/usr/local/grizzly/neutron/bin/quantum-dhcp-agent)を誤って指定しても、なんとなく起動してしまっていた。

$ diff -u /usr/local/grizzly/neutron/bin/quantum-dhcp-agent /usr/local/grizzly/bin/quantum-dhcp-agent
--- /usr/local/grizzly/neutron/bin/quantum-dhcp-agent	2013-09-08 22:59:13.286938001 +0900
+++ /usr/local/grizzly/bin/quantum-dhcp-agent	2013-09-08 23:00:50.662938001 +0900
@@ -1,20 +1,10 @@
-#!/usr/bin/env python             <-- ★ソースコードそのままのquantum-dhcp-agent
-# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#!/usr/local/grizzly/bin/python   <-- ★setuptoolsで正しくインストール後のquantum-dhcp-agent
+# EASY-INSTALL-ENTRY-SCRIPT: 'quantum==2013.1.2','console_scripts','quantum-dhcp-agent'
+__requires__ = 'quantum==2013.1.2'
+import sys
+from pkg_resources import load_entry_point

-# Copyright (c) 2012 OpenStack Foundation.
-# All Rights Reserved.
-#
-#    Licensed under the Apache License, Version 2.0 (the "License"); you may
-#    not use this file except in compliance with the License. You may obtain
-#    a copy of the License at
-#
-#         http://www.apache.org/licenses/LICENSE-2.0
-#
-#    Unless required by applicable law or agreed to in writing, software
-#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-#    License for the specific language governing permissions and limitations
-#    under the License.
-
-from quantum.agent.dhcp_agent import main
-main()
+if __name__ == '__main__':
+    sys.exit(
+        load_entry_point('quantum==2013.1.2', 'console_scripts', 'quantum-dhcp-agent')()
+    )

まぁこの際は動けばいいんだけれど、今回はそうもいかなかった。

--dhcp-scriptの落とし穴

冒頭に書いたエラーメッセージの原因はココだった。

<...略...>

267     def spawn_process(self):
268         """Spawns a Dnsmasq process for the network."""
269         env = {
270             self.QUANTUM_NETWORK_ID_KEY: self.network.id,
271             self.QUANTUM_RELAY_SOCKET_PATH_KEY:
272             self.conf.dhcp_lease_relay_socket
273         }
274
275         cmd = [
276             'dnsmasq',
277             '--no-hosts',
278             '--no-resolv',
279             '--strict-order',
280             '--bind-interfaces',
281             '--interface=%s' % self.interface_name,
282             '--except-interface=lo',
283             '--pid-file=%s' % self.get_conf_file_name(
284                 'pid', ensure_conf_dir=True),
285             #TODO (mark): calculate value from cidr (defaults to 150)
286             #'--dhcp-lease-max=%s' % ?,
287             '--dhcp-hostsfile=%s' % self._output_hosts_file(),
288             '--dhcp-optsfile=%s' % self._output_opts_file(),
289             '--dhcp-script=%s' % self._lease_relay_script_path(),     <--★ココ
290             '--leasefile-ro',
291         ]

<…略...>

437     def _lease_relay_script_path(self):
438         return os.path.join(os.path.dirname(sys.argv[0]),            <--★ココ
439                             'quantum-dhcp-agent-dnsmasq-lease-update')

<…略...>

問題はos.path.dirname(sys.argv[0])でディレクトリパスを決めているところ。
設定ファイルで指定もできないので、quantum-dhcp-agentと同じパスにあるquantum-dhcp-agent-dnsmasq-lease-updateを起動してしまう。

  • sys.argv[0] -> /usr/local/grizzly/neutron/bin/quantum-dhcp-agent
  • os.path.dirname(sys.argv[0]) -> /usr/local/grizzly/neutron/bin
  • os.path.join(os.path.dirname(sys.argv[0]), 'quantum-dhcp-agent-dnsmasq-lease-update') -> /usr/local/grizzly/neutron/bin/quantum-dhcp-agent-dnsmasq-lease-updat

ということで、/usr/local/grizzly/neutron/bin/quantum-dhcp-agent-dnsmasq-lease-update が--dhcp-scriptとして使われて、こいつは virtualenv環境が反映されず(1行目が #!/usr/bin/env python なので)、いろんなモジュールが見つからずにエラーがになるってことだった。

教訓:余計なことをするときは、ちゃんと徹底すること。