Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 2 additions & 8 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,8 @@ image:
unittest:
go test -cover -v -tags musl ./...

snyk:
./runSnyk.sh

ct:
./runCT.sh

ct_image:
docker build --no-cache -f test/ct/Dockerfile test/ct/ --tag smd-test:$(VERSION})
ct-image:
docker build --no-cache -f test/Dockerfile test/ --tag smd-test:$(VERSION)

binaries: smd smd-init smd-loader native

Expand Down
71 changes: 69 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,76 @@ goreleaser release --snapshot --clean
Built binaries will be located in the `dist/` directory.

## Testing
SMD includes continuous test (CT) verification for live HPC systems. The test image is invoked via `helm test`.
In addition to the service itself, this repository builds and publishes cray-smd-test images containing tests that verify HSM on live Shasta systems. The tests are invoked via helm test as part of the Continuous Test (CT) framework during CSM installs and upgrades. The version of the cray-smd-test image (vX.Y.Z) should match the version of the cray-smd image being tested, both of which are specified in the helm chart for the service.

### Running the CT Tests in a Docker Compose Environment

1. Start services using the quick start guide

Use the quick start guide to start the services. See [README](https://github.com/OpenCHAMI/deployment-recipes/tree/main/quickstart)

Edit `openchami-svcs.yml` and add `ENABLE_DISCOVERY=true` to the SMD container's environment variable list.

Create a docker compose file to start the Redfish Emulator. For an example see [computes.yml](test/docker-compose/computes.yml)

Start the docker compose containers. Use the directions in the quick start, but also add `-d computes.yml` to start the simulator containers.

For example:
```
docker compose -f base.yml -f postgres.yml -f jwt-security.yml -f haproxy-api-gateway.yml -f openchami-svcs.yml -f autocert.yml -f coredhcp.yml -f configurator.yml -f computes.yml up -d
```

2. Build the SMD test image

```
make ct-image
```

3. Set environment variables

```
export COMPOSE_NAME=quickstart
export SMD_VERSION=v2.18.0
```
Note: `SMD_VERSION` is the version of the test image. The version of the running SMD container is in `openchami-svcs.yml`

4. Add nodes to SMD

This discovers hardware using the redfish interfaces simulated by the Redfish Interface Emulator ([RIE](https://github.com/OpenCHAMI/csm-redfish-interface-emulator)).
```
docker run -it --rm --network ${COMPOSE_NAME}_internal smd-test:${SMD_VERSION} smd-test smd-discover -n x0c0s1b0 -n x0c0s2b0 -n x0c0s3b0 -n x0c0s4b0
```

5. Run non-destructive tests

```
docker run -it --rm --network ${COMPOSE_NAME}_internal smd-test:${SMD_VERSION} smd-test test -t smoke -t 1-hardware-checks -t 2-non-disruptive -t 3-disruptive
```

6. Run destructive tests (Optional)

```
docker run -it --rm --network ${COMPOSE_NAME}_internal smd-test:${SMD_VERSION} smd-test test -t 4-destructive-initial -t 5-destructive-final
```

These tests will destore some of SMD's data, which will not be easily recovered.

### Miscellaneous Test Options
#### List the available tests

```
docker run -it --rm --network ${COMPOSE_NAME}_internal smd-test:${SMD_VERSION} smd-test list
```

#### Run the tests with tavern files from a local directory
```
docker run -it --rm --network ${COMPOSE_NAME}_internal -v $(pwd)/test/ct:/tests/ct smd-test:${SMD_VERSION} smd-test test -t smoke -t 1-hardware-checks -t 2-non-disruptive -t 3-disruptive
```
Note: This example can be run from the root directory of a clone of the SMD git repository.

#### Run the tavern tests directly with pytest
```
docker run -it --rm --network ${COMPOSE_NAME}_internal smd-test:${SMD_VERSION} pytest -vvvv /tests/api/1-hardware-checks --rootdir=/ --tavern-global-cfg /opt/smd-test/libs/tavern_global_config_ct_test.yaml
```

## Running

Expand Down
54 changes: 54 additions & 0 deletions test/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# SPDX-FileCopyrightText: Copyright © 2025 OpenCHAMI a Series of LF Projects, LLC
#
# SPDX-License-Identifier: MIT

FROM docker.io/library/alpine:3.15

STOPSIGNAL SIGTERM

# Install the necessary packages
RUN set -ex \
&& apk -U upgrade \
&& apk add --no-cache \
python3 \
python3-dev \
py3-pip \
bash \
curl \
tar \
gcc \
musl-dev \
&& pip3 install --upgrade \
pip \
pytest==7.1.2 \
tavern==1.23.1 \
allure-pytest==2.12.0 \
&& apk del \
python3-dev \
tar \
gcc \
musl-dev

RUN mkdir -p /opt/smd-test && \
mkdir -p /var/logs/smd-test && \
mkdir -p /tests && \
mkdir -p /.pytest_cache && \
chown -R 65534:65534 /.pytest_cache && \
chmod 755 /.pytest_cache && \
chown -R 65534:65534 /opt/smd-test && \
chown -R 65534:65534 /tests && \
chown -R 65534:65534 /var/logs/smd-test && \
chmod 755 /var/logs/smd-test
COPY bin /opt/smd-test/bin
COPY libs /opt/smd-test/libs
COPY ct /tests/ct
COPY smoke_pytest /opt/smd-test/smoke_pytest



# Run as nobody
USER 65534:65534
ENV PATH="$PATH:/opt/smd-test/bin"

WORKDIR /
CMD [ "/opt/smd-test/bin/smd-test" ]
135 changes: 135 additions & 0 deletions test/bin/smd-test
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
#!/usr/bin/env python3

# SPDX-FileCopyrightText: Copyright © 2025 OpenCHAMI a Series of LF Projects, LLC
#
# SPDX-License-Identifier: MIT

import argparse
import logging
import signal
import subprocess
import sys
import requests
import json
import pytest
import traceback
import os
from glob import glob


OPAAL_URL= "http://opaal:3333"
SMD_URL = "http://smd:27779"
TEST_DIR = "/tests"


def get_access_token(opaal_url):
url = f"{opaal_url}/token"
r = requests.get(url=url, verify=False, timeout=30)
if r.status_code != 200:
raise Exception(f"request to {url} failed with {r.status_code}")
body = json.loads(r.text)
return body.get("access_token")


def discover_node(smd_url, token, node_hostname):
url = f"{smd_url}/hsm/v2/Inventory/RedfishEndpoints"
req_headers = {
'Authorization': f'Bearer {token}',
'cache-control': 'no-cache',
'Content-Type': 'application/json',
}

req_body = {
"RedfishEndpoints": [
{
"ID": node_hostname,
"FQDN": node_hostname,
"RediscoverOnUpdate":True,
"User": "root",
"Password": "root_password",
}
]
}
req_body_str = json.dumps(req_body)

resp = requests.post(url, headers=req_headers, data=req_body_str, verify=False)

print()
print(f"node: {node_hostname}")
print(f"code: {resp.status_code}")
print(f"body: {resp.text}")


def list_tests(test_dir):
tests = []
if os.path.exists(f'{test_dir}/ct/smoke/smoke.json'):
tests.append("smoke")

api_test_dir = f"{test_dir}/ct/api"
for item in os.listdir(api_test_dir):
item_path = os.path.join(api_test_dir, item)
if os.path.isdir(item_path):
tests.append(item)

for test in tests:
print(test)



def smoke_test(test_dir):
result = pytest.main(["-vvvv", "/opt/smd-test/smoke_pytest", "--smoke-json", f"{test_dir}/ct/smoke/smoke.json"])
if result != 0:
sys.exit(result)


def tavern_test(test_dir, test_name):
if test_name == "smoke":
smoke_test(test_dir)
return
test_path = os.path.join(test_dir, "ct", "api", test_name)
config_file = "/opt/smd-test/libs/tavern_global_config_ct_test.yaml"
result = pytest.main(["-vvvv", str(test_path), "--rootdir=/", "--tavern-global-cfg", config_file])
if result != 0:
sys.exit(result)


def main(argv):
command_choices = [
"help",
"smd-discover",
"list",
"test",
"smoke",
]
parser = argparse.ArgumentParser()
parser.add_argument("command", type=str, help="command", choices=command_choices)
parser.add_argument("-n", "--node", type=str, action="append", help="Node Hostname or IP. Used in the smd-discover command.")
parser.add_argument("-t", "--test", type=str, action="append", help="Test name. Used in the smd-discover command.")

if len(sys.argv) == 1:
parser.print_help(sys.stderr)
sys.exit(1)

args = parser.parse_args()

try:
if args.command == "help":
parser.print_help()
elif args.command == "smd-discover":
token = get_access_token(OPAAL_URL)
for node in args.node:
discover_node(SMD_URL, token, node)
elif args.command == "list":
list_tests(TEST_DIR)
elif args.command == "smoke":
smoke_test(TEST_DIR)
elif args.command == "test":
for test in args.test:
tavern_test(TEST_DIR, test)
except Exception as e:
print(e)
print(traceback.format_exc())


if __name__ == '__main__':
sys.exit(main(sys.argv))
Loading
Loading