Skip to content

Commit a2cd65e

Browse files
Locharla, SandeepLocharla, Sandeep
authored andcommitted
Addressed comments
1 parent 44a2b33 commit a2cd65e

12 files changed

Lines changed: 4782 additions & 1621 deletions

File tree

plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMStoragePoolManager.java

Lines changed: 47 additions & 88 deletions
Large diffs are not rendered by default.

plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LibvirtStorageAdaptor.java

Lines changed: 204 additions & 327 deletions
Large diffs are not rendered by default.

test/integration/plugins/ontap/OVERVIEW.html

Lines changed: 1969 additions & 0 deletions
Large diffs are not rendered by default.

test/integration/plugins/ontap/README.md

Lines changed: 273 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,282 @@
1616
specific language governing permissions and limitations
1717
under the License.
1818
-->
19-
# ONTAP plugin — Marvin integration tests
19+
# NetApp ONTAP Integration Tests — README
2020

21-
Add Marvin tests here (e.g. `test_ontap_smoke.py`). They can be included in Apache upstream PRs when ready.
21+
This folder contains end-to-end integration tests for the NetApp ONTAP primary storage plugin in Apache CloudStack. The tests use the **Marvin** framework to drive real CloudStack API calls against a live management server and verify outcomes on a real ONTAP storage system.
2222

2323
CI wiring:
24-
2524
- Bundles: `private-cicd/marvin/bundles.txt`
2625
- Zone config: `private-cicd/marvin/zones/` (downstream only)
2726

28-
Follow patterns in `test/integration/plugins/solidfire/` and `test/integration/plugins/linstor/`.
27+
---
28+
29+
## Directory layout
30+
31+
```
32+
test/integration/plugins/ontap/
33+
├── ontap.cfg # Environment config (IPs, credentials, zone info)
34+
├── ontap_test_base.py # Shared base class and ONTAP REST client
35+
├── TEST_CASES.md # Full test case reference table (62 tests)
36+
├── README.md # This file
37+
38+
├── nfs3/
39+
│ ├── pool/
40+
│ │ ├── test_pool_lifecycle.py # Pool create/disable/enable/maintenance/delete
41+
│ │ ├── test_pool_with_volumes.py # Same lifecycle with a CS volume present
42+
│ │ └── test_zone_scoped_pool.py # Zone-scoped pool (attachZone)
43+
│ ├── volume/
44+
│ │ └── test_volume_lifecycle.py # Volume create/delete/negative-delete
45+
│ └── instance/
46+
│ └── test_vm_volume_attach.py # Pool + volume + VM + attach/detach
47+
48+
└── iscsi/
49+
├── pool/
50+
│ ├── test_pool_lifecycle.py # iSCSI pool lifecycle + igroup assertions
51+
│ ├── test_pool_with_volumes.py # Same lifecycle with a LUN-backed volume
52+
│ └── test_zone_scoped_pool.py # Zone-scoped iSCSI pool
53+
├── volume/
54+
│ └── test_volume_lifecycle.py # LUN create/delete/negative-delete
55+
└── instance/
56+
└── test_vm_volume_attach.py # Pool + LUN + VM + attach/LUN-map lifecycle
57+
```
58+
59+
---
60+
61+
## What is being tested
62+
63+
The ONTAP plugin (`plugins/storage/volume/ontap/`) integrates CloudStack's primary storage API with the NetApp ONTAP REST API. Every test suite verifies **both sides** of an operation:
64+
65+
1. **CloudStack side** — the expected `listStoragePools` / `listVolumes` / `listVirtualMachines` state after each API call.
66+
2. **ONTAP side** — the actual ONTAP object state (FlexVol, LUN, igroup, export policy, LUN-map) via direct REST API queries.
67+
68+
### NFS3 vs iSCSI — key differences
69+
70+
| Aspect | NFS3 | iSCSI |
71+
|--------|------|-------|
72+
| ONTAP object per pool | FlexVol + export policy | FlexVol + igroup per KVM host |
73+
| ONTAP object per CS volume | None (FlexVol is shared) | One LUN inside the FlexVol |
74+
| Host connectivity | NFS mount | iSCSI login (IQN-based) |
75+
| Volume detach from running VM | Works via virtio hot-unplug | Requires KVM guest to support SCSI hot-unplug |
76+
77+
---
78+
79+
## Prerequisites
80+
81+
Before running any test:
82+
83+
1. **CloudStack management server** running with the ONTAP plugin deployed (jar in `/usr/share/cloudstack-management/lib/`).
84+
2. **Integration API port 8096 enabled** — run on the management server:
85+
```sql
86+
UPDATE configuration SET value='8096' WHERE name='integration.api.port';
87+
```
88+
Then restart: `systemctl restart cloudstack-management`
89+
3. **MySQL accessible remotely** from your laptop (port 3306). If not:
90+
```bash
91+
sudo sed -i 's/^bind-address.*/bind-address = 0.0.0.0/' /etc/mysql/mysql.conf.d/mysqld.cnf
92+
sudo iptables -I INPUT -p tcp --dport 3306 -j ACCEPT
93+
sudo systemctl restart mysql
94+
```
95+
4. **ONTAP SVM** with NFS3 service and/or iSCSI service enabled, and at least one data LIF per protocol.
96+
5. **KVM cluster** registered in CloudStack. For iSCSI tests, every KVM host must have iSCSI configured (its `storageUrl` starts with `iqn.`).
97+
6. **`ontap.cfg` populated** — see the [Configuration](#configuration) section.
98+
99+
### Python / Marvin setup
100+
101+
```bash
102+
# Install Marvin from the repo's bundled tarball
103+
python3 -m pip install --user \
104+
"$(ls tools/marvin/dist/Marvin-*.tar.gz | tail -1)"
105+
106+
# Verify
107+
python3 -c "import marvin; print('Marvin OK')"
108+
```
109+
110+
---
111+
112+
## Configuration — `ontap.cfg`
113+
114+
`ontap.cfg` is a JSON file that tells Marvin where CloudStack and ONTAP are. **Never commit real credentials.**
115+
116+
Key sections:
117+
118+
```json
119+
{
120+
"mgtSvr": [{ "mgtSvrIp": "<CS_IP>", "port": 8096, "user": "admin", "passwd": "password" }],
121+
"dbSvr": { "dbSvr": "<CS_IP>", "port": 3306, "user": "cloud", "passwd": "cloud" },
122+
"ontap": { "storageIP": "<ONTAP_IP>", "svmName": "<SVM>", "username": "admin", "password": "<pw>" }
123+
}
124+
```
125+
126+
The test classes read `storageIP`, `svmName`, `username`, and `password` from the `ontap` section at runtime. **No credentials appear in test code.**
127+
128+
---
129+
130+
## Running the tests
131+
132+
**Always run from the repo root** so that `PYTHONPATH` picks up `ontap_test_base.py`:
133+
134+
```bash
135+
# All ONTAP tests (takes ~60–90 min)
136+
PYTHONPATH=test/integration/plugins/ontap \
137+
python3 -m nose --with-marvin \
138+
--marvin-config=test/integration/plugins/ontap/ontap.cfg \
139+
test/integration/plugins/ontap/ -v
140+
141+
# Single suite (e.g. NFS3 pool lifecycle)
142+
PYTHONPATH=test/integration/plugins/ontap \
143+
python3 -m nose --with-marvin \
144+
--marvin-config=test/integration/plugins/ontap/ontap.cfg \
145+
test/integration/plugins/ontap/nfs3/pool/test_pool_lifecycle.py -v
146+
147+
# By tag (e.g. all iSCSI workflow tests)
148+
PYTHONPATH=test/integration/plugins/ontap \
149+
python3 -m nose --with-marvin \
150+
--marvin-config=test/integration/plugins/ontap/ontap.cfg \
151+
-a tags=iscsi_workflow \
152+
test/integration/plugins/ontap/ -v
153+
```
154+
155+
> **Important:** `PYTHONPATH=test/integration/plugins/ontap` is always required. The test files in subdirectories import `ontap_test_base` from the parent directory; without this prefix, Python cannot find it.
156+
157+
Test results are written to:
158+
- `/tmp/marvin_last_run.txt` — stdout/stderr summary
159+
- `/tmp/MarvinLogs/<timestamp>/results.txt` — per-test pass/fail
160+
- `/tmp/MarvinLogs/<timestamp>/runinfo.txt` — full trace with API call details
161+
162+
---
163+
164+
## Code structure — how a test file is organised
165+
166+
Every test file follows the same layout:
167+
168+
```
169+
1. Apache 2.0 license header
170+
2. Module docstring ← workflow summary, prerequisites, run command
171+
3. Imports
172+
4. TestData class ← holds all config values read from ontap.cfg; builds the
173+
createStoragePool command parameters
174+
5. Test class (extends OntapTestBase)
175+
├── Class-level state attributes (pool, volume, vm, etc.) initialised to None
176+
├── setUpClass() ← connects to CloudStack; creates a test account and
177+
│ disk offering; resolves zone/cluster/hosts
178+
├── tearDownClass() ← best-effort cleanup: deletes pool (forced=True),
179+
│ volume, account, disk offering
180+
├── Helper methods ← _create_pool(), _create_volume(), _poll_pool_state(),
181+
│ _lun_maps() (iSCSI only), etc.
182+
└── test_01 … test_N ← sequential, numbered test methods
183+
```
184+
185+
### Key patterns to know
186+
187+
**Sequential state sharing — always use `self.__class__.<attr>`**
188+
189+
Tests share state via class attributes, never instance attributes:
190+
```python
191+
# Correct
192+
self.__class__.pool = pool
193+
pool = self.__class__.pool
194+
195+
# Wrong — state is lost between test method invocations
196+
self.pool = pool
197+
```
198+
199+
**Guard assertion at the start of every test (except test_01)**
200+
201+
Every test after the first starts with an assertion that the previous step's resource exists. This produces a clear, readable failure message instead of a confusing `AttributeError`:
202+
```python
203+
def test_03_enable_storage_pool(self):
204+
self.assertIsNotNone(self.__class__.pool, "Pool absent — test_01 must pass first")
205+
```
206+
207+
**Creating a storage pool — always use indexed `details[N].key` syntax**
208+
209+
The CloudStack API for `createStoragePool` requires plugin details to be passed as indexed parameters. **Never call `StoragePool.create()` directly** — it does not support this syntax:
210+
```python
211+
count = 1
212+
for key, value in ps["details"].items():
213+
setattr(cmd, "details[{}].{}".format(count, key), value)
214+
count += 1
215+
```
216+
217+
**Polling for async state changes**
218+
219+
CloudStack operations are asynchronous. Use `_poll_pool_state()` rather than reading state immediately after an API call:
220+
```python
221+
result = self._poll_pool_state(pool.id, "Maintenance", timeout=120)
222+
self.assertEqual(result.state, "Maintenance")
223+
```
224+
225+
---
226+
227+
## Shared base — `ontap_test_base.py`
228+
229+
`OntapTestBase` provides everything individual test classes inherit:
230+
231+
| What | Purpose |
232+
|------|---------|
233+
| `_setup_cloudstack_resources()` | Creates a test account, domain, disk offering; resolves zone/cluster/hosts |
234+
| `tearDownClass()` | Best-effort cleanup: deletes pool (forced=True), volume, disk offering, account |
235+
| `_poll_pool_state(pool_id, state, timeout)` | Polls `listStoragePools` until pool reaches the target state |
236+
| `_create_volume(pool_id)` | Creates a CloudStack data volume on the given pool |
237+
| `_delete_pool(pool_id, forced)` | Enters Maintenance then calls `deleteStoragePool` |
238+
| `_parse_pool_details(pool)` | Extracts key→value pairs from the pool's `details` list |
239+
| `OntapRestClient` | Thin HTTPS client for ONTAP REST API calls |
240+
241+
### `OntapRestClient` methods at a glance
242+
243+
| Method | What it checks | Used in |
244+
|--------|---------------|---------|
245+
| `get_volume(name)` | FlexVol existence and state | All suites |
246+
| `get_export_policy(name)` | NFS export policy existence | NFS3 suites |
247+
| `get_data_lifs(svm_name)` | NFS data LIF count | NFS3 pool lifecycle |
248+
| `get_igroup(svm_name, name)` | iSCSI igroup existence and initiator list | iSCSI suites |
249+
| `list_luns_in_volume(svm_name, vol_name)` | LUNs present in a FlexVol | iSCSI volume/instance suites |
250+
| `list_lun_maps_for_volume(svm_name, vol_name)` | Active LUN-maps for a volume | iSCSI instance suite |
251+
| `list_files_in_volume(svm_name, vol_name)` | Files inside a FlexVol | NFS3 instance suite |
252+
253+
---
254+
255+
## Test suite quick reference
256+
257+
| Suite | File | Tests | What it covers |
258+
|-------|------|-------|---------------|
259+
| NFS3 Pool Lifecycle | `nfs3/pool/test_pool_lifecycle.py` | 8 | Create, disable, enable, maintenance, delete |
260+
| NFS3 Pool with Volumes | `nfs3/pool/test_pool_with_volumes.py` | 7 | Same + live volume present; negative delete guard |
261+
| NFS3 Zone-Scoped Pool | `nfs3/pool/test_zone_scoped_pool.py` | 4 | Zone scope — all hosts connected via `attachZone` |
262+
| NFS3 Volume Lifecycle | `nfs3/volume/test_volume_lifecycle.py` | 5 | Volume is metadata-only; FlexVol unchanged on delete |
263+
| NFS3 VM + Volume Attach | `nfs3/instance/test_vm_volume_attach.py` | 8 | Full VM lifecycle with hot-plug/detach |
264+
| iSCSI Pool Lifecycle | `iscsi/pool/test_pool_lifecycle.py` | 8 | Create, disable, enable, maintenance, delete + igroups |
265+
| iSCSI Pool with Volumes | `iscsi/pool/test_pool_with_volumes.py` | 7 | Same + live LUN present; negative delete guard |
266+
| iSCSI Zone-Scoped Pool | `iscsi/pool/test_zone_scoped_pool.py` | 4 | Zone scope |
267+
| iSCSI Volume Lifecycle | `iscsi/volume/test_volume_lifecycle.py` | 5 | LUN created per CS volume; LUN removed on delete |
268+
| iSCSI VM + Volume Attach | `iscsi/instance/test_vm_volume_attach.py` | 8 | Full VM lifecycle; LUN-maps on VM start/stop/detach |
269+
270+
For the goal, dependencies, and exact success criteria of every individual test, see [TEST_CASES.md](TEST_CASES.md).
271+
272+
---
273+
274+
## Troubleshooting
275+
276+
| Symptom | Likely cause | Fix |
277+
|---------|-------------|-----|
278+
| `ModuleNotFoundError: No module named 'ontap_test_base'` | Missing `PYTHONPATH` prefix | Prefix every run with `PYTHONPATH=test/integration/plugins/ontap` |
279+
| `Marvin Init Failed` | CloudStack API unreachable | Check `mgtSvrIp:8096` is reachable; restart `cloudstack-management` |
280+
| `Lost connection to MySQL` | MySQL not accepting remote connections | Enable remote MySQL access (see Prerequisites §3) |
281+
| `sh: python: command not found` (repeated) | Marvin internal call — harmless on macOS | Ignore; Marvin Init still succeeds |
282+
| Pool state never reaches `Maintenance` | KVM agent not responding | Check `cloudstack-agent` on KVM host; verify host is connected in CloudStack UI |
283+
| iSCSI `test_07` error 530 | KVM guest does not ACK SCSI hot-unplug | Known environment limitation — see TEST_CASES.md Suite 10 note |
284+
| ONTAP REST `401 Unauthorized` | Wrong credentials in `ontap.cfg` | Verify `username`/`password` under `ontap` section |
285+
| `No ready KVM user template available` | Template still downloading | Wait for template `isready=true` in the CloudStack UI, then rerun |
286+
287+
---
288+
289+
## Adding new test cases
290+
291+
1. Pick the existing file closest to what you need and copy its structure.
292+
2. Read `ontap_test_base.py` for the exact method signatures you can reuse.
293+
3. Copy the `_create_pool()` helper from an existing file that matches your protocol — **never** call `StoragePool.create()`.
294+
4. Number your methods `test_01`, `test_02`, … and add `@attr(tags=["<your_tag>"], required_hardware=True)` to each.
295+
5. Use `self.__class__.<attr>` for all state shared between test methods.
296+
6. Syntax-check before the first full run: `python3 -m py_compile <your_file>.py`
297+
7. Add your test cases to [TEST_CASES.md](TEST_CASES.md).

0 commit comments

Comments
 (0)