thirose’s blog

openstackやpythonなどなど

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は使用していなかったので削除し、ログインができることを確認しました。

  1. idPから渡されるREMOTE_USERを修正
  2. 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が 98588start98598, 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_heartbeatif 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になっていることが確認できます。

プルリクエス

このイシューについてはアップストリームに反映してマージされました。

review.opendev.org

designateでdeadlockが発生する原因と解決手段

English follow is here =>

Designate got the DBDeadLock – Hirose Takahito – Medium

designateを使っていてたまにdeadlockがおきていることに気がついたので、その原因を探っていきました。
原因は登録/更新/削除のフローで起きる構造になっていたのでそれについてまとめました。

f:id:hirosetakahito:20190402133046p:plain

これは簡単にまとめた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桁の番号を入力するとログインできるようになるという仕組みです。

実際に設定してみる

f:id:hirosetakahito:20190207190255p:plain 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秒に一回更新されます。

使い方

f:id:hirosetakahito:20190207190251p:plain 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)
}

結果

f:id:hirosetakahito:20190101232238p:plain
result_of_slice

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

最近インフラ周りでも、golangOSSが増えてきてコードを読まないといけないことが増えたから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が最後に評価され、それが出力される。 だから、確実にそのメソッドを終わらせるために定義したい場合に使うのだろうなと思う。 調べてたら、検証している人がいた。

blog.amedama.jp

自分でも以下のようなコードで調べてみたら、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のデータベースが壊れてしまっているので、再構築するのがてっとり早いです。