Qmonus SDK Core のplugin installが正しく実施され、アプリケーションとして想定動作するような正しい状態であるかどうかを確認したいです。

公開日: 2023/9/21

Qmonus SDKのplugin installが完了していることをAPIで確認する

Qmonus SDK に対してGET /heartbeat?cluster=trueをリクエストすることで、レスポンスボディから全コンポーネントの状態を確認することができます。 body内にはデプロイされているコンポーネントごとの稼働情報がオブジェクトとしてまとまっており、各オブジェクトのplugin_versionが一致している場合はplugin_installが完了しています。レスポンス例についてはQ13をご参照ください。

# インタラクティブシェルから以下のようにリクエスト可能です。
>>> r = await callout("/heartbeat?cluster=true")

Qmonus SDKのシナリオサーバーでinstallされているpluginとinstall対象のgit管理されているファイルを比較する

scenarioサーバーではgitリポジトリを対象に起動パラメーターplugin_base_dirspluginsignore_pluginsでinstall対象のディレクトリおよびファイルを設定しています。gitリポジトリでinstall対象としているファイルとAPIで取得したリソースを比較することで、ユーザーが定義したpluginのうちinstall対象としているものの数と、実際にQmonus SDKにinstallされているリソースの数を比較することができます。以下に比較するためのスクリプト例を示します。

### 起動パラメータでinstallする対象のpathおよびworkspaceを判断する
### pluginでinstall可能なリソースについて、ファイル内からリソース数を判断する
### ファイル内に複数のリソースが含まれていても、そのリソース数をカウントする
### account/roleはlabの情報や環境に入ったタイミングで作成されるアカウントと区別ができない(workspaceからinstallされたものか判断できない)ので対象外とする
### routingはplugin由来のものかSDK内部で使用するroutingかの区別が判断できないため比較対象外とする
### illusionはanonymous-が接頭でついているものを比較対象外とする
### api calloutで各resourceを取得し、ファイルに含まれるリソースの数と比較する
### 所属するworkspaceを情報として返却しないリソースについてはcalloutのレスポンスを直接比較する

import os, yaml, json
paths             = options.plugins_base_dirs.split(",")
target_workspaces = options.plugins.split(",")
ignore_plugins = options.ignore_plugins.split(",")
workspaces = []
for path in paths:
  try:
    workspaces.extend(os.listdir(path))
  except:
    continue
if "*" in target_workspaces:
  target_workspaces = workspaces
resources = {}
for w in workspaces:
  for p in paths:
    try:
      os.listdir(f"{p}/{w}")
    except:
      continue
    for resource in os.listdir(f"{p}/{w}"):
      if resource in ignore_plugins:
        continue
      try:
        files = os.listdir(f"{p}/{w}/{resource}")
        for file in files:
          with open(f"{p}/{w}/{resource}/{file}") as f:
            yml = yaml.load(f, Loader=yaml.SafeLoader)
            l = len(yml) if type(yml) == list else 1
          if resource not in resources.keys():
            resources[resource] = l
          else:
            resources[resource] = resources[resource] + l
      except:
        continue
resources.pop("accounts", None)
resources.pop("roles", None)
resources.pop("routings", None)
result = {}
flag = True
for resource in resources.keys():
  r = await callout(f"/{resource}")
  if r.code not in [200]:
    result[resource] = "request fail"
    continue
  body = json.loads(r.body)
  if resource == "illusions":
    body_resources = [e for e in body if not e["name"].startswith("anonymous")]
  elif resource == "classes":
    resources["modelschemas"] = resources["modelschemas"] + len([e for e in body if e.get("persistence")])
    body_resources = body
  else:
    body_resources = body
  if len(body_resources) == resources[resource]:
    result[resource] = f"OK, {len(body_resources)} plugin resources found." 
  else:
    result[resource] = f"resource not match plugin_resource({len(body_resources)}), files({resources[resource]}))"
    flag = False
if not flag: print("----- some plugin resources did not match -----")
else: print("----- plugin resources succesfully installed -----")
print(json.dumps(result, indent=2))
# インタラクティブシェルでの実行結果
----- plugin resources succesfully installed -----
{
  "scenarios": "OK, 168 plugin resources found.",
  "daemons": "OK, 2 plugin resources found.",
  "classes": "OK, 11 plugin resources found.",
  "transactionquotas": "OK, 3 plugin resources found.",
  "counters": "OK, 1 plugin resources found.",
  "configs": "OK, 3 plugin resources found.",
  "formats": "OK, 1 plugin resources found.",
  "deviceroles": "OK, 1 plugin resources found.",
  "protobufs": "OK, 1 plugin resources found.",
  "testsuites": "OK, 16 plugin resources found.",
  "fakers": "OK, 5 plugin resources found.",
  "ipam": "OK, 1 plugin resources found.",
  "collectionhooks": "OK, 2 plugin resources found.",
  "grpcservicers": "OK, 1 plugin resources found.",
  "jsonschemas": "OK, 1 plugin resources found.",
  "cliproxy": "OK, 1 plugin resources found.",
  "reflections": "OK, 2 plugin resources found.",
  "modelschemas": "OK, 12 plugin resources found.",
  "jobs": "OK, 1 plugin resources found.",
  "illusions": "OK, 1 plugin resources found.",
  "closedlooptypes": "OK, 1 plugin resources found.",
  "functions": "OK, 2 plugin resources found.",
  "devices": "OK, 1 plugin resources found.",
  "exclusives": "OK, 1 plugin resources found.",
  "aborthooks": "OK, 1 plugin resources found.",
  "variablegroups": "OK, 1 plugin resources found.",
  "workers": "OK, 1 plugin resources found.",
  "lambdafunctions": "OK, 1 plugin resources found.",
  "testcases": "OK, 138 plugin resources found.",
  "closedloopcomponents": "OK, 1 plugin resources found.",
  "outgoingwebhooks": "OK, 54 plugin resources found.",
  "collectionpatterns": "OK, 2 plugin resources found.",
  "netconfproxy": "OK, 1 plugin resources found.",
  "templates": "OK, 1 plugin resources found."
}

Warning

Qmonus SDK Labでデプロイした開発環境ではLab側でroleリソースを追加するため、roleリソースを正しく比較することができません。Qmonus Value Streamでデプロイした環境では比較が可能です。resources.pop("roles", None)を削除することでroleが比較対象に含まれるようになります。 routingはplugin由来のものかSDK内部で使用するroutingかの区別が判断できません。ただし、本記事では取り扱いませんが起動パラメーターdeveloper_modeTrueである、workspaceの概念がある開発環境では__WORKSPACE__の値によって例外的に比較が可能です。

Qmonus SDKのシナリオサーバー以外でinstallされているATOMを確認する

起動パラメーターhotspot_sharing_modeTrueに設定しているコンポーネントでは対象コンポーネントでのATOMおよびデータベース操作を想定しています。 インタラクティブシェルにてatomやmodelの属性を確認することで、想定通りclassおよびmodelschemaがinstallされているかどうかを確認することが可能です。 installするclassでpersistenceをTrueに設定している場合はmodelschemaが自動生成されるため、classと同名の属性がatomおよびmodelに追加されます。install対象にmodelschemaリソースが存在する場合、modelschemaリソースの名前と同名の属性がmodelのみに属性が追加されます。

# APIGWのインタラクティブシェルでの実行結果
>>> print(dir(atom))↵
... ↵
↵
['SampleClass', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__illusion__', '__init__', '__init_subclass__', '__iter__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__pure__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__shell__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'apply_callback', 'apply_illusion', 'callback', 'illusion', 'instantiation', 'shell']
>>> print(dir(model))↵
... ↵
↵
['SampleClass', 'SampleModel', '__doc__', '__loader__', '__name__', '__package__', '__spec__', 'aiodb', 'qmonus_account', 'qmonus_role']
>>>