ストレージ管理I/F: Swordfish API Emulator のコンテナ実行

# はじめに

ストレージ業界団体SNIAが提供するストレージ管理I/Fの標準仕様[Swordfish](https://www.snia.org/forums/smi/swordfish)の[API Emulator(Swordfish-API-Emulator)](https://github.com/SNIA/Swordfish-API-Emulator)のコンテナイメージを用意したため、これの実行方法を紹介します。
なお、SwordfishのAPI Emulatorは、Swordfishの母体となるDMTFが提供するシステム管理I/Fの標準仕様[Redfish]の[API Emulator(Redfish-Interface-Emulator)](https://github.com/DMTF/Redfish-Interface-Emulator)を活用しているため、本コンテナイメージには両API Emulatorが入っています。

# Dockerでの実行方法

以下のコマンドでコンテナにてSwordfish-API-Emulatorを起動します。

```
$ docker run -p 5000:5000 --name swordfish ysakashita/swordfish-api-emulator:latest 
```

Webブラウザで`http://localhost:5000/`にアクセスします。
以下の画面が出てきたら、"here for a GPI Redfish browser." の `here` をクリックします。
(Swordfish-API-Emulatorはストレージ関連のリソースのみであり、エミュレータ本体はRedfish-Interface-Emulatorを利用するため、GUIではRedfishの画面となります)

![スクリーンショット 2022-01-07 17.50.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/229880/7b705140-772d-d5be-ae66-8537cba673a0.png)

すると以下のようにAPI Emulatorで用意されているリソースにアクセスできます。
Namespaceは`/redfish/v1`となります。

![スクリーンショット 2022-01-07 17.51.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/229880/a3e24a96-1067-e648-9f93-fa39d1339060.png)

なお、curlでのアクセスを試したい方は、後述の補足に例を記載していますので、それらをご参照ください。

# Kubernetesでの実行方法

Kubernetes上で起動させる場合は、以下のManifestを使いデプロイします。
なお、今回はminikubeでIngressをaddonで有効にした環境で試しています。

:memo:
<details>
<summary>minikubeでのaddonを使ったIngressの有効化の方法</summary>
```
$ minikube start
$ minikube addons enable ingress
```
</details>

- deploy-swordfish-api-emulator.yaml

```YAML
apiVersion: apps/v1
kind: Deployment
metadata:
  name: swordfish
spec:
  replicas: 1
  selector:
    matchLabels:
      app: swordfish
  template:
    metadata:
      labels:
        app: swordfish
    spec:
      containers:
      - name: swordfish
        image: ysakashita/swordfish-api-emulator:latest
        resources:
          limits:
            memory: "128Mi"
            cpu: "500m"
        ports:
        - containerPort: 5000
```

- svc-swordfish-api-emulator.yaml

```YAML
apiVersion: v1
kind: Service
metadata:
  name: swordfish
spec:
  selector:
    app: swordfish
  ports:
  - port: 5000
    targetPort: 5000
```

今回は、Ingressも用意します。
Ingressのhostは環境にあわせて設定してください。

- ing-swordfish-api-emurator.yaml

```YAML
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: swordfish
  labels:
    name: swordfish
spec:
  rules:
  - host: swordfish.192.168.99.137.nip.io  # Please update much as your env.
    http:
      paths:
      - pathType: Prefix
        path: "/"
        backend:
          service:
            name: swordfish
            port: 
              number: 5000
```


上記3つのManifestをデプロイします。

```
$ kubectl apply -f deploy-swordfish-api-emulator.yaml -f svc-swordfish-api-emulator.yaml -f ing-swordfish-api-emurator.yaml 
```

実行を確認します。

```
$ kubectl get ing,svc,pod
NAME                                  CLASS    HOSTS                             ADDRESS          PORTS   AGE
ingress.networking.k8s.io/swordfish   <none>   swordfish.192.168.99.137.nip.io   192.168.99.137   80      41s

NAME                 TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE
service/kubernetes   ClusterIP   10.96.0.1        <none>        443/TCP    112d
service/swordfish    ClusterIP   10.110.217.188   <none>        5000/TCP   41s

NAME                             READY   STATUS    RESTARTS   AGE
pod/swordfish-7cdf5f6bc9-qm56p   1/1     Running   0          41s
```

以上で起動完了です。
Dockerの場合と同じくWebブラウザでアクセスします。
URLはIngressで作成したホストです(URL: `http://swordfish.192.168.99.137.nip.io/`)。

# 補足

2022/1/6時点のSwordfish-API-Emulatorでは、Staticなリソースとして`/redfish/v1/Storage`配下に`IPAttachedDrive`として、SSDのドライブが1つだけと少し寂しいものとなっています。

- curlでの取得例 (コンテナイメージ `ysakashita/swordfish-api-emulator:latest`)

```
$ curl -X GET http://swordfish.192.168.99.137.nip.io/redfish/v1/Storage |jq
...
{
  "@odata.id": "/redfish/v1/Storage",
  "@odata.type": "#StorageCollection.StorageCollection",
  "Members": [
    {
      "@odata.id": "/redfish/v1/Storage/IPAttachedDrive"
    }
  ],
  "Members@odata.count": 1,
  "Name": "Storage Collection"
}

$ curl -X GET http://swordfish.192.168.99.137.nip.io/redfish/v1/Storage/IPAttachedDrive | jq
...
{
  "@odata.id": "/redfish/v1/Storage/IPAttachedDrive",
  "@odata.type": "#Storage.v1_10_0.Storage",
  "Controllers": {
    "@odata.id": "/redfish/v1/Storage/IPAttachedDrive/Controllers"
  },
  "Description": "An NVM Express Subsystem is an NVMe device that contains one or more NVM Express controllers and may contain one or more namespaces.",
  "Drives": [
    {
      "@odata.id": "/redfish/v1/Chassis/IPAttachedDrive/Drives/IPAttachedDrive"
    }
  ],
  "Id": "1",
  "Identifiers": [
    {
      "DurableName": "nqn.2014-08.org.nvmexpress:uuid:6c5fe566-10e6-4fb6-aad4-8b4159f50245",
      "DurableNameFormat": "NQN"
    }
  ],
  "Links": {
    "Enclosures": [
      {
        "@odata.id": "/redfish/v1/Chassis/IPAttachedDrive"
      }
    ]
  },
  "Name": "NVMe IP Attached Drive Configuration",
  "Status": {
    "Health": "OK",
    "HealthRollup": "OK",
    "State": "Enabled"
  },
  "Volumes": {
    "@odata.id": "/redfish/v1/Storage/IPAttachedDrive/Volumes"
  }
}

$ curl -X GET http://swordfish.192.168.99.137.nip.io/redfish/v1/Chassis/IPAttachedDrive/Drives/IPAttachedDrive | jq
...
{
  "@odata.id": "/redfish/v1/Chassis/IPAttachedDrive/Drives/IPAttachedDrive",
  "@odata.type": "#Drive.v1_12_0.Drive",
  "Actions": {
    "#Drive.Reset": {
      "target": "/redfish/v1/Chassis/IPAttachedDrive/Drives/IPAttachedDrive/Actions/Drive.Reset"
    },
    "#Drive.SecureErase": {
      "target": "/redfish/v1/Chassis/IPAttachedDrive/Drives/IPAttachedDrive/Actions/Drive.SecureErase"
    }
  },
  "BlockSizeBytes": 512,
  "CapableSpeedGbs": 12,
  "CapacityBytes": 899527000000,
  "Description": "IP Attached drive.",
  "EncryptionAbility": "None",
  "FailurePredicted": false,
  "Id": "IPAttachedDrive",
  "Identifiers": [
    {
      "DurableName": "500003942810D13A",
      "DurableNameFormat": "NAA"
    }
  ],
  "Links": {
    "Volumes": [
      {
        "@odata.id": "/redfish/v1/Storage/IPAttachedDrive/Volumes/SimpleNamespace"
      }
    ]
  },
  "LocationIndicatorActive": true,
  "Manufacturer": "Contoso",
  "MediaType": "SSD",
  "Model": "ST9146802SS",
  "Name": "NVMe IPAttachedDrive Drive",
  "NegotiatedSpeedGbs": 12,
  "PartNumber": "SG0GP8811253178M02GJA00",
  "PhysicalLocation": {
    "PartLocation": {
      "LocationType": "Slot"
    }
  },
  "PredictedMediaLifeLeftPercent": 86,
  "Protocol": "NVMe",
  "Revision": "S20A",
  "SKU": "N/A",
  "SerialNumber": "72D0A037FRD26",
  "Status": {
    "Health": "OK",
    "State": "Enabled"
  },
  "StatusIndicator": "OK",
  "WriteCacheEnabled": true
}

```

そこで、少し古くはなりますが、2つのストレージのリソースが用意されていた2018/08時点のもののコンテナイメージ(`ysakashita/swordfish-api-emulator:20180801`)も用意しました。Docker or Kubernetesで実行するコンテナイメージのタグに`20180801`を指定してください。

- curlでの取得例 (コンテナイメージ `ysakashita/swordfish-api-emulator:20180801`)

```
$ curl -X GET http://swordfish.192.168.99.137.nip.io/redfish/v1/StorageServices |jq
...
{
  "@Redfish.Copyright": "Copyright 2015-2016 SNIA. All rights reserved.",
  "@odata.context": "/redfish/v1/$metadata#StorageService.StorageService",
  "@odata.id": "/redfish/v1/StorageServices",
  "@odata.type": "#StorageServiceCollection.1.0.0.StorageServiceCollection",
  "Members": [
    {
      "@odata.id": "/redfish/v1/StorageServices/1"
    },
    {
      "@odata.id": "/redfish/v1/StorageServices/2"
    }
  ],
  "Members@odata.count": 2,
  "Name": "Storage Service Collection"
}

$ curl -X GET http://swordfish.192.168.99.137.nip.io/redfish/v1/StorageServices/1 |jq
...
{
  "@Redfish.Copyright": "Copyright 2014-2016 SNIA. All rights reserved.",
  "@odata.context": "/redfish/v1/$metadata#StorageServices/1",
  "@odata.id": "/redfish/v1/StorageServices/1",
  "@odata.type": "#StorageService.1.0.0.StorageService",
  "ClassesOfService": {
    "@odata.id": "/redfish/v1/StorageServices/1/ClassesOfService"
  },
  "ClientEndpointGroups": {
    "@odata.id": "/redfish/v1/StorageServices/1/ClientEndpointGroups"
  },
  "Description": "Description of storage",
  "Drives": {
    "@odata.id": "/redfish/v1/StorageServices/1/Drives"
  },
  "Endpoints": {
    "@odata.id": "/redfish/v1/StorageServices/1/Endpoints"
  },
  "Id": "1",
  "Links": {
    "DataProtectionLoSCapabilities": {
      "@odata.id": "/redfish/v1/StorageServices/1/DataProtectionLoSCapabilities"
    },
    "DataSecurityLoSCapabilities": {
      "@odata.id": "/redfish/v1/StorageServices/1/DataSecurityLoSCapabilities"
    },
    "DataStorageLoSCapabilities": {
      "@odata.id": "/redfish/v1/StorageServices/1/DataStorageLoSCapabilities"
    },
    "Enclosures": {
      "@odata.id": "/redfish/v1/Chassis/1"
    },
    "HostingSystem": {
      "@odata.id": "/redfish/v1/Systems/Complex"
    },
    "IOConnectivityLoSCapabilities": {
      "@odata.id": "/redfish/v1/StorageServices/1/IOConnectivityLoSCapabilities"
    },
    "IOPerformanceLoSCapabilities": {
      "@odata.id": "/redfish/v1/StorageServices/1/IOPerformanceLoSCapabilities"
    }
  },
  "Name": "My Storage Service",
  "Oem": {},
  "ServerEndpointGroups": {
    "@odata.id": "/redfish/v1/StorageServices/1/ServerEndpointGroups"
  },
  "Status": {
    "Health": "OK",
    "State": "Enabled"
  },
  "StorageGroups": {
    "@odata.id": "/redfish/v1/StorageServices/1/StorageGroups"
  },
  "StoragePools": {
    "@odata.id": "/redfish/v1/StorageServices/1/StoragePools"
  },
  "StorageSubsystems": {
    "@odata.id": "/redfish/v1/StorageServices/1/StorageSubsystems"
  },
  "Volumes": {
    "Members": [
      {
        "@odata.id": "/redfish/v1/StorageServices/1/Volumes"
      }
    ]
  }
}

$ curl -X GET http://swordfish.192.168.99.137.nip.io/redfish/v1/StorageServices/1/Volumes |jq
...
{
  "@Redfish.Copyright": "Copyright 2014-2016 SNIA. All rights reserved.",
  "@odata.context": "/redfish/v1/$metadata#VolumeCollection.VolumeCollection",
  "@odata.id": "/redfish/v1/StorageServices/1/Volumes",
  "@odata.type": "#VolumeCollection_1_0_0.VolumeCollection",
  "Members": [
    {
      "@odata.id": "/redfish/v1/StorageServices/1/Volumes/61001234876545676100123487654567"
    },
    {
      "@odata.id": "/redfish/v1/StorageServices/1/Volumes/65456765456761001234876100123487"
    },
    {
      "@odata.id": "/redfish/v1/StorageServices/1/Volumes/3"
    },
    {
      "@odata.id": "/redfish/v1/StorageServices/1/Volumes/4"
    },
    {
      "@odata.id": "/redfish/v1/StorageServices/1/Volumes/5"
    },
    {
      "@odata.id": "/redfish/v1/StorageServices/1/Volumes/6"
    }
  ],
  "Members@odata.count": 6,
  "Name": "Volumes",
  "Permissions": [
    {
      "Read": "True"
    },
    {
      "Write": "True"
    }
  ]
}

$ curl -X GET http://swordfish.192.168.99.137.nip.io/redfish/v1/StorageServices/1/Volumes/61001234876545676100123487654567 |jq
...
{
  "@Redfish.Copyright": "Copyright 2014-2016 SNIA. All rights reserved.",
  "@odata.context": "/redfish/v1/$metadata#Volume.Volume",
  "@odata.id": "/redfish/v1/StorageServices/1/Volumes/61001234876545676100123487654567",
  "@odata.type": "#Volume_1_0_0.Volume",
  "AccessCapabilities": [
    "Read",
    "Write"
  ],
  "BlockSizeBytes": 512,
  "Capacity": {
    "Data": {
      "AllocatedBytes": 10737418240,
      "ConsumedBytes": 0,
      "GuaranteedBytes": 536870912,
      "ProvisionedBytes": 1099511627776
    },
    "Metadata": {
      "AllocatedBytes": 536870912,
      "ConsumedBytes": 536870912,
      "GuaranteedBytes": 536870912,
      "ProvisionedBytes": 2199023255552
    },
    "Snapshot": {
      "AllocatedBytes": 21474836480,
      "ConsumedBytes": 0,
      "GuaranteedBytes": 536870912,
      "ProvisionedBytes": 2199023255552
    }
  },
  "CapacitySources": [
    {
      "Links": {
        "ClassOfService": {
          "@odata.id": "/redfish/v1/StorageServices/1/ClassesOfService/GoldBoston"
        },
        "ProvidingPool": {
          "Members": [
            {
              "@odata.id": "/redfish/v1/StorageServices/1/StoragePools/BasePool"
            }
          ]
        },
        "ProvidingVolume": null
      },
      "ProvidedCapacity": {
        "AllocatedBytes": 140759500062720,
        "ConsumedBytes": 70390755885056,
        "GuaranteedBytes": 17592722915328,
        "ProvisionedBytes": 565148976676864
      }
    }
  ],
  "Description": "Bob's Pizza Recipe",
  "Id": "61001234876545676100123487654567",
  "Identifiers": [
    {
      "DurableName": "61001234876545676100123487654567",
      "DurableNameFormat": "NAA6"
    }
  ],
  "LowSpaceWarningThresholdPercent": [
    70,
    80,
    90,
    null,
    null
  ],
  "Manufacturer": "SuperDuperStorageProvider",
  "Model": "Drive Model string",
  "Name": "Volume 1",
  "Status": {
    "Health": "Warning",
    "State": "Enabled"
  }
}
```

# 感想

今回は、SNIA日本支部の活動で、Swordfishを紹介する機会があったため、これにあわせてコンテナイメージを用意しました。[Swordfish-API-Emulator](https://github.com/SNIA/Swordfish-API-Emulator)のドキュメントでは、Windowsの場合のセットアップしか記載がなくPythonのパッケージもセットアップが必要だったため、PCの環境を汚さないためにコンテナイメージを作りました。これによりWindows以外のOSを利用されている方や事前セットアップが面倒な方などでも、手軽にSwordfish-API-Emulatorが試せるかと思いますので、興味のある方は触ってみてください。