砂漠の旅人(たびと)

UNIX / MS-DOS 時代から電脳砂漠を旅しています

【JSON 編集】Redfish の @odata.id を読み込みながらマージする

こんにちは、たびとです。

前回、Redfish のモックアップから Redfish のファイルを読み込むコードを作りましたが、 今回はファイル読み込みの箇所を Web 接続して、サーバ(BMC)から情報を取得できるように修正してみました。

前回は、Redfish に興味があってもサーバ(BMC)がないとどうしようもないため、 モックアップJSON をファイル化して、疑似的に試してみました。 前回の記事はこちらから。

sabakunotabito.hatenablog.com

この記事の対象者

  • サーバ(BMC)から Redfish の情報を一気に取得したいと思っている方
  • Redfish などの REST API の作り方に興味のある方

ファイルから Web へ

前回のソースのファイルアクセス箇所を HTTP アクセスを簡単に実装するため、RestSharp を使うことにします。

restsharp.dev

気を付けるポイントは、サーバ(BMC)の Redfish が SSL 用の自己証明書を使っている場合、 RemoteCertificateValidationCallback を使って、ture を返す必要があります。

また、実際にサーバ(BMC)から Redfish 情報を取得したところ、10 数 MB から 100Mb のサイズになったので、 コンソールへの出力からファイルの出力に変更しました。

RestSharp を使ったソースコード

RestSharp の実装は、以下の手順を組み込むだけです。

  1. 最初に一度 RestClient を作成する。SSL 用の自己証明書に備えて、RestClientOptions で RemoteCertificateValidationCallback を定義する。
  2. Redfish の URL を渡されたら RestRequest でリクエストを作成する。必要に応じて、AddHeader でヘッダ情報を指定する。
  3. RestSharp の ExecuteAsync or Execute メソッドを呼び出して、RestResponse を取得する。
using Newtonsoft.Json;
using RestSharp;
using RestSharp.Authenticators;

var hostname = "サーバ(BMC)のIPアドレス";
var userName = "ユーザ名";
var password = "パスワード";
var filename = $@"C:\tmp\redfish_{hostname}.json";

var options = new RestClientOptions($"https://{hostname}") { RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true };
var redfish = new Redfish
{
    Client = new RestClient(options) { Authenticator = new HttpBasicAuthenticator(userName, password) }
};
redfish.Read($"/redfish/v1/");
redfish.Write(filename);
Console.WriteLine("finished.");

/// <summary>
/// JSONの要素
/// </summary>
class JsonElement
{
    public JsonToken TokenType { get; set; }
    public object? Value { get; set; }
}

/// <summary>
/// Redfishマージ用
/// </summary>
class Redfish
{
    public RestClient? Client { get; set; }
    public List<JsonElement> Elements { get; set; } = new List<JsonElement>();

    HashSet<JsonToken> JsonTags { get; } = new HashSet<JsonToken>() { JsonToken.StartObject, JsonToken.EndObject, JsonToken.StartArray, JsonToken.EndArray };
    HashSet<string> OdataItems { get; set; } = new HashSet<string>() { "/redfish/v1/" };

    /// <summary>
    /// 全てを読み込む
    /// </summary>
    /// <param name="path"></param>
    public void Read(string path)
    {
        if (Client == null) return;

        // Redfish 情報取得
        var request = new RestRequest(path);
        request.AddHeader("Content-Type", "application/json");
        var response = Client.ExecuteAsync(request, Method.Get).Result;
        if (response == null || response.Content == null || response.ResponseUri == null) return;
        Console.WriteLine($"{response.ResponseUri.AbsoluteUri}\n{response.Content}\n");

        // Redfish情報の読み込み
        var reader = new JsonTextReader(new StringReader(response.Content));
        while (reader.Read())
        {
            var val1 = reader.Value;
            Elements.Add(new JsonElement() { TokenType = reader.TokenType, Value = val1 });

            if (reader.TokenType != JsonToken.PropertyName) continue;   // プロパティ以外
            reader.Read();                                              // プロパティの値を読み込む
            var val2 = reader.Value;
            Elements.Add(new JsonElement() { TokenType = reader.TokenType, Value = val2 });

            if (val1 == null || val2 == null) continue;                 // 警告対策
            if (JsonTags.Contains(reader.TokenType)) continue;          // {} or [] 出現
            if (!((string)val1).Equals("@odata.id")) continue;          // @odata.id 以外
            if (OdataItems.Contains((string)val2)) continue;            // 同じ @odata.id が出現

            // @odata.id 処理
            OdataItems.Add((string)val2);                               // "@odata.id" 値を保持
            Elements.Add(new JsonElement() { TokenType = JsonToken.PropertyName, Value = "/* @odata.child */" });
            Read((string)val2);                                         // 再帰
        }
    }

    /// <summary>
    /// マージしたJSONを出力する
    /// </summary>
    public void Write(string filename)
    {
        using var sw = new StreamWriter(filename);
        using var writer = new JsonTextWriter(sw);
        writer.Formatting = Formatting.Indented;
        foreach (var elem in Elements)
        {
            switch (elem.TokenType)
            {
                case JsonToken.StartObject: writer.WriteStartObject(); break;
                case JsonToken.EndObject: writer.WriteEndObject(); break;
                case JsonToken.StartArray: writer.WriteStartArray(); break;
                case JsonToken.EndArray: writer.WriteEndArray(); break;
                case JsonToken.PropertyName: writer.WritePropertyName((string)(elem.Value ?? "")); break;
                default: writer.WriteValue(elem.Value); break;
            }
        }
    }
}

RestSharp の注意点

以前は、GetAsync() メソッドで記載していましたが、使い勝手が悪いため、 ExecuteAsync() に変更しました。

今回は GET リクエストのみなので、GetAsync() を採用してみましたが、 このメソッドは、認証エラーでも例外が発生します。 認証エラーの場合、HTTP ステータスコード 401 (Unauthrorized) を返して欲しいところです。

簡易的なプログラムであれば、それでもいいのでしょうが、 HTTP ステータスコードを使って判定したい場合、 ExecuteAsync(request, Method.Get) に変更する必要があります。 エラーの場合、response.ErrorException に例外が格納されるため、 HTTP レスポンスが返ってきた後に制御することが可能となります。

まとめ

このソースコードを使って、何台かのサーバ(BMC)で試してみました。 100 MB の JSON を取得したときは、例外で止まったので、 エラー処理やリトライ処理を実装して取得しました。

ただし、ここに掲載しているソースコードは、前回との対比もあり、 できるだけ簡単に Redfish にアクセスする方法を紹介しているため、 リトライ&エラー処理は省略しています。

今後、機会があれば、データベースを活用し、 もっと使いやすい GUI 版の Redfish ツールを作ってみたいと思います。

では、皆さん、よい旅を。