AWS Emulatorの501エラーを見分けて対応状況を調べる方法

​​AWS Emulator​AWS CLI,Python,テスト

AWS Emulator は、まだ作られていない操作を呼ばれると、HTTP 501 とエラーコード NotImplemented を返します。ローカルの結合テストや CI で AWS の API を呼ぶテストが落ちたとき、このエラーを見分けられれば、自分のコードの誤りを探す手間を省けます。あわせて、リリースごとに公開される fidelity のスコアのファイルを読み、使う操作が AWS と同じに動くかを調べる手順も載せます。

補足

検証環境は Ubuntu 24.04(x86_64)、AWS Emulator 1.0.352(Debian パッケージ)、AWS CLI 2.40.3、Python 3.11 と boto3 1.44.3 です。AWS のアカウントは使わず、リクエストはすべて手元の AWS Emulator(127.0.0.1:9297)に送りました。本物の AWS には接続していないので、本物の AWS での動きは、公式ドキュメントと、AWS が公開しているスコアのファイルの記録をもとに書いています。評価スイートのランナー(fidelity_tests_runner)での実行、Docker での起動、Python 以外の SDK は試していません。

対応していない操作を呼んだときのエラー

AWS Emulator は、公式ガイドにある Linux での入れ方のとおりに Debian パッケージで入れました。1つ目のターミナルで起動します。-t off は、使用状況のデータ(テレメトリー)を送らない指定です。AWS Emulator は、止めるまでこのターミナルで動き続けます。

bash
aws-emulator -t off

2つ目のターミナルで、公式ガイドにある、SDK の接続先を AWS Emulator に向ける手順のとおりに環境変数を設定しました。最後の AWS_PAGER は、AWS CLI の出力をページャーで開かずにそのまま表示するための指定で、検証のために足したものです。

bash
export AWS_ENDPOINT_URL=https://127.0.0.1:9297
export AWS_SHARED_CREDENTIALS_FILE=~/.aws/emulator/9297/emulator_credentials
export AWS_CONFIG_FILE=~/.aws/emulator/9297/config
export AWS_CA_BUNDLE=~/.aws/emulator/9297/ca.pem
export AWS_EC2_METADATA_DISABLED=true
export AWS_DISABLE_HOST_PREFIX_INJECTION=true
export AWS_REGION=us-east-1
export AWS_DEFAULT_REGION=us-east-1
unset AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN AWS_PROFILE
export AWS_PAGER=""

この状態で、Systems Manager(SSM)の OpsCenter の項目を検索する DescribeOpsItems を呼ぶと、次のエラーが出ました。

console
$ aws ssm describe-ops-items

aws: [ERROR]: An error occurred (NotImplemented) when calling the DescribeOpsItems operation: operation DescribeOpsItems is scaffolded but not implemented in this emulator

--debug を付けて HTTP の応答を見ると、ステータスは 501 でした。

console
$ aws ssm describe-ops-items --debug 2>&1 | grep -E 'HTTP/1.1" [0-9]+|^b'
2026-11-14 02:22:16,178 - MainThread - urllib3.connectionpool - DEBUG - https://127.0.0.1:9297 "POST / HTTP/1.1" 501 117
b'{"__type":"NotImplemented","message":"operation DescribeOpsItems is scaffolded but not implemented in this emulator"}'
botocore.exceptions.ClientError: An error occurred (NotImplemented) when calling the DescribeOpsItems operation: operation DescribeOpsItems is scaffolded but not implemented in this emulator

公式ガイドの fidelity の説明にも、まだ作られていない操作には HTTP 501 とエラーコード NotImplemented を返す、と書かれています。CloudWatch Logs の TestTransformer でも、同じ形のメッセージが返りました。

console
$ aws logs test-transformer --transformer-config '[{"parseJSON":{}}]' --log-event-messages '{"a":1}'

aws: [ERROR]: An error occurred (NotImplemented) when calling the TestTransformer operation: operation TestTransformer is scaffolded but not implemented in this emulator

AWS CLI は 501 の応答を再試行しませんでした。--debug の出力で 501 の応答を数えると、1 回だけでした。

対応している操作のエラーとの違い

比べるために、AWS Emulator が対応している SSM の Parameter Store を使いました。値の保存と読み出しは成功します。

console
$ aws ssm put-parameter --name /demo/greeting --value hello --type String
{
    "Version": 1,
    "Tier": "Standard"
}
$ aws ssm get-parameter --name /demo/greeting --query Parameter.Value --output text
hello

存在しない名前を読みにいくと、ParameterNotFound のエラーになりました。ステータスは 400 です。公式ガイドの fidelity の説明によると、AWS Emulator は、作られている操作では正しくない呼び出しに AWS と同じエラーを返すことを目指しています。

console
$ aws ssm get-parameter --name /demo/missing

aws: [ERROR]: An error occurred (ParameterNotFound) when calling the GetParameter operation:
console
$ aws ssm get-parameter --name /demo/missing --debug 2>&1 | grep -E 'HTTP/1.1" [0-9]+|^b'
2026-11-14 02:22:15,433 - MainThread - urllib3.connectionpool - DEBUG - https://127.0.0.1:9297 "POST / HTTP/1.1" 400 30
b'{"__type":"ParameterNotFound"}'
b'{"__type":"ParameterNotFound"}'
botocore.errorfactory.ParameterNotFound: An error occurred (ParameterNotFound) when calling the GetParameter operation: 

違いをまとめると、次のようになります。

呼んだ操作 HTTP ステータス エラーコード メッセージ AWS CLI の終了コード
GetParameter(存在しない名前) 400 ParameterNotFound なし 254
DescribeOpsItems(未対応) 501 NotImplemented … is scaffolded but not implemented in this emulator 254

AWS CLI の終了コードは、どちらも 254 でした。AWS CLI の終了コードの説明では、254 は「リクエストを送り、サービスがエラーを返した」ことを表します。終了コードでは区別できないので、シェルのスクリプトではエラーの文面を見ます。

S3 の RenameObject は、AWS でも 501 になると記録されている

NotImplemented が出ても、AWS Emulator の未対応とは限りません。Amazon S3 の RenameObject を、汎用バケット(ふつうの S3 バケット)に対して呼ぶと、メッセージが違いました。

console
$ aws s3api rename-object --bucket demo-bucket-501 --key b.txt --rename-source a.txt

aws: [ERROR]: An error occurred (NotImplemented) when calling the RenameObject operation: A query parameter you provided implies functionality that is not implemented

同じコマンドに --debug を付けると、ステータスは 501 で、本文は S3 の形式の XML でした(RequestId と HostId は省略しました)。

text
<Error><Code>NotImplemented</Code><Message>A query parameter you provided implies functionality that is not implemented</Message><QueryParameter>renameObject</QueryParameter>…</Error>

S3 の RenameObject の API リファレンスによると、この操作を使えるのは S3 Express One Zone のディレクトリバケットだけです。S3 のエラーコードの一覧にも、NotImplemented(501 Not Implemented)が S3 自身のエラーとして載っています。さらに、後で読むスコアのファイルには、汎用バケットでの RenameObject について、AWS でも 501 の NotImplemented が返ったと読める記録がありました。

ポイント

NotImplemented が返ったら、メッセージを読みます。… is scaffolded but not implemented in this emulator なら、AWS Emulator の未対応です。それ以外の文面なら、本物の AWS でも同じエラーになる場合があるので、サービスのドキュメントやスコアのファイルで確かめます。

テストのコードで見分ける(Python・boto3)

boto3 では、エラーを ClientError として受け取れます。HTTP ステータス、エラーコード、メッセージの3つを見る関数を書き、ここまでの3つの操作で試しました。

python
import boto3
from botocore.exceptions import ClientError

def not_implemented_in_emulator(err: ClientError) -> bool:
    status = err.response["ResponseMetadata"]["HTTPStatusCode"]
    code = err.response["Error"]["Code"]
    message = err.response["Error"].get("Message", "")
    return status == 501 and code == "NotImplemented" and "not implemented in this emulator" in message

ssm = boto3.client("ssm")
s3 = boto3.client("s3")
calls = {
    "ssm.get_parameter": lambda: ssm.get_parameter(Name="/demo/missing"),
    "ssm.describe_ops_items": lambda: ssm.describe_ops_items(),
    "s3.rename_object": lambda: s3.rename_object(Bucket="demo-bucket-501", Key="b.txt", RenameSource="a.txt"),
}
for name, call in calls.items():
    try:
        call()
        print(name, "ok")
    except ClientError as err:
        print(name,
              err.response["ResponseMetadata"]["HTTPStatusCode"],
              err.response["Error"]["Code"],
              "emulator-gap" if not_implemented_in_emulator(err) else "service-error")
console
ssm.get_parameter 400 ParameterNotFound service-error
ssm.describe_ops_items 501 NotImplemented emulator-gap
s3.rename_object 501 NotImplemented service-error

未対応と判定されたのは DescribeOpsItems だけで、S3 の RenameObject は 501 でも未対応とは判定されませんでした。

なお、公式ガイドの版と互換性の説明には、同じメジャーバージョンの中で変わらないものの一覧がありますが、エラーのメッセージはその中に入っていません。版を上げたら、メッセージの文面が変わっていないか確かめてください。

fidelity のスコアのファイルで対応状況を調べる

ある操作が AWS と同じに動くかは、AWS が公開している fidelity のスコアで調べられます。公式ガイドの fidelity の説明によると、fidelity テストは、AWS SDK で一連の操作を実行し、AWS での結果やエラーと同じになるかを確かめる短いプログラムです。サービスごとのスコアは「通った fidelity テストの数/そのサービスの fidelity テストの数」で、リリースのたびに AWS Emulator のリリースのページで公開されます。公式ガイドには、スコアを比べるのは同じ版の fidelity テストどうしに限るよう書かれています。

スコアのファイルとテストの一覧を取得する

まず、使っている版とコミットを確かめます。

console
$ aws-emulator -v
Version: 1.0.352
Release date: 2026.11.13
Commit: c479b9c7e416e3201fdeba8fafd9c65b0eed8c74

v1.0.352 のリリースには、scores.json.gz というファイルが付いていました。これと、テストの一覧(manifest.jsonl)がある評価スイートのリポジトリを、同じフォルダに取得します。

bash
curl -fsSLO https://github.com/aws/aws-emulator/releases/download/v1.0.352/scores.json.gz
git clone -q --depth 1 https://github.com/aws/aws-emulator-evaluation-suite.git

manifest.jsonl は1行が1つのテストで、テストが呼ぶ操作が operations に、テストの種類が labels に入っています(次の例は operations の一部を省略)。

json
{"check_id": "accessanalyzer.access-preview-detects-public-s3-bucket-policy", "deployments": [], "key": "accessanalyzer/access-preview-detects-public-s3-bucket-policy", "labels": ["axis:happy-path", "plane:control"], "operations": ["accessanalyzer:CreateAccessPreview", "accessanalyzer:CreateAnalyzer", …], "path": "behavioral/accessanalyzer/check_access_preview_detects_public_s3_bucket_policy.py", "service": "accessanalyzer"}

スコアのファイルを読むスクリプト

scores.json.gz の形式を説明した文書は、公式ガイドにも評価スイートの README にも見つかりませんでした。そこで、中身を見ながら次のスクリプトで数えました。項目の意味は、項目名と中身から私が読み取ったものです。

python
import gzip, json, collections

scores = json.load(gzip.open("scores.json.gz"))
units = scores["units"]
manifest = [json.loads(l) for l in open("aws-emulator-evaluation-suite/manifest.jsonl")]

print("[1] トップレベルのキー:", list(scores))
print("    commit:", scores["commit"], " scoredAt:", scores["scoredAt"])
print("    units:", len(units), collections.Counter(u["kind"] for u in units))
print("    outcome:", collections.Counter(u["outcome"] for u in units))

tests = [u for u in units if u["kind"] == "behavioral"]
ids = {m["check_id"] for m in manifest}
print("[2] behavioral の unitId のうち manifest.jsonl の check_id と一致:",
      sum(u["unitId"] in ids for u in tests), "/", len(tests))
missing = [u["unitId"] for u in tests if u["unitId"] not in ids]
print("    manifest.jsonl にない unitId:", len(missing), " うち _smoke. で始まるもの:", sum(i.startswith("_smoke.") for i in missing))

print("[3] サービスごとの fidelity テストの結果(kind=behavioral、サービス名に __ が入らないテスト)")
by = collections.defaultdict(collections.Counter)
for u in tests:
    svc = u["unitId"].split(".")[0]
    if "__" not in svc and not svc.startswith("_"):
        by[svc][u["outcome"]] += 1
for svc in ["dynamodb", "s3", "sqs", "ssm", "logs"]:
    c = by[svc]
    print(f"    {svc:9} pass {c['pass']:4} / 全体 {sum(c.values()):4}(fail {c['fail']}, skipped {c['skipped']})")

print("[4] ssm:DescribeOpsItems を呼ぶ fidelity テスト(manifest.jsonl)と結果")
result = {u["unitId"]: u["outcome"] for u in tests}
for m in manifest:
    if "ssm:DescribeOpsItems" in m["operations"]:
        print("   ", m["check_id"], m["labels"], "->", result.get(m["check_id"]))

print("[5] wire の記録(expected と actual)")
byid = {u["unitId"]: u for u in units}
for uid in ("ssm-ops-item-lifecycle-and-query", "s3-mvb-rename-object-general-purpose"):
    u = byid[uid]; d = u["detail"]
    print("   ", uid, u["kind"], u["outcome"], d["operation"],
          "expected", d["expected"], "actual", d["actual"], "mismatch", d["mismatch"])

print("[6] actual が 501 の記録の数と、そのうち expected も 501 の記録")
a501 = [u for u in units if u["detail"].get("actual", {}).get("status") == 501]
print("    actual 501:", len(a501), collections.Counter(u["kind"] for u in a501))
for u in a501:
    if u["detail"].get("expected", {}).get("status") == 501:
        print("    expected も 501:", u["unitId"], u["detail"]["operation"])

結果を、出力の番号の順に読んでいきます。

ファイルの全体と、テストの一覧との対応

text
[1] トップレベルのキー: ['commit', 'schemaVersion', 'scoredAt', 'units']
    commit: c479b9c7e416e3201fdeba8fafd9c65b0eed8c74  scoredAt: 2026-11-13T23:52:50.952383+00:00
    units: 43548 Counter({'behavioral': 32839, 'wire': 10709})
    outcome: Counter({'pass': 27430, 'fail': 16035, 'skipped': 83})
[2] behavioral の unitId のうち manifest.jsonl の check_id と一致: 29676 / 32839
    manifest.jsonl にない unitId: 3163  うち _smoke. で始まるもの: 17

commit は、aws-emulator -v のコミットと一致しました。units の1件が1つの記録で、kind は behavioral と wire の2種類、outcome は pass、fail、skipped の3種類でした。

評価スイートの README には、check_id が実行結果や公開されたスコアで使う ID だと書かれています。behavioral の 32,839 件のうち 29,676 件の unitId が、取得した manifest.jsonl(2026-11-11 のコミット、29,676 行)の check_id と一致しました。残りの 3,163 件は manifest.jsonl に見つかりませんでした(_smoke. で始まる 17 件を含みます)。

サービスごとの結果

text
[3] サービスごとの fidelity テストの結果(kind=behavioral、サービス名に __ が入らないテスト)
    dynamodb  pass  844 / 全体 1073(fail 229, skipped 0)
    s3        pass  826 / 全体  930(fail 104, skipped 0)
    sqs       pass  288 / 全体  306(fail 18, skipped 0)
    ssm       pass   86 / 全体  282(fail 196, skipped 0)
    logs      pass  396 / 全体  460(fail 64, skipped 0)

評価スイートの README では、複数のサービスをまたぐテストのサービス名を a__b の形で書くことになっています。そこで、unitId の先頭のサービス名に __ が入らないテストだけを数えました。公式ガイドの言う「サービスごとのスコア」と同じ数え方かは、説明がないので分かりません。この版では、SSM のテストは 282 件のうち 86 件しか通っていませんでした。

使う操作のテストを探す

text
[4] ssm:DescribeOpsItems を呼ぶ fidelity テスト(manifest.jsonl)と結果
    cloudwatch__ssm.xsvc-cloudwatch-3-alarm-ssm-opsitem-action-creates-opsitem-and-disabled-twin-blocked ['axis:negative-delivery', 'dp:api', 'plane:data'] -> fail
    ssm.ops-item-delete-and-events-list ['axis:happy-path', 'plane:control'] -> fail

operations に ssm:DescribeOpsItems を含むテストは2件で、2件とも fail でした。labels の意味は、評価スイートの docs/TAXONOMY.md に書かれています。

ラベル 意味
plane:control リソースの作成・取得・更新・削除といった、設定の操作を確かめる
plane:data データの書き込みや読み出し、メッセージの送信など、リソースを使う操作を確かめる
dp:api 署名付きの AWS の API そのものが、データを扱う経路になっている
axis:happy-path 操作が成功し、その結果が AWS と同じになるかを確かめる
axis:negative-delivery 届いてはいけないものが、送り先に届かないことを確かめる

公式ガイドの fidelity の説明には、いまの AWS Emulator は control plane の API がもっとも AWS に近く、data plane はサービスによって差がある、と書かれています。

失敗したテストの中身を見る

text
[5] wire の記録(expected と actual)
    ssm-ops-item-lifecycle-and-query wire fail DescribeOpsItems expected {'status': 200} actual {'errorCode': 'NotImplemented', 'status': 501} mismatch status
    s3-mvb-rename-object-general-purpose wire fail RenameObject expected {'errorCode': 'NotImplemented', 'status': 501} actual {'errorCode': 'NotImplemented', 'status': 501} mismatch body
[6] actual が 501 の記録の数と、そのうち expected も 501 の記録
    actual 501: 1059 Counter({'wire': 1059})
    expected も 501: s3-mvb-rename-object-general-purpose RenameObject

wire の記録には、どの操作で、何が食い違ったかが入っていました。公式ガイドでは、fidelity テストは AWS での振る舞いを確かめるものとされています。このことと項目名から、expected は AWS で記録された応答、actual は AWS Emulator の応答だと読めます。

この読み方に従うと、DescribeOpsItems は、AWS では 200 が返るところで AWS Emulator が 501 を返しています。汎用バケットでの RenameObject は、AWS でも 501 の NotImplemented で、ステータスとエラーコードは一致しています(本文が違うので、テストとしては fail)。AWS Emulator が 501 を返した記録は 1,059 件あり、そのうち AWS 側も 501 だったのは、この RenameObject の1件だけでした。

テストが見つからない操作については、公式ガイドにある、プロジェクトが AWS Emulator で動くかを確かめる手順で、AWS Emulator と AWS の両方で動かして比べるよう案内されています。テストがないのは、まだ確かめられていないというだけで、正しく動く場合もあるとも書かれています。AWS Emulator の動きが AWS と違うときは、aws-emulator リポジトリの Issues に、送ったリクエスト、返ってきた応答、期待した結果を書いて報告できます。

​Posted by 86