Skip to content

Instantly share code, notes, and snippets.

@yfakariya
Last active November 29, 2021 15:48
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save yfakariya/c2a9e373c9dee635f2f539523ee84f98 to your computer and use it in GitHub Desktop.
Save yfakariya/c2a9e373c9dee635f2f539523ee84f98 to your computer and use it in GitHub Desktop.
IoT Edge Deep Dive (ja) -- notes of Azure IoT Edge source code readings. See https://github.com/azure/iotedge for original source code (MIT License).

IoT Edge Deep Dive 1 -- Edgelet

IoT Edgeの中で、ホスト(つまりDockerコンテナーの外側)で動作するコンポーネントとして、IoT Edgeセキュアデーモン(iotedged)とCLI(iotedge)がある。 これらはedgeletと呼ばれるネイティブコンポーネントで動作する(ちなみに、edgeletのほとんどはRustで記述されている。HSMと直接通信する部分のみC言語で記述されている)。

IoT Edgeセキュアデーモンは、以下の役割を持つコンポーネントである。

  • HSM(Hardware Security Module)を抽象化する
  • Dockerコンテナー内で動作するIoT Edgeのモジュールに対して、モジュールのアイデンティティに関わる情報のストレージを提供する
  • IoT Edgeのモジュール間通信に必要なTLS証明書を管理する
  • Docker(Moby)デーモンと通信してその機能をEdgeAgentに対して公開する
  • edgeagentのブートストラップを行う

それに対し、CLIは、IoT Edge全体の管理やトラブルシューティングに必要なユーティリティツールである。

この文章は、1.0.9-rc1のコードベースを基にして記述されている。

構造

ここでは、edgeletの中にあるRustのクレートの一覧を示す。 これらのクレートの概要を抑えておくことで、全体像が見えてくるだろう。順番は、大雑把にコアから周辺部(インターフェイス)に向けた順番になっている。

  • edgelet_coreクレート
    • コアとなる構造体やトレイトの定義。
  • managementクレート
    • 管理系のAPIの定義
  • workloadクレート
    • ワークロードAPIの定義
  • hsm_sysクレート
    • HSMのFFIとC言語実装
  • hsm_rsクレート
    • HSMのRust側ライブラリ
  • edgelet_hsmクレート
    • HSM関連のユーティリティ。
  • iothubserviceクレート
    • IoT Hub互換の構造体や列挙体の定義。
  • edgelet_iothubクレート
    • IoT Hubのデバイスレジストリを使用したアイデンティティマネージャーの実装
  • docker_rsクレート
    • Docker APIクライアント。
  • edgelet_dockerクレート
    • Dockerを使用したIoT Edgeセキュアデーモンランタイム実装。
  • edgelet_kubeクレート
    • Kubernetesを使用したIoT Edgeセキュアデーモンランタイム実装。
  • edgelet_httpクレート
    • HTTP APIのコアとなる構造体やトレイトの定義。
  • edgelet_http_mgmtクレート
    • managementのHTTP APIとしての実装。
  • edgelet_http_workloadクレート
    • workloadのHTTP APIとしての実装。
  • provisioningクレート
    • マニュアルプロビジョニングロジック。
  • iotedgedクレート
    • IoT Edgeセキュアデーモンとしてのアプリケーションロジック。
  • iotedgeクレート
    • CLIとしてのアプリケーションロジック。

初期化シーケンス

IoT Edgeを起動するとは、すなわちIoT Edgeセキュアデーモン(iotedged)を起動するということである。これは、以下のように動作する。

  1. ビルド時に構成されたunixモジュールまたはwindowsモジュールのrun()関数が呼び出される。
  2. アプリケーションの初期化シーケンスが実行される。
    • unixモジュールの場合、またはwindowsモジュールで環境変数IOTEDGE_RUN_AS_CONSOLEが存在する場合は以下のように動作する。
      1. appモジュールのinit()関数が呼び出され、以下を行う。
        1. logging::init()関数でロギングシステムを初期化する。Windowsの場合、use-event-logger引数が指定されている場合はlogging::init_win_log()を使用する。
        2. 構成ファイルをconfig-file引数で指定されたパスから読み込む。
      2. ルートモジュールのMainがビルド時に構成されたランタイム(edgelet_dockerモジュールのDockerModuleRuntimeまたはedgelet_kubeモジュールのKubeModuleRuntime)を指定してインスタンス化される。
      3. Mainrun_until()メソッドが呼び出される。signal::shutdown()を使用して、シグナルでシャットダウンできるようになっている。
    • そうではない場合、つまりWindowsサービスの場合、以下のように動作する。
      1. appモジュールのinit_win_svc_logging()が呼び出され、サービス起動時のエラーが記録されるようにする。
      2. windows_serviceクレートを使用して、Windowsサービスとして起動する。
      3. appモジュールのinit_win_svc()関数が呼び出され、構成ファイルをconfig-file引数で指定されたパスから読み込む。
      4. Windowsサービスの状態をRunningにする。
      5. ルートモジュールのMainがビルド時に構成されたランタイム(edgelet_dockerモジュールのDockerModuleRuntimeまたはedgelet_kubeモジュールのKubeModuleRuntime)を指定してインスタンス化される。
      6. Mainrun_until()メソッドが呼び出される。signal::shutdown()を使用して、シグナルでシャットダウンできるようになっている。さらに、このシグナルを受け取った時に、Windowsサービスの状態をStopPendingにする。
  3. 設定ファイル(config.yaml)の内容のバリデーションを行う(Main::run_until())。
    • このとき、provisioningの値がexternalの場合、環境変数IOTEDGE_EXTERNAL_PROVISIONING_ENDPOINTconfig.yamlendpointの値を設定する。
  4. config.yamlで指定された値を環境変数に設定する(set_iot_edge_env_vars())。
    • IOTEDGE_HOMEDIRhomedirの値。
    • certificatesがある場合、IOTEDGE_DEVICE_CA_CERTdevice_ca_certの値(ファイルパス)、IOTEDGE_DEVICE_CA_PKdevice_ca_pkの値(ファイルパス)、IOTEDGE_TRUSTED_CA_CERTStrusted_ca_certsの値(ファイルパス)を設定する。
      • certificatesがない場合はクイックスタートモードになる。
  5. config.yamlprovisioningの値がdpsの場合、DPSの初期化を行う(set_iot_edge_env_vars())。
    1. attestationに応じた値を設定する。
      • attestationtpmまたはsymmetric_keyの場合、IOTEDGE_REGISTRATION_IDregistration_idを設定する。
      • attestationx509の場合、IOTEDGE_REGISTRATION_IDregistration_idを設定する(オプション)。また、IOTEDGE_DEVICE_IDENTITY_CERTidentity_certの値(ファイルパスまたはURI)、IOTEDGE_DEVICE_IDENTITY_PKidentity_pkの値(ファイルパスまたはURI)を設定する。
  6. HSMを初期化する。
    1. edgeletという名前でインメモリストアを初期化する(hsm_client_crypto_init -> edge_hsm_client_store_create-> create_store)。
    2. デバイスCA証明書を環境変数 IOTEDGE_DEVICE_CA_CERTIOTEDGE_DEVICE_CA_PKIOTEDGE_TRUSTED_CA_CERTSで指定されたパスから読み込む。これらが設定されていない場合は、90日間有効な証明書を生成する。(hsm_client_crypto_init -> edge_hsm_client_store_create -> hsm_provision -> hsm_provision_edge_ca_certificates
    3. 環境変数IOTEDGE_DEVICE_IDENTITY_CERTIOTEDGE_DEVICE_IDENTITY_PKがあれば、それらからデバイスID証明書のパスを取得し、HSMに格納する(hsm_client_crypto_init -> edge_hsm_client_store_create -> hsm_provision -> hsm_provision_edge_id_certificate
  7. HSMライブラリのバージョンチェックをおこなう。1.0.2でない場合はエラー終了。
  8. マスター暗号化キーを作成する。
    1. edgelet-masterという名前でキーを作成する(edge_hsm_client_create_master_encryption_key -> edge_hsm_client_store_insert_encryption_key
    2. 既存のキーを読み込む。
      1. キーのファイルパスを構築する。具体的には、以下のディレクトリ名またはパスを連結したファイルパスに、拡張子 .enc.key を付けたもの。(build_enc_key_file_path
        • ホームディレクトリ。具体的には環境変数 IOTEDGE_HOMEDIR の値。ない場合は /var/lib/iotedge または %ProgramData%\iotedge\
        • enc_keys
        • キー名のSHA256ダイジェストをBASE64エンコードした文字列
      2. キーを読み込む(load_encryption_key_from_file
    3. 既存のキーがなければ、暗号化キーを生成する(generate_encryption_key)。
      1. OpenSSLを初期化する。
      2. 32バイトのランダムなキーを生成する。
    4. 生成したキーを保存する。ファイルパスは前述のとおり(save_encryption_key_to_file
  9. hyper clientを初期化する。X509のDPSが構成されている場合はHSMからデバイスID証明書を取得し、Hyper Clientに設定する。(prepare_httpclient_and_identity_data
  10. ホームディレクトリにcacheサブディレクトリを作成する。
  11. ホームディレクトリのhybrid_idサブディレクトリを使用して、X509のDPSの設定を行う(prepare_master_hybrid_identity_key
    • DPSのX509認証構成場合、ハイブリッドアイデンティティ鍵を取得または作成する(get_or_create_hybrid_identity_key)。
      1. キーを読み取る(get_hybrid_identity_key_inner)。
        1. ファイル{ホームディレクトリ}/hybrid_id/iotedge_hybrid_keyの内容を鍵として読み取る。
        2. ファイル{ホームディレクトリ}/hybrid_id/iotedge_hybrid_ivの内容をIVとして読み取る。
        3. IVの長さが16バイトであることを検証する。
        4. クライアントIDを$iotedgeにして、IVを渡して、暗号APIのdecryptを呼び出して鍵を復号する。
        5. 復号した鍵(マスターキー)の長さが32バイトであることを確認する。
      2. キーの取得に失敗した場合、ハイブリッドアイデンティティ鍵を作成する(create_hybrid_identity_key)。
        1. ディレクトリ{ホームディレクトリ}/hybrid_id/を作成する。
        2. 暗号APIのget_random_bytesを呼び出し、32バイトのマスターキーを生成する。
        3. 暗号APIのget_random_bytesを呼び出し、16バイトのIVを生成する。
        4. クライアントIDを$iotedgeにして、IVを渡して、暗号APIのencryptを呼び出して鍵を暗号化する。
        5. ファイル{ホームディレクトリ}/hybrid_id/iotedge_hybrid_keyに暗号化した鍵を書き込む。
        6. ファイル{ホームディレクトリ}/hybrid_id/iotedge_hybrid_ivにIVを書き込む。
        7. マスターキーを返す。
    • DPSでない場合、{ホームディレクトリ}/hybrid_id/以下を削除する。
  12. デバイスのプロビジョニングを行う。
    • manualの場合(manual_provision()
      1. 接続文字列をパースする。
      2. キーストアとしてedge_coreクレートのMemoryKeyStoreを初期化する。
      3. プロビジョニングを実施する(provisioningクレートのManualProvisioning::provision()
        1. キーストア(MemoryKeyStore)にデバイスのprimaryアイデンティティ鍵として接続文字列のデバイスキーを保存する(activate_identity_key)。
      4. DerivedKeyStoreMemoryKeyStoreから取り出したデバイスのprimaryアイデンティティ鍵で初期化する。
    • externalの場合
      1. config.yamlprovisioning.endpointを使用して、ExternalProvisioningClient構造体(edgelet_http_exgternal_provisioning)を初期化する。
        • 内部にはexternal_provisioningクレートのAPIClient構造体をラップしている。さらに、そのAPIClient構造体は、external_provisioningクレートのapisモジュールのExternalProvisioningApiClient構造体をラップしている。
        • provisioning.endpointのベースパスが保存される。
      2. ExternalProvisioningClientをラップするExternalProvisioning構造体(provisioningクレート)を初期化する。
      3. キーストアとしてMemoryKeyStoreを初期化する。
      4. プロビジョニングを実行する(ExternalProvisioningClient::provision()
        1. {basePath}/device/provisioninginformation?api-version=2019-04-10GETリクエストを実行する。
        2. 結果のcredentialを解析し、AuthTypeCredentialSource列挙体の値に変換する。
          • authTypesymmetric-keyの場合:
            • sourcepayloadの場合:
              1. keyをBase64でデコードする。
              2. キーストア(MemoryKeyStore)にデバイスキーprimaryとして保存する(activate_identity_key)。
            • hsmの場合:
              1. 何もしない。
            • それ以外の場合:エラー
          • それ以外の場合:エラー
      5. プロビジョニングの結果のcredentials.authTypesymmetricKeyであることを検証する。x509の場合はエラーとする。
        • credentials.symmetricKey.key がある場合:
          1. keyのコピーをMemoryKeyとして初期化し、DervicedKeyStoreを初期化する。
        • credentials.symmetricKey.key がない場合:
          1. TpmKeyStore構造体を初期化する。
          2. TpmKeyStoreからデバイスのprimaryキーを取得し、そのキーでDerivedKeyStoreを初期化する。
    • dpsの場合
      • config.yamlprovisioning.attestationtpmの場合:
        1. DPSによるプロビジョニングを実行する(dps_tpm_provision)。
          1. TPMインターフェイス(hsm_rsクレートのTpm構造体)を初期化する。
            1. TPM初期化処理を呼び出す(hsm_client_tpm_init@hsm_client_tpm_select.c -> hsm_client_tpm_device_init@hsm_client_tpm_device)。なお、実装は空。
            2. インターフェイス取得処理を呼び出す(hsm_client_tpm_interface@hsm_client_tpm_select.c -> hsm_client_tpm_device_interface@hsm_client_tpm_device.c)。
            3. azure-iot-hsm-cライブラリのAPIを使用して、TPMクライアント情報を初期化する(hsm_client_tpm_create@hsm_client_tpm_device.c -> initialize_tpm_device)。
          2. TPM(HSM)ライブラリのバージョンチェックをおこなう。1.0.2でない場合はエラー終了。
          3. TPMのEK(Endosement Key)を取得する(Tpm::get_ek()
          4. TPMのSRK(Storage Root Key)を取得する(Tpm::get_srk()
          5. DPSクライアント(provisioningクレートのDpsTpmProvisioning構造体)をHyper Client、config.yamlglobal_endpointscope_idregistration_id、APIバージョン2018-11-01、TPMから取得したEKとSRKで初期化する。
          6. DPS用のキーストア(edgelet_hsmクレートのTpmKeyStore構造体)を初期化する。
          7. バックアップファイル{ホームディレクトリ}/cache/provisioning_backup.jsonを使用し、DpsTpmProvisioningをラップするBackupProvisioning構造体provisioningクレート)を初期化する。
          8. プロビジョニングを行う(BackupProvisioning::provision())。
            1. DpsTpmProvisioning::provision()を呼び出す。
              1. DpsClientDpsAuthKind::TpmとEK、SRKで初期化する。
              2. DpsClient::register()を呼び出す。
                1. TPM固有の登録処理を実行する(register_with_tpm_auth)。
                  1. /{scopeId}/registrations/{registrationId}/registerに対して、registrationIdtpm.endorsementKeytpm.storageRootKeyを持つJSONをボディにして、PUTリクエストを送信する。結果は401になるはず。
                  2. TPMチャレンジ鍵を取得する(get_tpm_challenge_key
                    1. レスポンスのauthenticationKeyをバイト列にデコードし、キーストア(TpmKeyStore)に、デバイスのparimaryアイデンティティ鍵として保存する(activate_identity_key)。
                      • 実装としては、hsm_client_tpm_activate_identity_key@hsm_client_tpm_device.c
                    2. キーストア(TpmKeyStore)から、デバイスのparimaryアイデンティティ鍵を取得する(get)。
                      • 実体はTPMのラッパーであるTpmKeyが返る。
                  3. scopeIdregistrationId、チャレンジ鍵からDpsTokenSourceを初期化する。
                  4. 引数とDpsTokenSourceをもとに、もう一度/{scopeId}/registrations/{registrationId}/registerに対して、registrationIdtpm.endorsementKeytpm.storageRootKeyを持つJSONをボディにして、先ほど作成したDpsTokenSourceで生成するSASトークンを使用して、PUTリクエストを送信する(get_operation_id())。
                    • ここで、SASトークンの署名には、DpsTokenSourceのチャレンジ鍵(TpmKey)のSign::signトレイトメソッドの実装が使用される。これは、hsm_rsクレートのTpm::sign_with_identity()メソッドに処理を委譲し、hsm_client_tpm_sign_data@hsm_client_tpm_device経由でTPMによる署名が行われる。
                2. キーストア(TpmKeyStore)からデバイスのprimaryアイデンティティ鍵を取得する。
                3. 取得したキーを使用して、DpsTokenSourceを初期化する。
                4. DPSの結果を構築する(get_device_registration_result)。
                  1. 先ほど構築したDpsTokenSourceで生成するSASトークンを使用して、/{scopeId}/registrations/{registrationId}/operations{operationId}GETリクエストを送信し(operationIdPUTの結果に含まれている)、DPSの結果を取得する(get_operation_status)。
                5. TPM固有の後処理を実行する。
                  1. DPSの戻り値のauthentication_keyをバイト列にデコードし、キーストア(TpmKeyStore)に、デバイスのparimaryアイデンティティ鍵として保存する(activate_identity_key)。
                6. DPS処理の戻り値を応答から生成する(get_device_info)。
              3. ProvisioningResultを構築する。
                • device_id:DPSから返されたデバイスID。
                • hub_name:DPSから返されたIoT Hubの名前。
                • reconfigure:常にReprovisioningStatus::InitialAssignment
                • sha256_thumbprint:常にNone
                • credentials:常にNone
            2. 結果をバックアップファイルに記録する。
            3. TpmKeyStoreからデバイスのprimary鍵を取り出し、DervicedKeyStoreを初期化する。
      • config.yamlprovisioning.attestationsymmetric_keyの場合:
        1. DPSによるプロビジョニングを実行する(dps_symmetric_key_provision)。
          1. キーストア(edgelet_coreクレートのMemoryKeyStore構造体)を初期化する。
          2. config.yamlattestation.symmetric_keyをバイト列にデコードし、デバイスのprimaryキーとしてキーストアに格納する(activate_identity_key)。
          3. DPSクライアント(provisioningクレートのDpsSymmetricKeyProvisioning構造体)をHyper Client、config.yamlglobal_endpointscope_idregistration_id、APIバージョン2018-11-01で初期化する。
          4. バックアップファイル{ホームディレクトリ}/cache/provisioning_backup.jsonを使用し、DpsSymmetricKeyProvisioningをラップするBackupProvisioning構造体provisioningクレート)を初期化する。
          5. プロビジョニングを行う(BackupProvisioning::provision())。
            1. DpsSymmetricKeyProvisioning::provision()を呼び出す。
              1. DpsClientDpsAuthKind::SymmetricKeyで初期化する。
              2. DpsClient::register()を呼び出す。
                1. 対象鍵固有の登録処理を実行する(register_with_symmetric_key_auth)。
                  1. キーストア(MemoryKeyStore)からチャレンジ鍵として対称鍵を取得する(get_symmetric_challenge_key
                  2. scopeIdregistrationId、チャレンジ鍵からDpsTokenSourceを初期化し、DpsClientに設定する。
                  3. /{scopeId}/registrations/{registrationId}/registerに対して、registrationIdを持つJSONをボディにして、PUTリクエストを送信する。
                2. キーストア(MemoryKeyStore)からデバイスのprimaryアイデンティティ鍵を取得する。
                3. 取得したキーを使用して、DpsTokenSourceを初期化する。
                4. DPSの結果を構築する(get_device_registration_result)。
                  1. 先ほど構築したDpsTokenSourceで生成するSASトークンを使用して、/{scopeId}/registrations/{registrationId}/operations{operationId}GETリクエストを送信し(operationIdPUTの結果に含まれている)、DPSの結果を取得する(get_operation_status)。
                5. DPS処理の戻り値を応答から生成する(get_device_info)。
              3. ProvisioningResultを構築する。
                • device_id:DPSから返されたデバイスID。
                • hub_name:DPSから返されたIoT Hubの名前。
                • reconfigure:DPSから返されたsubstatusの値。ない場合はReprovisioningStatus::InitialAssignment
                • sha256_thumbprint:常にNone
                • credentials:常にNone
            2. 結果をバックアップファイルに記録する。
            3. MemoryKeyStoreからデバイスのprimary鍵を取り出し、DervicedKeyStoreを初期化する。
      • config.yamlprovisioning.attestationx509の場合:
        1. DPSによるプロビジョニングを実行する(dps_x509_provision)。
          1. キーストア(edgelet_coreクレートのMemoryKeyStore構造体)を初期化する。
          2. ハイブリッドアイデンティティ鍵をデバイスのprimaryキーとしてキーストアに格納する(activate_identity_key)。
          3. DPSクライアント(provisioningクレートのDpsX509Provisioning構造体)をHyper Client、config.yamlglobal_endpointscope_idregistration_id(未指定の場合は証明書のCN)、APIバージョン2018-11-01で初期化する。
          4. バックアップファイル{ホームディレクトリ}/cache/provisioning_backup.jsonを使用し、DpsTpmProvisioningをラップするBackupProvisioning構造体provisioningクレート)を初期化する。
          5. プロビジョニングを行う(BackupProvisioning::provision())。
            1. DpsX509Provisioning::provision()を呼び出す。
              1. DpsClientDpsAuthKind::X509で初期化する。
              2. DpsClient::register()を呼び出す。
                1. X509固有の登録処理を実行する(register_with_x509_auth)。
                  1. /{scopeId}/registrations/{registrationId}/registerに対して、registrationIdを持つJSONをボディにして、PUTリクエストを送信する。認証はHyper Clientに設定されたデバイスID証明書による。
                2. DPSの結果を構築する(get_device_registration_result)。
                  1. /{scopeId}/registrations/{registrationId}/operations{operationId}GETリクエストを送信し(operationIdPUTの結果に含まれている)、DPSの結果を取得する(get_operation_status)。認証はHyper Clientに設定されたデバイスID証明書による。
                3. DPS処理の戻り値を応答から生成する(get_device_info)。
              3. ProvisioningResultを構築する。
                • device_id:DPSから返されたデバイスID。
                • hub_name:DPSから返されたIoT Hubの名前。
                • reconfigure:DPSから返されたsubstatusの値。ない場合はReprovisioningStatus::InitialAssignment
                • sha256_thumbprint:常にNone
                • credentials:常にNone
            2. 結果をバックアップファイルに記録する。
            3. DerivedKeyStoreを初期化する(prepare_derived_hybrid_key)。
              1. MemoryKeyStoreからデバイスのprimary鍵を取り出す。
              2. {hubName}/devices/{deviceId}/{x509CertThumbprint}という文字列を、取得した鍵を使用してHMACSHA256アルゴリズムで署名してダイジェストを取得する。
              3. ダイジェストをMemoryKeyとしてDervicedKeyStoreを初期化する。
        2. デバイスID証明書のX509サムプリントを起動引数に渡す。
  13. 起動する
    1. ランタイムのmake_runtimeメソッドを呼び出す。
      • DockerModuleRuntimeの場合
        1. config.yamlからDocker APIのURIとネットワーク名、IPv6設定、IPAM設定を取得する。
        2. Docker APIのnetwork listで既存のネットワークがあるかどうかを確認する。
        3. ネットワークがないならば、Docker APIのnetwork createでネットワークを作成する。
    2. DPSで再プロビジョニングが指定された場合、再プロビジョニングを行う。
    3. 構成が前回起動時から変更されていないかをチェックする(check_settings_state
      1. ホームディレクトリのcacheサブディレクトリにあるsettings_stateのSHA256ダイジェストと、config.yamlの内容およびX509サムプリントを連結した値のSHA256ダイジェストを比較する。
      2. ダイジェストが異なっている場合、またはワークロードCA証明書の署名元であるデバイスCA証明書有効期限が残り5分未満の場合、ランタイム構成の再構成を行う(reconfigure)。
        1. ランタイムAPIremove_allを呼び出して、全てのモジュールを削除する。
        2. ホームディレクトリのcacheサブディレクトリを削除する。
        3. HSMでワークロードCA証明書を再生成する。
    4. ワークロードデータを初期化する。プロビジョニングの結果からIoT Hubの名前とデバイスID、デバイスID証明書の最大有効期限(2時間)、サーバー証明書の最大有効期限(90日)を設定する。
    5. APIの実行を開始する(start_api)。なお、これは
      1. IoT HubへのHTTPS接続用のDeviceClientを初期化する。
      2. IoT Hubベースのモジュールアイデンティティ管理用のHubIdentityManagerを初期化する。
      3. サーバー証明書用のパラメーターCertificateProperties(有効期限90日、サーバー証明書、CNはiotedged)を使用して、CertificateManagerを初期化する。
      4. サーバー証明書の期限切れ対応処理のタイマーを設定する(CertificateManager::schedule_expiration_timer)。サーバー証明書の期限が切れると、各APIは一度シャットダウンされ、start_apiがもう一度実行される。期限は、サーバー証明書の期限までの猶予時間の95%。
      5. edgelet_http_mangaement::serverモジュールのManagementServiceを作成し、マネジメントAPIを開始する。
      6. edgelet_http_workload::serverモジュールのWorkloadServiceを作成し、マネジメントAPIを開始する。
      7. Watchdog経由で$edgeAgentモジュールを作成、実行する。内部的には、レジストリAPIpullとランタイムAPIcreatestartを呼び出して開始される(start_runtime
        1. config.yamlagentを読み込む。
        2. agent.envに指定された環境変数に対して以下のように値をマージする(build_env)。
          • IOTEDGE_IOTHUBHOSTNAMEにプロビジョニングの結果のIoT Hubホスト名。
          • EDGEDEVICEHOSTNAMEconfig.yamlhostnameの値。
          • IOTEDGE_DEVICEIDにプロビジョニングの結果のデバイスID。
          • IOTEDGE_MODULEID$edgeAgent
          • IOTEDGE_WORKLOADURIconfig.yamlconnect.workload_uriの値。
          • IOTEDGE_MANAGEMENTURIconfig.yamlconnect.management_uriの値。
          • IOTEDGE_AUTHSCHEMEsasToken
          • Modeiotedged
          • configy.yamlagent.envに指定された値。
          • IOTEDGE_APIVERSION2019-01-30
        3. Watchdog::run_untilを呼び出す(処理はstart_watchdog)。
          1. 60秒ごとに状態を監視する(監視内容はcheck_runtime)。
            1. EdgeAgentの状態を取得する(get_edge_runtime_mod)。
              1. ランタイムAPIのlist()を呼び出し、その結果をフィルタリングする。
              2. 状態に応じて以下のいずれかを行う。
                • モジュールが存在し、状態がruningであれば、情報ログを出力する。
                • モジュールが存在し、状態がrunningでなければ、情報ログを出力し、ランタイムAPIのstart()を呼び出して開始する。
                • モジュールが存在しないならば、作成する(create_and_start
                  1. 情報ログを出力する。
                  2. アイデンティティマネージャーAPIのgetを呼び出し、IoT Hubから$edgeAgentの情報を取得する。
                  3. アイデンティティマネージャーAPIのupdateを呼び出し、$edgeAgentに認証情報を設定する。
                  4. 環境変数IOTEDGE_MODULEGENERATIONIDに生成IDを設定する。
                  5. imagePullPolicyonCreateならば、レジストリAPIのpull()を呼び出す。
                  6. ランタイムAPIのcreate()を呼び出す。
                  7. ランタイムAPIのstart()を呼び出す。

外部プロビジョニング

レスポンスボディ
{
    "required": ["hubName", "deviceId", "credentials"],
    "properties": {
        "hubName": {
            "type": "string",
            "description": "IoT Hubのホスト名。"
        },
        "deviceId": {
            "type": "string",
            "description": "IoT HubでのデバイスのID。"
        },
        "credentials": {
            "type": "object",
            "required": ["authType", "source"],
            "properties": {
                "authType": {
                    "type": "string",
                    "enum": ["symmetric-key", "x509"],
                    "description": "使用される認証資格情報の種類を示します。"
                },
                "source": {
                    "type": "string",
                    "enum": ["payload"],
                    "description": "認証資格情報のソースを示します。"
                },
                "key": {
                    "type": ["string", "null"],
                    "default": null,
                    "description": "認証に使用されるキー。'authType'が'symmetric-key'で、'source'が'payload'の場合にのみ指定されます。"
                },
                "identityCert": {
                    "type": ["string", "null"],
                    "default": null,
                    "description": "アイデンティティ証明書。'authType'が'x509'で'source'が'payload'の場合はPEMフォーマットのバイト列となり、'authType'が'x509'で'source'が'hsm'の場合は証明書への参照となるはずです。"
                },
                "identityPrivateKey": {
                    "type": ["string", "null"],
                    "default": null,
                    "description": "アイデンティティ証明書の秘密鍵。'authType'が'x509'で'source'が'payload'の場合はPEMフォーマットのバイト列となり、'authType'が'x509'で'source'が'hsm'の場合は秘密鍵への参照となるはずです。"
                }
            }
        }
    }
}

API

IoT Edgeセキュアデーモンは、IoT Edgeモジュール向けにRESTライクなAPIを公開している。モジュールは、Unix Domain Socket経由のHTTPS通信で、JSONを使用したAPI呼び出しを実行できる。

認証と認可

IoT EdgeセキュアデーモンのAPIの認証と認可は、PIDベースで行われている。その概要は以下のとおり。

  1. Unix Domain Socketから呼び出し元のPIDを取得する。
  2. edgelet_http::authenticationモジュールにより、認証が行われる。
  • Policy::AnonymousなAPIの場合、AuthId::Anyになる。
  • Policy::ModuleなAPIの場合、ランタイムのauthenticateに処理を委譲する。
    • Pid::Noneならば、AuthId::Noneになる。
    • Pid::Anyならば、AuthId::Anyになる。
    • Pid::Value(pid)ならば、モジュール名による認証が行われる。呼び出し元のモジュールIDのPIDをランタイムのlistAPIで取得し、一致していればAuthId::Value(module)、そうでなければAuthId::Noneになる。
  1. edgelet_http::authorizeモジュールにより、認可が行われる。
  • Policy::AnonymousなAPIの場合、常に認可は成功する。
  • Policy::ModuleなAPIの場合、AuthIdを見て、AuthId::Anyならば認可は成功する。AuthId::Value(module)の場合、認証済みモジュールが呼び出し元モジュール名と一致(認証処理でこの呼び出し先モジュール名からAuthId::Value(module)を作成しているのだから、これは常に一致するはず)すれば認可は成功する。それ以外の場合、HTTP 404になる。

マネジメントAPI

edgelet_http_mgmt クレートにAPI、edgelet_coreクレートにトレイト、実装はedgelet_dockerクレートにあるランタイムAPIとレジストリAPIである(将来的にはedgelet_kubeクレートにも実装ができるだろう)。

modules

list modules
GET /modules?api-version={apiVersion}
  • 認証/認可
    • 匿名呼び出し可能
  • クエリパラメーター
    • api_version:APIのバージョン(既定値は 2018-06-28
  • レスポンスボディ
    • 後述
レスポンスボディ
{
    "type": "array",
    "items": {
        "type" : "object",
        "properties": {
            "id": {
                "type": "string",
                "description": "システムが生成した一意な識別子。"
            },
            "name": {
                "type": "string",
                "description": "モジュールの名前。"
            },
            "type": {
                "type": "string",
                "description": "モジュールの種類。"
            },
            "config": {
                "type": "object",
                "required": ["settings"],
                "properties": {
                    "settings": {
                        "type": "object",
                        "properties": {
                            "image": {
                                "type": "string"
                            },
                            "imageHash": {
                                "type": ["string", "null"],
                                "default": null
                            },
                            "createOptions": {
                                "type": ["object", "null"],
                                "description": "DockerのCreate APIに渡す引数。https://docs.docker.com/engine/api/v1.40/#operation/ContainerCreate を参照してください。"
                            },
                            "auth": {
                                "type": ["object", "null"],
                                "default": null,
                                "properties": {
                                    "username": {
                                        "type": ["string", "null"],
                                        "default": null
                                    },
                                    "password": {
                                        "type": ["string", "null"],
                                        "default": null
                                    },
                                    "email": {
                                        "type": ["string", "null"],
                                        "default": null
                                    },
                                    "serveraddress": {
                                        "type": ["string", "null"],
                                        "default": null
                                    }
                                }
                            }
                        }
                    },
                    "env": {
                        "type": ["array", "null"],
                        "default": null,
                        "items": {
                            "type": "object",
                            "properties": {
                                "key": {
                                    "type": "string"
                                },
                                "value": {
                                    "type": "string"
                                }
                            }
                        }
                    }
                }
            },
            "status": {
                "startTime": {
                    "type": "string"
                },
                "exitStatus": {
                    "type": "object",
                    "properties": {
                        "exitTime": {
                            "type": "string"
                        },
                        "statusCode": {
                            "type": "string"
                        }
                    }
                },
                "runtimeStatus": {
                    "type": "object",
                    "properties": {
                        "status": {
                            "type": "string"
                        },
                        "description": {
                            "type": "string"
                        }
                    }
                }
            }
        }
    }
}

IoT Edgeモジュールの情報を列挙する。ランタイムAPIlist_with_detailsを呼び出す。

なお、Dockerベースの実装におけるstatusの値は、Dockerのcontainer inspect APIの戻り値のプロパティの値である。詳細は以下のとおり。

APIの戻り値のプロパティ Dockerのcontainer inspect APIの戻り値のプロパティ
startTime startedAt
exitStatus.exitTime finishedAt
exitStatus.statusCode exitCode
runtimeStatus.status statusexitCodeから算出された値。詳細後述。
runtimeStatus.description status

runtimeStatus.statusはDockerのcontainer inspect APIの戻り値の値に応じて以下のようになる。

Dockerのcontainer inspect APIの戻り値のstatusプロパティの値|Dockerのcontainer inspect APIの戻り値のexitCodeの値|runtimeStatus.statusの値 running|(任意)|running created|(任意)|stopped paused|(任意)|stopped restring|(任意)|stopped removing|0|stopped removing|0以外|failed dead|0|stopped dead|0以外|failed exited|0|stopped exited|0以外|failed (上記以外)|(任意)|unknown

create module
POST /modules?api-version={apiVersion}
  • 認証/認可
    • Edge Agentのみ呼び出し可能
  • クエリパラメーター
    • api_version:APIのバージョン(既定値は 2018-06-28
  • リクエストボディ
    • 後述
  • レスポンスボディ
    • 後述
  • 呼び出し元
    • $edgeAgent
      • CreateCommand
リクエストボディ
{
    "required": ["name", "type", "config"],
    "properties": {
        "name": {
            "type": "string",
            "description": "モジュールの名前。"
        },
        "type": {
            "type": "string"
        },
        "config": {
            "type": "object",
            "required": ["settings"],
            "properties": {
                "settings": {
                    "type": "object",
                    "properties": {
                        "image": {
                            "type": "string"
                        },
                        "imageHash": {
                            "type": ["string", "null"],
                            "default": null
                        },
                        "createOptions": {
                            "type": ["object", "null"],
                            "description": "DockerのCreate APIに渡す引数。https://docs.docker.com/engine/api/v1.40/#operation/ContainerCreate を参照してください。"
                        },
                        "auth": {
                            "type": ["object", "null"],
                            "default": null,
                            "properties": {
                                "username": {
                                    "type": ["string", "null"],
                                    "default": null
                                },
                                "password": {
                                    "type": ["string", "null"],
                                    "default": null
                                },
                                "email": {
                                    "type": ["string", "null"],
                                    "default": null
                                },
                                "serveraddress": {
                                    "type": ["string", "null"],
                                    "default": null
                                }
                            }
                        }
                    }
                },
                "env": {
                    "type": ["array", "null"],
                    "default": null,
                    "items": {
                        "type": "object",
                        "properties": {
                            "key": {
                                "type": "string"
                            },
                            "value": {
                                "type": "string"
                            }
                        }
                    }
                }
            }
        },
        "imagePullPolicy": {
            "type": ["string", "null"],
            "default": null,
            "enum": ["on-create", "never"]
        }
    }
}
レスポンスボディ
{
    "properties": {
        "id": {
            "type": "string",
            "description": "システムが生成した一意な識別子。"
        },
        "name": {
            "type": "string",
            "description": "モジュールの名前。"
        },
        "type": {
            "type": "string",
            "description": "モジュールの種類。"
        },
        "config": {
            "type": "object",
            "required": ["settings"],
            "properties": {
                "settings": {
                    "type": "object",
                    "properties": {
                        "image": {
                            "type": "string"
                        },
                        "imageHash": {
                            "type": ["string", "null"],
                            "default": null
                        },
                        "createOptions": {
                            "type": ["object", "null"],
                            "description": "DockerのCreate APIに渡す引数。https://docs.docker.com/engine/api/v1.40/#operation/ContainerCreate を参照してください。"
                        },
                        "auth": {
                            "type": ["object", "null"],
                            "default": null,
                            "properties": {
                                "username": {
                                    "type": ["string", "null"],
                                    "default": null
                                },
                                "password": {
                                    "type": ["string", "null"],
                                    "default": null
                                },
                                "email": {
                                    "type": ["string", "null"],
                                    "default": null
                                },
                                "serveraddress": {
                                    "type": ["string", "null"],
                                    "default": null
                                }
                            }
                        }
                    }
                },
                "env": {
                    "type": ["array", "null"],
                    "default": null,
                    "items": {
                        "type": "object",
                        "properties": {
                            "key": {
                                "type": "string"
                            },
                            "value": {
                                "type": "string"
                            }
                        }
                    }
                }
            }
        },
        "status": {
            "startTime": {
                "type": "string"
            },
            "exitStatus": {
                "type": "object",
                "properties": {
                    "exitTime": {
                        "type": "string"
                    },
                    "statusCode": {
                        "type": "string"
                    }
                }
            },
            "runtimeStatus": {
                "type": "object",
                "properties": {
                    "status": {
                        "type": "string"
                    },
                    "description": {
                        "type": "string"
                    }
                }
            }
        }
    }
}

IoT Edgeモジュールを作成する。

  1. imagePullPolicyonCreateの場合はレジストリAPIpullを呼び出す。
  2. ランタイムAPIcreateを呼び出す。
get module
GET /modules/{name}?api-version={apiVersion}
  • 認証/認可
    • 匿名呼び出し可能
  • クエリパラメーター
    • api_version:APIのバージョン(既定値は 2018-06-28
    • name:モジュールの名前(URLエンコード済み)

IoT Edgeモジュールの情報を取得する。現状は何もしないで空の結果を返す。

update module
PUT /modules/{name}?api-version={apiVersion}[&start=true]
  • 認証/認可
    • Edge Agentのみ呼び出し可能
  • クエリパラメーター
    • api_version:APIのバージョン(既定値は 2018-06-28
  • リクエストボディ
    • create module を参照
  • レスポンスボディ
    • create module を参照
  • 呼び出し元
    • $edgeAgent
      • UpdateCommand

アップデート。クエリ文字列でstartを指定すると同時に起動する。

  1. ランタイムAPI remove を呼び出す。
  2. imagePullPolicyonCreateの場合はレジストリAPI pull を呼び出す。
  3. ランタイムAPIcreateを呼び出す。
  4. startが指定されている場合は、ランタイムAPIstartを呼び出す。
prepare update module
POST /modules/{name}/prepareupdate?api-version={apiVersion}
  • 認証/認可
    • Edge Agentのみ呼び出し可能
  • クエリパラメーター
    • api_version:APIのバージョン(既定値は 2019-01-30
  • リクエストボディ
    • create module を参照
  • レスポンスボディ
    • なし
  • 呼び出し元
    • $edgeAgent
      • UpdateCommand

アップデートの準備を行う。具体的には、imagePullPolicyonCreateの場合はレジストリAPIpullを呼び出す。

delete module
DELETE /modules/{name}?api-version={apiVersion}
  • 認証/認可
    • Edge Agentのみ呼び出し可能
  • クエリパラメーター
    • api_version:APIのバージョン(既定値は 2018-06-28
  • リクエストボディ
    • なし
  • レスポンスボディ
    • なし
  • 呼び出し元
    • $edgeAgent
      • RemoveCommand

IoT Edgeモジュールを削除する。引数はモジュールの名前。

ランタイムAPIremoveを呼び出す。

start module
POST /modules/{name}/start?api-version={apiVersion}
  • 認証/認可
    • 匿名呼び出し可能
  • クエリパラメーター
    • api_version:APIのバージョン(既定値は 2018-06-28
  • リクエストボディ
    • なし
  • レスポンスボディ
    • なし
  • 呼び出し元
    • $edgeAgent
      • StartCommand

IoT Edgeのモジュールの開始を行う。引数は対象のモジュール名。ランタイムAPIstartを呼び出す。

stop module
POST /modules/{name}/stop?api-version={apiVersion}
  • 認証/認可
    • 匿名呼び出し可能
  • クエリパラメーター
    • api_version:APIのバージョン(既定値は 2018-06-28
  • リクエストボディ
    • なし
  • レスポンスボディ
    • なし
  • 呼び出し元
    • $edgeAgent
      • UpdateCommand
      • StopCommand

IoT Edgeのモジュールの停止を行う。引数は対象のモジュール名。ランタイムAPIstopを呼び出す(wait_before_kill引数にはNoneを渡す)。

restart module
POST /modules/{name}/restart?api-version={apiVersion}
  • 認証/認可
    • 匿名呼び出し可能
  • クエリパラメーター
    • api_version:APIのバージョン(既定値は 2018-06-28
  • リクエストボディ
    • なし
  • レスポンスボディ
    • なし
  • 呼び出し元
    • $edgeAgent
      • RestartCommand

IoT Edgeのモジュールの再起動を行う。引数は対象のモジュール名。ランタイムAPIrestartを呼び出す。

module logs
POST /modules/{name}/logs?api-version={apiVersion}&tail={(all|integer)}&follow={bool}&since={integer}
  • 認証/認可
    • 匿名呼び出し可能
  • クエリパラメーター
    • api_version:APIのバージョン(既定値は 2018-06-28
    • tail:末尾何行を対象にするか。既定値はall
    • follow:ログを返した後もDocker APIとの接続を維持する場合はtrue。既定値はfalse
    • since:開始タイムスタンプ(Unixタイムスタンプ)。既定値は0
  • リクエストボディ
    • なし
  • レスポンスボディ
    • Dockerログのストリーム。

モジュールのログを取得する。引数はモジュール名とその他のオプション群。ランタイムAPIlogsを呼び出す。

identities API

list identities
GET /identities?api-version={apiVersion}
  • 認証/認可
    • Edge Agentのみ呼び出し可能
  • クエリパラメーター
    • api_version:APIのバージョン(既定値は 2018-06-28
  • リクエストボディ
    • なし
  • レスポンスボディ
    • 後述
レスポンスボディ
{
    "type": "array",
    "items": {
        "type": "object",
        "properties": {
            "moduleId": {
                "type": "string"
            },
            "managedBy": {
                "type": "string"
            },
            "generationId": {
                "type": "string"
            },
            "authType": {
                "type": "string"
            }
        }
    }
}

モジュールアイデンティティを列挙する。アイデンティティマネージャーのlistAPIを呼び出し、その結果を整形して返す。

create identity
POST /identities?api-version={apiVersion}
  • 認証/認可
    • Edge Agentのみ呼び出し可能
  • クエリパラメーター
    • api_version:APIのバージョン(既定値は 2018-06-28
  • リクエストボディ
    • 後述
  • レスポンスボディ
    • 後述
リクエストボディ
{
    "required": ["moduleId"],
    "properties": {
        "moduleId": {
            "type": "string",
            "description": "作成するモジュールアイデンティティのID。"
        },
        "managedBy": {
            "type": ["string", "null"],
            "default": null,
            "description": "作成するモジュールアイデンティティを管理するモジュールのID。"
        }
    }
}
レスポンスボディ
 {
    "type": "object",
    "properties": {
        "moduleId": {
            "type": "string",
            "description": "このモジュールアイデンティティのID。"
        },
        "managedBy": {
            "type": "string",
            "description": "このモジュールアイデンティティを管理するモジュールアイデンティティのID。"
        },
        "generationId": {
            "type": "string",
            "description": "アイデンティティマネージャーがこのモジュールアイデンティティに付与したID。"
        },
        "authType": {
            "type": "string",
            "enum": ["none", "sas", "x509"],
            "description": "このモジュールアイデンティティの認証種別。"
        }
    }
}

モジュールアイデンティティを作成する。引数としてスペックを受け取る。アイデンティティマネージャーのロックを取り、createAPIを呼び出し、その結果を整形して返す。

なお、現在authTypeは常にsasになる。

update identity
PUT /identities/{name}?api-version={apiVersion}
  • 認証/認可
    • Edge Agentのみ呼び出し可能
  • クエリパラメーター
    • api_version:APIのバージョン(既定値は 2018-06-28
  • リクエストボディ
    • 後述
  • レスポンスボディ
    • create identityと同じ
リクエストボディ
{
    "required": ["moduleId"],
    "properties": {
        "moduleId": {
            "type": "string",
            "description": "更新対象のモジュールアイデンティティのID。"
        },
        "generationId": {
            "type": "string",
            "description": "モジュールアイデンティティのシステム側生成ID。"
        },
        "managedBy": {
            "type": ["string", "null"],
            "default": null,
            "description": "モジュールアイデンティティを管理するモジュールのID。"
        }
    }
}

モジュールアイデンティティを更新する。引数としてスペックを受け取る。アイデンティティマネージャーのロックを取り、updateAPIを呼び出す。

delete identity
DELETE /identities/{name}?api-version={apiVersion}
  • 認証/認可
    • Edge Agentのみ呼び出し可能
  • クエリパラメーター
    • api_version:APIのバージョン(既定値は 2018-06-28
  • リクエストボディ
    • なし
  • レスポンスボディ
    • なし

モジュールアイデンティティを削除する。引数としてモジュール名を受け取る。アイデンティティマネージャーのロックを取り、deleteAPIを呼び出す。

system info API

get system info
GET /systeminfo?api-version={apiVersion}
  • 認証/認可
    • 匿名呼び出し可能
  • クエリパラメーター
    • api_version:APIのバージョン(既定値は 2018-06-28
  • リクエストボディ
    • なし
  • レスポンスボディ
    • 後述
レスポンスボディ
{
    "properties": {
        "osType": {
            "type": "string",
            "enum": ["linux", "windows"],
            "description": "ランタイムが認識しているOSの種別。"
        },
        "architecture": {
            "type": "string",
            "enum": ["386", "amd64", "arm", "arm64"],
            "description": "ランタイムが認識しているCPUのアーキテクチャ。"
        },
        "version": {
            "type": "string",
            "description": "IoT Edgeセキュアデーモンのバージョン情報。"
        }
    }
}

システム情報を取得する。ランタイムのsystem_infoAPIを呼び出し、結果を整形して返す。システム情報とは、OSの種類、CPUアーキテクチャ、バージョン。

OSの種別とCPUアーキテクチャは、現在のDockerの実装ではGo言語の実装に依存している。詳細は https://golang.org/doc/install/source#environment を参照のこと。

バージョンは、ビルド時にビルドツールによって埋め込まれるバージョンで、${VERSION} (${BUILD_SOURCEVERSION})の形式。VERSIONBUILD_SOURCEVERSIONは環境変数である。VERSIONversion.txtの値。BUILD_SOURCEVERSIONはCIツール(おそらくはAzure Pipeline)が埋め込むgitのコミットハッシュと思われる。

ワークロードAPI

モジュール間認証に関わるAPI群である。

list modules

GET /modules?api-version={apiVersion}

マネジメントAPIのGET /modulesと同一である。

sign

POST /modules/{name}/genid/{genid}/sign?api-version={apiVersion}
  • 認証/認可
    • IoT Edgeモジュールからのみ呼び出し可能
  • クエリパラメーター
    • api_version:APIのバージョン(既定値は 2018-06-28
  • リクエストボディ
    • 後述
  • レスポンスボディ
    • 後述
リクエストボディ
{
    "required": ["keyId", "algo", "data"],
    "properties": {
        "keyId": {
            "type": "string",
            "description": "署名を行うキーの名前。"
        },
        "algo": {
            "type": "string",
            "description": "使用する署名アルゴリズム。"
        },
        "data": {
            "type": "string",
            "format": "base64",
            "description": "署名対象のデータ。"
        }
    }
}
レスポンスボディ
{
    "properties": {
        "digest": {
            "type": "string",
            "format": "base64"
        }
    }
}

データを指定されたキーで署名して返す。なお、リクエストボディのalgoは無視される。

  1. キーIDにURLコンポーネントgenidを連結する。
  2. キーストア(KeyStoreトレイトオブジェクト、実装はedgelet_coreクレートのDerivedKeyStore構造体)から、モジュールID(URLコンポーネントname)と先ほど連結したキーIDを渡し、キー(MemoryKeyトレイトオブジェクト、実装はedgelet_coreクレートのMemoryKey構造体)のsignメソッドを呼び出す。アルゴリズムはHMACSHA256で、対象はBase64デコードしたリクエストボディ。このHMAC処理はRustのhmacクレートの実装を使用する。
    • DervicedKeyStore は、以下のようにして都度MemoryKeyを生成する。
      1. モジュールIDとキー名を連結したもの(デバイスに対するキーを求められた場合はキー名のみ)をHMACSHA256で署名する(この署名処理はMemoryKeyの実装によるので、Rustのhmacクレートの実装である)。この時使用するキーは、デバイス接続文字列のデバイスキーである。
    • すなわち、署名処理とは、「{moduleId}{keyId}{genId}に対してデバイス接続文字列のキーをキーとしてHMACSHA256署名したもの」をキーとして、任意のデータをHMACSHA256署名することである。
  3. 結果をBase64エンコードする。

呼び出し元:

  • ModuleClient(Device SDK for .NET)

decrypt

POST /modules/{name}/genid/{genid}/decript?api-version={apiVersion}
}
  • 認証/認可
    • IoT Edgeモジュールからのみ呼び出し可能
  • クエリパラメーター
    • api_version:APIのバージョン(既定値は 2018-06-28
  • リクエストボディ
    • 後述
  • レスポンスボディ
    • 後述
  • 呼び出し元
    • $edgeAgent
      • EncryptionProvider
        • バックアップ配置マニフェストの復号
        • RocksDB内の配置マニフェストの復号
リクエストボディ
{
    "required": ["ciphertext", "initializationVector"],
    "properties": {
        "ciphertext": {
            "type": "string",
            "format": "base64",
            "descritpion": "復号対象のデータ。"
        },
        "initializationVector": {
            "type": "string",
            "format": "base64",
            "description": "データの復号に使用される初期化ベクター。"
        }
    }
}
レスポンスボディ
{
    "properties": {
        "plaintext": {
            "type": "string",
            "format": "base64",
            "descritpion": "Base64エンコードされた、復号後のデータ。"
        }
    }
}

暗号化されたデータを復号する。暗号化APIのdecryptを呼び出す。その引数で渡すクライアントIDは{name}{genId}である。

encrypt

POST /modules/{name}/genid/{genid}/encrypt?api-version={apiVersion}
}
  • 認証/認可
    • IoT Edgeモジュールからのみ呼び出し可能
  • クエリパラメーター
    • api_version:APIのバージョン(既定値は 2018-06-28
  • リクエストボディ
    • 後述
  • レスポンスボディ
    • 後述
  • 呼び出し元
    • $edgeAgent
      • EncryptionProvider
        • バックアップ配置マニフェストの暗号化
        • RocksDB内の配置マニフェストの暗号化
リクエストボディ
{
    "required": ["plaintext", "initializationVector"],
    "properties": {
        "plaintext": {
            "type": "string",
            "format": "base64",
            "descritpion": "暗号化対象のデータ。"
        },
        "initializationVector": {
            "type": "string",
            "format": "base64",
            "description": "データの暗号化に使用される初期化ベクター。"
        }
    }
}
レスポンスボディ
{
    "properties": {
        "ciphertext": {
            "type": "string",
            "format": "base64",
            "descritpion": "Base64エンコードされた、暗号化後のデータ。"
        }
    }
}

データを暗号化する。暗号化APIのencryptを呼び出す。その引数で渡すクライアントIDは{name}{genId}である。

create identity cert

POST /modules/{name}/certificate/identity?api-version={apiVersion}
  • 認証/認可
    • IoT Edgeモジュールからのみ呼び出し可能
  • クエリパラメーター
    • api_version:APIのバージョン(既定値は 2018-06-28
  • リクエストボディ
    • 後述
  • レスポンスボディ
    • 後述
リクエストボディ
{
    "properties": {
        "expiration": {
            "type": ["string", "null"],
            "format": "date-time",
            "default": null,
            "descritpion": "証明書の有効期限の日時(ISO 8601)。"
        }
    }
}
レスポンスボディ
{
    "properties": {
        "privateKey": {
            "type": "object",
            "description": "",
            "required": ["type"],
            "properties": {
                "type": {
                    "type": "string",
                    "description": "キーの形式を示します(PEMフォーマットのバイト列か参照)。"
                },
                "ref": {
                    "type": ["string", "null"],
                    "description": "秘密鍵への参照。"
                },
                "bytes": {
                    "type": ["string", "null"],
                    "format": "base64",
                    "description": "PEMフォーマットのバイト列をBase64エンコードしたもの。"
                }
            }
        },
        "certificate": {
            "type": "string",
            "format": "base64",
            "descritpion": "Base64エンコードされた、証明書とそのチェインを含むPEMフォーマットのバイト列。"
        },
        "expiration": {
            "type": "string",
            "format": "date-time",
            "descritpion": "証明書の有効期限の日時(ISO 8601)。"
        }
    }
}

指定した情報でクライアントID証明書を作成して返す。

  1. WorkloadConfigトレイトの実装(iotedgedクレートのWorkloadData構造体)から、クライアント証明書(CertificateType::Client)用の証明書の最大有効期限(2時間の固定値)を取得する。
  2. クライアントID証明書のエイリアスを{モジュールID}identityという名前で構築する。
  3. クライアントID証明書のSAN(Subject Alt Name)となるURI: azureiot://{IoT Hubの名前}/devices/{デバイスID}/modules/{モジュールID}という文字列を構築する。
  4. 入力のexpirationが最大有効期限を越えていないことを検証する。これはクライアントID証明書の有効期限となる。
  5. 入力name(モジュールID)が空でないことを検証する。これはクライアントID証明書のCNになる。
  6. 証明書APIのdestroy_certificateを呼び出し、エイリアスに一致する証明書を破棄する。
  7. 有効期限、CN(モジュールID)、CertificateType::Client、エイリアス、SAN、CertificateIssuer::DefaultCaを使用して、証明書APIのcreate_certificateを呼び出し、クライアントID証明書を作成する。
  8. 応答を構築する。証明書のPEM形式のデータをcerticate、有効期限をRFC3339形式でexpirationにマッピングする。さらに、秘密鍵がref形式であればprivateKey.typerefを指定してprivateKey.refにその値を指定する。そうではなく、秘密鍵がbytes形式であればprivateKey.typebytesを指定してprivateKey.bytesにその値を指定する。

create server cert

POST /modules/{name}/genid/{genid}/certificate/server?api-version={apiVersion}
  • 認証/認可
    • IoT Edgeモジュールからのみ呼び出し可能
  • クエリパラメーター
    • api_version:APIのバージョン(既定値は 2018-06-28
  • リクエストボディ
    • 後述
  • レスポンスボディ
    • 後述
リクエストボディ
{
    "required": ["commonName", "expiration"],
    "properties": {
        "commonName": {
            "type": "string",
            "description": "サブジェクトの共通名(CN)。"
        },
        "expiration": {            "type": "string",
            "format": "date-time",
            "descritpion": "証明書の有効期限の日時(ISO 8601)。"
        }
    }
}
レスポンスボディ
{
    "properties": {
        "privateKey": {
            "type": "object",
            "description": "",
            "required": ["type"],
            "properties": {
                "type": {
                    "type": "string",
                    "description": "キーの形式を示します(PEMフォーマットのバイト列か参照)。"
                },
                "ref": {
                    "type": ["string", "null"],
                    "description": "秘密鍵への参照。"
                },
                "bytes": {
                    "type": ["string", "null"],
                    "format": "base64",
                    "description": "PEMフォーマットのバイト列をBase64エンコードしたもの。"
                }
            }
        },
        "certificate": {
            "type": "string",
            "format": "base64",
            "descritpion": "Base64エンコードされた、証明書とそのチェインを含むPEMフォーマットのバイト列。"
        },
        "expiration": {
            "type": "string",
            "format": "date-time",
            "descritpion": "証明書の有効期限の日時(ISO 8601)。"
        }
    }
}

指定した情報でサーバー証明書を作成して返す。

  1. WorkloadConfigトレイトの実装(iotedgedクレートのWorkloadData構造体)から、サーバー証明書(CertificateType::Server)用の証明書の最大有効期限(90日の固定値)を取得する。
  2. サーバー証明書のエイリアスを{モジュールID}{生成ID}serverという名前で構築する。
  3. 入力のexpirationが最大有効期限を越えていないことを検証する。
  4. 入力commonName(モジュールID)が空でないことを検証する。
  5. サーバー証明書のSAN(Subject Alt Name)文字列を構築する。
    1. DNS: {モジュールID}という文字列を構築する。これを1番目のエントリとする。
      • モジュールIDは、以下のようにDNSエントリとして妥当な値に変換する。
        1. モジュールIDの先頭から、ASCIIの英字でない文字を全て取り除く。
        2. その結果のモジュールIDの末尾から、ASCIIの英数字でない文字を全て取り除く。
        3. その結果のモジュールIDを全て小文字化する。
        4. その結果のモジュールIDからASCII英数字でもASCIIハイフンでもない文字を全て取り除く。
        5. その結果の先頭から63文字までを採用する。
    2. DNS: {commonName}の値を末尾に追加する。
    3. これらを, で連結する。
  6. 証明書APIのdestroy_certificateを呼び出し、エイリアスに一致する証明書を破棄する。
  7. 有効期限、CN(commonName)、CertificateType::Server、エイリアス、SAN、CertificateIssuer::DefaultCaを使用して、証明書APIのcreate_certificateを呼び出し、クライアントID証明書を作成する。。
  8. 応答を構築する。証明書のPEM形式のデータをcerticate、有効期限をRFC3339形式でexpirationにマッピングする。さらに、秘密鍵がref形式であればprivateKey.typerefを指定してprivateKey.refにその値を指定する。そうではなく、秘密鍵がbytes形式であればprivateKey.typebytesを指定してprivateKey.bytesにその値を指定する。

get trust bundle

GET /trust-bundle?api-version={apiVersion}
}
  • 認証/認可
    • 匿名呼び出し可能
  • クエリパラメーター
    • api_version:APIのバージョン(既定値は 2018-06-28
  • リクエストボディ
    • なし
  • レスポンスボディ
    • 後述
レスポンスボディ
{
    "properties": {
        "certificate": {
            "type": "string",
            "format": "base64",
            "descritpion": "Base64エンコードされた、信頼済み証明書群を含むPEM形式のバイト列。"
        }
    }
}

証明書APIのget_trust_bundleを呼び出す。

内部API

ランタイム内部API

edgelet_dockerクレートのruntimeモジュールのDockerModuleRuntime構造体のModuleRuntimeトレイトを実装するメソッドとして提供される。

create

fn create(&self, module: ModuleSpec<DockerConfig>) -> Box<dyn Future<Item = (), Error = Self::Error> + Send>
  1. 環境変数を調整する。具体的には、スペックのcreate_optionsenvと、スペック自身のenvをマージする。
  2. ラベルを調整する。スペックのcreate_optionslabelsに対して、キーnet.azure-devices.edge.owner、値Microsoft.Azure.Devices.Edge.Agentを追加する。
  • これによって、IoT Edgeモジュールかどうかを判別する(たとえばlist API)。
  1. 調整した環境変数、ラベル、そしてimageの値をマージしたcreate_optionsを作成し、Docker APIのcontainer createを呼び出す。

get

fn get(&self, id: &str) -> Box<dyn Future<Item = (DockerModule<UrlConnector>, ModuleRuntimeState), Error = Error> + Send>

実装は、Docker APIを使用したcontainer inspectの結果から、イメージID、コンテナーのPID、開始時刻、終了時刻、終了コード、docker inspect結果のステータスに応じた値が入る。

  • コンテナーのステータスがcreatedpausedrestartingの場合stopped
  • コンテナーのステータスがrunningの場合、running
  • コンテナーのステータスがremovingdeadexitedの場合、終了コードが0ならstopped、それ以外ならfailed
  • コンテナーのステータスが上記以外の場合はunknown

system_info

fn system_info(&self) -> Box<dyn Future<Item = CoreSystemInfo, Error = Error> + Send>

Docker APIを使用したsystem system_infoを行う。このとき、os_typearchitectureそれぞれについて、返されなかった場合はUnknownに置き換える。

list

fn list(&self) -> Box<dyn Future<Item = Vec<DockerModule<UrlConnector>>, Error = Error> + Send>

Docker APIを使用したcontainer listを行い、その結果について、ラベルにキーnet.azure-devices.edge.owner、値Microsoft.Azure.Devices.Edge.Agentを持つもののみにフィルタリングする。結果を DockerModule にマッピングし、返す。

list_with_details

fn list_with_details(&self) -> Box<dyn Stream<Item = (DockerModule<UrlConnector>>, ModuleRuntimeState), Error = Error> + Send>
  1. list() メソッドを呼び出す。
  2. 結果をStreamに変換する。
  3. 個々の結果となるDockerModule<UrlConnector>runtime_state()を呼び出し、タプルにする。
    • 内部的にはDocker APIのcontainer inspectが実行される。
  4. エラーとなったモジュールについては除外する。

remove

fn remove(&self, id: &str) -> Box<dyn Future<Item = (), Error = Self::Error> + Send>

Docker APIを使用したcontainer deleteを行う。このとき、remove volumesにはfalseforceにはtrueremove linkにはfalseを渡す。

start

fn start(&self, id: &str) -> Box<dyn Future<Item = (), Error = Self::Error> + Send>

Docker APIを使用したcontainer startを行う。

stop

fn stop(&self, id: &str, wait_before_kill: Option<Duration>) -> Box<dyn Future<Item = (), Error = Self::Error> + Send>

Docker APIを使用したcontainer stopを行う。このとき、待機時間として、引数wait_before_killが指定されていればその時間、指定されてないならば固定値10秒を渡す。

restart

fn restart(&self, id: &str) -> Box<dyn Future<Item = (), Error = Self::Error> + Send>

Docker APIを使用したcontainer restartを行う。このとき、待機時間として、固定値10秒を渡す。

logs

fn logs(&self, id: &str, options: &LogOptions) -> Box<dyn Future<Item = Logs, Error = Error> + Send>

Docker APIを使用したcontainer logsを行う。引数は以下のとおり。

|仮引数|| |id|対象モジュールID(コンテナーID)| |follow|followオプションの値| |stdout|true| |stderr|true| |since|sinceオプションの値| |timestamps|false| |tail|tailオプションの値|

registry

fn registry(&self) -> &ModuleRegistry

ModuleRegistryトレイトオブジェクトしてDockerModuleRuntime自身を返す。

remove_all

fn remove_all(&self) -> Box<dyn Future<Item = (), Error = Self::Error> + Send>

レジストリ内部API

edgelet_dockerクレートのruntimeモジュールのDockerModuleRuntime構造体のModuleRegistryトレイトを実装するメソッドとして提供される。

pull

fn pull(&self, config: &DockerConfig) -> Box<dyn Future<Item = (), Error = Error> + Send>

Docker APIを使用してimage createを行う。

remove

fn remove(&self, name: &str) -> Box<dyn Future<Item = (), Error = Error>>

Docker APIを使用してimage deleteを行う。

アイデンティティマネージャー

edgelet_iothubクレートのルートモジュールのHubIdentityManager構造体のIdentityManagerトレイトを実装するメソッドとして提供される。

create

fn create(&mut self, id: IdentitySpec) -> Box<dyn Future<Item = HubIdentity, Error = Error> + Send>

引数として、module_idgeneration_idmanaged_byを受け取る。

  1. IoT HubのAPI(PUT /devices/{deviceID}/modules/{moduleID})をmodule_idmanaged_byを渡して呼び出し、モジュールを作成する。
  2. KeyStoreの実装(現在はedgelet_coreクレートのDerivedKeyStore)により、プライマリキーとセカンダリキーを生成する。キー名はprimary{generationID}secondary{generationID}。値は、モジュールIDとキー名を連結したものを、デバイスのプライマリキーをキーにしてHMACSHA256を取った結果(これはMemoryKeysignメソッド実装による)。
  3. IoT HubのAPI(PUT /devices/{deviceID}/modules/{moduleID})を呼び出し、認証種別をSASにし、キーに先ほど生成した値を設定する。

update

fn update(&mut self, id: IdentitySpec) -> Box<dyn Future<Item = HubIdentity, Error = Error> + Send>
  1. KeyStoreの実装(現在はedgelet_coreクレートのDerivedKeyStore)により、プライマリキーとセカンダリキーを生成する。キー名はprimary{generationID}secondary{generationID}。値は、モジュールIDとキー名を連結したものを、デバイスのプライマリキーをキーにしてHMACSHA256を取った結果(これはMemoryKeysignメソッド実装による)。
  2. IoT HubのAPI(PUT /devices/{deviceID}/modules/{moduleID})を呼び出し、認証種別をSASにし、キーに先ほど生成した値を設定し、それ以外は引数で受け取った値を設定する。

list

fn list(&self) -> Box<dyn Future<Item = Vec<HubIdentity>, Error = Error> + Send>
  1. IoT HubのAPI(GET /devices/{deviceID}/modules/)を呼び出し、結果を返す。

get

fn get(&mut self, id: IdentitySpec) -> Box<dyn Future<Item = HubIdentity, Error = Error> + Send>
  1. IoT HubのAPI(GET /devices/{deviceID}/modules/{moduleId})を呼び出し、結果を返す。

delete

fn delete(&mut self, id: IdentitySpec) -> Box<dyn Future<Item = (), Error = Error> + Send>
  1. IoT HubのAPI(DELETE /devices/{deviceID}/modules/{moduleId})を呼び出し、結果を返す。

暗号API

edgelet_hsmクレートのcryptoモジュールのCrypto構造体が実装する、edgelet_coreクレートのDecryptEncryptトレイトとして定義される。

実装は、hsm_rsクレートのCrypto構造体である(これをArcとして内部に保持するedgelet_hsmクレートのCrypto構造体がAPIとして公開される)。

encrypt

fn encrypt(&self, client_id: &[u8], plaintext: &[u8], initialization_vector: &[u8]) -> Result<Buffer, Error>

指定された平文を、指定されたクライアントID用の鍵で暗号化して返す。なお、戻り値のBufferはHSMのC言語側で管理しているメモリのラッパーであり、それを開放するためのDropトレイトを実装している。

  1. 引数をC言語とのFFI用にマーシャリングする。
  2. C言語で記述されたHSMの実装を呼び出す(edge_hsm_client_encrypt_data@edge_hsm_client_crypto.c)。
    1. HSMの初期化状態と引数をチェックする。
    2. 内部APIを呼び出す(encrypt_data)。
      1. ネイティブストアAPIを呼び出し、暗号化用の鍵をストアから取り出す(edge_hsm_client_open_key@edge_hsm_client_store.c)。種別はHSM_KEY_ENCRPTION、鍵の名前はedgelet-master
        1. HSMの初期化状態と引数をチェックする。
        2. 鍵を取得する(open_key)。
          1. 鍵の存在チェックを行う。
            1. インメモリの鍵を検索する(key_exists -> get_key
            2. 存在しない場合、ファイルから鍵を読み取る(load_encryption_key_from_file
              1. 鍵名からそのファイルパスを構築する(build_enc_key_file_path)。
                1. 鍵名のSHA256ダイジェストを計算し、Base64エンコードし、さらに+-に、\=_に置換する。
                2. エイリアスの先頭から、ASCII英数、_-を最大32文字抽出する。
                3. {ホームディレクトリ}/enc_keys/{2の結果}{1の結果}.enc.keyが結果である。
              2. ファイルの内容を読み取る。
              3. インメモリストアに挿入する(put_key)。
          2. インメモリの鍵を取得する(get_key)。
          3. 暗号化用の鍵情報であるENC_KEY構造体を作成し、そのハンドル(KEY_HANDLE)を返す(create_encryption_key@edge_enc_openssl_key.c)。このとき、ハンドルに関連する各メソッド(関数ポインター)はedge_enc_openssl_key.c内の関数になる。
      2. 暗号化を行う(edge_hsm_client_key_encrypt@edge_hsm_key_interface.c)。
        1. 引数をチェックする。
        2. 暗号化処理を実行する(key_encrypt@hsm_key_interface.h -> enc_key_encrypt@edge_enc_openssl_key.c)。
          1. CIPHER_VERSION_V1を使用して、OpenSSLの暗号化関数を呼び出す(encrypt@edge_enc_openssl_key.c)。
            • なお、クライアントIDはAAD(Additional Authenticated Data)として使用される。
      3. 鍵情報ハンドルをクローズする(edge_hsm_client_close_key@edge_hsm_client_store.c)。
        1. HSMの初期化状態と引数をチェックする。
        2. 鍵情報のメモリを解放する(key_destroy@hsm_key_interface.h -> enc_key_destroy@edge_enc_openssl_key.c

decrypt

fn decrypt(&self, client_id: &[u8], ciphertext: &[u8], initialization_vector: &[u8]) -> Result<Buffer, Error>

指定された暗号文を、指定されたクライアントID用の鍵で復号して返す。なお、戻り値のBufferはHSMのC言語側で管理しているメモリのラッパーであり、それを開放するためのDropトレイトを実装している。

  1. 引数をC言語とのFFI用にマーシャリングする。
  2. C言語で記述されたHSMの実装を呼び出す(edge_hsm_client_decrypt_data@edge_hsm_client_crypto.c)。
    1. HSMの初期化状態と引数をチェックする。
    2. 内部APIを呼び出す(decrypt_data)。
      1. ネイティブストアAPIを呼び出し、暗号化用の鍵をストアから取り出す(edge_hsm_client_open_key@edge_hsm_client_store.c)。種別はHSM_KEY_ENCRPTION、鍵の名前はedgelet-master
        1. HSMの初期化状態と引数をチェックする。
        2. 鍵を取得する(open_key)。
          1. 鍵の存在チェックを行う。
            1. インメモリの鍵を検索する(key_exists -> get_key
            2. 存在しない場合、ファイルから鍵を読み取る(load_encryption_key_from_file
              1. 鍵名からそのファイルパスを構築する(build_enc_key_file_path)。
                1. 鍵名のSHA256ダイジェストを計算し、Base64エンコードし、さらに+-に、\=_に置換する。
                2. エイリアスの先頭から、ASCII英数、_-を最大32文字抽出する。
                3. {ホームディレクトリ}/enc_keys/{2の結果}{1の結果}.enc.keyが結果である。
              2. ファイルの内容を読み取る。
              3. インメモリストアに挿入する(put_key)。
          2. インメモリの鍵を取得する(get_key)。
          3. 復号用の鍵情報であるENC_KEY構造体を作成し、そのハンドル(KEY_HANDLE)を返す(create_encryption_key@edge_enc_openssl_key.c)。このとき、ハンドルに関連する各メソッド(関数ポインター)はedge_enc_openssl_key.c内の関数になる。
      2. 復号を行う(edge_hsm_client_key_decrypt@edge_hsm_key_interface.c)。
        1. 引数をチェックする。
        2. 復号処理を実行する(key_decrypt@hsm_key_interface.h -> enc_key_decrypt@edge_enc_openssl_key.c)。
          1. CIPHER_VERSION_V1を使用して、OpenSSLの復号関数を呼び出す(decrypt@edge_enc_openssl_key.c)。
            • なお、クライアントIDはAAD(Additional Authenticated Data)として使用される。
      3. 鍵情報ハンドルをクローズする(edge_hsm_client_close_key@edge_hsm_client_store.c)。
        1. HSMの初期化状態と引数をチェックする。
        2. 鍵情報のメモリを解放する(key_destroy@hsm_key_interface.h -> enc_key_destroy@edge_enc_openssl_key.c

証明書API

edgelet_hsmクレートのcryptoモジュールのCrypto構造体が実装する、edgelet_coreクレートのCreateCertificateトレイトと、GetTrustBundleトレイトとして定義される。

実装は、hsm_rsクレートのCrypto構造体である(これをArcとして内部に保持するedgelet_hsmクレートのCrypto構造体がAPIとして公開される)。

create certificate

fn create_certificate(&self, properties: &CertificateProperties) -> Result<HsmCertificate, Error>
  1. 入力プロパティCertificatePropertiesをC言語とのFFI用にマーシャリングする。
  2. C言語で記述されたHSMの実装を呼び出す(edge_hsm_client_create_certificate@edge_hsm_client_crypto.c)。
    1. HSMの初期化状態と引数をチェックする。
    2. ネイティブストアAPIを呼び出し、証明書を作成する(edge_hsm_client_store_create_pki_cert@edge_hsm_client_store)。
      1. 初期化状態と引数をチェックする。
      2. ファイルに出力済みの証明書があればそれをロードする(load_if_cert_and_key_exist_by_alias)。
        1. エイリアスから証明書と秘密鍵のファイルパスを構築する(build_cert_file_paths)。
          1. エイリアスのSHA256ダイジェストを計算し、Base64エンコードし、さらに+-に、\=_に置換する。
          2. エイリアスの先頭から、ASCII英数、_-を最大32文字抽出する。
          3. {ホームディレクトリ}/certs/{2の結果}{1の結果}.cert.pem{ホームディレクトリ}/cert_keys/{2の結果}{1の結果}.key.pemが結果である。
        2. 証明書の内容を検証する(verify_certificate_helper)。
        3. インメモリストアにPKI証明書管理情報を挿入する(edge_hsm_client_store_insert_pki_cert -> put_pki_cert)。
      3. ファイルがなければ、作成処理本体を呼び出す(edge_hsm_client_store_create_pki_cert_internal)。
        1. 初期化状態と引数をチェックする。
        2. エイリアスから証明書と秘密鍵のファイルパスを構築する(build_cert_file_paths)。
          1. エイリアスのSHA256ダイジェストを計算し、Base64エンコードし、さらに+-に、\=_に置換する。
          2. エイリアスの先頭から、ASCII英数、_-を最大32文字抽出する。
          3. {ホームディレクトリ}/certs/{2の結果}{1の結果}.cert.pem{ホームディレクトリ}/cert_keys/{2の結果}{1の結果}.key.pemが結果である。
        3. generate_pki_cert_and_key_helperkey_propsNULLを渡して呼び出す。なお、シリアル番号はrand()の値。(generate_pki_cert_and_key@edge_pki_openssl.c)。
          1. 引数を検証する。
          2. 秘密鍵を生成する(generate_cert_key)。
            1. 発行者証明書がない場合、PKI_KEY_PROPS.key_type(とPKI_KEY_PROPS.ec_curve_name)に合わせたキーをOpenSSLのAPIを使用して、発行者証明書がある場合、その公開鍵に合わせた秘密鍵を生成する。
              • RSA鍵の長さは、CA証明書の場合4096バイト、そうでない場合8192バイト。
            2. ファイルに書き出す(write_private_key_file)。
          3. 証明書を生成する(generate_evp_certificate)。
            1. X509証明書の各種プロパティを設定する(cert_set_core_propertiescert_set_expirationcert_set_extensionscert_set_subject_fields_and_issuercert_set_key_id_extensions
            2. 秘密鍵で署名する。
            3. ファイルに書き出す(write_certificate_file)。
        4. インメモリストアにPKI証明書管理情報を挿入する(put_pki_cert
    3. ネイティブストアAPIを呼び出し、証明書を取得する(edge_hsm_client_store_get_pki_cert@edge_hsm_client_store.c)。
      1. 内部APIを呼び出す(get_cert_info_by_alias)。
        1. 初期化状態と引数チェック。
        2. インメモリの証明書管理情報を取り出す(get_pki_cert)。
        3. 証明書データを読み取る(prepare_cert_info_handle)。
          1. 秘密鍵を読み取る。
          2. 証明書ファイルを読み取る。
          3. 証明書構造体を構築する(certificate_info_create)。

destroy certificate

fn destroy_certificate(&self, alias: String) -> Result<(), Error>

エイリアスに一致する証明書を破棄する。

  1. C言語で記述されたHSMの実装を呼び出す(edge_hsm_client_destroy_certificate@edge_hsm_client_crypto.c)。
    1. HSMの初期化状態と引数をチェックする。
    2. ネイティブストアAPIを呼び出し、証明書を削除する(edge_hsm_client_store_remove_pki_cert@edge_hsm_client_store.c)。
      1. エイリアスをキーにして証明書情報を削除する内部処理を呼び出す(remove_cert_by_alias@edge_hsm_client_store.c)。
        1. 初期化状態と引数をチェックする。
        2. 削除処理の本体を呼び出す( remove_if_cert_and_key_exist_by_alias@edge_hsm_client_store.c) 。
          1. エイリアスから証明書と秘密鍵のファイルパスを構築する(build_cert_file_paths)。
            1. エイリアスのSHA256ダイジェストを計算し、Base64エンコードし、さらに+-に、\=_に置換する。
            2. エイリアスの先頭から、ASCII英数、_-を最大32文字抽出する。
            3. {ホームディレクトリ}/certs/{2の結果}{1の結果}.cert.pem{ホームディレクトリ}/cert_keys/{2の結果}{1の結果}.key.pemが結果である。
          2. 証明書と秘密鍵のファイルを削除する。ファイルがない場合は無視する。
          3. インメモリストアからPKI証明書管理情報を削除する(remove_pki_cert)。

get_trust_bundle

fn get_trust_bundle(&self) -> Result<HsmCertificate, Error>

信頼済み証明書のチェーンを返す。

  1. C言語で記述されたHSMの実装を呼び出す(edge_hsm_client_crypto_get_certificate@edge_hsm_client_crypto.c)。
    1. HSMの初期化状態と引数をチェックする。
    2. ネイティブストアAPIを呼び出し、証明書を取得する(edge_hsm_client_store_get_pki_trusted_certs@edge_hsm_client_store.c)。
      1. HSMの初期化状態と引数をチェックする。
      2. メモリ内に保存されている信頼済み証明書の情報を取得する(prepare_trusted_certs_info)。
      3. 信頼済み証明書の証明書ファイルを読み取り、連結する(certificate_info_create@hsm_utils.c)。
      4. 結果をCERT_INFO_DATA構造体に格納し、そのハンドル(CERT_INFO_HANDLE)を返す(certificate_info_create@hsm_utils.c)。

CLI

iotedge クレイトで実装されている。

各コマンドの実装

list

listモジュール。

  1. ランタイムAPIのlist_with_detailsを呼び出す。
  2. ステータスをわかりやすくする。
    • Stoppedの場合、終了時間(Unixタイムスタンプ)を人間が読みやすい形のどのくらい前の時間かに変えて(chrono-humarizeクレートを使用)表示。
    • Failedの場合、終了コードと、人間が読みやすい形のどのくらい前の時間かに変えた(chrono-humarizeクレートを使用)終了時間(Unixタイムスタンプ)を表示。
    • Runningの場合、開始時間(Unixタイムスタンプ)を人間が読みやすい形のどのくらい前の時間かに変えて(chrono-humarizeクレートを使用)表示。

logs

logsモジュール。引数としてfollowtailsinceを受け取る。

  1. ランタイムAPIのlogsを呼び出す。
  2. 結果を表示する。

restart

restartモジュール。引数としてidを受け取る。

  1. ランタイムAPIのrestartを呼び出す。
  2. 再起動したモジュールのIDを表示する。

version

versionモジュール。

  1. クレート名とバージョン情報(systeminfoと同じ値)を表示する。

check

checkモジュールとそのサブモジュール群。

(別途記述)

IoT Edge Deep Dive 2 -- Edge Agent

Edge Agentは、IoT Edgeの配置マニフェストをベースとしたIoT Edgeモジュール群の管理を行うコンポーネントで、$edgeAgentという名前である。具体的には、IoT Hubからモジュールツインとして配置マニフェストを受け取り、edgeletのマネジメントAPIまたはDocker APIを使用して配置マニフェスト通りにコンテナーを作成し(必要な環境変数やネットワーク構成を調整し)、edgeletのマネジメントAPIまたはDocker APIを使用してコンテナーのステータスを取得して(自身が管理する再起動野情報を追加して)モジュールツインとして報告する。

モジュールのステータスとは、つまるところはDockerのステータスである。そのため、unhealthy状態を検知するには、HEALTHCHECK文をDockerfileに記述し、ヘルスチェックを実装する必要がある。なお、再起動ポリシーによりEdge Agentが再起動を行っている間は、backoffというステータスになる。

配置マニフェストの適用、再起動ポリシーによる再起動は、いずれも5秒間隔の「調整(reconcile)」処理で行われる。

また、ダイレクトメソッドとしていくつかの管理用ユーティリティも提供する。

アクティブな配置マニフェストは(edgelet経由でHSMに保存されたキーを使用して)暗号化され、RocksDBに保存される。RocksDBには各モジュールの再起動情報も格納される。

Edge AgentはC#で実装された.NET Coreコンポーネントである。

私見

これは完全な私見である。

マネジメントAPIとその一部であるアイデンティティAPIはedgelet側ではIoT HubやDockerのAPIを呼び出すだけであり、さらにDockerモードではそれらの処理をEdge Agent自身で行ってしまう。そのため、edgeletとの処理の重複が多い。edgeletはHSMの抽象化と、Unixドメインソケット通信の確立(edgeAgentに対するソケットファイルのマウントというおぜん立て)にとどめ、APIの呼び出しやIoT Hubとの調整はEdge Agentに寄せ、OTA更新の範囲を広げるべきと思う(別に実装はC#でもRustでもいいだろう。ランタイムモジュールのサイズ、具体的にはヒープの最小サイズやDockerイメージのサイズを考えるとRustの方が良いと思われる。もちろん、.NET Core 3.xで改善する可能性はあるが)。そもそも、TPMを使っているのはDPSの存在証明(attestation)とワークロードAPIの署名処理のみであり(それも、デバイスID証明書を使っている場合のみだ)、TPMを使用する処理とEdge Agentのおぜん立て以外はEdge Agentに寄せる方が良いと思われる。

構成

Edge Agentはいくつかのアセンブリで構成されている。

  • Microsoft.Azure.Devices.Edge.Agent.Core
    • 実装に依存しないコアロジックとインターフェイスである。
  • Microsoft.Azure.Devices.Edge.Agent.Docker
    • Docker APIやそのデータ構造に依存した処理の実装である。
  • Microsoft.Azure.Devices.Edge.Agent.Edgelet
    • edgeletとの通信や、設定される環境変数に依存した処理の実装である。
  • Microsoft.Azure.Devices.Edge.Agent.Edgelet.Docker
    • *.Dockerアセンブリの拡張として実装される、edgeletとの通信や、設定される環境変数に依存した処理の実装である。
      • CombinedEdgeletConfigProviderのみ。
  • Microsoft.Azure.Devices.Edge.Agent.IoTHub
    • モジュールツインのやり取りなどのIoT Hubとの通信処理の実装である。
    • Dockerモード用のレジストリマネージャー通信処理を含む。
  • Microsoft.Azure.Devices.Edge.Agent.Kubernetes
    • Kubernetes用の実装である。実装途中であるため、本書では(まだ)触れない。
  • Microsoft.Azure.Devices.Edge.Agent.Service
    • DIの構成(コンポジションルート、モジュール)やMain、Windows Service実装など、アプリケーションエントリポイントを提供する。

構成情報

.NET Coreアプリケーション構成

Microsoft.Extensions.Configurationの構成情報で、ファイル名はappsettings_agent.json

キー 既定値 appsettings_agent.jsonの値 edgeletが設定する値 備考
RuntimeLogLevel info info
Mode "docker" docker iotedged docker iotedgedのいずれか
ConfigSource null twin twin localのいずれか
BackupConfigFilePath null backup.json
MaxRestartCount 0 20
IntensiveCareTimeInMinutes 0 10
CoolOffTimeUnitInSeconds 10 10
UsePersistentStorage true true
StorageFolder (システムの一時ディレクトリ) "" edgeAgentサブディレクトリが使用される。
EdgeDeviceHostName null <Edge device host name> config.yamlhostname
DockerLoggingDriver null "json-file"
DockerLoggingOptions (空のDictionary<string, string> (空)
DockerRegistryAuth (空のList<AuthConfig> null
ConfigRefreshFrequencySecs 3600 3600
UpstreamProtocol null null Amqp AmqpWs Mqtt MqttWs のいずれか。
https_proxy null null
CloseCloudConnectionOnIdleTimeout false null
CloudConnectionIdleTimeoutSecs 300 null
experimentalFeatures:enabled false null
experimentalFeatures:disableCloudSubscriptions false null
experimentalFeatures:enableUploadLogs false null
experimentalFeatures:enableGetLogs false null
DockerUri null http://localhost:2375 Modedockerの場合のみ
DeviceConnectionString null <Edge Device Connection String> Modedockerの場合のみ
IOTEDGE_MANAGEMENTURI null null config.yamlconnect.management_uriの値 Modeiotedgedの場合のみ
IOTEDGE_WORKLOADURI null null config.yamlconnect.workload_uriの値 Modeiotedgedの場合のみ
IOTEDGE_IOTHUBHOSTNAME null null プロビジョニングの結果のIoT Hubホスト名 Modeiotedgedの場合のみ
IOTEDGE_DEVICEID null null プロビジョニングの結果のデバイスID Modeiotedgedの場合のみ
IOTEDGE_MODULEID "$edgeAgent" null $edgeAgent Modeiotedgedの場合のみ
IOTEDGE_MODULEGENERATIONID null null null Modeiotedgedの場合のみ
IOTEDGE_APIVERSION null null 2019-01-30 Modeiotedgedの場合のみ
EnableStreams false null null CondfigSourcetwinの場合のみ
RequestTimeoutSecs 600 null null CondfigSourcetwinの場合のみ
LocalConfigPath config.json null null CondfigSourcelocalの場合のみ

AuthConfig

Docker.DotNetアセンブリの定義に従う。この情報で、配置マニフェストのregistryCredentialsを上書きできる。usernamepasswordserveraddress以外は無視される。

{
    "type": "array",
    "items": {
        "type": "object",
            "properties": {
                "username": {
                    "type": "string"
                },
                "password": {
                    "type": "string"
                },
                "auth": {
                    "type": "string"
                },
                "email": {
                    "type": "string"
                },
                "serveraddress": {
                    "type": "string"
                },
                "identitytoken": {
                    "type": "string"
                },
                "registrytoken": {
                    "type": "string"
                }
            }
        }
    }
}

DI構成

モジュール構成

Edge AgentはAutoFacによるDIを使用している。AutoFacでは、DI構成をいくつかのモジュールに分けて管理する。ここでは、そのDIモジュールの一覧と、そこで使用する構成情報を示す。

条件 モジュール 構成 備考
(常に) LoggingModule ${DockerLoggingDriver}
${DockerLoggingOptions}
Modedocker AgentModule ${MaxRestartCount}
${IntensiveCareTimeInMinutes}
${CoolOffTimeUnitInSeconds}
${UsePersistentStorage}
${StorageFolder}/edgeAgent
Modedocker DockerModule ${DeviceConnectionString}
${EdgeDeviceHostName}
${DockerUri}
${DockerRegistryAuth}
${UpstreamProtocol}
${https_proxy}
(バージョン情報)
${CloseCloudConnectionOnIdleTimeout}
${CloudConnectionIdleTimeoutSecs}
Modeiotedged AgentModule ${MaxRestartCount}
${IntensiveCareTimeInMinutes}
${CoolOffTimeUnitInSeconds}
${UsePersistentStorage}
${StorageFolder}/edgeAgent
${IOTEDGE_WORKLOADURI}
${IOTEDGE_APIVERSION}
${IOTEDGE_MODULEID}
${IOTEDGE_MODULEGENERATIONID}
Modeiotedged EdgeletModule ${IOTEDGE_IOTHUBHOSTNAME}
${EdgeDeviceHostName}
${IOTEDGE_DEVICEID}
${IOTEDGE_MANAGEMENTURI}
${IOTEDGE_WORKLOADURI}
${IOTEDGE_APIVERSION}
${DockerRegistryAuth}
${UpstreamProtocol}
${https_proxy}
(バージョン情報)
${CloseCloudConnectionOnIdleTimeout}
${CloudConnectionIdleTimeoutSecs}
Modetwin TwinConfigSourceModule ${IOTEDGE_IOTHUBHOSTNAME}または${DeviceConnectionString}hostname
${IOTEDGE_DEVICEID}または${DeviceConnectionString}deviceId
${BackupConfigFilePath}
IConfiguration
(バージョン情報)
${ConfigRefreshFrequencySecs}
${EnableStreams}
${RequestTimeoutSecs}
${experimentalFeatures:*}一式
Modelocal FileConfigSourceModule ${LocalConfigPath}
IConfiguration

サービス構成

Edge AgentはAutoFacによるDIを使用している。ここでは、DIされるサービス型、実装型、使用する構成情報、依存先、条件を一覧化している。

モジュール 条件 サービス 実装 構成 備考
LoggingModule (常に) ILoggerFactory SerilogのLogger.Factory
LoggingModule (常に) DockerLoggingConfig DockerLoggingConfig ${DockerLoggingDriver}
${DockerLoggingOptions}
AgentModule (常に) ISerde<ModuleSet> ModuleSetSerde {"docker": typeof(DockerModule)} 誰も使っていない。
AgentModule (常に) ISerde<DeploymentConfig> TypeSpecificSerDe<DeploymentConfig> {typeof(IModule): {"docker": typeof(DockerDesiredModule)}, typeof(IEdgeAgentModule): {"docker": typeof(EdgeAgentDockerModule)}, typeof(IEdgeHubModule): {"docker": typeof(EdgeHubDockerModule)}, typeof(IRuntimeInfo): {"docker": typeof(DockerRuntimeInfo)}}
AgentModule (常に) ISerde<DeploymentConfigInfo> TypeSpecificSerDe<DeploymentConfigInfo> {typeof(IModule): {"docker": typeof(DockerDesiredModule)}, typeof(IEdgeAgentModule): {"docker": typeof(EdgeAgentDockerModule)}, typeof(IEdgeHubModule): {"docker": typeof(EdgeHubDockerModule)}, typeof(IRuntimeInfo): {"docker": typeof(DockerRuntimeInfo)}}
AgentModule (常に) ISystemEnvironment SystemEnvironment
AgentModule (常に) IRocksDbOptionsProvider RocksDbOptionsProvider ISystemEnvironment
optimizeForPerformance: false
AgentModule ${usePersistentStorage}true IDbStoreProvider DbStoreProvider IRocksDbOptionsProvider
${StorageFolder}/edgeAgent
["moduleState", "deploymentConfig"]
AgentModule ${usePersistentStorage}falseまたはRocksDBの初期化に失敗) IDbStoreProvider InMemoryDbStoreProvider
AgentModule (常に) IStoreProvider StoreProvider IDbStoreProvider
AgentModule (常に) IEntityStore<string, ModuleState> IStoreProvider.GetEntityStore<string, ModuleState>("moduleState")
AgentModule (常に) IEntityStore<string, string> IStoreProvider.GetEntityStore<string, string>("deploymentConfig")
AgentModule (常に) IRestartPolicyManager RestartPolicyManager ${MaxRestartCount}
${CoolOffTimeUnitInSeconds}
AgentModule (常に) IPlanner HealthRestartPlanner ICommandFactory
IEntityStore<string, ModuleState>
${IntensiveCareTimeInMinutes}
IRestartPolicyManager
AgentModule (常に) IPlanRunner OrderedRetryPlanRunner ${MaxRestartCount}
${CoolOffTimeUnitInSeconds}
SystemTime.Instance
AgentModule IEncryptionProvider構築成功) IEncryptionProvider EncryptionProvider ${StorageFolder}/edgeAgent
${IOTEDGE_WORKLOADURI}
edgeletApiVersion: ${IOTEDGE_APIVERSION}
egdgeletClientApiVersion: 2019-01-30
${IOTEDGE_MODULEID}
${IOTEDGE_MODULEGENERATIONID}
"IOTEDGE_MODULEGENERATIONID"
AgentModule IEncryptionProvider構築失敗) IEncryptionProvider NullEncryptionProvider.Instance
AgentModule (常に) Agent Agent IConfigSource
IPlanner
IPlanRunner
IReporter
IModuleIdentityLifecycleManager
IEnvironmentProvider
ISerde<DeploymentConfigInfo>
IEntityStore<string, string>
IEncryptionProvider
EdgeletModule (常に) IModuleClientProvider ModuleClientProvider ISdkModuleClientProvider
${UpstreamProtocol}
${https_proxy}
(バージョン情報)
${CloseCloudConnectionOnIdleTimeout}
${CloudConnectionIdleTimeoutSecs}
DockerModule (常に) IModuleClientProvider ModuleClientProvider ${DeviceConnectionString};ModuleId=$edgeAgent
${ISdkModuleClientProvider}
${UpstreamProtocol}
${https_proxy}
(バージョン情報)
${CloseCloudConnectionOnIdleTimeout}
${CloudConnectionIdleTimeoutSecs}
EdgeletModule (常に) IModuleManager ModuleManagementHttpClient ${IOTEDGE_MANAGEMENTURI}
edgeletApiVersion: ${IOTEDGE_APIVERSION}
egdgeletClientApiVersion: 2019-01-30
EdgeletModule (常に) IIdentityManager ModuleManagementHttpClient ${IOTEDGE_MANAGEMENTURI}
edgeletApiVersion: ${IOTEDGE_APIVERSION}
egdgeletClientApiVersion: 2019-01-30
EdgeletModule (常に) IModuleIdentityLifecycleManager ModuleIdentityLifecycleManager IIdentityManager
ModuleIdentityProviderServiceBuilder(${IOTEDGE_IOTHUBHOSTNAME}, ${IOTEDGE_DEVICEID}, ${EdgeDeviceHostName})
${IOTEDGE_WORKLOADURI}
DockerModule (常に) IModuleIdentityLifecycleManager ModuleIdentityLifecycleManager IServiceClient
${DeviceConnectionString}HostName
${DeviceConnectionString}DeviceId
${EdgeDeviceHostName}
EdgeletModule (常に) ICombinedConfigProvider<CombinedDockerConfig> CombinedEdgeletConfigProvider ${DockerRegistryAuth}
IConfigSource
DockerModule (常に) ICombinedConfigProvider<CombinedDockerConfig> CombinedDockerConfigProvider ${DockerRegistryAuth}
EdgeletModule (常に) ICommandFactory LoggingCommandFactory EdgeletCommandFactory<CombinedDockerConfig>(IModuleManager, IConfigSource, ICombinedConfigProvider<CombinedDockerConfig>)
ILoggerFactory
DockerModule (常に) ICommandFactory LoggingCommandFactory DockerCommandFactory(IDockerClient, DockerLoggingConfig, IConfigSource, ICombinedConfigProvider<CombinedDockerConfig>)
ILoggerFactory
EdgeletModule (常に) IRuntimeInfoProvider Edgelet.RuntimeInfoProvider<DockerReportedConfig> IModuleManager
DockerModule (常に) IRuntimeInfoProvider Docker.RuntimeInfoProvider<DockerReportedConfig> IDockerClient
EdgeletModule (常に) IEnvironmentProvider DockerEnvironmentProvider IRuntimeInfoProvider
IEntityStore<string, ModuleState>
IRestartPolicyManager
DockerModule (常に) IEnvironmentProvider DockerEnvironmentProvider IRuntimeInfoProvider
IEntityStore<string, ModuleState>
IRestartPolicyManager
DockerModule (常に) IServiceClient RetryingServiceClient(ServiceClient) ${DeviceConnectionString}
${DeviceConnectionString}DeviceId
DockerModule (常に) IDockerClient DockerClient ${DockerUri}
TwinConfigSourceModule (常に) ILogsUploader AzureBlobLogsUploader ${IOTEDGE_IOTHUBHOSTNAME}または${DeviceConnectionString}hostname
${IOTEDGE_DEVICEID}または${DeviceConnectionString}deviceId
TwinConfigSourceModule (常に) ILogsProvider LogsProvider IRuntimeInfoProvider
LogsProcessor(LogMessageParser(${IOTEDGE_IOTHUBHOSTNAME}または${DeviceConnectionString}のhostname, ${IOTEDGE_DEVICEID}または${DeviceConnectionString}のdeviceId))
TwinConfigSourceModule (常に) IRequestManager RequestManager [PingRequestHandler, TaskStatusRequestHandler]
${RequestTimeoutSecs}
FileConfigSourceModule (常に) IRequestManager RequestManager []
TimeSpan.Zero
TwinConfigSourceModule experimentalFeatures.enableUploadLogstrue IRequestHandler LogsUploadRequestHandler ILogsUploader
ILogsProvider
IRuntimeInfoProvider
TwinConfigSourceModule experimentalFeatures.enableGetLogstrue IRequestHandler LogsRequestHandler ILogsProvider
IRuntimeInfoProvider
TwinConfigSourceModule (常に) IRequestHandler RestartRequestHandler IEnvironmentProvider
IConfigSource
ICommandFactory
TwinConfigSourceModule (常に) ISdkModuleClientProvider SdkModuleClientProvider
TwinConfigSourceModule (常に) IEdgeAgentConnection EdgeAgentConnection IModuleClientProvider
ISerde<DeploymentConfig>
IRequestManager
!${experimentalFeatures.disableCloudSubscriptions}
${ConfigRefreshFrequencySecs}
TwinConfigSourceModule ${EnableStreams}true IStreamRequestListener StreamRequestListener StreamRequestHandlerProvider(ILogsProvider, IRuntimeInfoProvider)
IEdgeAgentConnection
TwinConfigSourceModule ${EnableStreams}false IStreamRequestListener NullStreamRequestListener
FileConfigSourceModule (常に) IStreamRequestListener NullStreamRequestListener
TwinConfigSourceModule (常に) IConfigSource FileBackupConfigSource ${BackupConfigFilePath}
TwinConfigSource(IEdgeAgentConnection, IConfiguration)
ISerde<DeploymentConfigInfo>
IEncryptionProvider
FileConfigSourceModule (常に) IConfigSource FileConfigSource FileSystemWatcher(${LocalConfigPath}){ NotifyFilter = NotifierFilters.LastWrite }
Read(${LocalConfigPath})
IConfiguration
ISerde<DeploymentConfigInfo>
TwinConfigSourceModule (常に) IReporter IoTHubReporter IEdgeAgentConnection
TypeSpecificSerDe<AgentState>({typeof(IRuntimeInfo): {"docker": typeof(DockerReportedRuntimeInfo), "Unknown": typeof(UnknownRuntimeInfo)}, typeof(IEdgeAgentModule): {"docker": typeof(EdgeAgentDockerRuntimeModule), "Unknown": typeof(UnknownEdgeAgentModule)}, typeof(IEdgeHubModule): {"docker": typeof(EdgeHubDockerRuntimeModule), "Unknown": typeof(UnknownEdgeHubModule)}, typeof(IModule): {"docker": typeof(DockerRuntimeModule)}})
(バージョン情報)
FileConfigSourceModule (常に) IReporter NullReporter

永続化情報

パーティション キー 暗号化 説明
moduleState モジュールID なし モジュールの状態。具体的には再起動回数と最終再起動日時。
deploymentConfig CurrentConfig あり 現在アクティブな配置マニフェスト。

初期化シーケンス

Program.Mainの動作を示す。基本的には、ここを起点とし、後続の各サービスの詳細を参照すれば処理を追えるはずである。

  1. Microsoft.Extensions.Configurationの構成情報を初期化する。順序は以下のとおり(後のエントリで前のエントリを上書き)
    • appsettings_agent.jsonJSONファイル。
    • 環境変数(プレフィックスなし)
  2. RuntimeLogLevel構成設定(既定値はinfo)をもとに、ロガーを初期化する。
    • 中身はSerilog。
    • Serilogのログレベルをsyslog互換のSeverityに変換するEnricherを持つ(Fatal=>0Error=>3Warning=>4Information=>6Debug/Verbose=>7
  3. versionInfo.jsonからバージョン情報を取得し、ログに出力する。
  4. ロゴを出力する。
  5. 構成情報を取得する。
  6. DIでモジュールを初期化する。このとき、${Mode}によりどのDIモジュールを初期化するのかが異なる。
  7. シャットダウンハンドラーを初期化する(ShutdownHandler.Init())。
  8. リクエストハンドラーを登録する(RegisterRequestHandlers)。
    1. IRequestManagerサービスにIRequestHandlerサービス群を登録する(IRequestManager.RegisterHandlers)。
  9. ストリームリクエストを開始する(IStreamRequestListener.InitPump())。
    1. 1.0.9-rc1時点では空。
  10. IConfigSourceサービスの初期化を待機。
  11. 5秒おきにAgentサービスのReconclieAsyncを呼び出す。

シャットダウン処理

  1. シャットダウンハンドラーがトリガーされると、Agent.ReconcileAsync()呼び出しループが終了する(ReconcileAsync()内の非同期処理または5秒間の待機処理はキャンセルされる)。
  2. クリーンアップを行う(Cleanup())。
    1. エージェントをシャットダウンする。(Agent.HandleShutdown())を実行する。タイムアウト時間は1分。
      1. 各IoT Edgeモジュールをシャットダウンする(ShutdownModules())。
        1. 現在のモジュールセットをIEnvironmentから取得する。
        2. シャットダウン処理のプランを作成する(IPlanner.CreateShutdownPlanAsync())。
        3. シャットダウン処理を実行する(IPlanRunner.ExecuteAsync())。
      2. IoT Hubにシャットダウンを通知する(IReporter.ReportShutdown)。
  3. シャットダウンハンドラーにシャットダウン処理の終了を通知する。

ダイレクトメソッド

ping

  • メソッド名:ping
  • リクエスト:なし
  • レスポンス:なし

$edgeAgentモジュールとの疎通を確認します。

GetTaskStatus

  • メソッド名:GetTaskStatus
リクエストボディ
{
    "properties": {
        "schemaVersion": {
            "type": "string",
            "description": "リクエストJSONのスキーマバージョン。現在は1.0。"
        },
        "correlationId": {
            "type": "string",
            "description": "ステータスを確認するバックグラウンドタスクの相関ID。"
        }
    }
}
レスポンスボディ
{
    "properties": {
        "status": {
            "type": "string",
            "enum": ["NotStarted", "Running", "Completed", "Cancelled", "Failed", "Unknown"],
            "description": "バックグラウンドタスクの状態。"
        },
        "message": {
            "type": "string",
            "description": "タスクが失敗していた場合、それを説明するメッセージ。"
        },
        "correlationId": {
            "type": "string",
            "description": "バックグラウンドタスクの相関ID。"
        }
    }
}

バックグラウンドタスクの状態を返す。なお、現状実装されているのは以下のタスク。

  • upload logs
  1. schemaVersion1.0でない場合はエラー。
  2. 指定したバックグラウンドタスクのステータスを返す。

GetLogs

  • メソッド名:GetLogs
リクエストボディ
{
    "properties": {
        "schemaVersion": {
            "type": "string"
        },
        "items": {
            "type": ["array", "object"],
            "items": {
                "type": "object",
                "required": ["id", "filter"],
                "properties": {
                    "id": {
                        "type": "string"
                    },
                    "filter": {
                        "type": "object",
                        "properties": {
                            "tail": {
                                "type": ["integer", "null"],
                                "default": null
                            },
                            "since": {
                                "type": ["integer", "null"],
                                "default": null
                            },
                            "loglevel": {
                                "type": ["integer", "null"],
                                "default": null
                            },
                            "regex": {
                                "type": ["string", "null"],
                                "default": null
                            },
                        }
                    }
                }
            },
            "required": ["id", "filter"],
            "properties": {
                "id": {
                    "type": "string"
                },
                "filter": {
                    "type": "object",
                    "properties": {
                        "tail": {
                            "type": ["integer", "null"],
                            "default": null
                        },
                        "since": {
                            "type": ["integer", "null"],
                            "default": null
                        },
                        "loglevel": {
                            "type": ["integer", "null"],
                            "default": null
                        },
                        "regex": {
                            "type": ["string", "null"],
                            "default": null
                        },
                    }
                }
            }
        },
        "encoding": {
            "type": "string",
            "enum": ["none", "gzip"],
            "default": "none"
        },
        "contentType": {
            "type": "string",
            "enum": ["text", "json"],
            "default": "json"
        }
    }
}
レスポンスボディ
{
    "type": "array",
    "items": {
        "type": "object",
        "properties": {
            "id": {
                "type": "string"
            },
            "payloadBytes": {
                "type": "string",
                "format": "base64"
            },
            "payload": {
                "type": "string"
            }
        }
    }
}

全モジュールのログを返す。

  1. schemaVersion1.0でない場合はエラー。
  2. IRuntimeInfoProviderを使用して、モジュールIDのリストを取得する。
  3. リクエストから有効なitemsをフィルタリングし、ログリクエスト内の各種オプションも追加したオプションリストを作成する。
    • なお、最大のtail500に制限されている。
  4. 各モジュールに対し、ILogsProvider.GetLogs()にログオプションを渡し、ログのバイト列を取得する。
  5. エンコーディングがnoneならpayloadに、gzipならpayloadBytesにログを格納する。
  6. 全モジュールのログの取得が完了したら、配列として返す。

RestartModule

  • メソッド名:RestartModule
  • レスポンス:なし
リクエストボディ
{
    "required": ["schemaVersion", "id"],
    "properties": {
        "schemaVersion": {
            "type": "string"
        },
        "id": {
            "type": "string"
        }
    }
}

指定したモジュールを再起動する。

  1. schemaVersion1.0でない場合はエラー。
  2. IConfigSourceから配置マニフェスト構成情報を取得する。
  3. IEnvironmentProviderから配置マニフェスト構成情報に対応したIEnvironmentを取得する。
  4. IEnvironmentからモジュールのリストを取得する。
  5. 対象のモジュールがリスト内に存在することを確認する。
  6. ICommandFactory.RestartAsync()を呼び出し、ICommandを取得する。
  7. ICommand.ExecuteAsync()を呼び出し、再起動を実行する。

UploadLogs

  • メソッド名:UploadLogs
リクエストボディ
{
    "required": ["schemaVersion", "items", "sasUrl"],
    "properties": {
        "schemaVersion": {
            "type": "string"
        },
        "items": {
            "type": ["array", "object"],
            "items": {
                "type": "object",
                "required": ["id", "filter"],
                "properties": {
                    "id": {
                        "type": "string"
                    },
                    "filter": {
                        "type": "object",
                        "properties": {
                            "tail": {
                                "type": ["integer", "null"],
                                "default": null
                            },
                            "since": {
                                "type": ["integer", "null"],
                                "default": null
                            },
                            "loglevel": {
                                "type": ["integer", "null"],
                                "default": null
                            },
                            "regex": {
                                "type": ["string", "null"],
                                "default": null
                            },
                        }
                    }
                }
            },
            "required": ["id", "filter"],
            "properties": {
                "id": {
                    "type": "string"
                },
                "filter": {
                    "type": "object",
                    "properties": {
                        "tail": {
                            "type": ["integer", "null"],
                            "default": null
                        },
                        "since": {
                            "type": ["integer", "null"],
                            "default": null
                        },
                        "loglevel": {
                            "type": ["integer", "null"],
                            "default": null
                        },
                        "regex": {
                            "type": ["string", "null"],
                            "default": null
                        },
                    }
                }
            }
        },
        "encoding": {
            "type": "string",
            "enum": ["none", "gzip"],
            "default": "none"
        },
        "contentType": {
            "type": "string",
            "enum": ["text", "json"],
            "default": "json"
        },
        "sasUrl": {
            "type": "string",
            "format": "url"
        }
    }
}
レスポンスボディ
{
    "properties": {
        "status": {
            "type": "string",
            "enum": ["NotStarted", "Running", "Completed", "Cancelled", "Failed", "Unknown"],
            "description": "バックグラウンドタスクの状態。"
        },
        "correlationId": {
            "type": "string",
            "description": "バックグラウンドタスクの相関ID。"
        }
    }
}

指定されたSAS URLのBlobストレージに全モジュールのDockerログをアップロードする。

  1. schemaVersion1.0でない場合はエラー。
  2. IRuntimeInfoProviderを使用して、モジュールIDのリストを取得する。
  3. リクエストから有効なitemsをフィルタリングし、ログリクエスト内の各種オプションも追加したオプションリストを作成する。
    • なお、100件、10秒でグルーピングする。
  4. 各モジュールに対し、ログの形式に合わせて、以下のいずれかを行う。
    • textの場合
      1. ILogsProvider.GetLogs()にログオプションを渡し、ログのバイト列を取得する。
      2. ILogsUploader.Upload()でログをアップロードする。
    • jsonの場合
      1. ILogsUploader.GetUploaderCallback()で送信コールバックを取得する。
      2. ILogsProvider.GetLogsStreamにオプションと送信コールバックを渡し、ログストリーミングを開始する。
  5. 全てのモジュールのアップロードが完了したら、バックグラウンドタスクの状態を更新する。

ストリーミング

Logs

リクエストボディ
{
    "required": ["schemaVersion", "items"],
    "properties": {
        "schemaVersion": {
            "type": "string"
        },
        "items": {
            "type": ["array", "object"],
            "items": {
                "type": "object",
                "required": ["id", "filter"],
                "properties": {
                    "id": {
                        "type": "string"
                    },
                    "filter": {
                        "type": "object",
                        "properties": {
                            "tail": {
                                "type": ["integer", "null"],
                                "default": null
                            },
                            "since": {
                                "type": ["integer", "null"],
                                "default": null
                            },
                            "loglevel": {
                                "type": ["integer", "null"],
                                "default": null
                            },
                            "regex": {
                                "type": ["string", "null"],
                                "default": null
                            },
                        }
                    }
                }
            },
            "required": ["id", "filter"],
            "properties": {
                "id": {
                    "type": "string"
                },
                "filter": {
                    "type": "object",
                    "properties": {
                        "tail": {
                            "type": ["integer", "null"],
                            "default": null
                        },
                        "since": {
                            "type": ["integer", "null"],
                            "default": null
                        },
                        "loglevel": {
                            "type": ["integer", "null"],
                            "default": null
                        },
                        "regex": {
                            "type": ["string", "null"],
                            "default": null
                        },
                    }
                }
            }
        },
        "encoding": {
            "type": "string",
            "enum": ["none", "gzip"],
            "default": "none"
        },
        "contentType": {
            "type": "string",
            "enum": ["text", "json"],
            "default": "text"
        }
    }
}

コアサービス

Agent

Edge Agentの本体である。

依存先

サービス 実装
IConfigSource BackupFileConfigSourceTwinConfigSource)またはFileConfigSource
IEnvironmentProvider DockerEnvironmentProvider
IPlanner HealthRestartPlanner
IPlanRunner OrderedRetryPlanRunner
IReporter IoTHubReporeter
IModuleIdentityLifecycleManager ModuleIdentityLifecycleManager
IEntityStore<string, string> TimedEntityStore<>-EntityStore<>-KeyValueStoreMapper<>-ColumnFamilyDbStore
ISerde<DeploymentConfigInfo> TypeSpecificSerDe<DeploymentConfigInfo>
IEncryptionProvider EncryptionProvider

実装

Create
  1. 構成ストア(IEntityStore<string, string>)から構成情報(キーCurrentConfig)を取得し、復元する。
  2. Agentのインスタンスを返す。
ReconcileAsync

Agentのメイン処理である。

  1. ロックを取得する。
  2. 調整対象を取得する(GetReconcileData()
    1. 以下を並列で取得する。
      • 現在のモジュールセット(GetCurrentModuleSetAsync)。IEnvironmentから取得する。
        • つまりは、マネジメントAPIかDocker APIから取得する。
        • これは報告用のモジュールセットである。
      • 配置マニフェストの情報(GetDeploymentConfigInfoAsync)。IConfigSourceから取得する。
        • つまりは、モジュールツインの取得か固定値5秒のタイムアウトのいずれかを待つ。
    2. エラーがあればAggregateExceptionに変換する。
    3. モジュールセット、配置マニフェストの情報、エラーをセットで返す。
  3. 配置マニフェストが空でないならば、モジュールセットを取得する。
  4. IModuleIdentityLifecycleManager.GetModuleIdentitiesAsync()で(認証情報がIoT Hubと同期された)モジュールアイデンティティのリストを取得する。
  5. IPlanner.PlanAsync()でモジュール更新のプランを作成する。
  6. プランが空でなければ、IPlanRunner.ExecuteAsync()でプランを実行する。
  7. 現在の構成情報を更新する。
    1. 適用した配置マニフェスト情報を基に、IEnvironmentProvider.CreateIEnvironment(つまり、報告用の環境情報)を作成する。
    2. 適用した配置マニフェスト情報を現在の配置マニフェスト情報として記憶する。
    3. 適用した配置マニフェスト情報をISerde<DeploymentConfigInfo>.Serializeでシリアル化する。
    4. シリアル化した配置マニフェスト情報をIEncryptionProvider.EncryptAsyncで暗号化する。つまり、ワークロードAPIを呼び出して暗号化する。
    5. 暗号化した配置マニフェスト情報をキーCurrentConfigIEntityStore<string, string>、つまりはRocksDBに保存する。
  8. プランがスキップされなければ、ステータスはsuccessである。
  9. IEnviroment.GetRuntimeInfoAsync()から報告用のランタイム情報を取得する。
  10. 報告用のモジュールセット、報告用のランタイム情報、配置マニフェストのバージョン、ステータスのセットを、IReporter.ReportAsync()で報告する。
  11. ロックを解放する。
HandleShutdown

シャットダウンフックで呼び出されるシャットダウン処理である。

  1. モジュールをシャットダウンする(ShutdownModules())。
    1. 現在のモジュールセットをIEnvironmentから取得する。
      • つまりは、マネジメントAPIかDocker APIから取得する。
    2. IPlannner.CreateShutdownPlanAsyncでシャットダウンプランを作成する。
    3. IPlanRunner.ExecuteAsync()でプランを実行する。
  2. ステータスをunknown、メッセージをAgent is not runningにした報告用ステータス(DeploymentStatus)を作成する。
  3. IReporter.ReportShutdown()で報告する。

IReporter

メンバー
Task ReportAsync(CancellationToken token, ModuleSet moduleSet, IRuntimeInfo runtimeInfo, long version, Option<DeploymentStatus> status);

Task ReportShutdown(DeploymentStatus status, CancellationToken token);

Agentから呼び出され、状態の報告を行う。

IoTHubReporeter

依存先
サービス 実装
IEdgeAgentConnection EdgeAgentConnection
ISerde<AgentState> TypeSpecificSerDe<AgentState>
実装

IoT Hubに(IEdgeAgentConnection経由で)状態を報告する。

IPlanner

メンバー
Task<Plan> PlanAsync(ModuleSet desired, ModuleSet current, IRuntimeInfo runtimeInfo, IImmutableDictionary<string, IModuleIdentity> moduleIdentities);

Task<Plan> CreateShutdownPlanAsync(ModuleSet modules);

RestartPlanner

依存先
サービス 実装
ICommandFactory EdgeletCommandFactory
実装
  • CreateShutdownPlanAsync
    • $edgeAgent以外の各モジュールのStopCommandParallelGroupCommandで並列実行するプランを作成する。
  • PlanAsync
    1. 望ましい状態と現在の状態の差分を計算する。
    2. 現在の状態に存在する全てのモジュールに対して、ICommandFactory.StopAsync()Stopコマンドを作成する。
    3. 削除されたモジュールに対して、ICommandFactory.RemoveAsync()Removeコマンドを作成する。
    4. 望ましい状態に存在する、あるべきステータスがModuleStatus.Runningのモジュールに対して、ICommandFactory.StartAsync()Startコマンドを作成する。
    5. 追加または更新されたモジュールに対し、追加ならばICommandFactory.CreateAsync()Createコマンドを、それ以外にはICommandFactory.UpdateAsync()Updateコマンドを作成する。
    6. 以下の順で並べたコマンドリストでPlanオブジェクトを初期化する。
      1. Stopコマンド
      2. Removeコマンド
      3. CreateUpdateコマンド
      4. Startコマンド
備考

使用されていない。

HealthRestartPlanner

依存先
サービス 実装
ICommandFactory EdgeletCommandFactory
IEntityStore<string, ModuleState> TimedEntityStore<>-EntityStore<>-KeyValueStoreMapper<>-ColumnFamilyDbStore
IRestartPolicyManager RestartPolicyManager
実装
  • CreateShutdownPlanAsync
    • $edgeAgent以外の各モジュールのStopCommandParallelGroupCommandで並列実行するプランを作成する。
  • PlanAsync
    1. 対象のモジュールを抽出する(ProcessDiff())。
      1. 望ましい状態と現在の状態の差分を計算する。
      2. 追加、更新、あるべきステータスの変更、削除されたモジュールのIDのセットを作成する。
      3. 何も変更がないモジュールのリストを作成する。
      4. 実際のステータスがあるべきステータスと異なるモジュールのリストを作成する。
      5. 実際のステータスもあるべきステータスも実行中のモジュールのリストを作成する。
    2. ランタイム更新コマンドを作成する(GetUpdateRuntimeCommands
      1. $edgeAgentモジュール自身に更新がある場合、それ用のコマンドをICommandFactory.UpdateEdgeAgentAsync()で取得する。
      2. $edgeAgentモジュールを更新のあるモジュールのリストから削除する。
    3. 削除されたモジュールのリストについて、ICommandFactory.StopAsync()Stopコマンドのリストを作成する。
    4. Stopコマンドリストを並列に実行し、終了を待つ。
    5. 削除されたモジュールのリストについて、ICommandFactory.RemoveAsync()Removeコマンドのリストを作成する。
    6. Removeコマンドリストを並列に実行し、終了を待つ。
    7. 追加されたモジュールのリストについて、コマンド群を追加する(ProcessAddedUpdatedModules
      1. 各モジュールについて、以下を繰り返す。
        1. ICommandFactory.CreateAsync()Createコマンドを作成する。
        2. そのモジュールのあるべきステータスがModuleStatus.Runningの場合、ICommandFactory.StartAsync()Startコマンドを作成する。
        3. それら2つのコマンド生成をGroupCommandでまとめ、ICommandFactory.WrapAsyncでラップする。
      2. 生成したコマンドのコレクションを返す。
    8. 更新されたモジュールのリストについて、コマンド群を追加する(ProcessAddedUpdatedModules
      1. 各モジュールについて、以下を繰り返す。
        1. ICommandFactory.UpdateAsync()Updateコマンドを作成する。
        2. そのモジュールのあるべきステータスがModuleStatus.Runningの場合、ICommandFactory.StartAsync()Startコマンドを作成する。
        3. それら2つのコマンド生成をGroupCommandでまとめ、ICommandFactory.WrapAsyncでラップする。
      2. 生成したコマンドのコレクションを返す。
    9. あるべきステータスの変更があったモジュールのリストについて、コマンド群を追加する(ProcessDesiredStatusChangedModules)。
      1. 各モジュールについて、以下を繰り返す。
        • そのモジュールのあるべきステータスがModuleStatus.Runningの場合、ICommandFactory.StartAsync()Startコマンドを作成する。
        • そのモジュールのあるべきステータスがModuleStatus.Stoppedの場合、ICommandFactory.StopAsync()Stopコマンドを作成する。
      2. 生成したコマンドのコレクションを返す。
    10. あるべきステータスと実際のステータスが異なるモジュールのリストについて、コマンド群を追加する(ProcessStateChangedModules)。
      1. あるべきステータスがModuleStatus.Stoppedのモジュールのリストを停止候補リストとして作成する。
      2. あるべきステータスがModuleStatus.Runningで実際のステータスがModuleStatus.Backoffのモジュールのリストを再起動候補リストとして作成する。
      3. 以下の条件全てに当てはまるモジュールのリストを起動候補リストとして作成する。
        • あるべきステータスがModuleStatus.Running
        • 現在のステータスがModuleStatus.Stopped
        • 再起動ポリシーがRestartPolicy.Neverでないか、一度も起動されていない(LastStartTimeUtcが初期値)
      4. 停止候補リストに対してICommandFactory.StopAsync()Stopコマンドを作成する。
      5. 起動候補リストに対してICommandFactory.StartAsync()Startコマンドを作成する。
      6. 再起動候補リストについて、以下のとおり調整する。
        1. IRestartPolicyManager.ApplyRestartPolicyを適用し、再起動候補を絞り込む。
        2. 各モジュールについて、以下を順番に行うGroupコマンドを作成し、ICommandFactory.WrapAsync()でラップする。これは、Windows版Dockerのバグに対処する回避策である。
          1. ICommandFactory.StopAsync()で作成するStopコマンド。
          2. ICommandFactory.StartAsync()で作成するStartコマンド。
          3. 再起動回数と最終再起動時間を記録するAddToStoreCommandコマンドを、ICommandFactory.WrapAsync()でラップしたコマンド。
      7. 以下の順で返す。
        1. Stopコマンド。
        2. Startコマンド。
        3. 再起動ポリシー適用後のラップされたコマンド。
    11. 更新、あるべきステータスの変更のいずれかがあったモジュールについて、モジュール状態ストアから削除するRemoveFromStoreCommandを作成し、ICommandFactory.WrapAsync()でラップする。
    12. 問題なく動作しているモジュールのリストについて、再起動情報をリセットするために、モジュール状態ストアから削除するRemoveFromStoreCommandを作成し、ICommandFactory.WrapAsync()でラップする。
    13. 作成したコマンド群を以下の順序で並べて、Planオブジェクトに詰めて返す。
      1. $edgeAgentへのUpdateEdgeAgentコマンド
      2. 削除されたモジュールへのStopコマンド
      3. 削除されたモジュールへのRemoveコマンド
      4. 更新、あるべきステータスの変更のいずれかがあったモジュールへのRemoveFromStoreCommandコマンド
      5. 追加されたモジュールにへのCreateコマンド
      6. 更新されたモジュールにへのUpdateコマンド
      7. あるべきステータスと実際のステータスが異なるモジュールへのStartおよびStopコマンド
      8. あるべきステータスが変更されたモジュールへのStartおよびStopコマンド
      9. 問題なく動作しているモジュールへのRemoveFromStoreCommandコマンド

IRestartPolicyManager

メンバー
ModuleStatus ComputeModuleStatusFromRestartPolicy(ModuleStatus status, RestartPolicy restartPolicy, int restartCount, DateTime lastExitTime);

IEnumerable<IRuntimeModule> ApplyRestartPolicy(IEnumerable<IRuntimeModule> modules);

再起動ポリシーの状態に合わせ、ステータスの計算や、再起動対象の選別を行う。

RestartPolicyManager

依存先

なし

実装

IRestartPolicyManagerの既定の実装である。

  • ComputeModuleStatusFromRestartPolicy
    • 以下のルールでモジュールのModuleStatusを調整する。
      1. 現在のステータスがModuleStatus.Runningであるか、再起動ポリシーがRestartPolicy.Neverの場合は何も調整しない。
      2. 再起動ポリシーがRestartPolicy.Alwaysであるか、または現在のステータスがModuleStatus.FailedもしくはModuleStatus.Unhealthyならば、以下のようになる。
        • 再起動回数が${MaxRestartCount}を越えているならばFailed
        • そうではないならばBackoff
  • ApplyRestartPolicy
    • 指定されたモジュールリストに対して、以下のルールで、再起動対象のモジュールのみにフィルタリングする。
      1. 現在のステータスがModuleStatus.Backoffでない場合は対象としない。
      2. モジュールの最終終了日時から、${CoolOffTimeUnitInSeconds}秒経っていない場合は対象としない。
        • なお、時計が正常に動作しておらず、経過時間が負の値になった場合は対象とする。

IPlanRunner

メンバー
Task<bool> ExecuteAsync(long deploymentId, Plan plan, CancellationToken token);

OrderedPlanRunner

依存先

なし

実装
  1. 引数で渡されたPlan内のICommandを順に実行する。エラーはキャッチする。
  2. キャッチしたエラーをAggregateExceptionとしてまとめてスローする。
  3. trueを返す。
備考

使用されていない。

OrderedRetryPlanRunner

依存先
サービス 実装
ISystemTime SystemTime
実装

IPlannerの実装が、(おそらくは配置マニフェストの適用がうまくいっていないので)もう一度適用するプランを作成した際に、前回エラー終了していた処理について指数関数的バックオフを使用して過剰な再試行が行われないように制御するIPlanRunner実装である。
なお、正常に終了する場合、IPlannerはそのモジュールに対するコマンドを生成しないはずという前提がある。

  1. ロックを取得する。
  2. 最終実行デプロイメントID(マニフェストのバージョン番号)があり、かつ実行しようとしているデプロイメントIDがあるならば、前回の実行状態をクリアする。
  3. 最終実行デプロイメントIDに、実行しようとしているデプロイメントIDを設定する。
  4. 引数で渡されたPlan内のICommandを順に処理する。
    1. 実行すべきかどうかを判定する(ShouldRunCommand)。
      1. 前回実行状態に存在しないならば、実行すべきである。
      2. 実行回数が(なぜか)${MaxRestartCount}を越えているならば、実行すべきでない。
      3. 前回実行日時が、${CoolOffTimeUnitInSeconds} * 2^実行回数を越えていないならば、実行すべきでない。
    2. コマンドを実行すべきなら、ICommand.ExecuteAsync()を呼び出して実行する。実行すべきでなければ、スキップした事実を覚えておく。
    3. コマンドが正常終了したならば、以下のようにする。
      1. 前回実行状態が存在するならば、ICommand.Idをキーにして、CommandRunStats.Defaultでリセットする。
    4. コマンドが例外をスローしたならば、キャッチして以下のようにする。
      1. AggregateExceptionに渡すためのリストに例外を追加する。
      2. 前回実行状態の実行回数に+1した値と、現在日時、キャッチした例外をまとめ、ICommand.Idをキーにして実行状態に記録する。
  5. キャッチしたエラーをAggregateExceptionとしてまとめてスローする。
  6. ロックを解放する。
  7. スキップしたモジュールがなければtrue、あればfalseを返す。
備考

使用されていない。

構成情報関連サービス

ICombinedConfigProvider

メンバー
T GetCombinedConfig(IModule module, IRuntimeInfo runtimeInfo);

構成情報とランタイム情報を合成して提供する。

CombinedDockerConfigProvider

依存先

なし

実装

単純にローカルの構成情報で配置マニフェストの情報を補完する。

  1. IRuntimeInfo<DockerConfig>が持つ資格情報を構成情報DockerRegistryAuthから読み取った値で上書きして返す。

CombinedEdgeletConfigProvider

依存先
サービス 実装
IConfigSource FileBackupedConfigSource or FileConfigSource
実装

単純にローカルの構成情報で配置マニフェストの情報を補完するだけでなく、edgeletが構築したネットワーク関連の情報も追加する。

  1. IRuntimeInfo<DockerConfig>が持つ資格情報を構成情報DockerRegistryAuthから読み取った値で上書きして返す。
  2. createOptionsに以下の設定を追加または上書きする。
  • hostConfig.bindsに以下を追加する。
    • ${IOTEDGE_MANAGEMENTURI}のURIのスキームがunixの場合、URIのパス部分(Windowsの場合はディレクトリパス)
    • ${IOTEDGE_WORKLOADURI}のURIのスキームがunixの場合、URIのパス部分(Windowsの場合はディレクトリパス)
  • NetworkingConfig.EndpointsConfigがない場合、以下を設定する。
    • $edgeHubの場合
      • NetworkingConfig.EndpointsConfig{"${NetworkId}":{"Alias": ["${EdgeDeviceHostName}"]}}
    • 全てのモジュール
      • NetworkingConfig.EndpointsConfig{"${NetworkId}":{}}

IConfigSource

メンバー
IConfiguration Configuration { get; }

Task<DeploymentConfigInfo> GetDeploymentConfigInfoAsync();

TwinConfigSource

依存先
サービス 実装
IEdgeAgentConnection EdgeAgentConnection
IConfiguration n/a
実装

モジュールツインベースの構成ソース。処理は全てIEdgeAgentConnectionIConfigurationに委譲。

FileBackupConfigSource

依存先
サービス 実装
ISerde<DeploymentConfigInfo> TypeSpecificSerDe<DeploymentConfigInfo>
IEncryptionProvider EncryptionProvider
実装

配置マニフェストをローカルのバックアップファイルに保存しておく構成ソース。

FileConfigSource

依存先
サービス 実装
ISerde<DeploymentConfigInfo> TypeSpecificSerDe<DeploymentConfigInfo>
実装

ローカルファイルベースの構成ソース。FileSystemWatcherで変更を監視し、変更があればJSONファイルを逆シリアル化して配置マニフェストとして格納する。

IoT Hub通信関連サービス

IEdgeAgentConnection

メンバー
Option<TwinCollection> ReportedProperties { get; }

IModuleConnection ModuleConnection { get; }

Task<Option<DeploymentConfigInfo>> GetDeploymentConfigInfoAsync();

Task UpdateReportedPropertiesAsync(TwinCollection patch);

EdgeAgentConnection

依存先
サービス 実装
IModuleClientProvider ModuleClientProvider
ISerde<DeploymentConfig> TypeSpecificSerDe<DeploymentConfig>
IRequestManagrer RequestManager
実装

Edge AgentとしてIoT Hubとの通信に依存した処理を実装する。IoT Hubとの通信はModuleConnectionに委譲する。

Desired Property更新の処理
  1. バージョンが確実にインクリメントされたものであることを検証する。
  2. 内容が空でないことを検証する。
  3. ローカルのDesired Propertyとマージする。
  4. Desired PropertyのJSONを配置マニフェスト構成情報として再逆シリアル化する。
  5. ローカルのモジュールツインをリフレッシュする。
定期的なツイン情報の更新

IoT Hubからの変更通知によらず、構成情報 ${ConfigRefreshFrequencySecs}の間隔でローカルのモジュールツインのリフレッシュと配置マニフェスト構成情報の更新を行う。
また、再接続検知時にも強制更新を行う。

IModuleConnection

メンバー
Task<IModuleClient> GetOrCreateModuleClient();

Option<IModuleClient> GetModuleClient();

ModuleConnection

依存先
サービス 実装
IModuleClientProvider ModuleClientProvider
IRequestManagrer RequestManager
実装

IModuleClientProviderから提供されたIModuleClientを使用し、IoT Hubとの通信を行う。

  • Desired Propertyの更新を呼び出し元(EdgeAgentConnection)に通知する
  • Direct Methodの呼び出しをIRequestManagerに委譲する

なお、enableSubscriptionsfalseの場合は何もしない。

IModuleClientProvider

メンバー
Task<IModuleClient> Create(ConnectionStatusChangesHandler statusChangedHandler);

ModuleClientProvider

プロトコル関連の初期化を行い、ModuleClientを作成する。

IModuleClient

メンバー
event EventHandler Closed;

bool IsActive { get; }

Task SetDesiredPropertyUpdateCallbackAsync(DesiredPropertyUpdateCallback onDesiredPropertyChanged);

Task SetMethodHandlerAsync(string methodName, MethodCallback callback);

Task SetDefaultMethodHandlerAsync(MethodCallback callback);

Task<Twin> GetTwinAsync();

Task UpdateReportedPropertiesAsync(TwinCollection reportedProperties);

////Task<DeviceStreamRequest> WaitForDeviceStreamRequestAsync(CancellationToken cancellationToken);

////Task<IClientWebSocket> AcceptDeviceStreamingRequestAndConnect(DeviceStreamRequest deviceStreamRequest, CancellationToken cancellationToken);

Task CloseAsync();

ModuleClient

ISdkModuleClientをラップし、リトライ機能を提供する。

ISdkModuleClientProvider

メンバー
ISdkModuleClient GetSdkModuleClient(string connectionString, ITransportSettings settings);

Task<ISdkModuleClient> GetSdkModuleClient(ITransportSettings settings);

Dockerモード用の接続文字列ベースとEdgeletモード用の環境変数ベースのメソッドを持つ。

SdkModuleClientProvider

Device SDKのModuleClientをラップした(モック用のISdkModuleClientインターフェイスを実装した)WrappingSdkModuleClientを提供する。

IModuleIdentityLifecycleManager

メンバー
Task<IImmutableDictionary<string, IModuleIdentity>> GetModuleIdentitiesAsync(ModuleSet desired, ModuleSet current);

ModuleIdentityLifecycleManager(Microsoft.Azure.Devices.Edge.Agent.Edgeletアセンブリ)

依存先
サービス 実装
IIdentityManager ModuleManatementHttpClient
ModuleIdentityProviderServiceBuilder ModuleIdentityProviderServiceBuilder
実装

edgeletのマネジメントAPI(アイデンティティAPI)を使用した実装(最終的にはedgeletがレジストリAPIをHTTPで呼び出している)

  1. あるべき状態と現在の状態の差分を取る。
  2. IoT Hubのレジストリと同期を取った完全なモジュールアイデンティティのリストを作成する(GetModuleIdentitiesAsync)。
    1. 追加または更新されたモジュールのアイデンティティ用のID(システムモジュールの場合プレフィックス$がつく)を取得する。
    2. 削除されたモジュールのアイデンティティ用のID(システムモジュールの場合プレフィックス$がつく)を取得する。
    3. マネジメントAPI(アイデンティティAPI)経由でIoT Hubのデバイスレジストリからモジュールアイデンティティのリストを取得する。
    4. 追加または更新されたモジュールのうち、IoT Hubのデバイスレジストリになかったモジュールを抽出し、作成対象とする。
    5. 追加または更新されたモジュールのうち、IoT Hubのデバイスレジストリにあり、かつIDが$edgeAgentでないモジュールを抽出し、更新対象とする。
    6. 削除対象のモジュールのうち、IoT Hubのデバイスレジストリに存在し、かつそのmanagedByIotEdgeであるものを抽出し、削除対象とする。
    7. 削除対象モジュールアイデンティティを、並列で、マネジメントAPI(アイデンティティAPI)経由でデバイスレジストリから削除する。
      • 先に削除しないとモジュール数制限に引っかかる可能性がある。
    8. 追加対象モジュールアイデンティティと更新対象モジュールアイデンティティをまとめて並列で、マネジメントAPI(アイデンティティAPI)経由でデバイスレジストリに作成・更新する。
    9. 追加・更新対象のモジュールのアイデンティティについて、ModuleIdentityProviderServiceBuilderを使用してIModuleIdentityオブジェクトを生成する。
      1. マネジメントAPI(アイデンティティAPI)の戻り値のgenerationId、および${IOTEDGE_WORKLOADURI}の値を使用して、IdentityProviderServiceCredentialsオブジェクトを作成する。このとき、authSchemesasTokenになる。
      2. ModuleIdentityProviderServiceBuilderに渡された、${IOTEDGE_IOTHUBHOSTNAME}${IOTEDGE_DEVICEID}${EdgeDeviceHostName}マネジメントAPI(アイデンティティAPI)の戻り値のmoduleIdIdentityProviderServiceCredentialsを使用してModuleIdentityオブジェクトを作成する。
    10. 生成したモジュールアイデンティティのリストと、変更のなかったモジュールアイデンティティのリストを連結する。
    11. システムモジュールのIDからプレフィックス$を取り除き、返す。

ModuleIdentityLifecycleManager(Microsoft.Azure.Devices.Edge.Agent.IoTHubアセンブリ)

依存先
サービス 実装
IServiceClient RetryingServiceClient
実装

Service SDKのRegistryManagerを使用した実装。

  1. あるべき状態と現在の状態の差分を取る。
  2. IoT Hubのレジストリと同期を取った完全なモジュールアイデンティティのリストを作成する(GetModuleIdentitiesAsync)。
    1. 追加または更新されたモジュールのアイデンティティ用のID(システムモジュールの場合プレフィックス$がつく)を取得する。
    2. 削除されたモジュールのアイデンティティ用のID(システムモジュールの場合プレフィックス$がつく)を取得する。
    3. IoT Hubデバイスレジストリ上のモジュールのリストを取得する。
    4. 追加または更新されたモジュールのうち、IoT Hubのデバイスレジストリになかったモジュールを抽出し、作成対象とする。
    5. 削除対象のモジュールのうち、IoT Hubのデバイスレジストリに存在し、かつそのmanagedByIotEdgeであるものを抽出し、削除対象とする。
    6. IoT Hubのデバイスレジストリ上のモジュールのうち、以下の条件に当てはまるものについては更新対象とし、認証方式にAuthenticationType.Sasを指定する。これは、Service SDKのApplyConfigurationContentOnDeviceAsyncでデプロイしたデバイス(単一配置)のモジュールのアイデンティティには認証情報が含まれないため、あらかじめ設定しておく必要があるためである。
      • authenticationが未定義。
      • または、authentication.typesasでない
      • または、authentication.symmetricKeyが未定義
      • または、authentication.symmetricKey.primaryKeyauthentication.symmetricKey.secondaryKeyの両方が未定義
    7. 削除対象モジュールアイデンティティを並列でデバイスレジストリから削除する。
      • 先に削除しないとモジュール数制限に引っかかる可能性がある。
    8. 追加対象モジュールアイデンティティと更新対象モジュールアイデンティティをまとめて並列でデバイスレジストリに作成・更新する。
    9. 追加・更新対象でないモジュールのアイデンティティと、追加・更新対象としたモジュールのアイデンティティのリストを連結する。
    10. 各モジュールアイデンティティについて、接続文字列を生成し、ConnectionStringCredentialsとしてModuleIdentityオブジェクトに設定する。
      • 先ほどIoT Hubのデバイスレジストリで自動生成されたSASキーを含む
      • $edgeHub以外ではGatewayHostNameを含む

IServiceClient

メンバー
Task<IEnumerable<Module>> GetModules();

Task<Module> GetModule(string moduleId);

Task<Module[]> CreateModules(IEnumerable<string> identities);

Task<Module[]> UpdateModules(IEnumerable<Module> modules);

Task RemoveModules(IEnumerable<string> identities);

ServiceClient

依存先

なし

実装

デバイス接続文字列からService SDKのRegistryManagerを初期化し、そのRegistryManagerに処理を委譲する。

RetryingServiceClient

依存先
サービス 実装
IServiceClient ServiceClient
実装

ArgumentExceptionUnauthorizedException以外のエラーが発生したらすべからくリトライするラッパー。

リクエスト処理

IRequestManager

メンバー
Task<(int statusCode, Option<string> responsePayload)> ProcessRequest(string request, string payloadJson);

void RegisterHandlers(IEnumerable<IRequestHandler> requestHandlers);

RequestManager

依存先

なし

実装

ConcurrentDictionaryを使用して、メソッド名とそれを処理するIRequestHandlerを管理する。

IStreamRequestHandlerProvider

デバイストリーミングのハンドラー群を提供する。現状、Logsに対するLogsStreamRequestHandlerのみを返す。

IStreamRequestHandler

デバイスストリーミングのハンドラー。

モジュール管理関連

IEnvironmentProvider

メンバー
IEnvironment Create(DeploymentConfig deploymentConfigInfo);

DockerEnvironmentProvider

依存先
サービス 実装
IRuntimeInfoProvider RuntimeInfoProvider<T>
IEntityStore<string, ModuleState> TimedEntityStore<>-EntityStore<>-KeyValueStoreMapper<>-ColumnFamilyDbStore
IRestartPolicyManager RestartPolicyManager
実装

DockerEnvironmentを作成して返す。

IEnvironment

メンバー
/// <summary>
/// Returns a ModuleSet representing the current state of the system (current reality)
/// </summary>
/// <returns></returns>
Task<ModuleSet> GetModulesAsync(CancellationToken token);

/// <returns>An <see cref="IRuntimeInfo"/> object that contains the runtime information.</returns>
Task<IRuntimeInfo> GetRuntimeInfoAsync();

DockerEnvironment

依存先
サービス 実装
IRuntimeInfoProvider RuntimeInfoProvider<T>
IEntityStore<string, ModuleState> TimedEntityStore<>-EntityStore<>-KeyValueStoreMapper<>-ColumnFamilyDbStore
IRestartPolicyManager RestartPolicyManager
実装
  • 初期化
    • IRuntimeInfoProvider.GetSystemInfoAsync()で取得した情報をコンストラクターで受け取っておく。
  • GetModuleAsync
    1. IRuntimeInfoProvider.GetModules()で実行時モジュール情報を取得する。
    2. 配置マニフェスト情報(DeploymentConfig)からモジュールセットを取得する。
    3. IEntityStore<string, ModuleState>からモジュールの再起動状態を取得する。
      • 取得できない場合、再起動回数0、最終再起動日時は実行時モジュール情報から取得した起動日時とする。
    4. モジュールのあるべきステータスがModuleStatus.Runningの場合、IRestartPolicyManager.ComputeModuleStatusFromRestartPolicy()で適切なステータスを取得する。ModuleStatus.Running以外の場合は実行時モジュール情報のステータスそのまま使用する。
    5. イメージ名を決定する。実行時モジュール情報にあればそれを、なければ配置マニフェスト情報を使用する。
    6. 決定したイメージ名、配置マニフェスト情報で指定された作成オプション、実行時モジュール情報のイメージハッシュを使用して、報告用Docker構成情報(DockerReportedConfig)を作成する。
    7. これまで作成した情報を基に、モジュールのIDに対応した報告用構成(EdgeHubDockerRuntimeModuleEdgeAgentDockerRuntimeModuleDockerRuntimeModule)を作成する。
  • GetRuntimeInfoAsync()
    1. コンストラクターで受け取ったシステム情報と配置マニフェスト情報のruntimeからDoDockerReportedRuntimeInfoを作成して返す。

IRuntimeInfoProvider

メンバー
Task<IEnumerable<ModuleRuntimeInfo>> GetModules(CancellationToken cancellationToken);

Task<Stream> GetModuleLogs(string module, bool follow, Option<int> tail, Option<int> since, CancellationToken cancellationToken);

Task<SystemInfo> GetSystemInfo();

IoT Edgeの実行時情報を提供する。

Edgelet.RuntimeInfoProvider

IModuleManagerに処理を委譲する実装。

Docker.RuntimeInfoProvider

IDockerClient経由でDocker APIを呼び出すことで、IoT Edgeモジュールの情報を提供する。

IModuleManager

メンバー
Task CreateModuleAsync(ModuleSpec moduleSpec);

Task StartModuleAsync(string name);

Task StopModuleAsync(string name);

Task DeleteModuleAsync(string name);

Task RestartModuleAsync(string name);

Task UpdateModuleAsync(ModuleSpec moduleSpec);

Task UpdateAndStartModuleAsync(ModuleSpec moduleSpec);

Task<SystemInfo> GetSystemInfoAsync();

Task<IEnumerable<ModuleRuntimeInfo>> GetModules<T>(CancellationToken token);

Task PrepareUpdateAsync(ModuleSpec moduleSpec);

Task<Stream> GetModuleLogs(string name, bool follow, Option<int> tail, Option<int> since, CancellationToken cancellationToken);

ModuleManagementHttpClient

edgeletのマネジメントAPIを呼び出すIModuleManager実装。

ログ関連

LogsStreamRequestHandler

ログストリーミングのハンドラー。

  1. IRuntimeInfoProviderを使用して、モジュールIDのリストを取得する。
  2. リクエストから有効なitemsをフィルタリングし、ログリクエスト内の各種オプションも追加したオプションリストを作成する。
  3. ILogsProvider.GetLogsStreamにオプションリストと送信コールバックを渡し、ログストリーミングを開始する。
  4. 送信コールバックは、WebSocketのストリームにバイナリメッセージとしてログバイト列を流す。

ILogsProvider

LogsProvider

モジュールのログを提供する。

  1. IRuntimeInfoProviderを使用して、ログストリームを取得する。
  2. ILogsProcessorにログを加工させる。

ILogsProcessor

ログのフォーマット変換を行う。

LogsProcessor

ログのフィルタリングを行う。ログの解析はILogMessageParserに委譲する。
内部的にはAkkaによるアクターモデルを採用。

ILogMessageParser

ログの解析を行う。

LogMessageParser

正規表現によるログの解析を行う。
内部的にはAkkaによるアクターモデルを採用。

コマンド

コマンドは、コマンドファクトリから開始する。

  • ConfigSourcetwinの場合は、EdgeletCommandFactoryをラップするLoggingCommandFactory
    • 各メソッドはTask.FromResultでコマンドインスタンスを返す。
  • ConfigSourcelocalの場合は、DockerCommandFactory

EdgeletCommandFactory経由

IModuleManager(実装はModuleManagementHttpClient経由のedgeletのマネジメントAPI)のコマンドを生成する。

Create

CreateOrUpdateCommandを作成する(Operation.Create)。 なお、作成時にはモジュールに設定する環境変数が補完される。
また、構成情報はICombinedConfigProviderにより、現在の情報と配置マニフェストの情報がマージされたものが使用される。

  • モジュールが$edgeAgent$edgeHubの場合、以下を設定する。
    • EdgeDeviceHostName
  • モジュールが$edgeAgentの場合、以下を設定する。
    • IOTEDGE_MANAGEMENTURI
    • NetworkId
    • Mode
  • モジュールがそれ以外の場合、以下を設定する。
    • IOTEDGE_GATEWAYHOSTNAME
  • IModuleIdenity.CredentialIdentityProviderServiceCredentialsの場合(すなわち、Microsoft.Azure.Devices.Agent.EdgeletアセンブリのModuleIdentityLifecycleManagerが使用されている場合)で、かつIdentityProviderServiceCredentialsオブジェクトの値を設定する。
    • IOTEDGE_WORKLOADURI
    • IOTEDGE_AUTHSCHEME
    • IOTEDGE_MODULEGENERATIONID
  • 存在する場合のみ、Edge Agent自身の値で上書き設定する
    • RuntimeLogLevel
    • UpstreamProtocol
  • 常に以下を設定する。
    • IOTEDGE_IOTHUBHOSTNAME
    • IOTEDGE_DEVICEID
    • IOTEDGE_MODULEID
    • IOTEDGE_APIVERSION

最終的には、IModuleManager.CreateModuleAsync()が実行される。

  1. edgeletのマネジメントAPIであるcreate module APIを呼び出し、container createが実行される。

Update

以下の3つのコマンドをグループ化したGroupCommandを作成する。

  • PrepareUpdateCommand

    • 構成情報はICombinedConfigProviderにより、現在の情報と配置マニフェストの情報がマージされたものが使用される。
  • StopCommand

  • CreateOrUpdateCommandOperation.Update)。

    • なお、作成時にはモジュールに設定する環境変数が補完される(Createを参照)。
      また、構成情報はICombinedConfigProviderにより、現在の情報と配置マニフェストの情報がマージされたものが使用される。
  • PrepareUpdateCommand

    • 最終的にはIModuleManager.PrepareUpdateModuleAsync()が実行される。
      1. edgeletのマネジメントAPIであるprepare update module APIを呼び出し、image create(いわゆるpull)が実行される。
  • StopCommand

    • 最終的にはIModuleManager.StopModuleAsync()が実行される。
      1. edgeletのマネジメントAPIであるstop module APIを呼び出し、container stopが実行される。
  • CreateOrUpdateCommandOperation.Update)。

    • 最終的には、IModuleManager.UpdateModuleAsync()が実行される。
      1. edgeletのマネジメントAPIであるupdate module APIを呼び出し、container deleteとcontainer createが実行される。

UpdateEdgeAgent

以下の3つのコマンドをグループ化したGroupCommandを作成する。

  • PrepareUpdateCommand

    • 構成情報はICombinedConfigProviderにより、現在の情報と配置マニフェストの情報がマージされたものが使用される。
  • StopCommand

  • CreateOrUpdateCommandOperation.UpdateAndStart)。

    • なお、作成時にはモジュールに設定する環境変数が補完される(Createを参照)。
      また、構成情報はICombinedConfigProviderにより、現在の情報と配置マニフェストの情報がマージされたものが使用される。
  • PrepareUpdateCommand

    • 最終的にはIModuleManager.PrepareUpdateModuleAsync()が実行される。
      1. edgeletのマネジメントAPIであるprepare update module APIを呼び出し、image createが実行される。
  • StopCommand

    • 最終的にはIModuleManager.StopModuleAsync()が実行される。
      1. edgeletのマネジメントAPIであるstop module APIを呼び出し、container stopが実行される。
  • CreateOrUpdateCommandOperation.Update)。

    • 最終的には、IModuleManager.UpdateModuleAsync()が実行される。
      1. edgeletのマネジメントAPIであるupdate module APIを呼び出し、container deleteとcontainer createが実行される。

Remove

RemoveCommandを作成する。

最終的には、IModuleManager.DeleteModuleAsync()が実行される。

  1. edgeletのマネジメントAPIであるdelete module APIを呼び出し、container deleteが実行される。

Start

StartCommandを作成する。

最終的には、IModuleManager.StartModuleAsync()が実行される。

  1. edgeletのマネジメントAPIであるstart module APIを呼び出し、container startが実行される。

Stop

StopCommandを作成する。

最終的には、IModuleManager.StopModuleAsync()が実行される。

  1. edgeletのマネジメントAPIであるstop module APIを呼び出し、container stopが実行される。

Restart

RestartCommandを作成する。

最終的には、IModuleManager.RestartModuleAsync()が実行される。

  1. edgeletのマネジメントAPIであるrestart module APIを呼び出し、container restartが実行される。

Wrap

  • 引数のコマンドをそのまま返す。これは、このコマンドファクトリが返さないコマンドに対し、デコレーターとしてのコマンドファクトリの機能を提供するために使用する。

DockerCommandFactory経由

Create

以下の2つのコマンドをグループ化したGroupCommandを作成する。

  • PullCommandimagePullPolicyNever以外の場合のみ)

  • CreateOrUpdateCommandOperation.Create)。

    • 構成情報はICombinedConfigProviderにより、現在の情報と配置マニフェストの情報がマージされたものが使用される。
  • PullCommmand

    • 最終的には、Docker ClientのCreateImageAsync()が実行される。
  • CreateOrUpdateCommand

    • 最終的には、IModuleManager.CreateModuleAsync()が実行される。
      1. Docker ClientのAPIが実行される。

Update

以下の4つのコマンドをグループ化したGroupCommandを作成する。

  • PullCommandimagePullPolicyNever以外の場合のみ)

  • StopCommand

  • RemoveCommand

  • CreateOrUpdateCommandOperation.Update)。

    • なお、作成時にはモジュールに設定する環境変数が補完される(Createを参照)。
      また、構成情報はICombinedConfigProviderにより、現在の情報と配置マニフェストの情報がマージされたものが使用される。
  • PullCommmand

    • 最終的には、Docker ClientのCreateImageAsync()が実行される。
  • StopCommand

    • 最終的にはIModuleManager.StopModuleAsync()が実行される。
      1. Docker ClientのAPIが実行される。
  • RemoveCommand

    • 最終的にはIModuleManager.DeleteModuleAsync()が実行される。
      1. Docker ClientのAPIが実行される。
  • CreateOrUpdateCommand

    • 最終的には、IModuleManager.CreateModuleAsync()が実行される。
      1. Docker ClientのAPIが実行される。

UpdateEdgeAgent

NullCommandを生成する。つまり、DockerモードではEdge Agent自身を更新できない。

Remove

RemoveCommandを作成する。

最終的には、IModuleManager.DeleteModuleAsync()が実行される。

  1. Docker ClientのAPIが実行される。

Start

StartCommandを作成する。

最終的には、IModuleManager.StartModuleAsync()が実行される。

  1. Docker ClientのAPIが実行される。

Stop

StopCommandを作成する。

最終的には、IModuleManager.StopModuleAsync()が実行される。

  1. Docker ClientのAPIが実行される。

Restart

RestartCommandを作成する。

最終的には、IModuleManager.RestartModuleAsync()が実行される。

  1. Docker ClientのAPIが実行される。

Wrap

  • 引数のコマンドをそのまま返す。これは、このコマンドファクトリが返さないコマンドに対し、デコレーターとしてのコマンドファクトリの機能を提供するために使用する。

IoT Edge Deep Dive 3 -- Edge Hub

WIP

構成

  • Microsoft.Azure.Devices.Edge.Hub.Core
  • Microsoft.Azure.Devices.Edge.Hub.Http
  • Microsoft.Azure.Devices.Edge.Hub.Amqp
  • Microsoft.Azure.Devices.Edge.Hub.Mqtt
  • Microsoft.Azure.Devices.Edge.Hub.CloudProxy
  • Microsoft.Azure.Devices.Roputing.Core
  • Microsoft.Azure.Devices.Edge.Hub.Service

構成情報

.NET Coreアプリケーション構成

Microsoft.Extensions.Configurationの構成情報で、ファイル名はappsettings_hub.json

キー 既定値 appsettings_agent.jsonの値 $edgeAgentが設定する値 備考
RuntimeLogLevel info info ${RuntimeLogLevel}
EnableRoutingLogging false null null
IotHubConnectionString null "" null
EdgeHubDevServerCertificateFile null "" null
EdgeHubDevServerPrivateKeyFile null "" null
EdgeHubDevTrustBundleFile null "" null
EdgeModuleHubServerCertificateFile null "" null
EdgeModuleHubServerCAChainCertificateFile null "" null
IOTEDGE_WORKLOADURI null null config.yamlconnect.workload_uriの値
EDGEDEVICEHOSTNAME null null config.yamlhostnameの値
IOTEDGE_MODULEID null null $edgeHub
IOTEDGE_MODULEGENERATIONID null null (デバイスレジストリ上の値)
IOTEDGE_APIVERSION null null 2019-01-30
ClientCertAuthEnabled false true null
IOTEDGE_IOTHUBHOSTNAME null null (IoT Hubのホスト名)
IOTEDGE_DEVICEID null null (デバイスID)
AuthenticationMode CloudAndScope CloudAndScope null
IOTEDGE_AUTHSCHEME null null (デバイスレジストリ上の値=sasToken ★未使用
httpSettings:enabled true null
httpSettings:port 443 443 null
OptimizeForPerformance true true null
experimentalFeatures:enabled false null null
experimentalFeatures:disableCloudSubscriptions false null null
experimentalFeatures:disableConnectivityCheck false null null
usePersistentStorage false true null
storageFolder (一時ディレクトリ) "" null edgeHubサブディレクトリが使用される。
storeAndForwardEnabled false true null
storeAndForward:timeToLiveSecs -1 -1 null
CacheTokens false false null
DeviceScopeCacheRefreshRateSecs 3600 3600 null
metrics:enabled false null null
metrics:listener:host * null null
metrics:listener:port 9600 null null
metrics:listener:suffix metrix null null
metrics:MetricsStoreType influxdb null ★未使用?
https_proxy "" null null
routes null {"r1": "FROM /* INTO $upstream"} null
IotHubConnectionPoolSize 0 1 null
configSource null "twin" null
UpstreamProtocol null null ${UpstreamProtocol}
ConnectivityCheckFrequencySecs 300 300 null
MaxConnectedClients 100 100 null
CloudConnectionIdleTimeoutSecs 3600 3600 null
CloseCloudConnectionOnIdleTimeout true true null
CloudOperationTimeoutSecs 20 null null
MinTwinSyncPeriodSecs 2 null null 既定値はStoringTwinManagerが持つ
ReportedPropertiesSyncFrequencySecs 5 null null 既定値はReportedPropertiesStoreが持つ
TwinManagerVersion null null null
MaxUpstreamBatchSize 10 null null ★使ってる?
UpstreamFanOutFactor 10 null null
EncryptTwinStore false null null
ConfigRefreshFrequencySecs 3600 null null
mqttSettings:enabled true null
mqttTopicNameConversion:InboundTemplates null ["devices/{deviceId}/messages/events/{params}/", "devices/{deviceId}/messages/events/", "devices/{deviceId}/modules/{moduleId}/messages/events/{params}/", "devices/{deviceId}/modules/{moduleId}/messages/events/", "devices/{deviceId}/messages/events/{params}", "devices/{deviceId}/messages/events", "devices/{deviceId}/modules/{moduleId}/messages/events/{params}", "devices/{deviceId}/modules/{moduleId}/messages/events", "$iothub/methods/res/{statusCode}/?$rid={correlationId}"] null
mqttTopicNameConversion:OutboundTemplates null {"C2D": "devices/{deviceId}/messages/devicebound", "TwinEndpoint": "$iothub/twin/res/{statusCode}/?$rid={correlationId}", "TwinDesiredPropertyUpdate": "$iothub/twin/PATCH/properties/desired/?$version={version}", "ModuleEndpoint": "devices/{deviceId}/modules/{moduleId}/inputs/{inputName}"} null
amqpSettings:enabled true null
amqpSettings:scheme null "amqps" null
amqpSettings:port 0 5671 null

DI構成

モジュール構成(AutoFac)

Edge HubはAutoFacによるDIを使用している。AutoFacでは、DI構成をいくつかのモジュールに分けて管理する。ここでは、そのDIモジュールの一覧と、そこで使用する構成情報を示す。

条件 モジュール 構成 備考
(常に) LoggingModule (なし)
(常に) CommonModule EdgeHub/$(version.json)
${IOTEDGE_IOTHUBHOSTNAME}
${IOTEDGE_DEVICEID}
${IOTEDGE_MODULEID}
${EDGEDEVICEHOSTNAME}
${IOTEDGE_MODULEGENERATIONID}
${AuthenticationMode}
${IotHubConnectionString}
${OptimizeForPerformance}
${usePersistentStorage}
${storageFolder}/EdgeHub
${IOTEDGE_WORKLOADURI}
${IOTEDGE_APIVERSION}
${DeviceScopeCacheRefreshRateSecs}
${CacheTokens}
(信頼済証明書チェイン)
${http_proxy}
${metrics:*}
(常に) RoutingModule ${IOTEDGE_IOTHUBHOSTNAME}
${IOTEDGE_DEVICEID}
${IOTEDGE_MODULEID}
${IotHubConnectionString}
${routes:*}
${storeAndForwardEnabled}
${storeAndForward:*}
${IotHubConnectionPoolSize}
${configSource} ~= "twin"
$(version.json)
${UpstreamProtocol}
${ConnectivityCheckFrequencySecs}
${MaxConnectedClients} + 1
${CloudConnectionIdleTimeoutSecs}
${CloseCloudConnectionOnIdleTimeout}
${MinTwinSyncPeriodSecs}
${ReportedPropertiesSyncFrequencySecs}
${TwinManagerVersion} ~= "v1"
${MaxUpstreamBatchSize}
${UpstreamFanOutFactor}
${EncryptTwinStore}
${ConfigRefreshFrequencySecs}
${experimentalFeatures:*}

(常に)|MqttModule|${mqttSettings:*}
(常に)|AmqpModule|${amqpSettings:scheme}
${amqpSettings:port}
(サーバー証明書)
${IOTEDGE_IOTHUBHOSTNAME}
${ClientCertAuthEnabled}
MessageAddressConversionConfiguration(${mqttTopicNameConversion:InboundTemplates}, ${mqttTopicNameConversion:OutboundTemplates})
(サーバー証明書)
${storeAndForwardEnabled}
${OptimizeForPerformance}| (常に)|HttpModule|(なし)|

サービス構成(AutoFac)

Edge HubはAutoFacによるDIを使用している。ここでは、DIされるサービス型、実装型、使用する構成情報、依存先、条件を一覧化している。

モジュール 条件 サービス 実装 構成 備考
LoggingModule (常に) ILoggerFactory SerilogのILoggerFactory (なし)
CommonModule ${metrics:enabled}true IMetricsListener MetricsListener ${metrics:listener:*}
CommonModule ${metrics:enabled}false IMetricsListener NullMetricsListener (なし)
CommonModule ${metrics:enabled}true IMetricsProvider MetricsProvider "edgehub"
${IOTEDGE_IOTHUBHOSTNAME} or ${IotHubConnectionString}.HostName
${IOTEDGE_DEVICEID} or ${IotHubConnectionString}.DeviceId
CommonModule ${metrics:enabled}false IMetricsProvider NullMetricsProvider (なし)
CommonModule ${IotHubConnectionString}がある ISignatureProvider SharedAccessKeySignatureProvider ${IotHubConnectionString}.SharedAccessKey
CommonModule ${IotHubConnectionString}がない ISignatureProvider HttpHsmSignatureProvider ${IOTEDGE_MODULEID}
${IOTEDGE_MODULEGENERATIONID}
${IOTEDGE_WORKLOADURI}
${IOTEDGE_APIVERSION}
"2019-01-30"
CommonModule (常に) ISystemEnvironment SystemEnvironment (なし)
CommonModule (常に) IRocksDbOptionsProvider RocksDbOptionsProvider ISystemEnvironment
${OptimizeForPerformance}
CommonModule ${usePersistentStorage}true IDbStoreProvider DbStoreProvider IRocksDbOptionsProvider
${storageFolder}/EdgeHub
["messages", "twins", "checkpoints"]
CommonModule ${usePersistentStorage}falseか、DbStoreProvider作成に失敗 IDbStoreProvider InMemoryDbStoreProvider (なし)
CommonModule (常に) IProductInfoStore ProductInfoStore IEntityStore<string, string>("ProductInfo")
CommonModule ${IOTEDGE_WORKLOADURI}がある Option<IEncryptionProvider> Option.Some(EncryptionProvider) ${storageFolder}/EdgeHub
${IOTEDGE_WORKLOADURI}
${IOTEDGE_APIVERSION}
"2019-01-30"
${IOTEDGE_MODULEID}
${IOTEDGE_MODULEGENERATIONID}
"EdgeHubIV"
CommonModule ${IOTEDGE_WORKLOADURI}がない Option<IEncryptionProvider> Option.None (なし)
CommonModule (常に) IStoreProvider StoreProvider IDbStoreProvider
CommonModule (常に) ITokenProvider("EdgeHubClientAuthTokenProvider") ClientTokenProvider ISignatureProvider
${IOTEDGE_IOTHUBHOSTNAME} or ${IotHubConnectionString}.HostName
${IOTEDGE_DEVICEID} or ${IotHubConnectionString}.DeviceId
${IOTEDGE_MODULEID} or ${IotHubConnectionString}.ModuleId
1時間
CommonModule (常に) ITokenProvider("EdgeHubServiceAuthTokenProvider") ClientTokenProvider ISignatureProvider
${IOTEDGE_IOTHUBHOSTNAME} or ${IotHubConnectionString}.HostName
${IOTEDGE_DEVICEID} or ${IotHubConnectionString}.DeviceId
${IOTEDGE_MODULEID} or ${IotHubConnectionString}.ModuleId
1時間
CommonModule (常に) IWebProxy WebProxy ${https_proxy}
ILogger
CommonModule ${AuthenticationMode}CloudAndScopeScope IDeviceScopeIdentitiesCache DeviceScopeIdentitiesCache ServiceProxy(DeviceScopeApiClient(${IOTEDGE_IOTHUBHOSTNAME} or ${IotHubConnectionString}.HostName, ${IOTEDGE_DEVICEID} or ${IotHubConnectionString}.DeviceId, ${IOTEDGE_MODULEID} or ${IotHubConnectionString}.ModuleId, 10, ITokenProvider("EdgeHubServiceAuthTokenProvider"), IWebProxy))
EncryptedStore(IEncryptionProvider, IEntityStore<string, string>("DeviceScopeCache"))
${DeviceScopeCacheRefreshRateSecs}
IEncryptionProviderが無効な場合はEncryptedStoreではなくNullKeyValueStoreになる。
CommonModule ${AuthenticationMode}がそれ以外 IDeviceScopeIdentitiesCache> NullDeviceScopeIdentitiesCache (なし)
CommonModule ${usePersistentStorage}true ICredentialsCache CredentialsCache PersistedTokenCredentialsCache(EncryptedStore(IEncryptionProvider, IEntityStore<string, string>("CredentialsCache")))
CommonModule ${usePersistentStorage}false ICredentialsCache CredentialsCache NullCredentialsCache
CommonModule ${AuthenticationMode}Cloudで、かつ${usePersistentStorage}true IAuthenticator Authenticator CloudTokenAuthenticator(CloudTokenAuthenticator(IConnectionManager, ${IOTEDGE_IOTHUBHOSTNAME} or ${IotHubConnectionString}.HostName), ICredentialsCache, ${IOTEDGE_IOTHUBHOSTNAME} or ${IotHubConnectionString}.HostName)
DeviceScopeCertificateAuthenticator(IDeviceScopeIdentitiesCache, NullAuthenticator, (信頼済証明書チェイン), true)
ICredentialsCache
CommonModule ${AuthenticationMode}Cloudで、かつ${usePersistentStorage}false IAuthenticator Authenticator CloudTokenAuthenticator(IConnectionManager, ${IOTEDGE_IOTHUBHOSTNAME} or ${IotHubConnectionString}.HostName)
DeviceScopeCertificateAuthenticator(IDeviceScopeIdentitiesCache, NullAuthenticator, (信頼済証明書チェイン), true)
ICredentialsCache
CommonModule ${AuthenticationMode}Scope IAuthenticator Authenticator DeviceScopeTokenAuthenticator(IDeviceScopeIdentitiesCache, ${IOTEDGE_IOTHUBHOSTNAME} or ${IotHubConnectionString}.HostName, ${EDGEDEVICEHOSTNAME}, NullAuthenticator, true, true)
DeviceScopeCertificateAuthenticator(IDeviceScopeIdentitiesCache, NullAuthenticator, (信頼済証明書チェイン), true)
ICredentialsCache
CommonModule ${AuthenticationMode}CloudAndScopeで、かつ${usePersistentStorage}true IAuthenticator Authenticator DeviceScopeTokenAuthenticator(IDeviceScopeIdentitiesCache, ${IOTEDGE_IOTHUBHOSTNAME} or ${IotHubConnectionString}.HostName, ${EDGEDEVICEHOSTNAME}, CloudTokenAuthenticator(CloudTokenAuthenticator(IConnectionManager, ${IOTEDGE_IOTHUBHOSTNAME} or ${IotHubConnectionString}.HostName), ICredentialsCache, ${IOTEDGE_IOTHUBHOSTNAME} or ${IotHubConnectionString}.HostName), true, true)
DeviceScopeCertificateAuthenticator(IDeviceScopeIdentitiesCache, NullAuthenticator, (信頼済証明書チェイン), true)
ICredentialsCache
CommonModule ${AuthenticationMode}CloudAndScopeで、かつ${usePersistentStorage}false IAuthenticator Authenticator DeviceScopeTokenAuthenticator(IDeviceScopeIdentitiesCache, ${IOTEDGE_IOTHUBHOSTNAME} or ${IotHubConnectionString}.HostName, ${EDGEDEVICEHOSTNAME}, CloudTokenAuthenticator(IConnectionManager, ${IOTEDGE_IOTHUBHOSTNAME} or ${IotHubConnectionString}.HostName), true, true)
DeviceScopeCertificateAuthenticator(IDeviceScopeIdentitiesCache, NullAuthenticator, (信頼済証明書チェイン), true)
ICredentialsCache
CommonModule (常に) IClientCredentialsFactory ClientCredentialsFactory IIdentityProvider
"EdgeHub/$(version.json)"
CommonModule (常に) ConnectionReauthenticator ConnectionReauthenticator IConnectionManager
IAuthenticator
ICredentialsCache
IDeviceScopeIdentitiesCache
5分
IClientCredentials("EdgeHubCredentials").Identity
IDeviceConnectivityManager
RoutingModule (常に) IMessageConverter<IRoutingMessage> RoutingMessageConverter (なし)
RoutingModule (常に) IRoutingPerfCounter NullRoutingPerfCounter (なし)
RoutingModule (常に) IRoutingUserAnalyticsLogger NullUserAnalyticsLogger (なし)
RoutingModule (常に) IRoutingUserMetricLogger NullRoutingUserMetricLogger (なし)
RoutingModule (常に) IMessageConverter<Message> DeviceClientMessageConverter (なし)
RoutingModule (常に) IMessageConverter<Twin> TwinMessageConverter (なし)
RoutingModule (常に) IMessageConverter<TwinCollection> TwinCollectionMessageConverter (なし)
RoutingModule (常に) IMessageConverterProvider MessageConverterProvider {typeof(Message): IMessageConverter<Message>, typeof(Twin): IMessageConverter<Twin>, typeof(TwinCollection): IMessageConverter<TwinCollection>}
RoutingModule ${experimentalFeatures:disableConnectivityCheck}true IDeviceConnectivityManager NullDeviceConnectivityManager (なし)
RoutingModule ${experimentalFeatures:disableConnectivityCheck}false IDeviceConnectivityManager DeviceConnectivityManager ${ConnectivityCheckFrequencySecs}
2分
IClientCredentials("EdgeHubCredentials")
RoutingModule (常に) IClientProvider ConnectivityAwareClientProvider ClientProvider
IDeviceConnectivityManager
RoutingModule (常に) ICloudConnectionProvider CloudConnectionProvider IMessageConverterProvider
${IotHubConnectionPoolSize}
IClientProvider
${UpstreamProtocol}
ITokenProvider("EdgeHubClientAuthTokenProvider")
IDeviceScopeIdentitiesCache
ICredentialsCache
IClientCredentials("EdgeHubCredentials")
${CloudConnectionIdleTimeoutSecs}
${CloseCloudConnectionOnIdleTimeout}
${CloudOperationTimeoutSecs}
IWebProxy
IProductInfoStore
RoutingModule (常に) IIdentityProvider IdentityProvider ${IOTEDGE_IOTHUBHOSTNAME} or ${IotHubConnectionString}.HostName
RoutingModule (常に) IConnectionManager ConnectionManager ICloudConnectionProvider
ICredentialsCache
IIdentityProvider
${MaxConnectedClients} + 1
RoutingModule (常に) IEndpointFactory EndpointFactory IConnectionManager
IMessageConverter<IRoutingMessage>
${IOTEDGE_DEVICEID} or ${IotHubConnectionString}.DeviceId
${MaxUpstreamBatchSize}
${UpstreamFanOutFactor}
RoutingModule (常に) RouteFactory EdgeRouteFactory IEndpointFactory
RoutingModule (常に) RouterConfig RouterConfig Route[0]
RoutingModule ${storeAndForwardEnabled}false EndpointExecutorConfig EndpointExecutorConfig 60秒
FixedInterval(0解、1秒)
1時間
true
RoutingModule ${storeAndForwardEnabled}true EndpointExecutorConfig EndpointExecutorConfig 30秒
ExponentialBackoff(Int32.MaxValue, 1秒, 60秒, 1秒)
30秒
RoutingModule ${storeAndForwardEnabled}true ICheckpointStore CheckpointStore StoreProvider(IDbStoreProvider).GetEntityStore("checkpoints")
RoutingModule ${storeAndForwardEnabled}true IMessageStore MessageStore StoreProvider(IDbStoreProvider)
ICheckpointStore
TimeSpan.MaxValue
RoutingModule ${storeAndForwardEnabled}false IEndpointExecutorFactory SyncEndpointExecutorFactory EndpointExecutorConfig
RoutingModule ${storeAndForwardEnabled}true IEndpointExecutorFactory StoringAsyncEndpointExecutorFactory EndpointExecutorConfig
AsyncEndpointExecutorOptions(10, 10秒)
IMessageStore
RoutingModule ${storeAndForwardEnabled}false Router Router GUID
${IOTEDGE_IOTHUBHOSTNAME} or ${IotHubConnectionString}.HostName
RouterConfig
IEndpointExecutorFactory
RoutingModule ${storeAndForwardEnabled}true Router Router GUID
${IOTEDGE_IOTHUBHOSTNAME} or ${IotHubConnectionString}.HostName
RouterConfig
IEndpointExecutorFactory
ICheckpointStore
RoutingModule ${storeAndForwardEnabled}false、かつTwinManagerVersionv1以外 ITwinManager PassThroughTwinManager IConnectionManager
IMessageConverterProvider.Get<Twin>()
RoutingModule ${storeAndForwardEnabled}true、かつTwinManagerVersionv1以外 ITwinManager StoringTwinManager IConnectionManager
IMessageConverterProvider.Get<TwinCollection>()
IMessageConverterProvider.Get<Twin>()
ReportedPropertiesValidator
TwinStore(IEntityStore<string, TwinStoreEntity>)
ReportedPropertiesStore(IEntityStore<string, TwinStoreEntity>, CloudSync(IConnectionManager, IMessageConverterProvider.Get<TwinCollection>(), IMessageConverterProvider.Get<Twin>()), ${ReportedPropertiesSyncFrequencySecs})
IDeviceConnectivityManager
${MinTwinSyncPeriodSecs}
ここで、IEntityStore<string, TwinStoreEntity>は、${EncryptTwinStore}trueの場合、EntityStore<string, TwinStoreEntity>(KeyValueStoreMapper<string, string, TwinStoreEntity, string>(UpdatableEncryptedStore<string, string>(IStoreProvider.GetEntityStore<string, string>("underlyingEdgeTwin"), IEncryptionProvider), JsonMapper<string>, JsonMapper<TwinStoreEntity>))であり、${EncryptTwinStore}falseの場合、IStoreProvider.GetEntityStore<string, string>("EdgeTwin")である。
RoutingModule ${storeAndForwardEnabled}false、かつTwinManagerVersionv1 ITwinManager TwinManager IConnectionManager
IMessageConverterProvider.Get<TwinCollection>()
IMessageConverterProvider.Get<Twin>()
Option.None<IEntityStore<string, TwinInfo>>
RoutingModule ${storeAndForwardEnabled}true、かつTwinManagerVersionv1 ITwinManager TwinManager IConnectionManager
IMessageConverterProvider.Get<TwinCollection>()
IMessageConverterProvider.Get<Twin>()
IStoreProvider.GetEntityStore("twins")
RoutingModule ${IotHubConnectionString}が未指定 IClientCredentials("EdgeHubCredentials") IotEdgedCredentials ModuleIdentity(${IOTEDGE_DEVICEID}, ${IOTEDGE_MODULEID})
"EdgeHub/$(version.json)"
IClientCredentialsFactory.GetWithIotEdged(${IOTEDGE_DEVICEID}, ${IOTEDGE_MODULEID})による
RoutingModule ${IotHubConnectionString}が指定済 IClientCredentials("EdgeHubCredentials") SharedKeyCredentials ModuleIdentity(${IotHubConnectionString})
${IotHubConnectionString}
"EdgeHub/$(version.json)"
IClientCredentialsFactory.GetWithConnectionString(${IotHubConnectionString})による
RoutingModule (常に) IInvokeMethodHandler InvokeMethodHandler IConnectionManager
RoutingModule ${experimentalFeatures:disableCloudSubscriptions}false ISubscriptionProcessor SubscriptionProcessor IConnectionManager
IInvokeMethodHandler
IDeviceConnectivityManager
RoutingModule ${experimentalFeatures:disableCloudSubscriptions}true ISubscriptionProcessor LocalSubscriptionProcessor IConnectionManager
RoutingModule (常に) IEdgeHub RoutingEdgeHub Router
IMessageConverter<IRoutingMessage>
IConnectionManager
ITwinManager
${IOTEDGE_DEVICEID} or ${IotHubConnectionString}.DeviceId
IInvokeMethodHandler
ISubscriptionProcessor
RoutingModule ${storeAndForwardEnabled}true ConfigUpdater ConfigUpdater Router
IMessageStore
RoutingModule ${storeAndForwardEnabled}false ConfigUpdater ConfigUpdater Router
null
RoutingModule ${configSource}twin IConfigSource EdgeHubConnection IClientCredentials("EdgeHubCredentials").Identity
IEdgeHub
ITwinManager
RouteFactory
IMessageConverter<TwinCollection>
IMessageConverter<Twin>
"EdgeHub/$(version.json)"
IDeviceScopeIdentitiesCache
RoutingModule ${configSource}twin以外 IConfigSource LocalConfigSource RouteFactory
${routes:*}
${storeAndForward:*}
RoutingModule (常に) IConnectionProvider ConnectionProvider IConnectionManager
IEdgeHub
MqttModule ${OptimizeForPerformance}true IByteBufferAllocator PooledByteBufferAllocator (なし)
MqttModule ${OptimizeForPerformance}false IByteBufferAllocator UnpooledByteBufferAllocator (なし)
MqttModule (常に) IByteBufferConverter ByteBufferConverter IByteBufferAllocator
MqttModule (常に) MessageAddressConverter MessageAddressConverter MessageAddressConversionConfiguration(${mqttTopicNameConversion:InboundTemplates}, ${mqttTopicNameConversion:OutboundTemplates})
MqttModule (常に) IMessageConverter<IProtocolGatewayMessage> ProtocolGatewayMessageConverter MessageAddressConverter
IByteBufferConverter
MqttModule (常に) ISettingsProvider MqttSettingsProvider ${mqttSettings:*}
MqttModule (常に) IMqttConnectionProvider MqttConnectionProvider IConnectionProvider
IMessageConverter<IProtocolGatewayMessage>
IByteBufferConverter
MqttModule ${storeAndForwardEnabled}true ISessionStatePersistenceProvider MqttConnectionProvider SessionStateStoragePersistenceProvider IEdgeHub
IEntityStore<string, SessionState>
MqttModule ${storeAndForwardEnabled}false ISessionStatePersistenceProvider SessionStatePersistenceProvider IEdgeHub
MqttModule (常に) MqttProtocolHead MqttProtocolHead ISettingsProvider
(サーバー証明書)
IMqttConnectionProvider
IAuthenticator
IClientCredentialsFactory
ISessionStatePersistenceProvider
IWebSocketListenerRegistry
IByteBufferAllocator
IProductInfoStore
${ClientCertAuthEnabled}
AmqpModule (常に) ITransportSettings DefaultTransportSettings ${amqpSettings.schemne}
"localhost"
${amqpSettings.port}
(サーバー証明書)
${ClientCertAuthEnabled}
IAuthenticator
IClientCredentialsFactory
AmqpModule (常に) ITransportListenerProvider AmqpTransportListenerProvider (なし)
AmqpModule (常に) ILinkHandlerProvider LinkHandlerProvider AmqpMessageConverter()
AmqpTwinMessageConverter()
AmqpDirectMethodMessageConverter()
IIdentityProvider
IProductInfoStore
AmqpModule (常に) AmqpProtocolHead AmqpProtocolHead ITransportSettings
AmqpSettingsProvider.GetDefaultAmqpSettings(${IOTEDGE_IOTHUBHOSTNAME}, IAuthenticator, IClientCredentialsFactory, ILinkHandlerProvider, IConnectionProvider, ICredentialsCache)
ITransportListenerProvider
IWebSocketListenerRegistry
IAuthenticator
IClientCredentialsFactory
HttpModule (常に) IValidator<MethodRequest> MethodRequestValidator (なし)
HttpModule (常に) IWebSocketListenerRegistry WebSocketListenerRegistry (なし)

サービス構成(Microsoft.Extensions.DependencyInjection)

Edge HubはAutoFacによるDIを使用しているが、ASP.NET CoreにはMicrosoft.Extensions.DependencyInjectionでサービスを登録する必要がある。ここでは、DIされるサービス型、実装型、使用する構成情報、依存先、条件を一覧化している。

サービス 実装 備考
IConfigurationRoot Mainで構築したIConfigurationRoot
IDependencyManager DependencyManager

永続化情報

パーティション|キー|暗号化|説明 --|--|-- messages|★|なし|★ twins|★|なし|${storeAndForwardEnabled}trueの場合にツインを保存する旧式(v1)のITwinManagerによる保存先★ checkpoints|★|なし|★ ProductInfo|★|なし|★ DeviceScopeCache|★|あり|★ CredentialsCache|★|あり|★ TwinStore|★|${EncryptTwinStore}の値|${storeAndForwardEnabled}trueの場合にツインを保存する新式(v2)のITwinManagerによる保存先★ underlyingEdgeTwin|★|なし|${EncryptTwinStore}trueの場合の生のストア。★

初期化シーケンス

  1. .NETの構成情報を初期化する。優先度は以下のとおり(後勝ち)。
    1. ファイルappsettings_hub.json
    2. 環境変数(プレフィックスなし)。
  2. 構成情報${RuntimeLogLevel}からログレベルを設定。
  3. 構成情報${EnableRoutingLogging}trueならば(既定値はfalse)、ルーティングのロガーを設定。
  4. 証明書を読み込む(EdgeHubCertificates.LoadAsync)。
    1. 構成情報${IotHubConnectionString}${EdgeHubDevServerCertificateFile}${EdgeHubDevServerPrivateKeyFile}${EdgeHubDevTrustBundleFile}${EdgeModuleHubServerCertificateFile}${EdgeModuleHubServerCAChainCertificateFile}を読み取る。
    2. 構成情報の読み取り結果を基に、EdgeHubCertficatesを作成する。
      • IoT Hub接続文字列が未指定の場合、edgeletが指定した情報を読み取る。
        1. 構成情報${IOTEDGE_WORKLOADURI}からワークロードAPIのURIを読み取る。
        2. 構成情報${EDGEDEVICEHOSTNAME}からエッジデバイスのホスト名を読み取る。
        3. 構成情報${IOTEDGE_MODULEID}からモジュールIDを読み取る。
        4. 構成情報${IOTEDGE_MODULEGENERATIONID}からモジュールの生成IDを読み取る。
        5. 構成情報${IOTEDGE_APIVERSION}からワークロードAPIのバージョンを受け取る。
        6. 証明書の有効期限をシステム日時の90日後にする。
        7. ワークロードAPIのサーバー証明書作成API(/modules/{name}/genid/{genid}/certificate/server?api-version=2019-01-30)を呼び出し、サーバー証明書を取得する(CertificateHelper.GetServerCertificatesFromEdgelet()->HttpWorkloadClient.CreateServerCertificateAsync())。
        8. 証明書データと秘密鍵データから、サーバー証明書と証明書チェインを作成する(ParseCertificateResponse->ParseCertificateAndKey)。
          1. 証明書データについて、-----END CERTIFICATE-----で分割し、かつ分割後の最後のエントリを切り捨てる(ParsePemCerts)。
          2. PEMデータのリストの2番目以降のエントリからX509Certificate2インスタンスのリストを取得する(GetCertificatesFromPem)。これは証明書チェインである。
          3. PEMデータのリストの1番目のエントリと秘密鍵データをCRLFで連結し、PEMデータとして読み取り、証明書チェインのエントリ(X509Certificate)、証明書(AsymmetricChipherKeyPair)、秘密鍵(RsaPrivateCrtKeyParameters)を読み取る。
          4. 秘密鍵をAsymmetricKeyEntryとして、EdgeというエイリアスでPKCS12ストア(BouncyCastle.CryptoライブラリのPkcs12Store)に設定する。このとき、証明書チェインも指定する(Pkcs12Store.SetKeyEntry())。
          5. PKCS12ストアの内容をパスワードなしでメモリ上に展開し(Pkcs12Store.Save())、そのバイト列を基にX509Certificate2オブジェクトを作成する。これはサーバー証明書である。
          6. 作成したサーバー証明書と証明書チェインを返す。
        9. 作成した証明書チェインをDockerコンテナーにインストールする(EdgeHubCertificates.InstallCertificates)。
          1. StoreLocation.CurrentUserに証明書チェインを保存する(CertificateHelper.InstallCerts)。証明書ストア名は、Windowsの場合はCertificateAuthority、Linuxの場合はRootである。実装は、corefxのX509StoreAPIである。
        10. edgeletから信頼済証明書チェインを取得する(CertificateHelper.GetTrustBundleFromEdgelet())。
          1. ワークロードAPIの信頼済証明書チェイン取得API(/trust-bundle?api-version=2019-01-30)を呼び出し、信頼済証明書チェインを取得する(HttpWorkloadClient.GetTrustBundleAsync())。
          2. 証明書データについて、-----END CERTIFICATE-----で分割し、かつ分割後の最後のエントリを切り捨てる(ParsePemCerts)。
          3. エントリのリストをX509Certificate2のリストに変換する(ParseTrustedBundleCerts -> GetCertificatesFromPem)。
        11. 作成したサーバー証明書、証明書チェイン、信頼済ルート証明書チェインからEdgeHubCertificateを作成する。
      • IoT Hub接続文字列が指定され、かつ構成情報EdgeHubDevServerCertificateFileで開発用証明書チェインのパスが指定され、かつ構成情報EdgeHubDevServerPrivateKeyFileで開発用秘密鍵が指定され、かつ構成情報EdgeHubDevTrustBundleFileで開発用信頼済ルート証明書が指定されている場合、開発用の証明書を作成、インストールする。
        1. 開発用証明書データと開発用秘密鍵データを解析する(CertificateHelper.GetServerCertificateAndChainFromFile)。
          1. 開発用証明書データと開発用秘密鍵データをそれぞれファイルから読み取る。
          2. 開発用証明書データと開発用秘密鍵データから、サーバー証明書と証明書チェインを作成する(ParseCertificateAndKey)。
            1. 証明書データについて、-----END CERTIFICATE-----で分割し、かつ分割後の最後のエントリを切り捨てる(ParsePemCerts)。
            2. PEMデータのリストの2番目以降のエントリからX509Certificate2インスタンスのリストを取得する(GetCertificatesFromPem)。これは証明書チェインである。
            3. PEMデータのリストの1番目のエントリと秘密鍵データをCRLFで連結し、PEMデータとして読み取り、証明書チェインのエントリ(X509Certificate)、証明書(AsymmetricChipherKeyPair)、秘密鍵(RsaPrivateCrtKeyParameters)を読み取る。
            4. 秘密鍵をAsymmetricKeyEntryとして、EdgeというエイリアスでPKCS12ストア(BouncyCastle.CryptoライブラリのPkcs12Store)に設定する。このとき、証明書チェインも指定する(Pkcs12Store.SetKeyEntry())。
            5. PKCS12ストアの内容をパスワードなしでメモリ上に展開し(Pkcs12Store.Save())、そのバイト列を基にX509Certificate2オブジェクトを作成する。これはサーバー証明書である。
            6. 作成したサーバー証明書と証明書チェインを返す。
        2. 作成した証明書オブジェクトをDockerコンテナーにインストールする(EdgeHubCertificates.InstallCertificates)。
          1. StoreLocation.CurrentUserに証明書チェインを保存する(CertificateHelper.InstallCerts)。証明書ストア名は、Windowsの場合はCertificateAuthority、Linuxの場合はRootである。実装は、corefxのX509StoreAPIである。
        3. 開発用信頼済ルート証明書を解析する(CertificateHelper.ParseTrustedBundleFromFile)。
          1. 開発用信頼済ルート証明書をファイルから読み取る。
          2. 証明書データについて、-----END CERTIFICATE-----で分割し、かつ分割後の最後のエントリを切り捨てる(ParsePemCerts)。
          3. エントリのリストをX509Certificate2のリストに変換する(ParseTrustedBundleCerts -> GetCertificatesFromPem)。
        4. 作成したサーバー証明書、証明書チェイン、信頼済ルート証明書チェインからEdgeHubCertificateを作成する。
      • そうではなく、構成情報EdgeModuleHubServerCertificateFileでサーバー証明書が指定された、構成情報EdgeModuleHubServerCAChainCertificateFileでサーバー証明書のCA証明チェインが指定されているならば、それらをそのままインストールする。
        1. ファイルからCA証明書チェインデータを取得する(CertificateHelper.ExtractCertsFromPem())。
          1. ファイルから証明書データを読み取る。
          2. 証明書データについて、-----END CERTIFICATE-----で分割し、かつ分割後の最後のエントリを切り捨てる(ParsePemCerts)。
          3. エントリのリストをX509Certificate2のリストに変換する(GetCertificatesFromPem)。
        2. 作成した証明書チェインをDockerコンテナーにインストールする(EdgeHubCertificates.InstallCertificates)。
          1. StoreLocation.CurrentUserに証明書チェインを保存する(CertificateHelper.InstallCerts)。証明書ストア名は、Windowsの場合はCertificateAuthority、Linuxの場合はRootである。実装は、corefxのX509StoreAPIである。
        3. サーバー証明書ファイルからX509Certificate2を作成する。
        4. サーバー証明書と証明書チェインから(信頼済証明書チェインは空)EdgeCertificateを作成する。
      • そうでないならば構成エラーである。
    3. 構成情報${ClientCertAuthEnabled}を読み取る(既定値はtrue)。
    4. IDependencyManagerの実装であるDependencyManagerを、構成情報、サーバー証明書、信頼済証明書チェインで初期化する。
    5. ASP.NET Coreを初期化する(Hosting.Initialize()
      1. Kestrelを構成する。
        1. 構成情報${httpSettings:port}を取得する(既定値は443)。
        2. HTTPS接続構成処理を初期化する。
          • サーバー証明書に先ほど作成したサーバー証明書を設定する。
          • クライアント証明書検証エラーは無視する。
          • クライアント証明書モードは、${ClientCertAuthEnabled}trueならClientCertificateMode.AllowCertificatefalseならClientCertificateMode.NoCertificate
        3. 取得したポート番号とHTTPS接続構成処理で、HTTPS接続のリッスンを開始する(なお、IPアドレスは任意で、OSがIPv6に対応しているならIPv6で待機する)。
      2. ソケット通信を有効にする。
      3. Startupが必要とするサービスを構成する。具体的には、IConfigurationRootIDependencyManager
      4. 共通初期化処理を呼び出す(Startupクラスに委譲)。
        1. サービスを構成する。
      5. ウェブホスト(IWebHost)を作成する。
      6. ウェブホストからIStartupサービスを取得し、DIコンテナーサービス(IContainer)を取得する
      7. ウェブホストとDIコンテナーサービスをまとめるHostingオブジェクトを作成し、返す。
    6. versionInfo.jsonからバージョン情報を取得し、ログに出力する。
    7. ロゴを出力する。
    8. メトリクスシステムを初期化する(Metrics.Init())。
    9. IEdgeHubICloudConnectionProviderの循環参照を手動解決するために、ICloudConnectionProvider.BindEdgeHub()を呼び出す。
    10. IConnectionManagerIDeviceConnectivityManagerの循環参照を手動解決するために、DeviceConnectivityManager.SetConnectionManager()を呼び出す。
    11. EdgeHubCredentialsという名前のIClientCredentialsをDIコンテナーから取得し、ICredentialsCacheサービスに登録する(ICredentialsCache.Add()による)。
    12. IConfigSourceConfigUpdaterを初期化する(ConfigUpdater.Init())。
    13. 構成情報${AuthenticationMode}を読み取り、その結果がAuthenticationMode.Cloudでない場合、ConnectionReauthenticatorサービスを初期化する(ConnectionReauthenticator.Init())。
    14. シャットダウンハンドラーを初期化する(ShutdownHandler.Init)。なお、タイムアウトは20秒(固定値)。
    15. プロトコルヘッドを取得する(GetEdgeHubProtocolHeadAsync
    16. 証明書更新処理(CertificateRenewal)を初期化する。
    17. プロトコルヘッドを開始する(IProtocolHead.StartAsync())。

シャットダウン処理

  1. 以下のいずれかをトリガーにして開始する。
    • シャットダウンハンドラー。
    • 証明書更新処理。
  2. プロトコルヘッドを終了する(IProtocolHead.CloseAsync())。
  3. 証明書更新処理を破棄する(CertificateRenewal.Dispose())。
  4. プロトコルヘッドを破棄する(IProtocolHead.Dispose())。
  5. シャットダウン処理の終了をシャットダウンハンドラーに通知する。
  6. シャットダウン終了を示すログを出力する。

ダイレクトメソッド

コアサービス

IEdgeHub

メンバー
Task ProcessDeviceMessage(IIdentity identity, IMessage message);

Task ProcessDeviceMessageBatch(IIdentity identity, IEnumerable<IMessage> message);

Task<DirectMethodResponse> InvokeMethodAsync(string id, DirectMethodRequest methodRequest);

Task UpdateReportedPropertiesAsync(IIdentity identity, IMessage reportedPropertiesMessage);

Task<IMessage> GetTwinAsync(string id);

Task UpdateDesiredPropertiesAsync(string id, IMessage twinCollection);

Task SendC2DMessageAsync(string id, IMessage message);

Task AddSubscription(string id, DeviceSubscription deviceSubscription);

Task RemoveSubscription(string id, DeviceSubscription deviceSubscription);

Task ProcessSubscriptions(string id, IEnumerable<(DeviceSubscription, bool)> subscriptions);

(XML Docより):
IEdgeHubはデバイスまたはモジュールによるEdge Hubへのメッセージ送信を処理する役割を持つ。たとえば、RoutingEdgeHubは、構成されたルーティングルールを実行することでメッセージを処理するルーターを保持することで、これを行う。

RoutingEdgeHub

依存先
サービス 実装
Router
IMessageConverter<IRoutingMessage>
IConnectionManager
ITwinManager
IInvokeMethodHandler
ISubscriptionProcessor
実装
  • ProcessDeviceMessage
    • DeviceMessageHandlerから呼び出され、メッセージをルーター経由で送信する。★
      1. メッセージを、サイズ検証ありで、ルーティングメッセージに変換する(ProcessMessageInternal)。
      2. メトリックにメッセージサイズを追記する。
      3. ルーターにメッセージを送信する。
  • ProcessDeviceMessageBatch
    • DeviceMessageHandlerから呼び出され、メッセージをルーター経由でバッチ送信する。★
      1. メッセージリスト内の各メッセージを、サイズ検証ありで、ルーティングメッセージに変換する(ProcessMessageInternal)。
      2. メトリックにメッセージサイズを追記する。
      3. ルーターにメッセージを送信する。
  • InvokeMethodAsync
    • CloudListenerまたはTwinsControllerから呼びされ、IInvokeMethodHandlerに処理を委譲する。
  • UpdateReportedPropertiesAsync
    • DeviceMessageHandlerから呼び出され、reported propertyをクラウドとモジュールに送信する。
      1. 以下を同時実行する。
        • 渡された情報を使用して、reporeted property更新メッセージをクラウドに送信する(ITwinManager.UpdateReportedPropertiesAsync())。
        • reporeted property更新メッセージをモジュールに配信する。
          1. サイズ検証なしで、更新メッセージをルーティングメッセージに変換する(ProcessMessageInternal)。
          2. reported property更新メッセージをルーティングする。
  • SendC2DMessageAsync
    • CloudListenerから呼び出され、C2Dメッセージを処理する。★
      1. 渡されたID({デバイスID}または{デバイスID}/{モジュールID})を基に、IDeviceProxyを取得する(IConnectionManager.GetDeviceConnection())。なお、取得できない場合は警告ログを出力し、何もしない(例外をスローしないので、メッセージは処理されるはず★)
      2. メッセージを送信する(IDeviceProxy.SendC2DMessageAsync())。
  • GetTwinAsync
    • DeviceMessageHandlerから呼び出され、ITwinManagerに処理を委譲する。
  • UpdateDesiredPropertiesAsync
    • CloudListenerから呼び出され、ITwinManagerに処理を委譲する。
  • AddSubscription
    • EdgeHubConnectionまたはDeviceMessageHandlerから呼び出され、ISubscriptionProcessorに処理を委譲する。
  • RemoveSubscription
    • DeviceMessageHandlerから呼び出され、ISubscriptionProcessorに処理を委譲する。
  • ProcessSubscriptions
    • SessionStatePersistentProviderから呼び出され、ISubscriptionProcessorに処理を委譲する。
  • ProcessMessageInternal
    • メッセージをルーティング用に加工する。
      1. システムプロパティを追加する(AddEdgeSystemProperties())。
        1. システムプロパティedgeMessageIdにGUIDを追加する。
        2. システムプロパティconnectionDeviceidがある場合、以下のようにする。
          • connectionDeviceidがこのデバイスのデバイスIDならば、システムプロパティedgeHubOriginInterfaceinternalを追加する。
          • connectionDeviceidがこのデバイスのデバイスIDでないならば、システムプロパティedgeHubOriginInterfacedownstreamを追加する。
        3. システムプロパティenqueuedTimeにシステム日時(UTC)を追加する。
      2. IMessageConverter<IRoutingMessage>を使用してIRoutingMessageに変換する(実装はRoutingMessageConverter.FromMessage())。これは、送信元情報を付与してルーティング可能にする処理である。
        1. 以下のようにして、IMessageSourceを作成する。
          1. システムプロパティmessageTypeが存在し、かつその値がtwinChangeNotificationであれば、TwinChangeEventMessageSourceのシングルトンインスタンスであり、ソースは/twinChangeNotificationsである。
          2. そうではなく、システムプロパティconnectionModuleIdがあるならば、モジュールメッセージであり、以下のようになる。
            1. システムプロパティoutputNameがあるならば、connectionModuleIdoutputNameを基にModuleMessageSourceを作成する。ソースは/messages/modules/{connectionModuleId}/outputs/{outputName}である。
            2. そうでないならば、connectionModuleIdを基にModuleMessageSourceを作成する。ソースは/messages/modules/{connectionModuleId}である。
          3. そうでないならば、TelemetryMessageSourceのシングルトンインスタンスであり、ソースは/messagesである。
        2. IMessageSourceBodyPropertiesSystemPropertiesを基に、RoutingMessageを作成する。
      3. 必要ならば、メッセージサイズのバリデーションを行う(ValidateMessageSize())。具体的には、256KiBを超える場合はエラーとする。

IDependencyManager

メンバー
void Register(ContainerBuilder builder);

AutoFacのDIコンテナーの依存先サービスの登録を行う。

DependencyManager

依存先
サービス 実装
実装
  • 初期化
    1. 構成情報${IotHubConnectionString}を読み取る。
    2. 構成情報の読み取り結果を基に、フィールドを初期化する。
      • IoT Hub接続文字列が未指定の場合、edgeletが指定した情報を読み取る。
        1. 構成情報${IOTEDGE_IOTHUBHOSTNAME}からIoT Hubのホスト名を読み取り、フィールドに設定する。
        2. 構成情報${IOTEDGE_DEVICEID}からデバイスIDを読み取り、フィールドに設定する。
        3. 構成情報${IOTEDGE_MODULEID}からモジュールIDを読み取り、フィールドに設定する。
        4. 構成情報${EDGEDEVICEHOSTNAME}からエッジデバイスのホスト名を読み取り、フィールドに設定する。
        5. 接続文字列フィールドにOption.None<string>を設定する。
      • IoT Hub接続文字列が指定されている場合、接続文字列をパースして、フィールドを初期化する。
    3. versioninfo.jsonからバージョン情報を読み取る。
  • Register
    1. LoggingModuleを登録する。
    2. LoggingModuleの構築コールバックとして、以下の処理を登録する。
      1. DotNettyのログ出力先をMicrosoft.Extensions.Logging(から出力されるSerilog)に設定する。
      2. 内部イベント(★AMQP?)用EventSourceのリスナーにMicrosoft.Extensions.Logging(から出力されるSerilog)を設定する。
    3. 構成情報${OptimizeForPerformance}を読み取る。
    4. ローカルバッファリング関連の構成を読み取る(GetStoreAndForwardConfiguration())。
    5. 構成セクション${experimentalFeatures}を読み取る。
    6. CommonModuleを登録する(RegisterCommonModule())。
    7. ルーティングモジュール群を登録する(RegisterRoutingModule())。
      1. RoutingModule.Load()内で、IConnectionManagerへのv1のITwinManagerであるTwinManagerのバインド(IConnectionManager.ConnectionEstablishedイベントへのTwinManager.ConnectionEstabilishedCallbackの登録)を行う。
      2. RoutingModule.Load()内で、${configSource}twinの際に使用されるIConfigSourceであるEdgeHubConnectionの初期化を行う。これは内部的なIDeviceProxyの作成を含む。
    8. MqttModuleを登録する(RegisterMqttModule())。
    9. AmqpModuleを登録する(RegisterAmqpModule())。
    10. HttpModuleを登録する。

★分類待ち

IConnectionManager

メンバー
event EventHandler<IIdentity> CloudConnectionEstablished;

event EventHandler<IIdentity> CloudConnectionLost;

event EventHandler<IIdentity> DeviceConnected;

event EventHandler<IIdentity> DeviceDisconnected;

Task AddDeviceConnection(IIdentity identity, IDeviceProxy deviceProxy);

Task RemoveDeviceConnection(string id);

Task<Try<ICloudProxy>> CreateCloudConnectionAsync(IClientCredentials identity);

Option<IDeviceProxy> GetDeviceConnection(string id);

Task<Option<ICloudProxy>> GetCloudConnection(string id);

void AddSubscription(string id, DeviceSubscription deviceSubscription);

void RemoveSubscription(string id, DeviceSubscription deviceSubscription);

Option<IReadOnlyDictionary<DeviceSubscription, bool>> GetSubscriptions(string id);

bool CheckClientSubscription(string id, DeviceSubscription subscription);

IEnumerable<IIdentity> GetConnectedClients();

(XML Docより):
IConnectionManagerは、Edge Hubに接続されたデバイス群を表す、IDeviceProxyICloudProxyオブジェクトに対して、デバイスIDを対応付けるディクショナリを維持する。デバイスごとに1つの接続が存在するようにするのはこのオブジェクトの役割である。IDeviceProxyはデバイス自身に対する接続を表し、デバイスに対するメッセージを送信する機能を持つ。ICloudProxyはAzure IoT Hubにおけるこのデバイスに対する接続を表し、このデバイスに属する(on behalf of)ものとしてIoT Hubにメッセージを送信する機能を持つ。

ConnectionManager

依存先
サービス 実装
ICloudConnectionProvider
ICredentialCache
IIdentityProvider

イベント

イベント名 発生条件 イベントハンドラー
CloudConnectionEstablished ICloudConnectionProviderによる接続状態変更コールバックで、状態がCloudConnectionStatus.ConnectionEstablishedだった場合 SubscriptionProcessor.CloudConnectionEstablished
TwinManager.ConnectionEstablishedCallback
CloudConnectionLost ICloudConnectionProviderによる接続状態変更コールバックで、状態がCloudConnectionStatus.TokenNearExpiryCloudConnectionStatus.DisconnectedTokenExpired、またはCloudConnectionStatus.Disconnectedだった場合 (なし)
DeviceConnected AddDeviceConnectionが呼び出された場合 EdgeHubConnection.DeviceConnected
DeviceDisconnected RemoveDeviceConnectionが呼び出された場合 EdgeHubConnection.DeviceDisconnected
実装
  • AddDeviceConnectionEdgeHubConnection.InitEdgeHubDeviceMessageHandler.BindDeviceProxyから呼び出される)
  • RemoveDeviceConnection
  • GetDeviceConnection
  • CreateCloudConnectionAsync
  • GetCloudConnection
  • AddSubscription
  • RemoveSubscription
  • GetSubscriptions
  • CheckClientSubscription
  • GetConnectedClientsConnectionReauthenticatorSubscriptionProcessorStoringTwinManagerから呼び出される)
    1. 内部で保持する接続済みデバイス情報(ConnectedDevice)から、アクティブ(内部で保持するDeviceConnectionIsActive、つまりはそれがラップするIDeviceProxyIsActivetrue)なものをフィルタリングする。
    2. ConnectedDevice.Identityを射影し、返す。
  • イベントハンドラー
    • CloudConnectionStatusChangedHandler

IConfigSource

メンバー
Task<Option<EdgeHubConfig>> GetConfig();

void SetConfigUpdatedCallback(Func<EdgeHubConfig, Task> callback);

EdgeHubConnection

(XML docより):

EdgeHubConnectionはEdge Hubとしてクラウドに接続し、Edge Hubのモジュールツインの取得と更新を行う役割を持つ。

依存先
サービス 実装
IIdentity IotEdgedCredentialsまたはSharedKeyCredentialsから取得したModuleIdentity
ITwinManager
RouteFactory
IMessageConverter<TwinCollection>
IMessageConverter<Twin>
VersionInfo
IDeviceScopeIdentitiesCache
実装
  • 初期化(CreateRouterModuleでのDIの初期化で呼び出される)
    1. EdgeHubConnectionインスタンスを作成する。
    2. 引数IEdgeHubの初期化を行う(InitEdgeHub)。
      1. EdgeHubConnectionからデバイスプロキシ(EdgeHubDeviceProxy)を作成する。
      2. 以下を並列で行う。
        • デバイスプロキシを接続マネージャーに登録する(IConnectionManager.AddDeviceConnection(IIdentity, EdgeHubDeviceProxy))。
        • クラウドからのdesired property更新の購読を開始する(IEdgeHub.AddSubscription(IIdentity.Id, DeviceSubscription.DesiredProperties)->ISubscriptionProcessor.AddSubscription(IIdentity.Id, DeviceSubscription.DesiredProperties))。
        • クラウドからのダイレクトメソッド呼び出しの購読を開始する(IEdgeHub.AddSubscription(IIdentity.Id, DeviceSubscription.Methods)->ISubscriptionProcessor.AddSubscription(IIdentity.Id, DeviceSubscription.Methods))。
        • 接続状態を初期化する(EdgeHubConnection.ClearDeviceConnectionStatus())。
    3. IConnectionManagerへのEdgeHubConnectionのバインド(IConnectionManager.DeviceConnectedイベントへのEdgeHubConnection.DeviceConnectedの登録、およびIConnectionManager.DeviceConnectedイベントへのEdgeHubConnection.DeviceDisconnectedの登録)を行う。
  • GetConfigConfigUpdaterから呼び出される)
    1. ロックを取る。
    2. モジュールID(IIdentity.Id)を使用して、モジュールツインを取得する(ITwinManager.GetTwinAsync)。
    3. desired properrtyを処理する。
      1. JSONとして逆シリアル化する。
      2. desired propertyから、RouteFactory.CreateによりRouteオブジェクトを作成しつつ、EdgeHubConfigを作成する(GetEdgeHubConfig)。
    4. desired propertyの処理の結果をreported propertyとして報告する。報告はITwinManager.UpdateReportedPropertiesAsync経由で行う。
      • 問題なく成功していたら、desired propertyのバージョンと、ステータスコード200。
      • 例外が発生していたら、desired propertyのバージョンと、ステータスコード400。
    5. 問題なく作成したEdgeHubConfig(失敗した場合はNone)を返す。
    6. ロックを開放する。
  • SetConfigUpdatedCallback(これはConfigUpdaterが使用している)。
    1. EdgeHubDeviceProxy.OnDesiredPropertyUpdates()で呼び出されるコールバックを、指定されたデリゲートに更新する。
  • HandleMethodInvocationEdgeDeviceProxyから呼び出される)
    1. メソッド名がRefreshDeviceScopeIdentityCacheでなければ、404でエラー終了。
    2. リクエストをJSON逆シリアル化し、対象のデバイスIDのリストを取得する。失敗したら400エラー。
    3. IDeviceScopeIdentityCache.RefreshServiceIdentities()を呼び出し、★
    4. 後からエッジ側のログと突き合わせるための相関IDを200レスポンスとして返す。
  • HandleDesiredPropertiesUpdateEdgeDeviceProxyから呼び出される)
    1. IMessageConverter<TwinCollection>を使用して、IMessageからTwinCollectionを取り出す。
    2. ロックを取得する。
    3. 最後のdesired propertyに対し、パッチ更新を行う(PatchDesiredProperties)。なお、desired propertyがローカルにない場合にはGetConfigInternalで完全なEdgeHubConfigを作る。
    4. SetConfigUpdatedCallback()で指定されたコールバックを呼び出す。
  • UpdateDeviceConnectionStatus
    1. 接続状態が変更されたモジュールがEdge Hubモジュールそのものであれば、何もせずに終了する。
    2. 接続状態と現在のシステム日時から、DeviceConnectionを作成する。
    3. 接続状態が変更されたモジュールのIDをキーにして(このとき、URLエンコードを行う)、DeviceConnectionを値とする1要素のディクショナリを作成する。
    4. 接続状態ディクショナリをreported propertyとして報告する(ITwinManager.UpdateReportedPropertiesAsync経由)。
  • イベントハンドラー
    • IConnectionManager.DeviceConnected
      1. 接続状態を更新する(UpdateDeviceConnectionStatus)。
    • IConnectionManager.DeviceDisconnected
      1. 接続状態を更新する(UpdateDeviceConnectionStatus)。

IDeviceProxy

メンバー
bool IsActive { get; }

IIdentity Identity { get; }

Task CloseAsync(Exception ex);

Task SendC2DMessageAsync(IMessage message);

Task SendMessageAsync(IMessage message, string input);

Task<DirectMethodResponse> InvokeMethodAsync(DirectMethodRequest request);

Task OnDesiredPropertyUpdates(IMessage desiredProperties);

Task SendTwinUpdate(IMessage twin);

void SetInactive();

Task<Option<IClientCredentials>> GetUpdatedIdentity();

(XML Docより):

IDeviceProxyはEdge Hubに接続されたデバイスに対する接続を表す。 このインターフェイスを実装するオブジェクトは、デバイスへのメッセージ送信と、デバイス上でのメソッド呼び出しのために使用される。全ての接続されるデバイスに対してIDeviceProxyのインスタンスを作成、保持する役割は、IConnectionManagerオブジェクトにある。たとえば、MQTTの実装では、このインターフェイスの実装は、MQTTパケットとIMessageオブジェクトの間のメッセージを変換することによってMQTTクライアントとのインターフェイスとなる、プロトコルゲートウェイのライブラリを使用する。

呼び出し元

  • IsActive
    • ConnectionManager+DeviceConnection.IsActive
    • DeviceMessageHandler.CloseAsync
    • DeviceMessageHandler.IsActive
    • ModuleEndpoint.GetDeviceProxy
  • Identity
    • ConnectionReauthenticator.HandleServiceIdentityUpdate
    • DeviceMessageHandler(多数)
    • Amqp.ClientConnectionHandler(多数)
    • Mqtt.DeviceProxy(多数)
    • Mqtt.MessagingServiceClient(多数)
  • CloseAsync
    • ConnectionManager+DeviceConnection.CloseAsync
    • DeviceMessageHandler+DeviceConnection.CloseAsync
  • SendC2DMessageAsync
    • DeviceMessageHandler.SendC2DMessageAsync
    • RoutingEdgeHub.SendC2DMessageAsync
  • SendMessagesAsync
    • DeviceMessageHandler.SendMessagesAsync
    • ModuleEndpoint.ProcessAsync
  • InvokeMethodAsync
    • InvokeMethodHandler.InvokeMethod(未使用)
    • InvokeMethodHandler.ProcessInvokeMethodsForClient
  • OnDesiredPropertyUpdates
    • TwinManager.SendDesiredPropertiesToDeviceProxy
    • DeviceMessageHandler.OnDesiredPropertyUpdates
    • PassThroughTwinManager.UpdatedDesiredPropertiesAsync
    • StoringTwinManager.SendPatchToDevice
  • SendTwinUpdate
    • DeviceMessageHandler.AddDesiredPropertyUpdatesSubscription
    • DeviceMessageHandler.RemoveDesiredPropertyUpdatesSubscription
    • DeviceMessageHandler.SendGetTwinRequest
    • DeviceMessageHandler.UpdateReportedPropertiesAsync
    • DeviceMessageHandler.HandleTwinOperationException
    • DeviceMessageHandler.SendTwinUpdate
  • SetInactive
    • DeviceMessageHandler.CloseAsync
    • DeviceMessageHandler.SetInactive
  • GetUpdatedIdentity
    • DeviceMessageHandler.GetUpdatedIdentity

EdgeHubDeviceProxy

(XML Docより):

Edge Hubのデバイスプロキシであり、クラウドからEdge Hubに対する通信を受信する。今のところ、desired propertyの更新のみを受信する。

注:実際にはダイレクトメソッド呼び出しも処理している。

依存先
サービス 実装
EdgeHubConnection EdgeHubConnection
実装
  • IsActive
    • 常にtrue
  • Identity
    • EdgeHubConnectionが保持するIIdentityを返す。
  • CloseAsync
    • 何もしない
  • SendC2DMessageAsync
    • NotImplementedException
  • SendMessagesAsync
    • NotImplementedException
  • InvokeMethodAsync
    • EdgeHubConnectioninternalメソッドに処理を委譲する。
  • OnDesiredPropertyUpdates
    • EdgeHubConnectioninternalメソッドに処理を委譲する。
  • SendTwinUpdate
    • NotImplementedException
  • SetInactive
    • NotImplementedException
  • GetUpdatedIdentity
    • NotImplementedException

Amqp.DeviceProxy

AMQPプロトコルヘッド(★ほんとに?)の初期化時に生成される、AMQP接続(★どことの?)用のIDeviceProxy設定。

依存先
サービス 実装
実装

Mqtt.DeviceProxy

依存先
サービス 実装
実装

IDeviceListener

メンバー
IIdentity Identity { get; }

Task ProcessDeviceMessageAsync(IMessage message);

Task ProcessDeviceMessageBatchAsync(IEnumerable<IMessage> message);

Task UpdateReportedPropertiesAsync(IMessage reportedPropertiesMessage, string correlationId);

Task SendGetTwinRequest(string correlationId);

Task ProcessMethodResponseAsync(IMessage message);

void BindDeviceProxy(IDeviceProxy deviceProxy);

Task CloseAsync();

Task ProcessMessageFeedbackAsync(string messageId, FeedbackStatus feedbackStatus);

Task AddSubscription(DeviceSubscription subscription);

Task RemoveSubscription(DeviceSubscription subscription);

Task AddDesiredPropertyUpdatesSubscription(string correlationId);

Task RemoveDesiredPropertyUpdatesSubscription(string correlationId);

DeviceMessageHandler

IDeviceProxyIDeviceListenerを実装する。ConnectionProviderが作成する。
AMQPのSendingLinkHandlerMethodReceivingLinkHandler、またはMQTTのMessagingServiceClientから呼び出され、プロトコル固有のIDeviceProxy実装のラッパー(デコレーター)として動作する。

依存先
サービス 実装
IIdentity
IEdgeHub RoutingEdgeHub
IConnectionManager
実装
  • IDeviceListener
    • ProcessDeviceMessageAsync
      • MQTTのMessagingServiceClientから呼び出される(AMQPでは使用されない)。
      1. IEdgeHub.ProcessDeviceMessageAsyncに処理を委譲する。
    • ProcessDeviceMessageBatchAsync
      • AMQPのEventsLinkHandlerから呼び出される(MQTTでは使用されない)。
      1. IEdgeHub.ProcessDeviceMessageBatchAsyncに処理を委譲する。
    • UpdateReportedPropertiesAsync
      • reported propertyのクラウド側への送信と、他モジュールへの通知を行う(★)
      • 例外発生時には、HandleTwinOperationException()を呼び出す。
      1. reported propertiesのメッセージにシステムプロパティを設定する。
        • enqueuedTime:現在のシステムUTC日時
        • messageSchematwinChangeNotification
        • messageTypetwinChangeNotification
        • connectionDeviceId:通知元デバイスまたはモジュールのデバイスID
        • connectionModuleId:通知元モジュールのモジュールID(モジュールツインの場合のみ)。
      2. IEdgeHub.UpdateReportedPropertiesAsync()を呼び出し、IoT Hubとモジュールに更新を送信する。
      3. ボディが空のEdgeMessageを構築する。
      4. システムプロパティを設定する。
        • correlationId:引数correlationId
        • enqueuedTime:現在のシステムUTC日時
        • statusCode204
      5. SendTwinUpdate()EdgeMessageを送信する。
    • SendGetTwinRequest
      • モジュールツイン取得リクエストを処理する。
      • 例外発生時には、HandleTwinOperationException()を呼び出す。
      1. IEdgeHub.GetTwinAsync()を呼び出す(★)。
      2. 応答結果のIMessageについて、システムプロパティcorrelationIdに引数correlationId、システムプロパティstatusCode200を設定する。
      3. 応答結果のIMessageを★する(SendTwinUpdate)。
    • ProcessMethodResponseAsync
      • ダイレクトメソッド応答メッセージを処理する。
      1. システムプロパティcorrelationIdを検査し、存在しないならば無視する。
      2. 内部のConcurrentDictionaryからcorrelationId(を小文字にしたもの)をキーにして、TaskCompletionSourceを取り出す(削除を伴う)。
      3. correlationId、応答のシステムプロパティstatusCode(存在しない場合は一般BadRequestを表す 400000)と、応答のBodyを使用して(statusCodeがない場合はnull)、DirectMethodResponse を構築する。
      4. TaskCompletionSourceDirectMethodResponseを設定して完了させる。
    • BindDeviceProxy
      • プロトコル固有のIDeviceProxy実装をバインドする。
      1. underlyingProxyフィールドに渡されたIDeviceProxyを格納する。
      2. IConnectionManager.AddDeviceConnection()を呼び出し、自身をIDeviceProxyとして上書き登録す る。
    • CloseAsync()
      1. ラップするIDeviceProxySetInactive()を呼び出す(SetInactive)。
      2. IConnectionManager.RemoveDeviceConnection()を呼び出し、自身のIDeviceProxyとしての登録を 解除する。
    • ProcessMessageFeedbackAsync
      • モジュール間/D2Cメッセージの送信結果、またはC2Dメッセージの受信応答の、非同期処理の結果を処理す る。
      1. 引数messageIdが未指定の場合は無視する。
      2. 内部のモジュール間/D2Cメッセージ用ConcurrentDictionaryからmessageId(を小文字にしたもの) をキーにして、TaskCompletionSourceを取り出す(削除を伴う)。
        • エントリの取り出しに成功した場合
          1. 引数feedbackStatusCompleteならば、TaskCompletionSourceを完了させる。そうでない 場合、TaskCompletionSourceEdgeHubIOExceptionで失敗させる。
        • エントリの取り出しに失敗した場合
          1. 内部のC2Dメッセージ用ConcurrentDictionaryからmessageId(を小文字にしたもの)をキーにして、配信結果を取り出す(削除を伴う)。
          2. 結果がtrueならば(必ずtrueになるはず)、IConnectionManager.GetCloudConnection()ICloudProxyを取り出 し、feedbackStatusを使用してICloudProxy.SendFeedbackMessageAsync()を呼び出す。
    • AddSubscription
      • AMQPのDeviceBoundLinkHandlerMethodSendingLinkHandlerModuleMessageLinkHandlerTwinSendingLinkHandlerから呼び出される(MQTT側では使用しない)。
      1. IEdgeHub.AddSubscription()に処理を委譲する。
    • RemoveSubscription
      • 未使用。
      1. IEdgeHub.RemoveSubscription()に処理を委譲する。
    • AddDesiredPropertyUpdatesSubscription
      • AMQPのTwinReceivingLinkHandlerから呼び出される(MQTT側では使用しない)。
      1. IEdgeHub.AddSubscription()DeviceSubscription.DesiredPropertyUpdatesで呼び出す。
      2. ボディが空のEdgeMessageを構築する。
      3. システムプロパティを設定する。
        • correlationId:引数correlationId
        • statusCode200
      4. SendTwinUpdate()EdgeMessageを送信する。
    • RemoveDesiredPropertyUpdatesSubscription
      • AMQPのTwinReceivingLinkHandlerから呼び出される(MQTT側では使用しない)。
      1. IEdgeHub.RemoveSubscription()DeviceSubscription.DesiredPropertyUpdatesで呼び出す。
      2. ボディが空のEdgeMessageを構築する。
      3. システムプロパティを設定する。
        • correlationId:引数correlationId
        • statusCode200
      4. SendTwinUpdate()EdgeMessageを送信する。
  • IDeviceProxy
    • CloseAsync(Exception)

    • SendC2DMessageAsync

      • RoutingEdgeHubから呼び出され、配信処理のステータス管理を実装する。
      1. システムプロパティlockTokenがない場合は無視する。
      2. 内部のC2Dメッセージ用ConcurrentDictionarylockTokenをキーにして、配信結果trueを追加する。
      3. ラップするIDeviceProxySendC2DMessageAsyncを呼び出す。
    • SendMessageAsync

      • **XML Docより:**このメソッドはデバイスにメッセージを送信し、messageTaskCompletionSourceリストに(応答を待つための)TaskCompletionSourceを追加する。メッセージのフィードバックがコールバックされたとき、ProcessMessageFeedbackメソッドがTaskCompletionSourceの値を設定し、その結果としてawaitされているTaskが完了する。応答が来ない場合、タイムアウトする。
      • ModuleEndpointから呼び出され、配信処理のステータス管理を実装する。
      1. メッセージ送信ロックを獲得する(複数のキューが同時に送信を試みる可能性があるため)。
      2. システムプロパティlockTokenに新規GUIDを設定する。
      3. 内部のモジュール間メッセージ用のConcurrentDictionaryに、lockTokenをキーにして、TaskCompletionSourceを追加する。
      4. ラップするIDeviceProxySendMessageAsyncを呼びだす。
      5. TaskCompletionSourceの完了、またはタイムアウト(30秒固定)の経過を待つ。
        • タイムアウトした場合、TaskCompletionSourceTimeoutExceptionで失敗させ、内部ConcurrentDictionaryから削除する。
      6. TaskCompletionSource.Taskを返す。
    • InvokeMethodAsync

      • **XML Docより:**このメソッドはデバイスにあるメソッドを呼び出し、methodCallTaskCompletionSourcesリストに(応答を待つための)TaskCompletionSourceを追加する。応答が返ってきたとき、SendMethodResponseメソッドがTaskCompletionSourceの値を設定し、その結果としてawaitされているTaskが完了する。応答が来ない場合、タイムアウトする。
      • InvokeMethodHandler.InvokeMethodから呼び出される。
      1. 内部のモジュール間メッセージ用のConcurrentDictionaryに、correlationIdを小文字にしたものをキーにして、TaskCompletionSourceを追加する。
      2. ラップするIDeviceProxyInvokeMethodAsyncを呼びだす。
      3. TaskCompletionSourceの完了、またはタイムアウト(ResponseTimeout)の経過を待つ。
        • タイムアウトした場合、TaskCompletionSourceEdgeHubTimeoutExceptionで失敗させ、内部ConcurrentDictionaryから削除する。
      4. TaskCompletionSource.Taskを返す。
    • OnDesiredPropertyUpdates

    • SendTwinUpdate

    • SetInactive

      1. ラップするIDeviceProxySetInactive()を呼び出す。
    • GetUpdatedIdentity

  • private
    • HandleTwinOperationException
      1. 空のEdgeMessageを構築する。
      2. システムプロパティを設定する。
        • correlationId:引数correlationId
        • enqueuedTime:現在のUTCシステム日時
        • statusCode:発生した例外がArgumentExceptionまたはInvalidOperationExceptionなら400、それ以外なら500
      3. SendTwinUpdate()EdgeMessageを送信する。

ICloudProxy

メンバー
bool IsActive { get; }

Task<bool> CloseAsync();

Task<bool> OpenAsync();

Task SendMessageAsync(IMessage message);

Task SendMessageBatchAsync(IEnumerable<IMessage> inputMessages);

Task UpdateReportedPropertiesAsync(IMessage reportedPropertiesMessage);

Task<IMessage> GetTwinAsync();

Task SendFeedbackMessageAsync(string messageId, FeedbackStatus feedbackStatus);

Task SetupCallMethodAsync();

Task RemoveCallMethodAsync();

Task SetupDesiredPropertyUpdatesAsync();

Task RemoveDesiredPropertyUpdatesAsync();

Task StartListening();

(XML Docより):

ICloudProxyはEdge Hubに接続されたデバイスのクラウド側を表す。 このインターフェイスを実装するオブジェクトは、本質的に、Azure IoT Hub にあるモジュールのカウンターパートに対する接続を開き、維持するために、IoT HubのModule ClientまたはDevice Clientを使用する。Edge Hubに接続されたデバイスごとに、ただ1つのクラウドプロキシオブジェクトのインスタンスが存在する。全ての接続されるデバイスに対してICloudProxyのインスタンスを作成、保持する役割は、IConnectionManagerオブジェクトにある。

CloudProxy

依存先
サービス 実装
IClient
IMessageConverterProvider
ICloudListener
実装

IClient

メンバー

XXX

依存先
サービス 実装
実装

ICloudListener

メンバー
Task ProcessMessageAsync(IMessage message);

Task OnDesiredPropertyUpdates(IMessage desiredProperties);

Task<DirectMethodResponse> CallMethodAsync(DirectMethodRequest request);

CloudListener

依存先
サービス 実装
IEdgeHub RoutingEdgeHub
実装

全て、IEdgeHubに処理を委譲する。

IInvokeMethodHandler

メンバー
Task<DirectMethodResponse> InvokeMethod(DirectMethodRequest request);

/// <summary>
/// This method is called when a client subscribes to Method invocations.
/// It processes all the pending method requests for that client (i.e the method requests
/// that came in before the client subscribed to method invocations and that haven't expired yet)
/// </summary>
Task ProcessInvokeMethodSubscription(string id);

InvokeMethodHandler

依存先
サービス 実装
IConnectionManager
実装

キューの実行順は順不同である(ConcurrentDictionary)。

  • InvokeMethod
    1. 指定されたID(モジュールIDまたはデバイスID)に対応するIDeviceProxyを取得する(GetDeviceProxyWithSubscription)。
      1. IConnectionManager.GetDeviceConnectionIDeviceproxyを取得する。
      2. IConnectionManager.GetSubscriptionsで指定されたID★に対応するサブスクリプションを取得し、そのMethodsエントリがアクティブであることを確認する。
      3. IDeviceProxyを返す。
    2. ダイレクトメソッドを呼び出す。
      • デバイスプロキシがあった場合、それ経由でダイレクトメソッドを呼び出す。
      • デバイスプロキシがなかった場合、後から実行できるようにキューに入れる。
        1. TaskCompletionSourceを作成する。
        2. メソッド呼び出しのキューを取得または作成する(GetClientQueue)。
        3. キューにリクエストとTaskCompletionSourceを追加する。
        4. TaskCompletionSourceの完了を待つ。待機時間は、ダイレクトメソッドの接続タイムアウト時間の長さ。
        5. タイムアウトした場合、キューからエントリを削除し、TaskCompletionSourceEdgeHubTimeoutExceptionとステータスコード404から作成したDirectMethodResponseを設定する。
        6. 非同期ダイレクトメソッド実行の結果を返す。
  • ProcessInvokeMethodSubscription
    1. 非同期で保留中のメソッド群を呼び出し(ProcessInvokeMethodSubscriptionInternal)、Task.CompletedTaskを返す。
      1. このメソッド呼び出し前に呼び出された購読処理の完了を待つために、3秒待つ。
        • このスリープを辞めるには、プロトコルゲートウェイとクライアントSDKを修正し、AMQPリンク確立イベントが発生するようにしなければならないとのこと。
      2. 指定されたID★に対応するIDeviceProxyを取得する(GetDeviceProxyWithSubscription、既出)。
      3. メソッド呼び出しのキューを取得または作成する(GetClientQueue)。
      4. キューの各エントリ(キーがDirectMethodRequest、値がTaskCompletionSource<DirectMethodResponse>)に対して以下を繰り返す。
        1. キューからエントリを削除する。
        2. デバイスプロキシ経由でダイレクトメソッドを呼び出す。
        3. 応答をTaskCompletionSourceに設定する。

ISubscriptionProcessor

メンバー
Task AddSubscription(string id, DeviceSubscription deviceSubscription);

Task RemoveSubscription(string id, DeviceSubscription deviceSubscription);

Task ProcessSubscriptions(string id, IEnumerable<(DeviceSubscription, bool)> subscriptions);

SubscriptionProcessor

依存先
サービス 実装
IConnectionManager
IInvokeMethodHandler
IDeviceConnectivityManager
実装
  • AddSubscription
    1. 要素数1のサブスクリプションリストを作成する。タプルの第2項目(addSubscritpion)はtrue
    2. サブスクリプションリストの処理を行う(HandleSubscriptions -> AddToPendingSubscriptions)。詳細はProcessSubscriptionsを参照。
  • RemoveSubscription
    1. 要素数1のサブスクリプションリストを作成する。タプルの第2項目(addSubscritpion)はfalse
    2. サブスクリプションリストの処理を行う(HandleSubscriptions -> AddToPendingSubscriptions)。詳細はProcessSubscriptionsを参照。
  • ProcessSubscriptions
    1. 指定されたDeviceSubscriptionを、それを追加すべきか削除すべきかに応じて、IConnectionManager.AddSubscription()またはIConnectionManager.RemoveSubscription()を呼び出す。
    2. サブスクリプションリストの処理を行う(HandleSubscriptions -> AddToPendingSubscriptions)。
    3. 対象のID(モジュールID、またはデバイスID)について、サブスクリプション処理キューを取得または作成する(GetClientSubscriptionsQueue)。
    4. キューにDeviceSubscriptionを追加する。
    5. System.Threading.Tasks.DataFlow.ActionBlockを使用して、以下を実行する(★その意味)(ProcessPendingSubscriptions
      1. 対象のID(モジュールID、またはデバイスID)について、サブスクリプション処理キューを取得または作成する(GetClientSubscriptionsQueue)。
      2. キューが空でなければ、以下を行う。
        1. IConnectionManager.GetCloudConnection()により、ICloudProxyを取得する。
        2. DeviceSubscriptionについて、その種類と、それを追加すべきかどうか(addSubscription項目)に応じて処理する(ProcessSubscription)。詳細は、IDeviceConnectivityManager.DeviceConnectedイベントのイベントハンドラーの説明を参照。
  • イベント処理
    • IDeviceConnectivityManager.DeviceConnected

      1. IConnectionManager.GetConnectedClients()により、実際のクラウドからのイベントの購読状態を調整する。
      2. IIdentityについて、ProcessExistingSubscriptions()を呼び出し、★
        1. IConnectionManager.GetCloudConnection()により、ICloudProxyを取得する。
        2. IConnectionManager.GetSubscription()により、(DeviceSubscription, bool addSubscription)のコレクションを取得する。★意味 ★いつaddSubscriptionの値が変わるの?
        3. DeviceSubscriptionについて、その種類と、それを追加すべきかどうか(addSubscription項目)に応じて、以下のように処理する(ProcessSubscription)。
          • DeviceSubscription.C2Dの場合:
            • addSubscriptiontrueの場合、ICloudProxy.StartListening()を呼び出し、C2Dメッセージの受信を開始する。
            • addSubscriptionfalseの場合、何もしない。
          • DeviceSubscription.DesiredPropertyUpdatesの場合:
            • addSubscriptiontrueの場合、ICloudProxy.SetupDesiredPropertyUpdatesAsync()を呼び出し、プロパティ更新の購読を開始する★。
            • addSubscriptionfalseの場合、ICloudProxy.RemoveDesiredPropertyUpdatesAsync()を呼び出し、プロパティ更新の購読を中止する★。
          • DeviceSubscription.Methodsの場合:
            • addSubscriptiontrueの場合:
              1. ICloudProxy.SetupCallMethodAsync()を呼び出し、メソッド呼び出しの購読を開始する★。
              2. IInvokeMethodHandler.ProcessInvokeMethodSubscription()を呼び出し、これまで呼び出されていて保留中だったメソッドを実行する。
            • addSubscriptionfalseの場合、ICloudProxy.RemoveCallMethodAsync()を呼び出し、メソッド呼び出しの購読を中止する★。
          • それ以外の種類(ModuleMessagesTwinResponseUnknown)の場合、何もしない。
    • IConnectionManager.CloudConnectionEstabilished

      1. 接続されたIIdentityを使用して、ProcessExistingSubscriptions()を呼び出し、★
        • その後の処理はIDeviceConnectivityManager.DeviceConnectedのイベントハンドラーと同じ。

LocalSubscriptionProcessor

依存先
サービス 実装
実装

ルーティングサービス

xxxサービス

IXXX

メンバー

XXX

依存先
サービス 実装
実装

通信

IWebSocketListenerRegistry

メンバー
bool TryRegister(IWebSocketListener webSocketListener);

bool TryUnregister(string subProtocol, out IWebSocketListener webSocketListener);

Option<IWebSocketListener> GetListener(IEnumerable<string> subProtocols);
WebSocketListnerRegistry

ConcurrentDictionaryを使用したIWebSocketListnerRegistryの既定の実装。

バリデーター

IValidator

メンバー
void Validate(T value);

MethodRequestValidator

ダイレクトメソッドのバリデーションを行う。(★誰貴店の?)

  • MethodNameが100文字以内か。
  • ConnectTimeoutが0以上5分(ディスパッチタイムアウト)以下か。
  • ResponseTimeoutが5秒以上5分以下か。
  • Payloadのサイズが128KiB以下か。

プロキシ

接続情報の取得

ルーティング

ローカルバッファリング

プロトコルヘッド

Edge Hubは、${UpstreamProtocol}でクラウドとの接続プロトコルを指定できる。これはAMQPまたはMQTTである。また、Edge Hubから各モジュールへの通信、および各モジュールからEdge Hubへの通信(ダイレクトメソッドを除く)も、AMQPまたはMQTTで行われる。 ダイレクトメソッド呼び出しのみ、HTTPで行われる。

これらはプロトコルヘッドと呼ばれ(★本当に?)、${<プロトコル名>:enabled}構成設定で無効化できる。

Twin変更通知

EdgeHubは各モジュールまたはリーフデバイスのreporeted propertyの変更通知機能を持つ。

  • ルーティング
    • (★docs参照)
  • メッセージ
    • 以下のシステムプロパティを持つ
      • messageSchematwinChangeNotification
      • messageTypetwinChangeNotification
      • connectionDeviceId:通知元デバイスまたはモジュールのデバイスID
      • connectionModuleId:通知元モジュールのモジュールID(モジュールツインの場合のみ)。
    • ボディ
      • クライアントが送信したツイン更新メッセージ(★具体的には?)

ユーティリティ

WIP

IoT Edgeの.NET実装側には、edge-util以下のいくつかの共通ユーティリティがある。

構成

  • Microsoft.Azure.Devices.Edge.Util:共通ユーティリティ。暗号化を含む。
  • Microsoft.Azure.Devices.Edge.Storage:ストレージの実装。

共通ユーティリティ

AsyncLock

SemaphoreSlimベースの非同期ロック。

AsyncLockProvider

キーのハッシュ値ベースのロック管理。衝突する可能性あり。

ISystemTime

DateTime UtcNow { get; }

システム時計の抽象化。

SystemTime

DateTime.UtcNowベースのISystemTime実装。

シャットダウンハンドラー

ShutdownHandler

プラットフォーム非依存のシャットダウン開始ロジック。

LinuxShutdownHandler

Console.CancelKeyPress(つまりはSIGINTまたはSIGQUITシグナル)に応答するシャットダウンハンドラー。

WindowsShutdownHandler

システムイベントCTRL_SHUTDOWN_EVENTに応答(SetConsoleCtrlHandler Win32 APIで登録)するシャットダウンハンドラー。

ワークロードAPI

WorkloadClient

edgeletのワークロードAPIを呼び出すためのクライアントオブジェクト。

暗号化

IEncryptionProvider

Task<string> DecryptAsync(string encryptedText);

Task<string> EncryptAsync(string plainText);

EncryptionProvider

依存先

なし

実装
  • edgeletのワークロードAPIを使用して暗号化と復号を実施する。
  • IV(Initialization Vector)について
    • IVは${StorageFolder}/edgeAgent/IOTEDGE_BACKUP_IVに保存される。
    • ファイルがない場合、Guidが使用される。
  • 動作にはモジュールのgeneration IDが必要なため、初回起動時にエージェントのモジュールアイデンティが生成されるまではNullEncryptionProviderが代わりに使用される。
利用元
  • $edgeAgent
    • バックアップ配置マニフェストの暗号化と復号
    • ストレージに保存する配置マニフェストの暗号化と復号

ISignatureProvider

Task<string> SignAsync(string data);

HttpHsmSignatureProvider

EdgeletのワークロードAPI経由でHSMベースの署名を行う。
具体的には、sign APIをキーprimary、アルゴリズムHMACSHA256で呼び出す。

利用元

★EdgeHubで使用?

ITokenProvider

Task<string> GetTokenAsync(Option<TimeSpan> ttl);

ClientTokenProvider

ISignatureProviderを使用したIoT Hub用SASトークンプロバイダー。

利用元
  • ★EdgeHubで使用?

CertificateHelper

GetServerCertificatesFromEdgelet

利用元
  • ★EdgeHubで使用?

GetTrustBundleFromEdgelet

利用元
  • ★EdgeHubで使用?

ストレージ関連

IStoreProvider

IEntityStore<TK, TV> GetEntityStore<TK, TV>(string entityName);

Task<ISequentialStore<T>> GetSequentialStore<T>(string entityName);

Task<ISequentialStore<T>> GetSequentialStore<T>(string entityName, long defaultHeadOffset);

Task RemoveStore<T>(ISequentialStore<T> sequentialStore);

Task RemoveStore<TK, TV>(IEntityStore<TK, TV> entityStore);

各種ストレージを提供する。

StoreProvider

実装

IDbStoreProviderから返されるIDbStoreをラップする各種ストレージを提供する。

  • GetEntityStoreTimedEntityStore<>-EntityStore<>-KeyValueStoreMapper<>-IDbStore
  • GetSequentialStoreSequentialStore<>-TimedEntityStore<>-EntityStore<>-KeyValueStoreMapper<>-IDbStore
使用元
  • $edgeAgent ★EdgeHubで使用?

IKeyValueStore

Task Put(TK key, TV value);

Task<Option<TV>> Get(TK key);

Task Remove(TK key);

Task<bool> Contains(TK key);

Task<Option<(TK key, TV value)>> GetFirstEntry();

Task<Option<(TK key, TV value)>> GetLastEntry();

Task IterateBatch(int batchSize, Func<TK, TV, Task> perEntityCallback);

Task IterateBatch(TK startKey, int batchSize, Func<TK, TV, Task> perEntityCallback);

Task Put(TK key, TV value, CancellationToken cancellationToken);

Task<Option<TV>> Get(TK key, CancellationToken cancellationToken);

Task Remove(TK key, CancellationToken cancellationToken);

Task<bool> Contains(TK key, CancellationToken cancellationToken);

Task<Option<(TK key, TV value)>> GetFirstEntry(CancellationToken cancellationToken);

Task<Option<(TK key, TV value)>> GetLastEntry(CancellationToken cancellationToken);

Task IterateBatch(int batchSize, Func<TK, TV, Task> perEntityCallback, CancellationToken cancellationToken);

Task IterateBatch(TK startKey, int batchSize, Func<TK, TV, Task> perEntityCallback, CancellationToken cancellationToken);

シンプルなキーバリューストアのインターフェイス定義。

KeyValueStoreMapper

キーと値のマッピングを行い、基になるIKeyValueStoreで使用できる形式に変換するデコレーター。

利用元
  • $edgeHub
    • IStorageProvider.GetEntityStore()
    • IEntityStore<string, TwinStoreEntity>の実装★

TimedKeyValueStore

タイムアウト機能を提供するIKeyValueStoreのデコレーター。

利用元
  • $edgeHub
    • IStorageProvider.GetEntityStore()

EncryptedStore

依存先
サービス 実装
IKeyValueStore n/a
IEncryptionProvider EncryptionProvider
実装

IKeyValueStoreをラップし、IEncryptionProviderによる値の暗号化を行う。

利用元
  • $edgeHub
    • DeviceScopeIdentitiesCache

UpdatableEncryptedStore

実装

更新可能なEncryptedStore

利用元
  • $edgeHub
    • IEntityStore<string, TwinStoreEntity>の実装★

IEntityStore

string EntityName { get; }

Task<bool> Remove(TK key, Func<TV, bool> predicate);

Task<bool> Remove(TK key, Func<TV, bool> predicate, CancellationToken cancellationToken);

Task<TV> Update(TK key, Func<TV, TV> updator);

Task<TV> Update(TK key, Func<TV, TV> updator, CancellationToken cancellationToken);

Task<TV> PutOrUpdate(TK key, TV putValue, Func<TV, TV> valueUpdator);

Task<TV> PutOrUpdate(TK key, TV putValue, Func<TV, TV> valueUpdator, CancellationToken cancellationToken);

Task<TV> FindOrPut(TK key, TV putValue);

Task<TV> FindOrPut(TK key, TV putValue, CancellationToken cancellationToken);

IKeyValueStoreを拡張し、マージ処理(PutOrUpdate)、取得または挿入(FindOrPut)、条件付き削除、マージ更新(Update)といった処理を追加する。

EntityStore

IKeyValueStoreをラップするIEntityStore実装。

利用元
  • StoreProvider

TimedEntityStore

タイムアウト機能を提供するIEntityStoreのデコレーター。

利用元
  • StoreProvider
  • $edgeAgent
    • モジュールの状態の保存(キーはモジュールID)
      • 再起動回数
      • 最終再起動日時
    • 配置マニフェストの保存(キーはCurrentConfig

IDbStoreProvider

IDbStore GetDbStore(string partitionName);

IDbStore GetDbStore();

void RemoveDbStore(string partitionName);

DbStoreProvider

RocksDBベースのColumnFamilyDbStoreを提供する。

内部にパーティション名とIDbStoreのマッピングを保持する。

利用元
  • $edgeAgent

InMemoryDbStoreProvider

InMemoryDbStoreを返す実装。

内部にパーティション名とIDbStoreのマッピングを保持する。

利用元
  • $edgeAgent

IDbStore

現状はIKeyValueStoreを継承しただけ。

InMemoryDbStore

KeyedCollectionベースのIDbStore実装。AsyncReaderWriterLockNito.AsyncEx.Coordination)による排他制御を行う。

利用元
  • $edgeAgent

ColumnFamilyDbStore

RocksDBベースのIDbStore実装。

利用元
  • $edgeAgent

ISequentialStore

string EntityName { get; }

Task<long> Append(T item);

Task<bool> RemoveFirst(Func<long, T, Task<bool>> predicate);

Task<IEnumerable<(long, T)>> GetBatch(long startingOffset, int batchSize);

Task<long> Append(T item, CancellationToken cancellationToken);

Task<bool> RemoveFirst(Func<long, T, Task<bool>> predicate, CancellationToken cancellationToken);

Task<IEnumerable<(long, T)>> GetBatch(long startingOffset, int batchSize, CancellationToken cancellationToken);

キューのように使用できるストレージのインターフェイスの定義。キーはオフセットになる。

SequentialStore

IKeyValueStoreをラップするISequentialStore実装。

キーは、符号あり64bit整数であるオフセット値。

利用元
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment