別の注文のタグが付いてきた
目標
実際のPython関数7個を直して、実行時点のコンテキストを引き渡し、正常・例外・キャンセルのあとで元のコンテキストを復元します。続けて、外部baggageの抽出と、宛先ごとの伝播ポリシーを分離します。
なぜ重要なのか
リクエストの値が間違った処理に付くと、データが収集されても、原因を誤って解釈することになります。このラボは、Python 3.12とOpenTelemetry SDK 1.44.0がインストールされた専用環境で、実際のasyncio・ThreadPoolExecutor・W3Cのbaggageプロパゲーターを実行します。依存関係はイメージにあらかじめインストールされているため、インターネットからのダウンロードやAPIキーは必要ありません。
外部のHTTP・DNS・Collector・認証サーバーは実行しません。warehouse.internalは、関数に渡す論理的な宛先の文字列です。課題の合格は、実際のネットワーク宛先の検証、認証・認可、外部ストレージへの格納、包括的な個人情報保護の検証を意味しません。8つのステップのうち、baggageがスパン属性に自動で記録されるかどうかは、前の理論のSDK実験と公式の説明を根拠に判定します。残りのコード検査は、Context関数の実行の観測です。
準備と実行
作業ディレクトリは/root/otca-contextです。startersには、文法は合っているものの、動作が間違っている開始ファイル8個だけが用意されます。該当するファイルを作業ディレクトリにコピーして、直してください。
cd /root/otca-context
cp starters/scope.py scope.py
/opt/otel-lab/bin/python /opt/app/otca_sdk/runner.py --track context run 1
runのあとの数字を変えると、該当するステップのobservationsとchecksが出ます。現在の観測と期待値を比べてください。SDKや採点を変更する課題ではなく、同じ意味の別のコードでも、実際の動作が合っていれば合格します。答案のコピーを、制限された子プロセスで実行し、元のファイルは変更しません。個別のコードは最大8秒、総合の実行は全体で50秒の予算で、無限ループ・出力の暴走は失敗です。この実行制限を、悪意のあるコードに対する別個のセキュリティ上の分離と解釈しないでください。分離は、ラボのPodが担当します。
ステップ
- /root/otca-context/scope.py: scope.pyのwith_context(value, callback)を直します。コールバックを1回実行する間、baggageのrequestがvalueである必要があります。返り値と元の例外をそのまま伝え、正常・例外のどちらでも、呼び出し側の以前のコンテキストを復元してください。
- /root/otca-context/tasks.py: tasks.pyのstart_task(coro)は、現在のリクエストのコンテキストでコルーチンを予約し、呼び出し側がawaitするオブジェクトを返します。呼び出し側があとでコンテキストを変えても、処理は予約時点の値を読む必要があり、呼び出し側の新しい値は保持してください。
- /root/otca-context/threads.py: threads.pyのstart_thread(function)は、同期関数をスレッドで実行するように予約し、awaitするオブジェクトを返します。あとでawaitした時点ではなく、この関数を呼び出した時点のリクエストの値を、ワーカーが読むようにしてください。
- /root/otca-context/executor.py: executor.pyのsubmit(executor, function)は、与えられたThreadPoolExecutorに関数を提出し、Futureを返します。提出時点のリクエストの値が引き継がれ、同じワーカーの次の通常の処理には残らないようにします。呼び出し側のコンテキストも保持してください。
- /root/otca-context/requests.py: requests.pyのasync handle(value, callback)は、baggageのrequestをvalueとして適用して、非同期コールバックを1回awaitします。2つのリクエストが重なっても値を分離し、正常な返り値・例外・Task.cancelのいずれでも、以前のコンテキストを復元してください。返り値・例外・キャンセルは、呼び出し側にそのまま伝えます。
- /root/otca-context/incoming.py: incoming.pyのinbound(carrier)は、W3Cのbaggageヘッダーのdictを、空のOTel Contextを基準に抽出して、OTel Contextを返します。既存のローカルのbaggageを混ぜず、元のdictと呼び出し側のコンテキストを保持します。role=adminも、このステップでは抽出しますが、認証の成功とは解釈しません。
- /root/otca-context/outgoing.py: outgoing.pyのoutbound(source, destination)は、destinationがwarehouse.internalと完全に同じときにだけ、regionのtest-east/test-west、channelのweb/batchという文字列を許可します。新しいContextに選択して入れ、W3Cのbaggageヘッダーのdictを返してください。ほかのキー・値・宛先は除外し、元は保持します。残る値がなければ、空のdictです。
- /root/otca-context/report.json: report.jsonの8つの仮説を、JSONのブール値で判定します。前の7つのコードもすべて動作する必要があります。Taskの生成時点、遅延されたto_thread、通常のexecutor、キャンセル時の復元、baggageとスパン属性、空のinbound、認可、宛先の影響を区別してください。
参考
- baggageのキーrequestを使います。関数に渡されたvalueを使い、order-a1つに固定しないでください。
- コールバックは、採点側が提供します。同期コールバックと非同期コールバックは、ステップごとの関数の契約に従って実行します。
- executorは、与えられたものを使い、新しいプールで置き換えないでください。同じワーカーを再利用して、リークを確認します。
- 空のinboundヘッダーからは、空のbaggageが出てくる必要があり、既存のローカルのrequestがついてくると失敗します。
- outboundで許可する文字列は、正確に比較します。大文字・小文字、空白、長い値、数字を、勝手に変換して許可しないでください。
- report.jsonのキーは、task_copies_at_creation、lazy_to_thread_copies_at_call、plain_executor_copies_context、cancellation_requires_cleanup、baggage_becomes_span_attributes、fresh_inbound_excludes_local、baggage_role_proves_authorization、outbound_destination_mattersです。
- 前のステップの準備は、存在しない以前の答案だけを埋めます。すでに書いた答案と現在のステップは、上書きしません。
- セッションが終わるとファイルは消えます。必要なコードと観測結果を、事前に別に保管してください。
コールバックのあとで元のリクエストに戻る
/root/otca-context/scope.py: scope.pyのwith_context(value, callback)を直します。コールバックを1回実行する間、baggageのrequestがvalueである必要があります。返り値と元の例外をそのまま伝え、正常・例外のどちらでも、呼び出し側の以前のコンテキストを復元してください。
set_baggageの返り値と、現在のコンテキストに適用する動作は別です。コールバックのあとの復元が、例外の経路でも実行されるかを確認してください。
Task予約時点のリクエストを保持する
/root/otca-context/tasks.py: tasks.pyのstart_task(coro)は、現在のリクエストのコンテキストでコルーチンを予約し、呼び出し側がawaitするオブジェクトを返します。呼び出し側があとでコンテキストを変えても、処理は予約時点の値を読む必要があり、呼び出し側の新しい値は保持してください。
コルーチンオブジェクトを返すことと、現在のContextを持つTaskを作ることの違いを確認してください。
遅延されたスレッド処理の時点を直す
/root/otca-context/threads.py: threads.pyのstart_thread(function)は、同期関数をスレッドで実行するように予約し、awaitするオブジェクトを返します。あとでawaitした時点ではなく、この関数を呼び出した時点のリクエストの値を、ワーカーが読むようにしてください。
to_threadが返したコルーチンは、いつ実行されるでしょうか。現在のコンテキストで実行を予約する段階を考えてみてください。
再利用されるワーカーにコンテキストを残さない
/root/otca-context/executor.py: executor.pyのsubmit(executor, function)は、与えられたThreadPoolExecutorに関数を提出し、Futureを返します。提出時点のリクエストの値が引き継がれ、同じワーカーの次の通常の処理には残らないようにします。呼び出し側のコンテキストも保持してください。
コピーをワーカーの中で行うと、すでに手遅れです。提出ごとに別のコピーを作り、その中で実行する方法を探してみてください。
重なったリクエストと実際のキャンセルを後始末する
/root/otca-context/requests.py: requests.pyのasync handle(value, callback)は、baggageのrequestをvalueとして適用して、非同期コールバックを1回awaitします。2つのリクエストが重なっても値を分離し、正常な返り値・例外・Task.cancelのいずれでも、以前のコンテキストを復元してください。返り値・例外・キャンセルは、呼び出し側にそのまま伝えます。
正常な返りのあとにだけdetachがあると、キャンセルがその行を飛ばします。復元とキャンセルの隠蔽を区別してください。
外部ヘッダーとローカルのリクエストを分離する
/root/otca-context/incoming.py: incoming.pyのinbound(carrier)は、W3Cのbaggageヘッダーのdictを、空のOTel Contextを基準に抽出して、OTel Contextを返します。既存のローカルのbaggageを混ぜず、元のdictと呼び出し側のコンテキストを保持します。role=adminも、このステップでは抽出しますが、認証の成功とは解釈しません。
デフォルトのextractと、明示的に空のContextを指定したextractを比べてください。contextvars.ContextとOTel Contextを区別します。
宛先・キー・値をあわせて制限する
/root/otca-context/outgoing.py: outgoing.pyのoutbound(source, destination)は、destinationがwarehouse.internalと完全に同じときにだけ、regionのtest-east/test-west、channelのweb/batchという文字列を許可します。新しいContextに選択して入れ、W3Cのbaggageヘッダーのdictを返してください。ほかのキー・値・宛先は除外し、元は保持します。残る値がなければ、空のdictです。
キーだけを許可すると、値に別の情報が入ることがあります。宛先を接頭辞で比べると、似たホストを許可してしまわないかも確認してください。
動作と仮説をあわせて検証する
/root/otca-context/report.json: report.jsonの8つの仮説を、JSONのブール値で判定します。前の7つのコードもすべて動作する必要があります。Taskの生成時点、遅延されたto_thread、通常のexecutor、キャンセル時の復元、baggageとスパン属性、空のinbound、認可、宛先の影響を区別してください。
レポートだけ合っていても十分ではありません。実際の観測と公式の契約を区別して読み、各関数の失敗の原因もあわせて直してください。