keystoneの複数の認証メソッドが絡んだ認証の問題とその解決
概要
keystoneの複数の認証方法がある環境でおこった、一部ユーザがログインできなくなる問題がありました。 今回の記事ではkeystoneの認証の流れと、その中でおこった今回の問題の原因への対応をまとめました。
keystone
Keystoneとは、OpenStackの認証やユーザ・プロジェクト・ロールなどの管理を行っているコンポンポーネントです。今回はその認証部分についてまとめてあります。
keystoneの認証メソッド
まずはどのような認証メソッドがあるのかというと、以下の3種類が標準の認証メソッドになります。
- password
- ユーザIDとパスワードによる認証
- token
- tokenを利用した認証
- external
- 外部認証によって使われる場合に使われる認証
また、これらに加えSSOによる認証でmappedを追加しています。
今簡単に説明した認証メソッド4つをkeystone.confに設定された状態(下記サンプル)でおきた認証エラーについてまとめています。
[auth] methods = external, password, token, mapped
どのような問題がおきたのか
普段認証にはSSOを使用し、SSOを導入する前まではpasswordによる認証が使われていました。そのため、ユニークキーとしてはIDがあれば問題ありませんでした。 しかし、そこに検証でメールアドレスを追加してしまってたため、普段つかわれても問題なかったexternalに後から検証で登録されたユーザで 認証をしようとしてしまっていることで認証ができなくなっていました。
原因の把握
まず、この問題を認識した時に、確認したのは特定の(idにメールアドレスが登録された)ユーザがログインできないこと、かつpassword認証ではログインできたことを確認しました。
しかし、SSOを使った認証は認証エラーになってしまっている状態ということを確認。
続いてSSOのデータはどうか。Mellonを使い、idPと通信しkeystoneの認証情報とのmappingのためのデータをしっかり渡せているのか、どのフェーズでのエラーなのか、という確認をしました。
そこで判明したことは、まずデータのmappingのところまではしっかりデータを渡せているが、その後の処理でエラーがおきていました。
該当エラーについて
def build_local_user_context(auth_context, mapped_properties): user_info = auth_plugins.UserAuthInfo.create(mapped_properties, METHOD_NAME) auth_context['user_id'] = user_info.user_id
参照元: keystone/mapped.py at mitaka-eol · openstack/keystone · GitHub
mapped pluginの build_local_user_context メソッドの
auth_context['user_id'] = user_info.user_id
この行に問題があることがわかりました。まず、user_info.user_idにはmappedで評価される正しいログイン情報が入ってることが確認されました。
しかし、auth_context['user_id'] には別の(idにメールアドレスが登録され、メールアドレスが正しいユーザと重複されている)ユーザ情報が保持されてました。さらに auth_contextは frozonset
でセットされているため、該当の行で例外が発生していました。
参照元: keystone/core.py at d97832e8e826e37171b727072c720a9b589998dd · openstack/keystone · GitHub
解決方法
考えられる対応策は2つになります。そこで今回は2を選び対応をしました。 1も当初検討しましたが、本当にexternalメソッドが必要になったときにとても困ります。 また、externalは使用していなかったので削除し、ログインができることを確認しました。
- idPから渡されるREMOTE_USERを修正
- external メソッドをconfigから消す
designateでworkerを2以上に増やすとservice_statusが更新されない原因と解決策
English version -> medium.com
designateの各サービスのworker数をデフォルトの1から2以上にした時、service_statusが更新されないのでその原因を調べた。
まず、worker数が1の時の起動の流れを確認 すると、designate/service_status.HeartBeatEmitter.start
が実行されているのがわかります。
workerが1の場合
2019-04-04 12:14:52.670 96400 DEBUG designate.service_status [-] designate/service_status.HeartBeatEmitter.__init__: 96400 __init__ /usr/lib/python2.7/site-packages/designate/service_status.py:53 2019-04-04 12:14:52.730 96400 DEBUG designate.service_status [-] designate/service_status.HeartBeatEmitter._emit_heartbeat: 96400 _emit_heartbeat /usr/lib/python2.7/site-packages/designate/service_status.py:75 2019-04-04 12:14:52.730 96400 DEBUG designate.service_status [-] designate/service_status.HeartBeatEmitter._emit_heartbeat: _running=False _emit_heartbeat /usr/lib/python2.7/site-packages/designate/service_status.py:76 2019-04-04 12:14:52.731 96400 DEBUG designate.service_status [-] designate/service_status.HeartBeatEmitter.start: 96400 start /usr/lib/python2.7/site-packages/designate/service_status.py:100 2019-04-04 12:14:52.732 96400 DEBUG designate.service_status [-] designate/service_status.HeartBeatEmitter.start: _running=True start /usr/lib/python2.7/site-packages/designate/service_status.py:102 2019-04-04 12:14:57.735 96400 DEBUG designate.service_status [-] designate/service_status.HeartBeatEmitter._emit_heartbeat: 96400 _emit_heartbeat /usr/lib/python2.7/site-packages/designate/service_status.py:75 2019-04-04 12:14:57.735 96400 DEBUG designate.service_status [-] designate/service_status.HeartBeatEmitter._emit_heartbeat: _running=True _emit_heartbeat /usr/lib/python2.7/site-packages/designate/service_status.py:76 2019-04-04 12:15:02.732 96400 DEBUG designate.service_status [req-2b1269ed-5802-4787-a2f2-8ef583d01bd7 - - - - -] designate/service_status.HeartBeatEmitter._emit_heartbeat: 96400 _emit_heartbeat /usr/lib/python2.7/site-packages/designate/service_status.py:75 2019-04-04 12:15:02.733 96400 DEBUG designate.service_status [req-2b1269ed-5802-4787-a2f2-8ef583d01bd7 - - - - -] designate/service_status.HeartBeatEmitter._emit_heartbeat: _running=True _emit_heartbeat /usr/lib/python2.7/site-packages/designate/service_status.py:76 2019-04-04 12:15:07.731 96400 DEBUG designate.service_status [req-b3c89e7e-a321-4c4c-8a0b-d8bb880962bf - - - - -] designate/service_status.HeartBeatEmitter._emit_heartbeat: 96400 _emit_heartbeat /usr/lib/python2.7/site-packages/designate/service_status.py:75 2019-04-04 12:15:07.732 96400 DEBUG designate.service_status [req-b3c89e7e-a321-4c4c-8a0b-d8bb880962bf - - - - -] designate/service_status.HeartBeatEmitter._emit_heartbeat: _running=True _emit_heartbeat /usr/lib/python2.7/site-packages/designate/service_status.py:76
MariaDB [designate]> select * from service_statuses where service_name='api'\G *************************** 1. row *************************** id: 4e24db8d45be496f8119fda6eb706d96 created_at: 2019-04-04 03:14:57 updated_at: 2019-04-04 03:37:18 service_name: api hostname: dns001.host heartbeated_at: 2019-04-04 03:37:18 status: UP stats: {} capabilities: {} 1 row in set (0.00 sec)
ではworker数が2以上になった場合はどうなるのか。
わかりやすいように以下のようにデバック用のログが表示されるようにして起動します。
# diff -u /usr/lib/python2.7/site-packages/designate/service_status.py.org /usr/lib/python2.7/site-packages/designate/service_status.py --- /usr/lib/python2.7/site-packages/designate/service_status.py.org 2019-04-03 17:48:46.892646687 +0900 +++ /usr/lib/python2.7/site-packages/designate/service_status.py 2019-04-03 17:47:35.032081139 +0900 @@ -12,6 +12,7 @@ # License for the specific language governing permissions and limitations # under the License. import abc +import os from oslo_config import cfg from oslo_log import log as logging @@ -49,6 +50,7 @@ def __init__(self, service, threadgroup, status_factory=None): super(HeartBeatEmitter, self).__init__() + LOG.debug("designate/service_status.HeartBeatEmitter.__init__: %s", os.getpid()) self._service = service self._hostname = CONF.host @@ -70,6 +72,7 @@ """ Returns Status, Stats, Capabilities """ + LOG.debug("designate/service_status.HeartBeatEmitter._emit_heartbeat: %s", os.getpid()) if not self._running: return @@ -93,6 +96,7 @@ pass def start(self): + LOG.debug("designate/service_status.HeartBeatEmitter.start: %s", os.getpid()) self._running = True def stop(self):
そして、起動すると、以下のようなログになります。
2019-04-04 12:37:24.512 98588 DEBUG designate.service_status [-] designate/service_status.HeartBeatEmitter.__init__: 98588 __init__ /usr/lib/python2.7/site-packages/designate/service_status.py:53 2019-04-04 12:37:24.533 98598 DEBUG designate.service_status [-] designate/service_status.HeartBeatEmitter.start: 98598 start /usr/lib/python2.7/site-packages/designate/service_status.py:100 2019-04-04 12:37:24.533 98598 DEBUG designate.service_status [-] designate/service_status.HeartBeatEmitter.start: _running=True start /usr/lib/python2.7/site-packages/designate/service_status.py:102 2019-04-04 12:37:24.536 98599 DEBUG designate.service_status [-] designate/service_status.HeartBeatEmitter.start: 98599 start /usr/lib/python2.7/site-packages/designate/service_status.py:100 2019-04-04 12:37:24.536 98599 DEBUG designate.service_status [-] designate/service_status.HeartBeatEmitter.start: _running=True start /usr/lib/python2.7/site-packages/designate/service_status.py:102 2019-04-04 12:37:24.537 98600 DEBUG designate.service_status [-] designate/service_status.HeartBeatEmitter.start: 98600 start /usr/lib/python2.7/site-packages/designate/service_status.py:100 2019-04-04 12:37:24.538 98600 DEBUG designate.service_status [-] designate/service_status.HeartBeatEmitter.start: _running=True start /usr/lib/python2.7/site-packages/designate/service_status.py:102 2019-04-04 12:37:24.563 98601 DEBUG designate.service_status [-] designate/service_status.HeartBeatEmitter.start: 98601 start /usr/lib/python2.7/site-packages/designate/service_status.py:100 2019-04-04 12:37:24.563 98601 DEBUG designate.service_status [-] designate/service_status.HeartBeatEmitter.start: _running=True start /usr/lib/python2.7/site-packages/designate/service_status.py:102 2019-04-04 12:37:24.636 98588 DEBUG designate.service_status [-] designate/service_status.HeartBeatEmitter._emit_heartbeat: 98588 _emit_heartbeat /usr/lib/python2.7/site-packages/designate/service_status.py:75 2019-04-04 12:37:24.636 98588 DEBUG designate.service_status [-] designate/service_status.HeartBeatEmitter._emit_heartbeat: _running=False _emit_heartbeat /usr/lib/python2.7/site-packages/designate/service_status.py:76 2019-04-04 12:37:29.637 98588 DEBUG designate.service_status [-] designate/service_status.HeartBeatEmitter._emit_heartbeat: 98588 _emit_heartbeat /usr/lib/python2.7/site-packages/designate/service_status.py:75 2019-04-04 12:37:29.637 98588 DEBUG designate.service_status [-] designate/service_status.HeartBeatEmitter._emit_heartbeat: _running=False _emit_heartbeat /usr/lib/python2.7/site-packages/designate/service_status.py:76
__init__
と _emit_heartbeat
は process_idが 98588
で start
は 98598, 98599, 98600, 98601
になってます。
そして、各process idを確認すると、
# ps auxwwf | grep designate-api designa+ 98588 1.9 0.7 347336 62380 ? Ss 12:37 0:02 /usr/bin/python2 /usr/bin/designate-api --config-file /etc/designate/designate.conf --log-file /var/log/designate/api.log designa+ 98598 0.2 0.8 392348 69792 ? S 12:37 0:00 \_ /usr/bin/python2 /usr/bin/designate-api --config-file /etc/designate/designate.conf --log-file /var/log/designate/api.log designa+ 98599 0.2 0.8 392344 69784 ? S 12:37 0:00 \_ /usr/bin/python2 /usr/bin/designate-api --config-file /etc/designate/designate.conf --log-file /var/log/designate/api.log designa+ 98600 0.2 0.8 392344 69784 ? S 12:37 0:00 \_ /usr/bin/python2 /usr/bin/designate-api --config-file /etc/designate/designate.conf --log-file /var/log/designate/api.log designa+ 98601 0.2 0.8 392348 69784 ? S 12:37 0:00 \_ /usr/bin/python2 /usr/bin/designate-api --config-file /etc/designate/designate.conf --log-file /var/log/designate/api.log
となってるので、__init__
と_emit_heartbeat
は親プロセスでstartは子プロセスということがわかります。
start メソッドでは _running という値をTrue
に変更していて、これは defaultではFalse
なので実際に確認を行っている親プロセスでは、False
から変わることはありません。
def start(self): self._running = True
それが原因で_emit_heartbeat
の if not self._running
の判定は毎回 False
でreturnされるため、更新がされていません。
def _emit_heartbeat(self): """ Returns Status, Stats, Capabilities """ if not self._running: return status, stats, capabilities = self._get_status() service_status = objects.ServiceStatus( service_name=self._service, hostname=self._hostname, status=status, stats=stats, capabilities=capabilities, heartbeated_at=timeutils.utcnow() ) LOG.trace("Emitting %s", service_status) self._transmit(service_status)
だから親プロセスの _runningフラグを書き換える必要があります。
# diff -u /usr/lib/python2.7/site-packages/designate/cmd/api.py.org /usr/lib/python2.7/site-packages/designate/cmd/api.py --- /usr/lib/python2.7/site-packages/designate/cmd/api.py.org 2019-04-03 18:10:09.908918965 +0900 +++ /usr/lib/python2.7/site-packages/designate/cmd/api.py 2019-04-03 18:09:49.886038819 +0900 @@ -40,4 +40,5 @@ server = api_service.Service(threads=CONF['service:api'].threads) service.serve(server, workers=CONF['service:api'].workers) + server.heartbeat_emitter.start() service.wait()
こういう設定を他のサービス(central, pool_manager, zone_manager, mdns)にも修正を行うと、
以下のように止まってたservice_statusが更新されるようになります。
- before
MariaDB [designate]> select * from service_statuses where service_name='api'\G *************************** 1. row *************************** id: 4e24db8d45be496f8119fda6eb706d96 created_at: 2019-04-04 03:14:57 updated_at: 2019-04-04 03:37:18 service_name: api hostname: dns001.host heartbeated_at: 2019-04-04 03:37:18 status: UP stats: {} capabilities: {} 1 row in set (0.00 sec)
- after
MariaDB [designate]> select * from service_statuses where service_name='api'\G *************************** 1. row *************************** id: 4e24db8d45be496f8119fda6eb706d96 created_at: 2019-04-04 03:14:57 updated_at: 2019-04-04 03:42:23 service_name: api hostname: dns001.host heartbeated_at: 2019-04-04 03:42:23 status: UP stats: {} capabilities: {} 1 row in set (0.00 sec)
そしてlogも
2019-04-04 12:42:23.711 99088 DEBUG designate.service_status [req-8be27649-7819-4416-b936-9015b9515963 - - - - -] designate/service_status.HeartBeatEmitter._emit_heartbeat: 99088 _emit_heartbeat /usr/lib/python2.7/site-packages/designate/service_status.py:75 2019-04-04 12:42:23.712 99088 DEBUG designate.service_status [req-8be27649-7819-4416-b936-9015b9515963 - - - - -] designate/service_status.HeartBeatEmitter._emit_heartbeat: _running=True _emit_heartbeat /usr/lib/python2.7/site-packages/designate/service_status.py:76 2019-04-04 12:42:28.716 99088 DEBUG designate.service_status [req-286a4481-dad8-4fc5-802d-ad1d9e0b8d3b - - - - -] designate/service_status.HeartBeatEmitter._emit_heartbeat: 99088 _emit_heartbeat /usr/lib/python2.7/site-packages/designate/service_status.py:75 2019-04-04 12:42:28.717 99088 DEBUG designate.service_status [req-286a4481-dad8-4fc5-802d-ad1d9e0b8d3b - - - - -] designate/service_status.HeartBeatEmitter._emit_heartbeat: _running=True _emit_heartbeat /usr/lib/python2.7/site-packages/designate/service_status.py:76
と、しっかりTrue
になっていることが確認できます。
プルリクエスト
このイシューについてはアップストリームに反映してマージされました。
designateでdeadlockが発生する原因と解決手段
English follow is here =>
Designate got the DBDeadLock – Hirose Takahito – Medium
designateを使っていてたまにdeadlockがおきていることに気がついたので、その原因を探っていきました。
原因は登録/更新/削除のフローで起きる構造になっていたのでそれについてまとめました。
これは簡単にまとめたdesignateの登録フローです。 今回deadlockのポイントになったのが、2と3の最初のMySQLへの登録(更新)プロセスと、8と9のstatus更新プロセスの部分です。
for i in {1..100} ; do openstack recordset create --records 192.168.0.1 --type A 26b12550-6d64-49eb-a69d-0427472b7da2 z$i ; done
今回はシンプルなシェルスクリプトで確認しました。すると、
------------------------ LATEST DETECTED DEADLOCK ------------------------ 190320 15:32:32 *** (1) TRANSACTION: TRANSACTION 27B30A, ACTIVE 0 sec starting index read mysql tables in use 1, locked 1 LOCK WAIT 5 lock struct(s), heap size 1248, 3 row lock(s), undo log entries 2 MySQL thread id 243, OS thread handle 0x7f2ac428d700, query id 65726 10.127.163.56 designate Updating UPDATE zones SET version=(zones.version + 1), updated_at=‘2019-03-20 06:32:32.527418’, status=‘ACTIVE’, action=‘NONE’ WHERE zones.id = ‘26b125506d6449eba69d0427472b7da2’ AND zones.deleted = ‘0’ *** (1) WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 653 page no 3 n bits 72 index `PRIMARY` of table `designate`.`zones` trx id 27B30A lock_mode X locks rec but not gap waiting *** (2) TRANSACTION: TRANSACTION 27B306, ACTIVE 0 sec starting index read mysql tables in use 1, locked 1 6 lock struct(s), heap size 1248, 3 row lock(s), undo log entries 2 MySQL thread id 274, OS thread handle 0x7f2a70d71700, query id 65731 10.127.163.56 designate Updating UPDATE records SET version=(records.version + 1), updated_at=‘2019-03-20 06:32:32.545283’, data=‘ns1.example.com. domain.example.com. 1553063552 3562 600 86400 3600’, hash=‘a4718b30220d6ff2d4b4cc3602654509’, serial=1553063552 WHERE records.id = ‘78b667000767489a8fc821275d5fff0b’ *** (2) HOLDS THE LOCK(S): RECORD LOCKS space id 653 page no 3 n bits 72 index `PRIMARY` of table `designate`.`zones` trx id 27B306 lock_mode X locks rec but not gap *** (2) WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 649 page no 12 n bits 112 index `PRIMARY` of table `designate`.`records` trx id 27B306 lock_mode X locks rec but not gap waiting *** WE ROLL BACK TRANSACTION (1)
のようにやはり、タイミングがかさなるとDeadLockが発生していました。
なので、今回は以下のように修正してpatchを投げました。順番を修正してあげれば大丈夫です。
$ git diff diff --git a/designate/central/service.py b/designate/central/service.py index ffd12c6..f6f734d 100644 --- a/designate/central/service.py +++ b/designate/central/service.py @@ -2317,8 +2317,8 @@ class Service(service.RPCService, service.Service): “”" # TODO(kiall): If the status is SUCCESS and the zone is already ACTIVE, # we likely don’t need to do anything. - self._update_record_status(context, zone_id, status, serial) zone = self._update_zone_status(context, zone_id, status, serial) + self._update_record_status(context, zone_id, status, serial) return zone def _update_zone_status(self, context, zone_id, status, serial): zone = self._update_zone_status(context, zone_id, status, serial) + self._update_record_status(context, zone_id, status, serial) return zone def _update_zone_status(self, context, zone_id, status, serial):
patchは以下のURLです。 Gerrit Code Review
KeystoneのTime-based One-time Password(TOTP)を設定してみた
TOTPとは、どういうログインになるのか...それを知りたく設定してみました。なお当初期待していたログインはMulti Factor Authentication(MFA)的なのを期待していましたが、挙動としては期待していたものと少し違いました。 ここで自分のやりたかったことち違ってたことに気がつけると幸せな気がします。
で、TOTPを使うとどのようなことができるのか。という話
簡潔にまとめると、Google Authenticator を使ってログインが可能になります。
secret keyを作りkeystoneとGoogle authenticatorに登録しログインすると、
ログイン時にユーザ名とgoogle authenticatorに表示されている6桁の番号を入力するとログインできるようになるという仕組みです。
実際に設定してみる
OpenStack Docs: Time-based One-time Password (TOTP) OpenStackのドキュメントは基本的に役に立ちませんが、あるとないでは情報量の違いがあるので参照しています。
Keystoneは基本機能として、TOTPを提供しています。そのため、/etc/keystone/keystone.conf
の [auth] の methods に totp
を追加することになる。
[auth] methods = external,password,token,totp
書く環境でログイン方法はいろいろあると思いますが、上記のような感じで最後にtotpを追加しています。
次にユーザ用に使うsecret keyを作成します。これは、sample同様
import base64 message = '1234567890123456' print base64.b32encode(message).rstrip('=')
で作れます。 messageのところは、任意の文字列なので、自分で書き換えると良いです。
次はcreadentialの作成 これはサンプルがややこしく脱落する可能性があるところ。
USER_ID=YOUR_USER_ID SECRET=YOUR_CREATED_SECRET curl -i \ -H "Content-Type: application/json" \ -d ' { "credential": { "blob": "'$SECRET'", "type": "totp", "user_id": "'$USER_ID'" } }' \ http://YOUR_KEYSTONE_ENDPOINT:5000/v3/credentials ; echo
USER_IDは各自調べるのが良いでしょう。SECRETは先ほど作成したものを使います。
keystoneのendpointも自分のendpointを設定しましょう。
しかし、このまま実行すると、以下のような401 errorが返ってきます。
{"error": {"message": "The request you have made requires authentication.", "code": 401, "title": "Unauthorized"}}
なので、Content-typeの次の行に -H "X-Auth-Token: YOUR_TOKEN"\
の一行を追加する必要があります。
そして、実行すれば 201
が返ってくるので、keystone側の準備は完了です。
次にgoogle authenticatorをスマートフォンにインストールします。
インストールしている間に、登録用のQRコードを生成します。
import qrcode user_name=YOUR_USER_ID_OR_NAME secret='GEZDGNBVGY3TQOJQGEZDGNBVGY' uri = 'otpauth://totp/{name}?secret={secret}&issuer={issuer}'.format( name=user_name, secret=secret, issuer='Keystone') img = qrcode.make(uri) img.save('totp.png')
google authenticatorがログインされたら、そのアプリを開き、上記で作成したQRコードを読み取りましょう。
PASS CODEが表示されます。 ここで表示されるPASS CODEは30秒に一回更新されます。
使い方
Token発行をAPIで実現したい場合は、サンプルの最後に書かれているように、methodsをtotpにしてUSER_IDとPASS CODEでAPIを叩けばTokenが発行されます。
ちなみに、コードだと、
keystone/totp.py at d97832e8e826e37171b727072c720a9b589998dd · openstack/keystone · GitHub
この部分ですが、
keystoneに登録したsecretを使い6桁のPASS CODEを生成し、それが送られてきたPASS CODEと一致してるかどうかの確認をしています。
一致していた場合は、valid_passcode
フラグをTrueにして認証をおえています。
ここまできたところで、私が期待していたMFAではないことに気がつきましたが、
それはまた別にありそうなので調べることとしました。
OpenStack Docs: MFA Auth Receipt
A tour of Go の More Types の exercise
Exercise: Slices
この問題を読んでいくと、
この関数は、長さ dy のslice
と書かれているので、imageのsliceのlengthはdyが入ることになる。そうなると、image := make([][]uint, dy)
となるのが自然な気がします。
次に、各要素が8bitのunsigned int型で長さ dx
となっているので、各列の長さdxとなるはずだから、for文でimageのlenngh = dy のぶんだけ、forを繰り返し、各要素が、dxになります。
なので、forの中で、 image[y] = make()[]uint8, dx)
とすると、なんとなく骨格が見えてきました。
最後に各要素に生成する画像のための数式をいれていくと良さそうだなーと思い書きました。
コード
package main import "golang.org/x/tour/pic" func Pic(dx, dy int) [][]uint8 { image := make([][]uint8, dy) for y := range image { image[y] = make([]uint8, dx) } for image_y := 0; image_y < dy; image_y++ { for image_x := 0; image_x < dx; image_x++ { image[image_y][image_x] = uint8((image_x * image_y) / 2) } } return image } func main() { pic.Show(Pic) }
結果
exercise-maps.go
コード
package main import ( "golang.org/x/tour/wc" "strings" ) func WordCount(s string) map[string]int { return_value := make(map[string]int) str_field := strings.Fields(s) for _, value := range(str_field) { return_value[value]++ } return return_value } func main() { wc.Test(WordCount) }
まずは、returnされるmapを作る。
strings.Fieldsをみると何かヒントが得られるようなことが書かれてるので、ドキュメントを読んでみると、文字列を分割してくれるようなので宣言します。
strings - The Go Programming Language
その値をmapに入れてreturnをすれば、今回importしたgolang.org/x/tour/wc
の期待した動作になりました。
結果
PASS f("I am learning Go!") = map[string]int{"I":1, "am":1, "learning":1, "Go!":1} PASS f("The quick brown fox jumped over the lazy dog.") = map[string]int{"jumped":1, "over":1, "lazy":1, "fox":1, "quick":1, "brown":1, "the":1, "dog.":1, "The":1} PASS f("I ate a donut. Then I ate another donut.") = map[string]int{"I":2, "ate":2, "a":1, "donut.":2, "Then":1, "another":1} PASS f("A man a plan a canal panama.") = map[string]int{"canal":1, "panama.":1, "A":1, "man":1, "a":2, "plan":1}
Fibonacci closure
クロージャを使ってフィボナッチを出力する問題
コード
package main import "fmt" // fibonacci is a function that returns // a function that returns an int. func fibonacci() func() int { previous, next := 0, 1 return func() int { next, previous = previous, next+previous return next } } func main() { f := fibonacci() for i := 0; i < 10; i++ { fmt.Println(f()) } }
結果
0 1 1 2 3 5 8 13 21 34
A tour of Go の Flow control statements の exercise
最近インフラ周りでも、golangのOSSが増えてきてコードを読まないといけないことが増えたからgolangのチュートリアルで書き方を覚えようと思い始めた。
exercise-loops-and-functions.go
関数とループを使った簡単な練習として、平方根の計算を実装してみましょう: 数値 x が与えられたときに z² が最も x に近い数値 z を求めるプログラムです。
コード
$ cat exercise-loops-and-functions.go package main import ( "fmt" ) func Sqrt(x float64) float64 { z := 1.0 for i := 0; i < 10; i++ { z -= (z*z - x) / (2*z) } return z } func main() { fmt.Println(Sqrt(2)) }
結果
$ go run exercise-loops-and-functions.go 1.414213562373095
確認
問題文には、「 あなたの関数の結果は標準ライブラリの math.Sqrt にどれくらい近づきましたか?」と書かれていたので、実際に動かしてみる。
$ cat loop-and-functions.go package main import ( "fmt" "math" ) func main() { fmt.Println(math.Sqrt(2)) }
結果
$ go run loop-and-functions.go 1.4142135623730951
答えは1桁多く標準ライブラリの方が多く表示されてるけど、同じと言えるだろう。 ざっくりだけど、他は写経して終わり。
deferがとても新鮮だったような気がする。 deferで定義しておいたprintが最後に評価され、それが出力される。 だから、確実にそのメソッドを終わらせるために定義したい場合に使うのだろうなと思う。 調べてたら、検証している人がいた。
自分でも以下のようなコードで調べてみたら、deferでpanicによるハンドリングができることがわかった。
$ cat recover.go package main import ( "fmt" ) func judge_err() { fmt.Println("judge the error") err := recover() if err != nil { fmt.Println(err) } else { fmt.Println("no error") } } func hello_no_error() { defer judge_err() fmt.Println("Hello World") } func hello_error() { defer judge_err() fmt.Println("Hello World") panic("something error") } func main() { hello_no_error() hello_error() }
これを実行すると、
$ go run recover.go Hello World judge the error no error Hello World judge the error something error
と、一つ目はrecoverで何も入らずno errorが入り、
二つ目はpanicを使っているので、deferで後から評価されsomething error
が出力されていることがわかります。
追記
公式的にもしっかり書いてあった。 Defer, Panic, and Recover - The Go Blog
yumでDBが壊れた場合の対処
稀にしか遭遇しなくて、毎回忘れて調べてるので備忘録
久々にyum upgradeとかyum installとかしようとするとエラーがでます。
error: rpmdb: BDB0113 Thread/process 159007/140390399924224 failed: BDB1507 Thread died in Berkeley DB library error: db5 error(-30973) from dbenv->failchk: BDB0087 DB_RUNRECOVERY: Fatal error, run database recovery error: cannot open Packages index using db5 - (-30973) error: cannot open Packages database in /var/lib/rpm
ちなみにこのエラーが出たのは、CentOS7.5ですが、RedHad系のOSならだいたい同じかと思います。
こういったエラーが出た時は、 rm -rf /var/lib/rpm/__db.00*
して、rpm --rebuilddb
するのが一番早いです。
というのも、yumのデータベースが壊れてしまっているので、再構築するのがてっとり早いです。