OpenStack のネットワーク部分を担当する Neutron は、以前からプラグインを差し替えることで仮想ネットワークを構築する処理の実装を変更することができた。
そうした中でも、最近は ML2 (Modular Layer 2) という共通のプラグインが使われるようになってきている。
これには恐らく幾つかの理由があって、まず既存のプラグイン (ML2 以前のそれは"モノリシック・プラグイン"と呼んで区別される) 開発では多くのコードを書く必要があった。
その結果、出来上がったプラグイン群には似たようなコードがそれぞれ冗長に存在していた点が挙げられる。
そして、各プラグインは各ベンダーがそれぞれ個別に開発しているため、今のコードが本当に動くのかも不透明な状況だったようだ。
それに対し、ML2 ではプラグイン部分を共通化し、開発に必要なのは Neutron の各処理が走る際に呼ばれるコールバック関数 (メソッド) のみで良くなっている。
コールバック関数は処理毎にまとめられてドライバという形で提供されるので、開発者はそれを実装するだけで良い
以上が ML2 の背景で、今回は試しにドライバを実際に書いてみた。
ただし、実際には何もしないスケルトンなもの。
Neutron の API が呼ばれた際には、仮想ネットワークの構築の代わりにロギングだけを行う。
コードは以下の GitHub リポジトリに置いた。
https://github.com/momijiame/openstack-neutron-ml2-dummy-driver
ちなみに、ML2 プラグインが外部のライブラリから Driver をロードするには Stevedore というライブラリが使われている。
Stevedore 自体は Python のプラグイン機構を抽象化するためのものなので、実際に使われる実装は Setuptools の pkg_resources になる。
pkg_resources をそのまま使って実現するプラグイン機構については、このブログでも以前に一度書いた。
http://momijiame.tumblr.com/post/82484166907/python-setuptools-pkg-resources
今 (2014.2 “Juno”) のところML2 のドライバは Type, Mechanism, Extension の三種類が用意されているようだ。
まず、TypeDriver には仮想ネットワークセグメントを確保する方法について記述する。
MechanismDriver には特に決まりはないが、仮想ネットワークやサブネット、ポートなどの情報をデータベースに書き込むタイミングで行う何らかの処理について記述する。最後の ExtensionDriver については実装例が見当たらなかったためイマイチよく分からず今回は書いていない。
次は、今回書いたダミーのドライバを動かす手順について。
今回は、プラットフォームに CentOS7 を、ディストリビューションに RDO を使った。
(ディストリビューションについてはソースコードでも良かったけど、この組み合わせが面白そうだったので)
$ cat /etc/redhat-release CentOS Linux release 7.0.1406 (Core) $ uname -r 3.10.0-123.9.2.el7.x86_64
$ sudo yum -y install epel-release $ sudo yum -y install rabbitmq-server mariadb-server mariadb-devel
$ sudo systemctl start rabbitmq-server $ sudo systemctl enable rabbitmq-server $ sudo systemctl start mariadb $ sudo systemctl enable mariadb
$ sudo yum -y install http://rdo.fedorapeople.org/openstack/openstack-juno/rdo-release-juno.rpm
$ sudo yum -y install openstack-neutron-ml2
$ sudo yum -y install python-pip
$ git clone https://github.com/momijiame/openstack-neutron-ml2-dummy-driver.git $ cd openstack-neutron-ml2-dummy-driver/
$ sudo yum -y install rpm-build $ python setup.py bdist_rpm
$ ls dist/ | grep rpm$ dummyml2-0.0.1.dev4.g27d3b8e-1.noarch.rpm dummyml2-0.0.1.dev4.g27d3b8e-1.src.rpm
$ sudo yum -y install dist/dummyml2-*.noarch.rpm
$ sudo cp -a /etc/neutron/neutron.conf{,.back} $ sudo cp -a /etc/neutron/plugins/ml2/ml2_conf.ini{,.back}
$ cat << EOF | sudo tee /etc/neutron/neutron.conf > /dev/null [DEFAULT] core_plugin = ml2 service_plugins = router [matchmaker_redis] [matchmaker_ring] [quotas] [agent] [keystone_authtoken] [database] connection = mysql://root@localhost/neutron_ml2?charset=utf8 [service_providers] EOF
$ cat << EOF | sudo tee /etc/neutron/plugins/ml2/ml2_conf.ini > /dev/null [ml2] tenant_network_types = dummytype type_drivers = dummytype mechanism_drivers = dummymech [ml2_type_flat] [ml2_type_vlan] [ml2_type_gre] [ml2_type_vxlan] [securitygroup] EOF
$ mysql -u root -e "create database neutron_ml2"
$ sudo ln -s /etc/neutron/plugins/ml2/ml2_conf.ini /etc/neutron/plugin.ini
$ sudo neutron-db-manage --config-file /usr/share/neutron/neutron-dist.conf --config-file /etc/neutron/neutron.conf --config-file /etc/neutron/plugin.ini upgrade head
$ sudo neutron-server --config-file /usr/share/neutron/neutron-dist.conf --config-file /etc/neutron/neutron.conf --config-file /etc/neutron/plugin.ini ...(snip)... 2014-10-29 20:29:39.459 11984 INFO neutron.service [-] Neutron service started, listening on 0.0.0.0:9696 2014-10-29 20:29:39.460 11984 INFO oslo.messaging._drivers.impl_rabbit [-] Connecting to AMQP server on localhost:5672 2014-10-29 20:29:39.469 11984 INFO neutron.wsgi [-] (11984) wsgi starting up on http://0.0.0.0:9696/ 2014-10-29 20:29:39.472 11984 INFO oslo.messaging._drivers.impl_rabbit [-] Connected to AMQP server on localhost:5672
$ systemctl list-unit-files --type=service | grep neutron neutron-dhcp-agent.service disabled neutron-l3-agent.service disabled neutron-lbaas-agent.service disabled neutron-metadata-agent.service disabled neutron-netns-cleanup.service disabled neutron-server.service disabled
$ sudo systemctl start neutron-server $ sudo systemctl enable neutron-server
$ systemctl status neutron-server
$ tail -f /var/log/neutron/server.log
$ neutron --version 2.3.9
$ sudo pip install git+https://github.com/openstack/python-neutronclient.git
$ neutron --version 2.3.9.39
$ cat << EOF > ~/neutronrc export OS_URL=http://localhost:9696 export OS_TOKEN=admin export OS_AUTH_STRATEGY=noauth EOF $ source ~/neutronrc
$ neutron net-create network1 --tenant-id 1 Created a new network: +---------------------------+--------------------------------------+ | Field | Value | +---------------------------+--------------------------------------+ | admin_state_up | True | | id | e82cf5ca-26ae-4eff-8fd6-d007d96d593c | | name | network1 | | provider:network_type | dummytype | | provider:physical_network | | | provider:segmentation_id | | | router:external | False | | shared | False | | status | ACTIVE | | subnets | | | tenant_id | 1 | +---------------------------+--------------------------------------+
$ neutron port-create network1 --tenant-id 1 Created a new port: +-----------------------+--------------------------------------+ | Field | Value | +-----------------------+--------------------------------------+ | admin_state_up | True | | allowed_address_pairs | | | binding:host_id | | | binding:profile | {} | | binding:vif_details | {} | | binding:vif_type | unbound | | binding:vnic_type | normal | | device_id | | | device_owner | | | fixed_ips | | | id | 6889a112-177a-4e46-ac97-85ddc37235c9 | | mac_address | fa:16:3e:87:bd:e4 | | name | | | network_id | e82cf5ca-26ae-4eff-8fd6-d007d96d593c | | security_groups | 3770ac7a-bceb-45ac-8a22-f50da8fb5951 | | status | DOWN | | tenant_id | 1 | +-----------------------+--------------------------------------+
$ grep CALLED /var/log/neutron/server.log 2014-10-29 20:30:44.381 12010 INFO dummyml2.lib.interceptor [-] CALLED: __init__(*args=(<dummyml2.drivers.type_dummy.DummyTypeDriver object at 0x3a18bd0>,), **kwargs={}) 2014-10-29 20:30:44.382 12010 INFO dummyml2.lib.interceptor [-] CALLED: get_type(*args=(<dummyml2.drivers.type_dummy.DummyTypeDriver object at 0x3a18bd0>,), **kwargs={}) 2014-10-29 20:30:44.384 12010 INFO dummyml2.lib.interceptor [-] CALLED: __init__(*args=(<dummyml2.drivers.mech_dummy.DummyMechanismDriver object at 0x3a260d0>,), **kwargs={}) 2014-10-29 20:30:44.386 12010 INFO dummyml2.lib.interceptor [-] CALLED: initialize(*args=(<dummyml2.drivers.type_dummy.DummyTypeDriver object at 0x3a18bd0>,), **kwargs={}) 2014-10-29 20:30:44.386 12010 INFO dummyml2.lib.interceptor [-] CALLED: initialize(*args=(<dummyml2.drivers.mech_dummy.DummyMechanismDriver object at 0x3a260d0>,), **kwargs={}) 2014-10-29 20:34:05.890 12010 INFO dummyml2.lib.interceptor [req-758b33c6-3f6f-4f6e-8001-083d8551220d ] CALLED: allocate_tenant_segment(*args=(<dummyml2.drivers.type_dummy.DummyTypeDriver object at 0x3a18bd0>, <sqlalchemy.orm.session.Session object at 0x4658050>), **kwargs={}) 2014-10-29 20:34:05.897 12010 INFO dummyml2.lib.interceptor [req-758b33c6-3f6f-4f6e-8001-083d8551220d ] CALLED: create_network_precommit(*args=(<dummyml2.drivers.mech_dummy.DummyMechanismDriver object at 0x3a260d0>, <neutron.plugins.ml2.driver_context.NetworkContext object at 0x49cef90>), **kwargs={}) 2014-10-29 20:34:05.899 12010 INFO dummyml2.lib.interceptor [req-758b33c6-3f6f-4f6e-8001-083d8551220d ] CALLED: create_network_postcommit(*args=(<dummyml2.drivers.mech_dummy.DummyMechanismDriver object at 0x3a260d0>, <neutron.plugins.ml2.driver_context.NetworkContext object at 0x49cef90>), **kwargs={}) 2014-10-29 20:34:21.056 12010 INFO dummyml2.lib.interceptor [req-d0a28f88-99eb-4126-914c-8bf5fedcb79e ] CALLED: create_port_precommit(*args=(<dummyml2.drivers.mech_dummy.DummyMechanismDriver object at 0x3a260d0>, <neutron.plugins.ml2.driver_context.PortContext object at 0x49e1210>), **kwargs={}) 2014-10-29 20:34:21.058 12010 INFO dummyml2.lib.interceptor [req-d0a28f88-99eb-4126-914c-8bf5fedcb79e ] CALLED: create_port_postcommit(*args=(<dummyml2.drivers.mech_dummy.DummyMechanismDriver object at 0x3a260d0>, <neutron.plugins.ml2.driver_context.PortContext object at 0x49e1210>), **kwargs={})
Neutron は OpenStack の中でネットワーク周りを司るコンポーネント。
通常は OpenStack のその他のコンポーネントと組み合わせて使うそれを、動作の理解を深めるために今回は単独で動かしてみる。
使うプラグインは今風の ML2 にする。
使う環境は以下の通り。
$ cat /etc/redhat-release CentOS release 6.5 (Final) $ uname -r 2.6.32-431.11.2.el6.x86_64
$ sudo yum -y install python-devel $ wget https://bitbucket.org/pypa/setuptools/raw/bootstrap/ez_setup.py -O - | sudo python $ sudo easy_install pip $ sudo pip install virtualenv virtualenvwrapper $ cat << EOF >> ~/.bashrc source `which virtualenvwrapper.sh` export WORKON_HOME=$HOME/.virtualenvs export PIP_RESPECT_VIRTUALENV=true EOF $ source ~/.bashrc
$ sudo yum -y install git $ git clone https://github.com/openstack/neutron.git $ cd neutron $ git checkout -b havana 2013.2.3
$ mkvirtualenv neutron-dev $ pip install -r requirements.txt -r test-requirements.txt
$ ./run_tests.sh -N
$ python setup.py install
$ sudo ln -s -f `pwd`/etc /etc/neutron $ sudo chown `whoami` /etc/neutron/
$ sudo yum -y install mysql-server $ sudo chkconfig mysqld on $ sudo service mysqld start
$ mysql -u root -e "create database neutron_ml2" $ mysql -u root -e "set password = password('password')" $ mysql -u root -ppassword -e "create database neutron_ml2"
$ sudo yum -y install mysql-devel $ pip install mysql-python
$ sudo yum -y install http://dl.fedoraproject.org/pub/epel/6/x86_64/epel-release-6-8.noarch.rpm $ sudo yum -y install http://dl.iuscommunity.org/pub/ius/stable/CentOS/6/x86_64/ius-release-1.0-11.ius.centos6.noarch.rpm $ sudo yum -y install rabbitmq-server $ sudo service rabbitmq-server start $ sudo chkconfig rabbitmq-server on $ sudo rabbitmqctl change_password guest password
$ sudo yum -y install http://rdo.fedorapeople.org/openstack/openstack-havana/rdo-release-havana.rpm $ sudo yum -y update
$ sudo yum -y install openvswitch $ sudo service openvswitch start $ sudo chkconfig openvswitch on
$ cp /etc/neutron/neutron.conf{,.orig} $ cat << EOF > /etc/neutron/neutron.conf [DEFAULT] auth_strategy = noauth allow_overlapping_ips = True policy_file = /etc/neutron/policy.json debug = True verbose = True service_plugins = neutron.services.l3_router.l3_router_plugin.L3RouterPlugin core_plugin = neutron.plugins.ml2.plugin.Ml2Plugin rabbit_password = password rabbit_host = localhost rpc_backend = neutron.openstack.common.rpc.impl_kombu state_path = /var/tmp/neutron lock_path = \$state_path/lock notification_driver = neutron.openstack.common.notifier.rpc_notifier [quotas] [agent] root_helper = sudo [database] [service_providers] EOF $ cat << EOF > /etc/neutron/ml2.conf [ml2] type_drivers = local,flat,vlan,gre,vxlan mechanism_drivers = openvswitch,linuxbridge [ml2_type_flat] [ml2_type_vlan] [ml2_type_gre] [ml2_type_vxlan] [database] connection = mysql://root:password@localhost/neutron_ml2?charset=utf8 [ovs] local_ip = 192.168.33.10 [agent] [securitygroup] firewall_driver = neutron.agent.linux.iptables_firewall.OVSHybridIptablesFirewallDriver EOF $ cat << EOF >> /etc/neutron/l3_agent.ini interface_driver = neutron.agent.linux.interface.OVSInterfaceDriver EOF
$ sudo ovs-vsctl add-br br-ex $ sudo ovs-vsctl add-br br-int
$ workon neutron-dev $ neutron-server --config-file /etc/neutron/neutron.conf --config-file /etc/neutron/ml2.conf
$ workon neutron-dev $ neutron-l3-agent --config-file /etc/neutron/neutron.conf --config-file /etc/neutron/l3_agent.ini
$ workon neutron-dev $ neutron-dhcp-agent --config-file /etc/neutron/neutron.conf --config-file /etc/neutron/dhcp_agent.ini
$ workon neutron-dev $ neutron-openvswitch-agent --config-file /etc/neutron/neutron.conf --config-file /etc/neutron/ml2.conf
$ workon neutron-dev $ export OS_URL=http://localhost:9696 $ export OS_TOKEN=admin $ export OS_AUTH_STRATEGY=noauth
$ neutron agent-list +--------------------------------------+--------------------+-----------------------+-------+----------------+ | id | agent_type | host | alive | admin_state_up | +--------------------------------------+--------------------+-----------------------+-------+----------------+ | 17264570-cccf-41b2-86b0-455b9cff8127 | DHCP agent | localhost.localdomain | :-) | True | | 7e70133a-0703-4b4b-97ab-d8fcfa9bc175 | Open vSwitch agent | localhost.localdomain | :-) | True | | acee854c-d29b-441d-a88f-27ad92d48b9e | L3 agent | localhost.localdomain | :-) | True | +--------------------------------------+--------------------+-----------------------+-------+----------------+
$ neutron net-create public --tenant-id 1 --router:external True Created a new network: +---------------------------+--------------------------------------+ | Field | Value | +---------------------------+--------------------------------------+ | admin_state_up | True | | id | c22d62d9-3ee8-4bae-9f69-f662357d0795 | | name | public | | provider:network_type | local | | provider:physical_network | | | provider:segmentation_id | | | router:external | True | | shared | False | | status | ACTIVE | | subnets | | | tenant_id | 1 | +---------------------------+--------------------------------------+
$ neutron subnet-create public 10.0.0.0/24 --tenant-id 1 Created a new subnet: +------------------+--------------------------------------------+ | Field | Value | +------------------+--------------------------------------------+ | allocation_pools | {"start": "10.0.0.2", "end": "10.0.0.254"} | | cidr | 10.0.0.0/24 | | dns_nameservers | | | enable_dhcp | True | | gateway_ip | 10.0.0.1 | | host_routes | | | id | 3ce3d8e0-fbde-46de-abc8-cdcecb968611 | | ip_version | 4 | | name | | | network_id | c22d62d9-3ee8-4bae-9f69-f662357d0795 | | tenant_id | 1 | +------------------+--------------------------------------------+
$ neutron router-create router1 --tenant-id 1 Created a new router: +-----------------------+--------------------------------------+ | Field | Value | +-----------------------+--------------------------------------+ | admin_state_up | True | | external_gateway_info | | | id | a09cb0ef-c894-4e0f-9d47-262ffa2b8a3c | | name | router1 | | status | ACTIVE | | tenant_id | 1 | +-----------------------+--------------------------------------+
$ neutron router-gateway-set router1 public Set gateway for router router1
$ neutron net-create private --tenant-id 1 Created a new network: +---------------------------+--------------------------------------+ | Field | Value | +---------------------------+--------------------------------------+ | admin_state_up | True | | id | 8d70c69f-d6d5-4519-b779-a3e3935e167d | | name | private | | provider:network_type | local | | provider:physical_network | | | provider:segmentation_id | | | shared | False | | status | ACTIVE | | subnets | | | tenant_id | 1 | +---------------------------+--------------------------------------+
$ neutron subnet-create private 192.168.0.0/24 --name subnet1 --tenant-id 1 Created a new subnet: +------------------+--------------------------------------------------+ | Field | Value | +------------------+--------------------------------------------------+ | allocation_pools | {"start": "192.168.0.2", "end": "192.168.0.254"} | | cidr | 192.168.0.0/24 | | dns_nameservers | | | enable_dhcp | True | | gateway_ip | 192.168.0.1 | | host_routes | | | id | 20c41132-a179-4ed0-91b4-b20d032255b3 | | ip_version | 4 | | name | subnet1 | | network_id | 8d70c69f-d6d5-4519-b779-a3e3935e167d | | tenant_id | 1 | +------------------+--------------------------------------------------+
$ neutron router-interface-add router1 subnet1 Added interface 4015a6f9-de67-48b3-958e-674a5d6c16cb to router router1.
$ neutron port-create private --device-id=vm1 --binding:host_id=`hostname` --tenant-id 1 Created a new port: +-----------------------+------------------------------------------------------------------------------------+ | Field | Value | +-----------------------+------------------------------------------------------------------------------------+ | admin_state_up | True | | allowed_address_pairs | | | binding:capabilities | {"port_filter": true} | | binding:host_id | localhost.localdomain | | binding:vif_type | ovs | | device_id | vm1 | | device_owner | | | fixed_ips | {"subnet_id": "20c41132-a179-4ed0-91b4-b20d032255b3", "ip_address": "192.168.0.2"} | | id | b002cdcc-4225-411a-a4b1-6f695a2a9ee0 | | mac_address | fa:16:3e:6e:fe:31 | | name | | | network_id | 8d70c69f-d6d5-4519-b779-a3e3935e167d | | security_groups | bd362673-d5df-476e-8ed7-44c481cfbab9 | | status | DOWN | | tenant_id | 1 | +-----------------------+------------------------------------------------------------------------------------+
$ sudo ip link add tapb002cdcc-42 type veth peer name vnet0 $ sudo ifconfig tapb002cdcc-42 hw ether fa:16:3e:6e:fe:31 $ sudo ifconfig tapb002cdcc-42 up $ sudo ovs-vsctl add-port br-int tapb002cdcc-42 $ sudo ovs-vsctl set Interface tapb002cdcc-42 external-ids:iface-id=b002cdcc-4225-411a-a4b1-6f695a2a9ee0 $ sudo ovs-vsctl set Interface tapb002cdcc-42 external_ids:attached-mac=fa:16:3e:6e:fe:31 $ sudo ovs-vsctl set Interface tapb002cdcc-42 external-ids:iface-status=active $ sudo ovs-vsctl set Interface tapb002cdcc-42 external-ids:vm-uuid=vm1
$ neutron port-show b002cdcc-4225-411a-a4b1-6f695a2a9ee0 +-----------------------+------------------------------------------------------------------------------------+ | Field | Value | +-----------------------+------------------------------------------------------------------------------------+ | admin_state_up | True | | allowed_address_pairs | | | binding:capabilities | {"port_filter": true} | | binding:host_id | localhost.localdomain | | binding:vif_type | ovs | | device_id | vm1 | | device_owner | | | extra_dhcp_opts | | | fixed_ips | {"subnet_id": "20c41132-a179-4ed0-91b4-b20d032255b3", "ip_address": "192.168.0.2"} | | id | b002cdcc-4225-411a-a4b1-6f695a2a9ee0 | | mac_address | fa:16:3e:6e:fe:31 | | name | | | network_id | 8d70c69f-d6d5-4519-b779-a3e3935e167d | | security_groups | bd362673-d5df-476e-8ed7-44c481cfbab9 | | status | ACTIVE | | tenant_id | 1 | +-----------------------+------------------------------------------------------------------------------------+
$ sudo iptables -t nat -A POSTROUTING -s 10.0.0.0/24 -j MASQUERADE $ sudo ifconfig br-ex 10.0.0.1 $ sudo sysctl -w net.ipv4.ip_forward=1 $ sudo iptables -I FORWARD -j ACCEPT
$ sudo ip netns add vm1 $ sudo ip link set vnet0 netns vm1 $ sudo ip netns exec vm1 ifconfig vnet0 192.168.0.2 $ sudo ip netns exec vm1 route add default gw 192.168.0.1
$ sudo ip netns exec vm1 ping 8.8.8.8 -c 3 PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data. 64 bytes from 8.8.8.8: icmp_seq=1 ttl=59 time=55.6 ms 64 bytes from 8.8.8.8: icmp_seq=2 ttl=59 time=57.0 ms 64 bytes from 8.8.8.8: icmp_seq=3 ttl=59 time=154 ms --- 8.8.8.8 ping statistics --- 3 packets transmitted, 3 received, 0% packet loss, time 2160ms rtt min/avg/max/mdev = 55.673/88.920/154.044/46.053 msばっちり。
$ sudo rabbitmqctl stop_app $ sudo rabbitmqctl trace_on $ sudo rabbitmqctl start_app $ pip install rabbitracer $ rabbitracer -u guest -p passwordばらばらーっと RabbitMQ に流れるメッセージが JSON 形式で出力される。
# -*- mode: ruby -*- # vi: set ft=ruby : # Vagrantfile API/syntax version. Don't touch unless you know what you're doing! VAGRANTFILE_API_VERSION = "2" Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| config.vm.box = "centos65" config.vm.network "private_network", ip: "192.168.33.10" config.vm.provider "virtualbox" do |vb| vb.customize ["modifyvm", :id, "--memory", "2048", "--cpus", "2"] end end
RabbitMQ には Firehose 機能というものがあって、有効にするとルーティングされるメッセージが全てそこに転送される。
スイッチのミラーポートみたいなイメージかな。
これを使うと AMQP のデバッグが捗る。
Rabbitracer は、その Firehose 機能で転送されてくる全てのメッセージを JSON 形式でダンプする。
以前同様のスクリプトを書いたので、それをリファクタした上で GitHub で公開してみた。
https://github.com/momijiame/rabbitracer
使い方は GitHub の README に書いた。
パッケージは PyPI にも登録したので PIP でインストールできる。
$ pip install rabbitracer
近頃 RabbitMQ (AMQP) と戯れているんだけど、どのエクスチェンジに何が流れているのかは、チマチマ確認してると埒があかない。
そこで、RabbitMQ の持っている Firehose 機能を使って、サーバに流れるメッセージを全て JSON の形でダンプする Python スクリプトを書いてみた。
Firehose 機能というのは、有効にすると RabbitMQ の入力を一元的に扱うエクスチェンジが定義されて、そこにバインディングキーを指定したキューを繋ぐと全メッセージが流れこんでくる、というもの。
何故 JSON 形式なのか、その理由についてはこちらを見てほしい。
まずはじめに、RabbitMQ で Firehose 機能を有効にするには、サーバが起動した状態で以下を実行する。
環境によってはルート権限が必要かもしれないので、そこは適宜 sudo などで読み替える。
$ rabbitmqctl stop_app $ rabbitmqctl trace_on $ rabbitmqctl start_app
$ pip install kombu furl
#!/usr/bin/env python # -*- coding: utf-8 -*- import argparse import json import uuid import types from furl import furl from kombu import Connection, Exchange, Queue def process_message(body, message): def _is_hidden(attribute_name): return attribute_name.startswith('_') def _is_acceptable(attribute_refs): attribute_type = type(attribute_refs) acceptable_types = [ types.NoneType, types.ListType, types.DictType, types.StringTypes, types.UnicodeType, types.BooleanType, types.IntType, types.LongType, types.FloatType, ] for acceptable_type in acceptable_types: if attribute_type == acceptable_type: return True return False def _encode(attribute_name, attribute_refs): if attribute_name != 'payload': return attribute_refs properties = message.headers.get('properties') content_type = properties.get('content_type') if not content_type: return attribute_refs if content_type.startswith('application/json'): return json.loads(attribute_refs) return attribute_refs dump_dict = {} for attr_name in dir(message): if _is_hidden(attr_name): continue attribute = getattr(message, attr_name) if not _is_acceptable(attribute): continue dump_dict[attr_name] = _encode(attr_name, attribute) message.ack() print(json.dumps(dump_dict)) def parse_arguments(): description = 'The RabbitMQ firehose dumper script' option_n_help = 'hostname' option_u_help = 'username' option_w_help = 'password' option_v_help = 'virtualhost' arg_parser = argparse.ArgumentParser(description=description) arg_parser.add_argument('-n', '--hostname', help=option_n_help, required=False, default='localhost') arg_parser.add_argument('-u', '--username', help=option_u_help, required=False, default='guest') arg_parser.add_argument('-w', '--password', help=option_w_help, required=False, default='guest') arg_parser.add_argument('-v', '--virtualhost', help=option_v_help, required=False, default='/') global ARGS ARGS = arg_parser.parse_args() def main_loop(): def _build_url(): f = furl() f.scheme = 'amqp' f.username = ARGS.username f.password = ARGS.password f.hostname = ARGS.hostname f.path = ARGS.virtualhost return str(f) trace_exchange = Exchange('amq.rabbitmq.trace', 'topic') trace_queue = Queue('firehose.' + str(uuid.uuid4()), exchange=trace_exchange, routing_key='#', auto_delete=True) with Connection(_build_url()) as connection: with connection.Consumer(trace_queue, callbacks=[process_message]): while True: connection.drain_events() def main(): parse_arguments() main_loop() if __name__ == '__main__': main()
$ echo "Hello, World\!" | amqpcat amqp://guest:guest@localhost -t direct -n sample --publisher
$ python rabbitracer.py {"delivery_info": {"consumer_tag": "1", "redelivered": false, "routing_key": "publish.sample", "delivery_tag": 1, "exchange": "amq.rabbitmq.trace"}, "acknowledged": false, "accept": null, "properties": {"application_headers": {"node": "rabbit@localhost", "exchange_name": "sample", "routing_keys": [""], "properties": {"priority": 0, "delivery_mode": 1, "content_type": "application/octet-stream"}}}, "headers": {"node": "rabbit@localhost", "exchange_name": "sample", "routing_keys": [""], "properties": {"priority": 0, "delivery_mode": 1, "content_type": "application/octet-stream"}}, "content_encoding": null, "content_type": null, "delivery_tag": 1}やったー、諸々の情報含めてメッセージがダンプされたよー。
RabbitMQ はあらかじめインストールして起動しておく。
AMQPcat は Ruby gems でインストールする。
$ sudo gem install amqpcat --no-rdoc --no-ri $ gem list | grep amqpcat amqpcat (0.0.1)
$ echo "Hello, World\!" | amqpcat amqp://guest:guest@localhost -t direct -n sample --publisher
$ rabbitmqctl list_exchanges | grep sample sample direct $ rabbitmqctl list_queues | grep sample sample 1
$ amqpcat amqp://guest:guest@localhost -t direct -n sample --consumer Hello, World!めでたしめでたし。
RabbitMQ は AMQP (Advanced Message Queuing Protocol) のサーバ (ブローカ) 実装。
インストールは Homebrew から一発。
$ brew install rabbitmq
$ rabbitmq-server
$ rabbitmqctl --help
$ rabbitmqctl stop_app # サーバの一時停止 $ rabbitmqctl reset # サーバのリセット $ rabbitmqctl start_app # サーバの再開 $ rabbitmqctl list_exchanges # Exchange の一覧表示 $ rabbitmqctl list_queues # Queue の一覧表示 $ rabbitmqctl stop # サーバの停止めでたしめでたし。