Compare commits
95 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
ec6d531174 | |
|
|
ec3e42e2e5 | |
|
|
aef7439157 | |
|
|
d29dd10fbd | |
|
|
3e725244d0 | |
|
|
64b430f182 | |
|
|
c698801338 | |
|
|
a604ec0c09 | |
|
|
6c313dfa9a | |
|
|
b7acb605ee | |
|
|
1c723a8fb8 | |
|
|
da823569a7 | |
|
|
d0093f48b3 | |
|
|
f0150c225e | |
|
|
3bd9f551c7 | |
|
|
db730e39f5 | |
|
|
15d84637c9 | |
|
|
a534341d13 | |
|
|
0d195a9074 | |
|
|
0ce11dc6ba | |
|
|
80a78896bf | |
|
|
a47346161b | |
|
|
59d8a8100f | |
|
|
58a648e1a3 | |
|
|
da0aaff0d2 | |
|
|
1c98591ed4 | |
|
|
19a3ec2edd | |
|
|
03fe20b074 | |
|
|
fc74a42cf2 | |
|
|
259bdd4fca | |
|
|
95b71e9c7d | |
|
|
adcd691eea | |
|
|
e57b74c46f | |
|
|
93d7ed1270 | |
|
|
61373f9db7 | |
|
|
d3dfcb4452 | |
|
|
c9dff84c1e | |
|
|
dfbb4f6c31 | |
|
|
1d0cbca537 | |
|
|
b50e8dc229 | |
|
|
18a3aeade0 | |
|
|
4056e23267 | |
|
|
4e621a215c | |
|
|
84d459da02 | |
|
|
b1423a9d72 | |
|
|
5919910b3c | |
|
|
0a17a30838 | |
|
|
c7ce39d8ae | |
|
|
359c2af88e | |
|
|
0dea6bc078 | |
|
|
334f67e473 | |
|
|
eab964c5a2 | |
|
|
728cb136cb | |
|
|
96957c037f | |
|
|
7a66e59dc7 | |
|
|
2979d54279 | |
|
|
9b2ecb8093 | |
|
|
f30862f3c0 | |
|
|
2e0b5acb9a | |
|
|
d6e7e159e3 | |
|
|
3a8a5dfb3e | |
|
|
0a8e7e7484 | |
|
|
0ad5f88180 | |
|
|
f71a1171ec | |
|
|
c48a992385 | |
|
|
feb1776aa1 | |
|
|
eb058266a9 | |
|
|
e286929535 | |
|
|
415c2a250b | |
|
|
5e2e5a8cbe | |
|
|
a4eb3e5557 | |
|
|
678bf7a850 | |
|
|
d7b0735902 | |
|
|
01318919fe | |
|
|
5684b335a1 | |
|
|
178088197f | |
|
|
c1f021c5a6 | |
|
|
9cd3cfa234 | |
|
|
187f3f9e3f | |
|
|
377503f89d | |
|
|
594b533a06 | |
|
|
f521089e57 | |
|
|
c6f662b88c | |
|
|
835648fd69 | |
|
|
9c85fdb6e9 | |
|
|
132875a5c5 | |
|
|
c5868dd4f6 | |
|
|
efcad405e5 | |
|
|
ea1c2208e1 | |
|
|
8293a718a3 | |
|
|
8a487ef4a8 | |
|
|
f6ca0c9ef5 | |
|
|
432be1ba5c | |
|
|
33faf6eb34 | |
|
|
fbfb3f26e1 |
|
|
@ -1,7 +1,7 @@
|
|||
# ADR-003: APT Repository Hosting
|
||||
|
||||
**Date**: 2026-05-15
|
||||
**Status**: Accepted
|
||||
**Status**: Superseded by ADR-010
|
||||
**Deciders**: Kevin Adams
|
||||
|
||||
## Context
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# ADR-008: PVE Version Support Matrix
|
||||
|
||||
**Date**: 2026-05-19
|
||||
**Status**: Accepted
|
||||
**Status**: Superseded by ADR-009
|
||||
**Deciders**: Kevin Adams
|
||||
|
||||
## Context
|
||||
|
|
|
|||
|
|
@ -0,0 +1,86 @@
|
|||
# ADR-009: PVE Version Support Matrix — v3.x and v4.0 Versioning Strategy
|
||||
|
||||
**Date**: 2026-05-31
|
||||
**Status**: Accepted
|
||||
**Deciders**: Kevin Adams
|
||||
**Supersedes**: ADR-008
|
||||
|
||||
## Context
|
||||
|
||||
ADR-008 established the PVE version support matrix for v3.0. Three things emerged
|
||||
during v3.0 development that require revised decisions:
|
||||
|
||||
1. **PVE 9 confirmed working** — pve01-hq was upgraded to PVE 9.2.3. One blocking
|
||||
bug was found (#266, `lun` integer type in QEMU blockdev JSON) and fixed by
|
||||
overriding `qemu_blockdev_options` in TrueNAS.pm.
|
||||
|
||||
2. **`api()` version conflict** — PVE 8 has `APIVER = 11`; PVE 9 has `APIVER = 14`.
|
||||
A single `api()` return value cannot satisfy both without warning (return 11)
|
||||
or fatally failing on PVE 8 (return 14). The warning is cosmetic and deferred.
|
||||
|
||||
3. **WebSocket API scope** — TrueNAS SCALE 25.04+ exposes a new JSON-RPC 2.0
|
||||
WebSocket API alongside the REST API. This is a new transport layer, not an
|
||||
incremental improvement. It warrants a major version bump when adopted.
|
||||
|
||||
4. **ADR-008 stated `volume_snapshot*` must ship in v3.0** — deferred to v3.1 (#234).
|
||||
|
||||
## Decision
|
||||
|
||||
### Version → PVE support mapping
|
||||
|
||||
| Version | PVE support | TrueNAS API | Notes |
|
||||
|---------|------------|-------------|-------|
|
||||
| v3.0.x | PVE 8.x + PVE 9.x | REST (v2.0) | `api()` = 11; cosmetic warning on PVE 9 |
|
||||
| v3.1.x | PVE 9.x only | REST (v2.0) | `api()` bumped to PVE 9 APIVER; PVE 8 dropped |
|
||||
| v4.0.x | PVE 9.x+ | WebSocket JSON-RPC 2.0 | New transport; genuine breaking change |
|
||||
|
||||
### Why v3.1 drops PVE 8 (not v4.0)
|
||||
|
||||
Dropping an EOL'd host platform is a support boundary decision, not a behavior
|
||||
or API change. Users on PVE 8 stay on v3.0.x, which continues to work. The
|
||||
plugin's `storage.cfg` format, TrueNAS API calls, and iSCSI behavior are
|
||||
unchanged. Minor version bump is appropriate.
|
||||
|
||||
**PVE 8 EOL: 2026-08-31.** v3.1 must ship before that date so users have time
|
||||
to migrate to PVE 9 before their platform is unsupported.
|
||||
|
||||
### Why v4.0 for WebSocket API
|
||||
|
||||
WebSocket JSON-RPC 2.0 is a fundamentally different transport layer. Adopting it
|
||||
may require dropping or conditionally supporting the REST API for newer SCALE
|
||||
versions, changing how the plugin establishes connections, and potentially
|
||||
branching code paths per TrueNAS variant. This is an architectural change that
|
||||
warrants a major version, not a minor one.
|
||||
|
||||
### v3.0.x specifics
|
||||
|
||||
- `api()` returns `11` — works on PVE 8 (exact match) and PVE 9 (within APIAGE range)
|
||||
- PVE 9 emits "older storage API, upgrade recommended" — cosmetic, documented, expected
|
||||
- `qemu_blockdev_options` override present — workaround for PVE `Plugin.pm` bug (#266)
|
||||
- Remove when Proxmox fixes `lun => int($3)` in their code (see TrueNAS.pm comment)
|
||||
- `volume_snapshot*` not implemented — deferred to v3.1 (#234)
|
||||
|
||||
### v3.1.x specifics
|
||||
|
||||
- Minimum PVE: 9.x
|
||||
- `api()` bumped to match PVE 9's `APIVER` at time of release
|
||||
- `volume_snapshot*` implementation (#234)
|
||||
- Evaluate removal of `qemu_blockdev_options` override if Proxmox has fixed upstream
|
||||
- `api()` bump tracked in #270
|
||||
|
||||
## Consequences
|
||||
|
||||
- v3.0.0 can be tagged — PVE 8 + PVE 9 both verified
|
||||
- v3.1.0 milestone must add `api()` bump (#270) as a prerequisite
|
||||
- ROADMAP updated: v3.2.0 renamed to v4.0.0 for WebSocket API (#243)
|
||||
- ADR-008 marked Superseded when this ADR is accepted
|
||||
|
||||
## Alternatives Considered
|
||||
|
||||
**v4.0 for dropping PVE 8**: Rejected — dropping an EOL platform is not a
|
||||
behavioral break. Users on PVE 8 keep using v3.0.x. A major version would
|
||||
signal something fundamentally different about what the plugin does.
|
||||
|
||||
**v3.x for WebSocket API**: Rejected — WebSocket JSON-RPC 2.0 changes the
|
||||
transport layer and may require dropping or splitting REST API support. That is
|
||||
an architectural change deserving a major version signal to users.
|
||||
|
|
@ -0,0 +1,139 @@
|
|||
# ADR-010: APT Repository Dist Tracks — Per-Major-Version Isolation
|
||||
|
||||
**Date**: 2026-06-02
|
||||
**Status**: Accepted
|
||||
**Deciders**: Kevin Adams
|
||||
**Supersedes**: ADR-003
|
||||
|
||||
## Context
|
||||
|
||||
Issue #271 exposed a gap: a user running v2.4.0 with the stable Cloudsmith repo ran
|
||||
`apt upgrade` and was silently promoted to v3.0.0 — a rewrite with breaking auth and
|
||||
iSCSI model changes. The user had no warning, no release notes were published at the
|
||||
time, and their host broke.
|
||||
|
||||
The root cause is that a single dist (`main`) serves all major versions. `apt upgrade`
|
||||
has no way to know that crossing a major version boundary requires deliberate action.
|
||||
Debian solves this with release codenames (`bookworm`, `trixie`): users choose a
|
||||
release track explicitly and are never silently promoted across breaking boundaries.
|
||||
|
||||
ADR-003 established GitHub Pages as the target apt repo infrastructure and specified
|
||||
a single `dists/stable/` dist. That structure does not support per-major-version
|
||||
isolation. This ADR supersedes ADR-003 in full — the GitHub Pages hosting decision
|
||||
is retained, but the dist layout, CI publishing rules, and Cloudsmith transition
|
||||
plan replace what ADR-003 specified.
|
||||
|
||||
## Decision
|
||||
|
||||
### Dist track structure
|
||||
|
||||
| Dist | Content | Who uses it |
|
||||
|------|---------|-------------|
|
||||
| `main` | v2.x packages only | Existing users — backward-compatible; no change to their `sources.list` |
|
||||
| `v2` | v2.x packages (alias for `main`) | Users who want to explicitly pin to v2 |
|
||||
| `v3` | v3.x packages only | Users upgrading to v3, and all new installs |
|
||||
|
||||
`main` and `v2` serve identical package content. `main` exists purely for backward
|
||||
compatibility with users who already have it in their `sources.list`. `v2` is the
|
||||
explicit alias for those who add it knowingly.
|
||||
|
||||
When v4.0 ships, a `v4` dist is added. `v3` continues to receive v3.x point releases.
|
||||
No existing user is auto-promoted.
|
||||
|
||||
### Directory layout (GitHub Pages apt repo)
|
||||
|
||||
```
|
||||
dists/
|
||||
main/ ← v2.x — backward-compat alias
|
||||
Release
|
||||
InRelease (GPG signed)
|
||||
main/binary-all/
|
||||
Packages
|
||||
Packages.gz
|
||||
v2/ ← v2.x — explicit alias (identical content to main/)
|
||||
Release
|
||||
InRelease
|
||||
main/binary-all/
|
||||
Packages
|
||||
Packages.gz
|
||||
v3/ ← v3.x only
|
||||
Release
|
||||
InRelease
|
||||
main/binary-all/
|
||||
Packages
|
||||
Packages.gz
|
||||
pool/
|
||||
main/ ← shared pool — all .deb files for all versions
|
||||
truenas-proxmox_2.*.deb
|
||||
truenas-proxmox_3.*.deb
|
||||
...
|
||||
```
|
||||
|
||||
`main/` and `v2/` reference the same pool entries. CI generates both `Packages.gz`
|
||||
files from the same v2.x package list — no filesystem symlinks needed.
|
||||
|
||||
### Sources.list lines users will have
|
||||
|
||||
```
|
||||
# Existing users (v2.x, unchanged):
|
||||
deb [signed-by=/etc/apt/keyrings/truenas-proxmox.gpg] https://thegrandwazoo.github.io/freenas-proxmox main main
|
||||
|
||||
# Explicit v2 pin (same content):
|
||||
deb [signed-by=/etc/apt/keyrings/truenas-proxmox.gpg] https://thegrandwazoo.github.io/freenas-proxmox v2 main
|
||||
|
||||
# v3.x installs:
|
||||
deb [signed-by=/etc/apt/keyrings/truenas-proxmox.gpg] https://thegrandwazoo.github.io/freenas-proxmox v3 main
|
||||
```
|
||||
|
||||
### CI behavior
|
||||
|
||||
The build workflow publishes each `.deb` to the pool unconditionally, then regenerates
|
||||
only the `Packages.gz` files for the dists that cover that major version:
|
||||
|
||||
- v2.x tag (`v2.*.*`) → regenerate `main/` and `v2/` dists
|
||||
- v3.x tag (`v3.*.*`) → regenerate `v3/` dist only
|
||||
- v4.x tag (`v4.*.*`) → regenerate `v4/` dist only (created when needed)
|
||||
|
||||
`main/` is never updated by a v3+ build. This is the enforcement mechanism.
|
||||
|
||||
### Cloudsmith transition
|
||||
|
||||
Cloudsmith will continue to serve packages in parallel during the GitHub Pages rollout
|
||||
(per ADR-003). When the GitHub Pages repo is live and announced:
|
||||
|
||||
- New installs → GitHub Pages, `v3 main`
|
||||
- Existing Cloudsmith users → stay on Cloudsmith until they choose to migrate
|
||||
- Cloudsmith is not updated with per-version dists — it remains a transitional channel
|
||||
|
||||
Cloudsmith is dropped after v3.x is the stable generation and Cloudsmith user traffic
|
||||
has subsided (tracked under issue #230).
|
||||
|
||||
## Consequences
|
||||
|
||||
- Users on `main` never receive a v3+ package unless they change their `sources.list`.
|
||||
The v2.x → v3.0 silent-upgrade incident (#271) cannot recur for future major versions.
|
||||
- New users must explicitly choose a dist track. The README and getting-started docs
|
||||
must make `v3 main` the default for new installs.
|
||||
- The CI build workflow (issue #230) must be updated to maintain multiple `Packages.gz`
|
||||
files, keyed by the major version of the package being published.
|
||||
- This structure is gated on #230 (GitHub Pages apt repo). Cloudsmith does not support
|
||||
per-dist isolation within a single repo without additional paid repos.
|
||||
|
||||
## Alternatives Considered
|
||||
|
||||
**Option B — Separate Cloudsmith repo per major version** (`truenas-proxmox-v2`,
|
||||
`truenas-proxmox-v3`): Works today without #230. Rejected because it requires users
|
||||
to change their repo URL on every major version, and Cloudsmith repo sprawl adds cost
|
||||
and management overhead. GitHub Pages dist tracks are cleaner long-term.
|
||||
|
||||
**Option C — Debian package epochs** (`1:2.4.0-1`, `2:3.0.0-1`): Epochs prevent
|
||||
`apt upgrade` from crossing the boundary, but they are opaque to users, poorly
|
||||
documented in the ecosystem, and do not solve the "wrong content in the right repo"
|
||||
problem. Rejected as an internal hack with poor user-facing semantics.
|
||||
|
||||
## References
|
||||
|
||||
- Issue #271 — silent v2→v3 upgrade incident (motivation)
|
||||
- Issue #230 — GitHub Pages apt repo (implementation prerequisite)
|
||||
- ADR-003 — apt repo hosting decision (GitHub Pages approach retained; dist structure superseded)
|
||||
- ADR-006 — versioning strategy (major/minor/patch semantics)
|
||||
|
|
@ -0,0 +1,114 @@
|
|||
# ADR-011: APT Components for Optional Feature Packages
|
||||
|
||||
**Date**: 2026-06-06
|
||||
**Status**: Accepted
|
||||
**Deciders**: Kevin Adams
|
||||
|
||||
## Context
|
||||
|
||||
ADR-010 established per-major-version dist tracks (`main`, `v2`, `v3`, `v4`) on the
|
||||
GitHub Pages apt repo. That decision covers *which version* a user receives. It does
|
||||
not address *which features* within a version they receive.
|
||||
|
||||
The multipath plugin (#256) is the first optional feature that should not be installed
|
||||
by default. Bundling it into the `truenas-proxmox` package would force all users to
|
||||
carry `iscsiadm` dependencies and a `TrueNASMultipath.pm` code path they never use.
|
||||
A separate package (`truenas-proxmox-multipath`) is the right model, but it needs a
|
||||
distribution mechanism that:
|
||||
|
||||
- Does not require users to add a second, unrelated repo URL
|
||||
- Does not appear in `apt upgrade` for users who never opted in
|
||||
- Scales to future optional features (TPM workarounds, per-vendor drivers, etc.)
|
||||
|
||||
## Decision
|
||||
|
||||
Use **apt components** to distribute optional feature packages within the existing
|
||||
dist tracks.
|
||||
|
||||
### Structure
|
||||
|
||||
```
|
||||
dists/v3/
|
||||
Release ← Components: main multipath
|
||||
main/
|
||||
binary-all/
|
||||
Packages ← truenas-proxmox, freenas-proxmox (transitional)
|
||||
multipath/
|
||||
binary-all/
|
||||
Packages ← truenas-proxmox-multipath
|
||||
```
|
||||
|
||||
### Sources.list lines
|
||||
|
||||
```
|
||||
# Standard install — only main component, no optional packages
|
||||
deb [signed-by=/etc/apt/keyrings/truenas-proxmox.gpg] \
|
||||
https://thegrandwazoo.github.io/freenas-proxmox v3 main
|
||||
|
||||
# With multipath support
|
||||
deb [signed-by=/etc/apt/keyrings/truenas-proxmox.gpg] \
|
||||
https://thegrandwazoo.github.io/freenas-proxmox v3 main multipath
|
||||
```
|
||||
|
||||
Users who do not add `multipath` to their sources line never see
|
||||
`truenas-proxmox-multipath` as a candidate — `apt upgrade` will not install it.
|
||||
|
||||
### Component naming convention
|
||||
|
||||
| Component | Contents | Who uses it |
|
||||
|-----------|----------|-------------|
|
||||
| `main` | Base plugin (`truenas-proxmox`) | All installs |
|
||||
| `multipath` | `truenas-proxmox-multipath` | Users with multipath hardware |
|
||||
|
||||
Future components follow the same pattern: one component per optional feature,
|
||||
named for the feature not the package.
|
||||
|
||||
### CI publishing
|
||||
|
||||
The build workflow generates a `Packages.gz` per component. Optional-feature packages
|
||||
are built from subdirectories or separate workflow files and published into their
|
||||
component directory. The `Release` file is regenerated after all components are
|
||||
populated, listing all available components in the `Components:` field.
|
||||
|
||||
### Testing track
|
||||
|
||||
When the GitHub Pages testing track ships (#272), optional components exist there too:
|
||||
|
||||
```
|
||||
dists/testing/
|
||||
Release ← Components: main multipath
|
||||
main/binary-all/Packages
|
||||
multipath/binary-all/Packages
|
||||
```
|
||||
|
||||
Beta users opt into beta multipath builds via `testing main multipath`.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Users have a single repo URL for all packages from this project, regardless of
|
||||
which features they use
|
||||
- `apt upgrade` respects user intent: standard users never receive optional packages
|
||||
- Adding a new optional feature is a new component — no changes to existing packages
|
||||
or `main` component machinery
|
||||
- The `Release` file must list all components; CI must regenerate it when any
|
||||
component's content changes
|
||||
- `truenas-proxmox-multipath` has a `Depends: truenas-proxmox` so apt enforces
|
||||
install order and prevents the optional package from being installed alone
|
||||
|
||||
## Alternatives Considered
|
||||
|
||||
**Option A — Separate repo URL per optional feature** (e.g., a second GitHub Pages
|
||||
site or Cloudsmith repo): Requires users to manage multiple repo entries and GPG keys.
|
||||
Rejected — adds friction for users and management overhead.
|
||||
|
||||
**Option B — Bundle everything in `main`** with optional code gated by config flags:
|
||||
Forces all users to carry dependencies they may never use; makes the base package
|
||||
harder to audit. Rejected — this is what the v2.x `ZFSPlugin.pm` patch model did.
|
||||
|
||||
**Option C — GitHub Packages / OCI**: Does not support raw apt repos. Not viable.
|
||||
|
||||
## References
|
||||
|
||||
- ADR-010: per-major-version apt dist tracks (established the repo structure this extends)
|
||||
- Issue #256: multipath plugin (first consumer of a non-`main` component)
|
||||
- Issue #272: GitHub Pages testing track (testing component support needed here too)
|
||||
|
|
@ -1,70 +0,0 @@
|
|||
# Runbook: Generate a Patch for a New Proxmox VE Version
|
||||
|
||||
When Proxmox VE releases an update that breaks the existing patches, follow these steps.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- A Proxmox VE node (or VM) running the new version
|
||||
- SSH access to that node
|
||||
- The current `FreeNAS.pm` changes you want to apply
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Copy the original files from the PVE node
|
||||
|
||||
```bash
|
||||
# On your dev machine
|
||||
PVE_HOST=your-proxmox-node
|
||||
PVE_VER=$(ssh root@$PVE_HOST "dpkg-query --showformat='\${Version}' --show pve-manager")
|
||||
|
||||
mkdir -p stable-8/originals
|
||||
|
||||
scp root@$PVE_HOST:/usr/share/perl5/PVE/Storage/ZFSPlugin.pm \
|
||||
stable-8/perl5/PVE/Storage/ZFSPlugin.pm.orig
|
||||
|
||||
scp root@$PVE_HOST:/usr/share/pve-manager/js/pvemanagerlib.js \
|
||||
stable-8/pve-manager/js/pvemanagerlib.js.orig
|
||||
|
||||
scp root@$PVE_HOST:/usr/share/pve-docs/api-viewer/apidoc.js \
|
||||
stable-8/pve-docs/api-viewer/apidoc.js.orig
|
||||
```
|
||||
|
||||
### 2. Apply the desired modifications
|
||||
|
||||
Work on copies of the `.orig` files:
|
||||
|
||||
```bash
|
||||
cp stable-8/perl5/PVE/Storage/ZFSPlugin.pm.orig /tmp/ZFSPlugin.pm
|
||||
# ... make your changes manually or apply the known modifications ...
|
||||
```
|
||||
|
||||
### 3. Generate the patch
|
||||
|
||||
```bash
|
||||
diff -u stable-8/perl5/PVE/Storage/ZFSPlugin.pm.orig /tmp/ZFSPlugin.pm \
|
||||
> stable-8/perl5/PVE/Storage/ZFSPlugin-${PVE_VER}.pm.patch
|
||||
|
||||
# Do the same for the other files
|
||||
```
|
||||
|
||||
### 4. Test the patch
|
||||
|
||||
```bash
|
||||
# On the PVE node or a copy:
|
||||
patch --dry-run -p0 /usr/share/perl5/PVE/Storage/ZFSPlugin.pm \
|
||||
< stable-8/perl5/PVE/Storage/ZFSPlugin-${PVE_VER}.pm.patch
|
||||
```
|
||||
|
||||
### 5. Update the postinst version map
|
||||
|
||||
In `packaging/DEBIAN/postinst`, update the version-to-patch-file mapping to include the new PVE version.
|
||||
|
||||
### 6. Update the default `.patch` symlink or file
|
||||
|
||||
The `postinst` selects a patch file based on the installed PVE version. Make sure the new patch is in the version map.
|
||||
|
||||
## Notes
|
||||
|
||||
- The JS files are large (pvemanagerlib.js is several MB); patches are usually small diffs around the iSCSI provider section
|
||||
- Use `--ignore-whitespace` with patch to handle indentation differences
|
||||
- Test with `patch --dry-run` before committing
|
||||
|
|
@ -1,3 +1 @@
|
|||
github: TheGrandWazoo
|
||||
custom:
|
||||
- "https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=TCLNEMBUYQUXN&source=url"
|
||||
|
|
|
|||
|
|
@ -10,8 +10,8 @@ assignees: TheGrandWazoo
|
|||
|
||||
| Component | Version |
|
||||
|-----------|---------|
|
||||
| Plugin (`dpkg -l freenas-proxmox`) | |
|
||||
| Proxmox VE (`pveversion`) | |
|
||||
| Plugin (`dpkg -l truenas-proxmox freenas-proxmox`) | |
|
||||
| Proxmox VE (`pveversion -v`) | |
|
||||
| TrueNAS type | CORE / SCALE |
|
||||
| TrueNAS version | |
|
||||
| Authentication method | Token / Password |
|
||||
|
|
@ -39,7 +39,7 @@ What actually happened.
|
|||
Run the following on your Proxmox node and paste the output:
|
||||
|
||||
```bash
|
||||
grep -i freenas /var/log/syslog | tail -50
|
||||
journalctl -u pvedaemon --since "1 hour ago" | grep -i -E "truenas|freenas|iscsi" | tail -50
|
||||
```
|
||||
|
||||
```
|
||||
|
|
|
|||
|
|
@ -5,10 +5,12 @@ on:
|
|||
branches: ["**"]
|
||||
tags: ["v*.*.*"]
|
||||
pull_request:
|
||||
branches: [master]
|
||||
branches: [master, "release/3.x"]
|
||||
|
||||
env:
|
||||
PACKAGE_NAME: freenas-proxmox
|
||||
PACKAGE_NAME: truenas-proxmox
|
||||
TRANSITIONAL_PACKAGE_NAME: freenas-proxmox
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: 'true'
|
||||
|
||||
# Cancel in-flight runs for the same branch on new push
|
||||
concurrency:
|
||||
|
|
@ -21,96 +23,81 @@ jobs:
|
|||
name: Lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4.3.1
|
||||
|
||||
- name: Install lint tools
|
||||
run: |
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get install -y \
|
||||
shellcheck libperl-critic-perl \
|
||||
libwww-perl libio-socket-ssl-perl librest-client-perl libjson-perl
|
||||
# PVE::SafeSyslog only exists on Proxmox hosts; stub it for syntax checking
|
||||
mkdir -p /tmp/pve-stub/PVE
|
||||
libwww-perl libio-socket-ssl-perl libjson-perl
|
||||
|
||||
# Stub PVE modules not available outside Proxmox hosts
|
||||
mkdir -p /tmp/pve-stub/PVE/Storage
|
||||
printf 'package PVE::SafeSyslog;\nuse Exporter "import";\nour @EXPORT = qw(syslog);\nsub syslog {}\n1;\n' \
|
||||
> /tmp/pve-stub/PVE/SafeSyslog.pm
|
||||
printf 'package PVE::Tools;\nuse Exporter "import";\nour @EXPORT_OK = qw(run_command);\nsub run_command {}\n1;\n' \
|
||||
> /tmp/pve-stub/PVE/Tools.pm
|
||||
printf 'package PVE::Storage::Plugin;\nsub new {}\nsub register {}\nsub lookup_types { [] }\nsub properties { {} }\nsub options { {} }\n1;\n' \
|
||||
> /tmp/pve-stub/PVE/Storage/Plugin.pm
|
||||
|
||||
- name: Perl syntax check (all modules)
|
||||
- name: Perl syntax check
|
||||
run: |
|
||||
echo "==> Checking Perl syntax..."
|
||||
find perl5 stable-*/perl5 -name "*.pm" -print0 \
|
||||
| xargs -0 -I{} perl -c -I/tmp/pve-stub {} \
|
||||
&& echo "All .pm files OK"
|
||||
perl -c -I/tmp/pve-stub perl5/PVE/Storage/Custom/TrueNAS.pm \
|
||||
&& echo "TrueNAS.pm: OK"
|
||||
perl -c -I/tmp/pve-stub -Iperl5 \
|
||||
perl5/PVE/Storage/Custom/TrueNASMultipath.pm \
|
||||
&& echo "TrueNASMultipath.pm: OK"
|
||||
|
||||
- name: Perl static analysis (perlcritic)
|
||||
run: |
|
||||
echo "==> Running perlcritic..."
|
||||
perlcritic --profile .perlcriticrc \
|
||||
perl5/PVE/Storage/LunCmd/FreeNAS.pm
|
||||
perl5/PVE/Storage/Custom/TrueNAS.pm \
|
||||
perl5/PVE/Storage/Custom/TrueNASMultipath.pm
|
||||
|
||||
- name: Shell script lint (shellcheck)
|
||||
run: |
|
||||
echo "==> Running shellcheck..."
|
||||
shellcheck --severity=warning \
|
||||
packaging/DEBIAN/postinst \
|
||||
packaging/DEBIAN/postrm
|
||||
packaging/DEBIAN/postrm \
|
||||
packaging/DEBIAN-transitional/postinst \
|
||||
packaging/DEBIAN-multipath/postinst \
|
||||
packaging/DEBIAN-multipath/postrm
|
||||
echo "Shell scripts OK"
|
||||
|
||||
# ── Job 2: Validate patches apply cleanly ──────────────────────────────────
|
||||
validate-patches:
|
||||
name: Validate Patches
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: "Dry-run patch: ZFSPlugin (PVE 8)"
|
||||
run: |
|
||||
patch --dry-run --ignore-whitespace \
|
||||
stable-8/perl5/PVE/Storage/ZFSPlugin.pm.orig \
|
||||
< stable-8/perl5/PVE/Storage/ZFSPlugin.pm.patch \
|
||||
&& echo "ZFSPlugin PVE-8 patch: OK"
|
||||
|
||||
- name: "Dry-run patch: ZFSPlugin (PVE 8.4.x)"
|
||||
run: |
|
||||
patch --dry-run --ignore-whitespace \
|
||||
stable-8/perl5/PVE/Storage/ZFSPlugin-8.4.14_1.pm.orig \
|
||||
< stable-8/perl5/PVE/Storage/ZFSPlugin-8.4.14_1.pm.patch \
|
||||
&& echo "ZFSPlugin PVE-8.4 patch: OK"
|
||||
|
||||
- name: "Dry-run patch: apidoc.js (PVE 8)"
|
||||
run: |
|
||||
patch --dry-run --ignore-whitespace \
|
||||
stable-8/pve-docs/api-viewer/apidoc.js.orig \
|
||||
< stable-8/pve-docs/api-viewer/apidoc.js.patch \
|
||||
&& echo "apidoc PVE-8 patch: OK"
|
||||
|
||||
# ── Job 3: Build .deb ────────────────────────────────────────────────────────
|
||||
# ── Job 2: Build .deb ────────────────────────────────────────────────────────
|
||||
build:
|
||||
name: Build Package
|
||||
runs-on: ubuntu-latest
|
||||
needs: [lint, validate-patches]
|
||||
needs: [lint]
|
||||
|
||||
outputs:
|
||||
version: ${{ steps.vars.outputs.version }}
|
||||
deb_file: ${{ steps.vars.outputs.deb_file }}
|
||||
channel: ${{ steps.vars.outputs.channel }}
|
||||
cloudsmith_repo: ${{ steps.vars.outputs.cloudsmith_repo }}
|
||||
is_release: ${{ steps.vars.outputs.is_release }}
|
||||
version: ${{ steps.vars.outputs.version }}
|
||||
deb_file: ${{ steps.vars.outputs.deb_file }}
|
||||
transitional_deb_file: ${{ steps.vars.outputs.transitional_deb_file }}
|
||||
multipath_deb_file: ${{ steps.vars.outputs.multipath_deb_file }}
|
||||
channel: ${{ steps.vars.outputs.channel }}
|
||||
cloudsmith_repo: ${{ steps.vars.outputs.cloudsmith_repo }}
|
||||
is_release: ${{ steps.vars.outputs.is_release }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4.3.1
|
||||
with:
|
||||
# Fetch full history so tag-based versioning works
|
||||
fetch-depth: 0
|
||||
|
||||
# ── Version resolution ─────────────────────────────────────────────────
|
||||
# Version strategy:
|
||||
# Tagged release (v1.2.3) → 1.2.3 (stable channel)
|
||||
# master branch → <tag>-beta+<sha> (testing channel)
|
||||
# feature_* branch → <tag>-alpha+<sha> (development channel)
|
||||
# any other branch / PR → <tag>-dev+<sha> (no publish)
|
||||
# Tagged release (v3.x.x) → 3.x.x-1 (stable channel)
|
||||
# release/3.x branch → <ver>~beta+<sha> (testing channel)
|
||||
# master branch → <ver>~beta+<sha> (testing channel)
|
||||
# release/* branches → <ver>~alpha+<sha> (development)
|
||||
# everything else / PRs → <ver>~dev+<sha> (no publish)
|
||||
#
|
||||
# The base version is derived from the most recent git tag (vX.Y.Z).
|
||||
# No VERSION file needed — the tag IS the version.
|
||||
# Tilde (~) sorts BELOW the base version in dpkg, so pre-release builds
|
||||
# never upgrade over a stable package. Stable uses "-1" Debian revision.
|
||||
- name: Resolve version and channel
|
||||
id: vars
|
||||
run: |
|
||||
|
|
@ -118,31 +105,25 @@ jobs:
|
|||
REF="${{ github.ref }}"
|
||||
IS_RELEASE="false"
|
||||
|
||||
# Base version from our $VERSION in FreeNAS.pm — the single source of truth.
|
||||
# Bump that variable when starting a new release series; git tags trigger publishing.
|
||||
# $VERSION in TrueNAS.pm is the single source of truth.
|
||||
BASE_VERSION="$(perl -ne 'if (/our\s+\$VERSION\s*=\s*['"'"'"]([^'"'"'"]+)/) { print $1; exit }' \
|
||||
perl5/PVE/Storage/LunCmd/FreeNAS.pm 2>/dev/null || echo '0.0.0')"
|
||||
perl5/PVE/Storage/Custom/TrueNAS.pm 2>/dev/null || echo '0.0.0')"
|
||||
|
||||
# Only an exact vX.Y.Z tag (no suffix) is a stable release.
|
||||
# Stable packages use a "-1" Debian revision so they sort higher than
|
||||
# any pre-release build (e.g. 2.3.0~beta+sha < 2.3.0-1).
|
||||
# Pre-release builds use "~" (tilde) which sorts BELOW the base version
|
||||
# in dpkg, ensuring stable always wins on apt upgrade.
|
||||
if [[ "$REF" =~ ^refs/tags/v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||
VERSION="${REF#refs/tags/v}-1"
|
||||
CHANNEL="stable"
|
||||
CLOUDSMITH_REPO="truenas-proxmox"
|
||||
IS_RELEASE="true"
|
||||
if [[ "${REF#refs/tags/v}" != "$BASE_VERSION" ]]; then
|
||||
echo "::warning::Tag version (${REF#refs/tags/v}) does not match \$VERSION in FreeNAS.pm ($BASE_VERSION)"
|
||||
echo "::warning::Tag version (${REF#refs/tags/v}) does not match \$VERSION in TrueNAS.pm ($BASE_VERSION)"
|
||||
fi
|
||||
|
||||
elif [[ "$REF" == refs/heads/master ]]; then
|
||||
elif [[ "$REF" == refs/heads/master || "$REF" == refs/heads/release/3.x ]]; then
|
||||
VERSION="${BASE_VERSION}~beta+${SHORT_SHA}"
|
||||
CHANNEL="testing"
|
||||
CLOUDSMITH_REPO="truenas-proxmox-testing"
|
||||
|
||||
elif [[ "$REF" == refs/heads/feature_* ]]; then
|
||||
elif [[ "$REF" == refs/heads/release/* ]]; then
|
||||
VERSION="${BASE_VERSION}~alpha+${SHORT_SHA}"
|
||||
CHANNEL="development"
|
||||
CLOUDSMITH_REPO="truenas-proxmox-snapshots"
|
||||
|
|
@ -154,12 +135,16 @@ jobs:
|
|||
fi
|
||||
|
||||
DEB_FILE="${PACKAGE_NAME}_${VERSION}_all.deb"
|
||||
TRANSITIONAL_DEB_FILE="${TRANSITIONAL_PACKAGE_NAME}_${VERSION}_all.deb"
|
||||
MULTIPATH_DEB_FILE="truenas-proxmox-multipath_${VERSION}_all.deb"
|
||||
|
||||
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
|
||||
echo "deb_file=${DEB_FILE}" >> "$GITHUB_OUTPUT"
|
||||
echo "channel=${CHANNEL}" >> "$GITHUB_OUTPUT"
|
||||
echo "cloudsmith_repo=${CLOUDSMITH_REPO}" >> "$GITHUB_OUTPUT"
|
||||
echo "is_release=${IS_RELEASE}" >> "$GITHUB_OUTPUT"
|
||||
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
|
||||
echo "deb_file=${DEB_FILE}" >> "$GITHUB_OUTPUT"
|
||||
echo "transitional_deb_file=${TRANSITIONAL_DEB_FILE}" >> "$GITHUB_OUTPUT"
|
||||
echo "multipath_deb_file=${MULTIPATH_DEB_FILE}" >> "$GITHUB_OUTPUT"
|
||||
echo "channel=${CHANNEL}" >> "$GITHUB_OUTPUT"
|
||||
echo "cloudsmith_repo=${CLOUDSMITH_REPO}" >> "$GITHUB_OUTPUT"
|
||||
echo "is_release=${IS_RELEASE}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
{
|
||||
echo "### Build Summary"
|
||||
|
|
@ -177,9 +162,7 @@ jobs:
|
|||
STAGING="dist"
|
||||
|
||||
mkdir -p "${STAGING}/DEBIAN"
|
||||
mkdir -p "${STAGING}/usr/share/freenas-proxmox/patches/ZFSPlugin"
|
||||
mkdir -p "${STAGING}/usr/share/freenas-proxmox/patches/pvemanagerlib"
|
||||
mkdir -p "${STAGING}/usr/share/freenas-proxmox/patches/apidoc"
|
||||
mkdir -p "${STAGING}/usr/share/truenas-proxmox"
|
||||
|
||||
# Generate control file from template
|
||||
sed "s/\${VERSION}/${VERSION}/" packaging/DEBIAN/control.j2 \
|
||||
|
|
@ -188,79 +171,107 @@ jobs:
|
|||
# Maintainer scripts
|
||||
cp packaging/DEBIAN/postinst "${STAGING}/DEBIAN/postinst"
|
||||
cp packaging/DEBIAN/postrm "${STAGING}/DEBIAN/postrm"
|
||||
cp packaging/DEBIAN/triggers "${STAGING}/DEBIAN/triggers"
|
||||
chmod 0755 "${STAGING}/DEBIAN/postinst" "${STAGING}/DEBIAN/postrm"
|
||||
|
||||
# Plugin source files
|
||||
cp perl5/PVE/Storage/LunCmd/FreeNAS.pm "${STAGING}/usr/share/freenas-proxmox/FreeNAS.pm"
|
||||
cp perl5/REST/Client.pm "${STAGING}/usr/share/freenas-proxmox/REST-Client.pm"
|
||||
# Plugin files (postinst copies these to their final destinations)
|
||||
cp perl5/PVE/Storage/Custom/TrueNAS.pm "${STAGING}/usr/share/truenas-proxmox/TrueNAS.pm"
|
||||
cp ui/truenas-storage.js "${STAGING}/usr/share/truenas-proxmox/truenas-storage.js"
|
||||
cp ui/truenas-storage-help.html "${STAGING}/usr/share/truenas-proxmox/truenas-storage-help.html"
|
||||
|
||||
# PVE 8 patches (8.0.x – 8.3.x)
|
||||
cp stable-8/perl5/PVE/Storage/ZFSPlugin.pm.patch \
|
||||
"${STAGING}/usr/share/freenas-proxmox/patches/ZFSPlugin/8.patch"
|
||||
|
||||
# PVE 8.4.x ZFSPlugin patch — indentation changed in 8.4, incompatible with 8.patch
|
||||
cp stable-8/perl5/PVE/Storage/ZFSPlugin-8.4.14_1.pm.patch \
|
||||
"${STAGING}/usr/share/freenas-proxmox/patches/ZFSPlugin/8.4.patch"
|
||||
echo "Bundled PVE 8.4.x ZFSPlugin patch"
|
||||
cp stable-8/pve-manager/js/pvemanagerlib.js.patch \
|
||||
"${STAGING}/usr/share/freenas-proxmox/patches/pvemanagerlib/8.patch"
|
||||
cp stable-8/pve-docs/api-viewer/apidoc.js.patch \
|
||||
"${STAGING}/usr/share/freenas-proxmox/patches/apidoc/8.patch"
|
||||
|
||||
# PVE 8.4.x pvemanagerlib patch — JS was reformatted in 8.4, incompatible with 8.patch
|
||||
cp stable-8/pve-manager/js/pvemanagerlib-8.4.14_1.js.patch \
|
||||
"${STAGING}/usr/share/freenas-proxmox/patches/pvemanagerlib/8.4.patch"
|
||||
echo "Bundled PVE 8.4.x pvemanagerlib patch"
|
||||
|
||||
# PVE 7 patches (best-effort — use latest versioned patch available)
|
||||
for type in ZFSPlugin pvemanagerlib apidoc; do
|
||||
case "$type" in
|
||||
ZFSPlugin) glob="stable-7/perl5/PVE/Storage/ZFSPlugin-*.pm.patch" ;;
|
||||
pvemanagerlib) glob="stable-7/pve-manager/js/pvemanagerlib-*.js.patch" ;;
|
||||
apidoc) glob="stable-7/pve-docs/api-viewer/apidoc-*.js.patch" ;;
|
||||
esac
|
||||
latest=$(ls $glob 2>/dev/null | sort -V | tail -1 || true)
|
||||
if [ -n "$latest" ]; then
|
||||
cp "$latest" "${STAGING}/usr/share/freenas-proxmox/patches/${type}/7.patch"
|
||||
echo "Bundled PVE-7 ${type} patch: $(basename $latest)"
|
||||
else
|
||||
echo "No PVE-7 ${type} patch found — skipping"
|
||||
fi
|
||||
done
|
||||
# Debian changelog (required by Debian policy §12.7)
|
||||
mkdir -p "${STAGING}/usr/share/doc/truenas-proxmox"
|
||||
gzip -9c packaging/changelog.Debian \
|
||||
> "${STAGING}/usr/share/doc/truenas-proxmox/changelog.Debian.gz"
|
||||
|
||||
echo "==> Package contents:"
|
||||
find "${STAGING}" | sort
|
||||
|
||||
- name: Build .deb
|
||||
- name: Assemble transitional package staging directory
|
||||
run: |
|
||||
sudo dpkg-deb -Zgzip --build dist "${{ steps.vars.outputs.deb_file }}"
|
||||
VERSION="${{ steps.vars.outputs.version }}"
|
||||
STAGING="dist-transitional"
|
||||
|
||||
- name: Verify .deb
|
||||
mkdir -p "${STAGING}/DEBIAN"
|
||||
|
||||
sed "s/\${VERSION}/${VERSION}/" packaging/DEBIAN-transitional/control.j2 \
|
||||
> "${STAGING}/DEBIAN/control"
|
||||
|
||||
cp packaging/DEBIAN-transitional/postinst "${STAGING}/DEBIAN/postinst"
|
||||
chmod 0755 "${STAGING}/DEBIAN/postinst"
|
||||
|
||||
# Debian changelog
|
||||
mkdir -p "${STAGING}/usr/share/doc/freenas-proxmox"
|
||||
gzip -9c packaging/DEBIAN-transitional/changelog.Debian \
|
||||
> "${STAGING}/usr/share/doc/freenas-proxmox/changelog.Debian.gz"
|
||||
|
||||
echo "==> Transitional package contents:"
|
||||
find "${STAGING}" | sort
|
||||
|
||||
- name: Assemble multipath package staging directory
|
||||
run: |
|
||||
echo "==> Package info:"
|
||||
VERSION="${{ steps.vars.outputs.version }}"
|
||||
STAGING="dist-multipath"
|
||||
|
||||
mkdir -p "${STAGING}/DEBIAN"
|
||||
mkdir -p "${STAGING}/usr/share/truenas-proxmox-multipath"
|
||||
|
||||
sed "s/\${VERSION}/${VERSION}/" packaging/DEBIAN-multipath/control.j2 \
|
||||
> "${STAGING}/DEBIAN/control"
|
||||
|
||||
cp packaging/DEBIAN-multipath/postinst "${STAGING}/DEBIAN/postinst"
|
||||
cp packaging/DEBIAN-multipath/postrm "${STAGING}/DEBIAN/postrm"
|
||||
chmod 0755 "${STAGING}/DEBIAN/postinst" "${STAGING}/DEBIAN/postrm"
|
||||
|
||||
cp perl5/PVE/Storage/Custom/TrueNASMultipath.pm \
|
||||
"${STAGING}/usr/share/truenas-proxmox-multipath/TrueNASMultipath.pm"
|
||||
cp ui/truenas-multipath.js \
|
||||
"${STAGING}/usr/share/truenas-proxmox-multipath/truenas-multipath.js"
|
||||
cp packaging/DEBIAN-multipath/multipath.conf.example \
|
||||
"${STAGING}/usr/share/truenas-proxmox-multipath/multipath.conf.example"
|
||||
|
||||
mkdir -p "${STAGING}/usr/share/doc/truenas-proxmox-multipath"
|
||||
gzip -9c packaging/DEBIAN-multipath/changelog.Debian \
|
||||
> "${STAGING}/usr/share/doc/truenas-proxmox-multipath/changelog.Debian.gz"
|
||||
|
||||
echo "==> Multipath package contents:"
|
||||
find "${STAGING}" | sort
|
||||
|
||||
- name: Build .deb packages
|
||||
run: |
|
||||
sudo dpkg-deb -Zgzip --build dist "${{ steps.vars.outputs.deb_file }}"
|
||||
sudo dpkg-deb -Zgzip --build dist-transitional "${{ steps.vars.outputs.transitional_deb_file }}"
|
||||
sudo dpkg-deb -Zgzip --build dist-multipath "${{ steps.vars.outputs.multipath_deb_file }}"
|
||||
|
||||
- name: Verify .deb packages
|
||||
run: |
|
||||
echo "==> Package info (truenas-proxmox):"
|
||||
dpkg-deb --info "${{ steps.vars.outputs.deb_file }}"
|
||||
echo ""
|
||||
echo "==> Package contents:"
|
||||
dpkg-deb --contents "${{ steps.vars.outputs.deb_file }}"
|
||||
echo "==> Package info (freenas-proxmox transitional):"
|
||||
dpkg-deb --info "${{ steps.vars.outputs.transitional_deb_file }}"
|
||||
echo ""
|
||||
echo "==> Package info (truenas-proxmox-multipath):"
|
||||
dpkg-deb --info "${{ steps.vars.outputs.multipath_deb_file }}"
|
||||
|
||||
- name: Upload package artifact
|
||||
uses: actions/upload-artifact@v7
|
||||
- name: Upload package artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ steps.vars.outputs.deb_file }}
|
||||
path: ${{ steps.vars.outputs.deb_file }}
|
||||
name: packages-${{ steps.vars.outputs.version }}
|
||||
path: |
|
||||
${{ steps.vars.outputs.deb_file }}
|
||||
${{ steps.vars.outputs.transitional_deb_file }}
|
||||
${{ steps.vars.outputs.multipath_deb_file }}
|
||||
retention-days: 30
|
||||
|
||||
# ── Job 4: Security scan ─────────────────────────────────────────────────────
|
||||
# ── Job 3: Security scan ─────────────────────────────────────────────────────
|
||||
security:
|
||||
name: Security Scan
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4.3.1
|
||||
|
||||
# Scan the repository for secrets and known vulnerabilities
|
||||
- name: Run Trivy (repo scan — secrets + misconfig)
|
||||
uses: aquasecurity/trivy-action@master
|
||||
with:
|
||||
|
|
@ -271,11 +282,10 @@ jobs:
|
|||
exit-code: 1
|
||||
format: table
|
||||
|
||||
# Download and scan the built .deb
|
||||
- name: Download built package
|
||||
uses: actions/download-artifact@v8
|
||||
- name: Download built packages
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ${{ needs.build.outputs.deb_file }}
|
||||
name: packages-${{ needs.build.outputs.version }}
|
||||
|
||||
- name: Extract and scan .deb contents
|
||||
run: |
|
||||
|
|
@ -292,23 +302,30 @@ jobs:
|
|||
exit-code: 1
|
||||
format: table
|
||||
|
||||
# ── Job 5: Publish ───────────────────────────────────────────────────────────
|
||||
# ── Job 4: Publish ───────────────────────────────────────────────────────────
|
||||
publish:
|
||||
name: Publish
|
||||
runs-on: ubuntu-latest
|
||||
needs: [build, security]
|
||||
# Only publish on direct pushes (not PRs) to tracked branches or tags
|
||||
if: github.event_name == 'push' && needs.build.outputs.channel != 'none'
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4.3.1
|
||||
|
||||
- name: Download built package
|
||||
uses: actions/download-artifact@v8
|
||||
- name: Download built packages
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ${{ needs.build.outputs.deb_file }}
|
||||
name: packages-${{ needs.build.outputs.version }}
|
||||
|
||||
- name: Publish to Cloudsmith
|
||||
- name: Publish truenas-proxmox to Cloudsmith
|
||||
# Cloudsmith now serves v2.x stable only. v3+ stable → GitHub Pages.
|
||||
# beta/alpha → GitHub Pages testing dist (see step below).
|
||||
if: >-
|
||||
startsWith(needs.build.outputs.version, '2.') &&
|
||||
needs.build.outputs.channel == 'stable'
|
||||
uses: cloudsmith-io/action@v0.6.14
|
||||
with:
|
||||
api-key: ${{ secrets.CLOUDSMITH_API_KEY }}
|
||||
|
|
@ -320,39 +337,252 @@ jobs:
|
|||
release: any-version
|
||||
file: ${{ needs.build.outputs.deb_file }}
|
||||
|
||||
- name: Publish transitional freenas-proxmox to Cloudsmith
|
||||
if: >-
|
||||
startsWith(needs.build.outputs.version, '2.') &&
|
||||
needs.build.outputs.channel == 'stable'
|
||||
uses: cloudsmith-io/action@v0.6.14
|
||||
with:
|
||||
api-key: ${{ secrets.CLOUDSMITH_API_KEY }}
|
||||
command: push
|
||||
format: deb
|
||||
owner: ksatechnologies
|
||||
repo: ${{ needs.build.outputs.cloudsmith_repo }}
|
||||
distro: debian
|
||||
release: any-version
|
||||
file: ${{ needs.build.outputs.transitional_deb_file }}
|
||||
|
||||
- name: Publish to GitHub Pages APT repo
|
||||
if: needs.build.outputs.is_release == 'true'
|
||||
env:
|
||||
APT_SIGNING_KEY: ${{ secrets.APT_SIGNING_KEY }}
|
||||
APT_SIGNING_KEY_PASSPHRASE: ${{ secrets.APT_SIGNING_KEY_PASSPHRASE }}
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
# Abort clearly if signing key not configured
|
||||
if [[ -z "$APT_SIGNING_KEY" ]]; then
|
||||
echo "::error::APT_SIGNING_KEY secret is not set — run scripts/setup-apt-signing-key.sh and add the secret"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Determine major version from package version (e.g. 3.0.0-1 → 3)
|
||||
VERSION="${{ needs.build.outputs.version }}"
|
||||
MAJOR="${VERSION%%.*}"
|
||||
|
||||
# Import GPG signing key
|
||||
echo "$APT_SIGNING_KEY" | base64 -d | gpg --batch --import
|
||||
GPG_KEY_ID="$(gpg --list-secret-keys --with-colons 2>/dev/null \
|
||||
| awk -F: '/^sec/{print $5; exit}')"
|
||||
|
||||
# Checkout gh-pages branch into a temp directory
|
||||
PAGES_DIR="$(mktemp -d)"
|
||||
git clone --branch gh-pages \
|
||||
"https://x-access-token:${GH_TOKEN}@github.com/${{ github.repository }}.git" \
|
||||
"$PAGES_DIR"
|
||||
|
||||
# Add .deb files to the versioned pool directories
|
||||
mkdir -p "${PAGES_DIR}/pool/v${MAJOR}"
|
||||
mkdir -p "${PAGES_DIR}/pool/multipath/v${MAJOR}"
|
||||
cp "${{ needs.build.outputs.deb_file }}" "${PAGES_DIR}/pool/v${MAJOR}/"
|
||||
cp "${{ needs.build.outputs.transitional_deb_file }}" "${PAGES_DIR}/pool/v${MAJOR}/"
|
||||
cp "${{ needs.build.outputs.multipath_deb_file }}" "${PAGES_DIR}/pool/multipath/v${MAJOR}/"
|
||||
|
||||
# Install apt tooling
|
||||
sudo apt-get install -y --no-install-recommends dpkg-dev apt-utils
|
||||
|
||||
# Determine which dists to update
|
||||
if [[ "$MAJOR" == "2" ]]; then
|
||||
DISTS="v2 main"
|
||||
else
|
||||
DISTS="v${MAJOR}"
|
||||
fi
|
||||
|
||||
_gen_component() {
|
||||
local PAGES_DIR="$1" DIST="$2" COMP="$3" POOL="$4"
|
||||
mkdir -p "${PAGES_DIR}/dists/${DIST}/${COMP}/binary-all"
|
||||
mkdir -p "${PAGES_DIR}/dists/${DIST}/${COMP}/binary-amd64"
|
||||
mkdir -p "${PAGES_DIR}/dists/${DIST}/${COMP}/binary-arm64"
|
||||
dpkg-scanpackages --multiversion "$POOL" \
|
||||
| sed "s|^Filename: ${PAGES_DIR}/|Filename: |" \
|
||||
> "${PAGES_DIR}/dists/${DIST}/${COMP}/binary-all/Packages"
|
||||
gzip -kf "${PAGES_DIR}/dists/${DIST}/${COMP}/binary-all/Packages"
|
||||
touch "${PAGES_DIR}/dists/${DIST}/${COMP}/binary-amd64/Packages"
|
||||
gzip -kf "${PAGES_DIR}/dists/${DIST}/${COMP}/binary-amd64/Packages"
|
||||
touch "${PAGES_DIR}/dists/${DIST}/${COMP}/binary-arm64/Packages"
|
||||
gzip -kf "${PAGES_DIR}/dists/${DIST}/${COMP}/binary-arm64/Packages"
|
||||
}
|
||||
|
||||
_dist_codename() {
|
||||
case "$1" in
|
||||
v2|main) echo "limelight" ;;
|
||||
v3) echo "error" ;;
|
||||
v4) echo "rivendell" ;;
|
||||
*) echo "$1" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
for DIST in $DISTS; do
|
||||
_gen_component "$PAGES_DIR" "$DIST" "main" \
|
||||
"${PAGES_DIR}/pool/v${MAJOR}"
|
||||
_gen_component "$PAGES_DIR" "$DIST" "multipath" \
|
||||
"${PAGES_DIR}/pool/multipath/v${MAJOR}"
|
||||
|
||||
CODENAME=$(_dist_codename "$DIST")
|
||||
|
||||
# Generate Release file listing both components
|
||||
apt-ftparchive \
|
||||
-o "APT::FTPArchive::Release::Origin=truenas-proxmox" \
|
||||
-o "APT::FTPArchive::Release::Label=truenas-proxmox" \
|
||||
-o "APT::FTPArchive::Release::Suite=${DIST}" \
|
||||
-o "APT::FTPArchive::Release::Codename=${CODENAME}" \
|
||||
-o "APT::FTPArchive::Release::Components=main multipath" \
|
||||
-o "APT::FTPArchive::Release::Architectures=all amd64 arm64" \
|
||||
release "${PAGES_DIR}/dists/${DIST}" \
|
||||
> "${PAGES_DIR}/dists/${DIST}/Release"
|
||||
|
||||
gpg --batch --yes \
|
||||
--passphrase "${APT_SIGNING_KEY_PASSPHRASE}" \
|
||||
--default-key "${GPG_KEY_ID}" \
|
||||
--clearsign \
|
||||
-o "${PAGES_DIR}/dists/${DIST}/InRelease" \
|
||||
"${PAGES_DIR}/dists/${DIST}/Release"
|
||||
done
|
||||
|
||||
# Create codename alias directories (e.g. dists/error/ mirrors dists/v3/)
|
||||
# so that both Suite ("v3") and Codename ("error") work in sources.list.
|
||||
# rm -rf first: cp -r puts src inside dst when dst already exists.
|
||||
for DIST in $DISTS; do
|
||||
CODENAME=$(_dist_codename "$DIST")
|
||||
if [[ "$CODENAME" != "$DIST" ]]; then
|
||||
rm -rf "${PAGES_DIR}/dists/${CODENAME}"
|
||||
cp -r "${PAGES_DIR}/dists/${DIST}" "${PAGES_DIR}/dists/${CODENAME}"
|
||||
fi
|
||||
done
|
||||
|
||||
# Commit and push
|
||||
cd "$PAGES_DIR"
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git add .
|
||||
git diff --staged --quiet || \
|
||||
git commit -m "apt: publish ${VERSION} to dist(s): ${DISTS}"
|
||||
git push
|
||||
|
||||
- name: Publish to GitHub Pages testing dist
|
||||
if: >-
|
||||
needs.build.outputs.channel == 'testing' ||
|
||||
needs.build.outputs.channel == 'development'
|
||||
env:
|
||||
APT_SIGNING_KEY: ${{ secrets.APT_SIGNING_KEY }}
|
||||
APT_SIGNING_KEY_PASSPHRASE: ${{ secrets.APT_SIGNING_KEY_PASSPHRASE }}
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
if [[ -z "$APT_SIGNING_KEY" ]]; then
|
||||
echo "::error::APT_SIGNING_KEY secret is not set"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
VERSION="${{ needs.build.outputs.version }}"
|
||||
|
||||
echo "$APT_SIGNING_KEY" | base64 -d | gpg --batch --import
|
||||
GPG_KEY_ID="$(gpg --list-secret-keys --with-colons 2>/dev/null \
|
||||
| awk -F: '/^sec/{print $5; exit}')"
|
||||
|
||||
PAGES_DIR="$(mktemp -d)"
|
||||
git clone --branch gh-pages \
|
||||
"https://x-access-token:${GH_TOKEN}@github.com/${{ github.repository }}.git" \
|
||||
"$PAGES_DIR"
|
||||
|
||||
# Replace pool/testing/ contents with the latest build only
|
||||
rm -rf "${PAGES_DIR}/pool/testing"
|
||||
rm -rf "${PAGES_DIR}/pool/multipath/testing"
|
||||
mkdir -p "${PAGES_DIR}/pool/testing"
|
||||
mkdir -p "${PAGES_DIR}/pool/multipath/testing"
|
||||
cp "${{ needs.build.outputs.deb_file }}" "${PAGES_DIR}/pool/testing/"
|
||||
cp "${{ needs.build.outputs.transitional_deb_file }}" "${PAGES_DIR}/pool/testing/"
|
||||
cp "${{ needs.build.outputs.multipath_deb_file }}" "${PAGES_DIR}/pool/multipath/testing/"
|
||||
|
||||
sudo apt-get install -y --no-install-recommends dpkg-dev apt-utils
|
||||
|
||||
DIST="testing"
|
||||
|
||||
_gen_component() {
|
||||
local PAGES_DIR="$1" DIST="$2" COMP="$3" POOL="$4"
|
||||
mkdir -p "${PAGES_DIR}/dists/${DIST}/${COMP}/binary-all"
|
||||
mkdir -p "${PAGES_DIR}/dists/${DIST}/${COMP}/binary-amd64"
|
||||
mkdir -p "${PAGES_DIR}/dists/${DIST}/${COMP}/binary-arm64"
|
||||
dpkg-scanpackages --multiversion "$POOL" \
|
||||
| sed "s|^Filename: ${PAGES_DIR}/|Filename: |" \
|
||||
> "${PAGES_DIR}/dists/${DIST}/${COMP}/binary-all/Packages"
|
||||
gzip -kf "${PAGES_DIR}/dists/${DIST}/${COMP}/binary-all/Packages"
|
||||
touch "${PAGES_DIR}/dists/${DIST}/${COMP}/binary-amd64/Packages"
|
||||
gzip -kf "${PAGES_DIR}/dists/${DIST}/${COMP}/binary-amd64/Packages"
|
||||
touch "${PAGES_DIR}/dists/${DIST}/${COMP}/binary-arm64/Packages"
|
||||
gzip -kf "${PAGES_DIR}/dists/${DIST}/${COMP}/binary-arm64/Packages"
|
||||
}
|
||||
|
||||
_gen_component "$PAGES_DIR" "$DIST" "main" "${PAGES_DIR}/pool/testing"
|
||||
_gen_component "$PAGES_DIR" "$DIST" "multipath" "${PAGES_DIR}/pool/multipath/testing"
|
||||
|
||||
apt-ftparchive \
|
||||
-o "APT::FTPArchive::Release::Origin=truenas-proxmox" \
|
||||
-o "APT::FTPArchive::Release::Label=truenas-proxmox" \
|
||||
-o "APT::FTPArchive::Release::Suite=${DIST}" \
|
||||
-o "APT::FTPArchive::Release::Codename=testing" \
|
||||
-o "APT::FTPArchive::Release::Components=main multipath" \
|
||||
-o "APT::FTPArchive::Release::Architectures=all amd64 arm64" \
|
||||
release "${PAGES_DIR}/dists/${DIST}" \
|
||||
> "${PAGES_DIR}/dists/${DIST}/Release"
|
||||
|
||||
gpg --batch --yes \
|
||||
--passphrase "${APT_SIGNING_KEY_PASSPHRASE}" \
|
||||
--default-key "${GPG_KEY_ID}" \
|
||||
--clearsign \
|
||||
-o "${PAGES_DIR}/dists/${DIST}/InRelease" \
|
||||
"${PAGES_DIR}/dists/${DIST}/Release"
|
||||
|
||||
cd "$PAGES_DIR"
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git add .
|
||||
git diff --staged --quiet || \
|
||||
git commit -m "apt: publish ${VERSION} to testing dist"
|
||||
git push
|
||||
|
||||
- name: Create draft GitHub Release
|
||||
if: needs.build.outputs.is_release == 'true'
|
||||
uses: softprops/action-gh-release@v3
|
||||
with:
|
||||
name: "v${{ needs.build.outputs.version }}"
|
||||
draft: true
|
||||
files: ${{ needs.build.outputs.deb_file }}
|
||||
files: |
|
||||
${{ needs.build.outputs.deb_file }}
|
||||
${{ needs.build.outputs.transitional_deb_file }}
|
||||
${{ needs.build.outputs.multipath_deb_file }}
|
||||
generate_release_notes: false
|
||||
body: |
|
||||
## freenas-proxmox v${{ needs.build.outputs.version }}
|
||||
## truenas-proxmox v${{ needs.build.outputs.version }}
|
||||
|
||||
> **Edit before publishing** — fill in tested versions and remove inapplicable notices below.
|
||||
> **Edit before publishing** — fill in tested versions before publishing.
|
||||
|
||||
### ⚠️ Version Compatibility Notices
|
||||
### Compatibility
|
||||
|
||||
| Your Proxmox VE | What to do |
|
||||
|:----------------|:-----------|
|
||||
| **PVE 9+** | ❌ Do **not** install v2.x — use v3.0 when released |
|
||||
| **PVE 8.x** | ✅ Supported. Note: PVE 8 EOL is **2026-08-31** — plan your upgrade to PVE 9 + v3.0 |
|
||||
| **PVE 7.x** | ⚠️ Best-effort only. This is the **last v2.x release** supporting PVE 7. Do **not** upgrade to v3.0 — stay on v2.x |
|
||||
| **PVE 6 or older** | ❌ Not supported |
|
||||
| Proxmox VE | Status |
|
||||
|:-----------|:-------|
|
||||
| **PVE 9+** | ✅ Supported |
|
||||
| **PVE 8.x** | ✅ Supported (EOL 2026-08-31 — plan upgrade to PVE 9) |
|
||||
| **PVE 7 or older** | ❌ Not supported — use v2.x |
|
||||
|
||||
### Tested Proxmox VE Versions
|
||||
- Proxmox VE 8.4.x (tested: )
|
||||
- Proxmox VE 8.3.x (tested: )
|
||||
- Proxmox VE 7.x (best-effort: )
|
||||
- Proxmox VE 9.x (tested: )
|
||||
|
||||
### Tested TrueNAS Versions
|
||||
- TrueNAS CORE:
|
||||
- TrueNAS SCALE:
|
||||
- TrueNAS CORE 13.x (tested: )
|
||||
- TrueNAS SCALE 24.10.x (tested: )
|
||||
|
||||
### Installation
|
||||
See [README → Installation](https://github.com/TheGrandWazoo/freenas-proxmox#installation).
|
||||
See [README → Installation](https://github.com/TheGrandWazoo/truenas-proxmox#installation).
|
||||
|
||||
### Changes
|
||||
See [CHANGELOG.md](https://github.com/TheGrandWazoo/freenas-proxmox/blob/master/CHANGELOG.md#unreleased).
|
||||
See [CHANGELOG.md](https://github.com/TheGrandWazoo/truenas-proxmox/blob/release/3.x/CHANGELOG.md).
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# Perl::Critic configuration for freenas-proxmox
|
||||
# Severity scale: 1 (brutal) → 5 (gentle). We start at 4 and tighten over time.
|
||||
# Run: perlcritic --profile .perlcriticrc perl5/PVE/Storage/LunCmd/FreeNAS.pm
|
||||
# Run: perlcritic --profile .perlcriticrc perl5/PVE/Storage/Custom/TrueNAS.pm
|
||||
|
||||
severity = 4
|
||||
theme = core
|
||||
|
|
@ -10,16 +10,16 @@ verbose = %f:%l:%c [%p] %m\n
|
|||
# These are known issues being fixed in Phase 2 (code hardening).
|
||||
# Remove entries here as the underlying code is fixed.
|
||||
|
||||
# eval $value pattern — being replaced with explicit substitution map (Phase 2)
|
||||
# eval { } block is fine; only string eval is risky — keep disabled
|
||||
[-BuiltinFunctions::ProhibitStringyEval]
|
||||
|
||||
# Postfix conditionals (if/unless at end of line) — style preference, keep
|
||||
[-ControlStructures::ProhibitPostfixControls]
|
||||
|
||||
# Long subs — FreeNAS.pm has some; will be refactored in Phase 3
|
||||
# TrueNAS.pm has necessarily large subs (PVE plugin interface methods)
|
||||
[-Subroutines::ProhibitExcessComplexity]
|
||||
|
||||
# Global variables — known issue, fixing in Phase 2
|
||||
# our $VERSION and our $state are intentional package-level declarations
|
||||
[-Variables::ProhibitPackageVars]
|
||||
|
||||
# ── Rules with custom settings ────────────────────────────────────────────────
|
||||
|
|
|
|||
|
|
@ -4,11 +4,11 @@ All notable changes to this project will be documented here.
|
|||
|
||||
Format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||
Versions follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html) independent of Proxmox VE or TrueNAS versions.
|
||||
See each [GitHub Release](https://github.com/TheGrandWazoo/freenas-proxmox/releases) for the specific Proxmox VE and TrueNAS versions supported.
|
||||
See each [GitHub Release](https://github.com/TheGrandWazoo/truenas-proxmox/releases) for the specific Proxmox VE and TrueNAS versions supported.
|
||||
|
||||
---
|
||||
|
||||
## [Unreleased] — v3.0.0
|
||||
## [3.0.0] — 2026-05-31
|
||||
|
||||
### Added
|
||||
- New `PVE::Storage::Custom::TrueNASPlugin` — proper Proxmox VE custom storage type (no more ZFSPlugin.pm patching)
|
||||
|
|
@ -64,4 +64,4 @@ See each [GitHub Release](https://github.com/TheGrandWazoo/freenas-proxmox/relea
|
|||
|
||||
## [2.1.x] and Earlier
|
||||
|
||||
See the [commit history](https://github.com/TheGrandWazoo/freenas-proxmox/commits/master) for earlier changes.
|
||||
See the [commit history](https://github.com/TheGrandWazoo/truenas-proxmox/commits/master) for earlier changes.
|
||||
|
|
|
|||
100
CLAUDE.md
100
CLAUDE.md
|
|
@ -2,79 +2,87 @@
|
|||
|
||||
## What This Project Is
|
||||
|
||||
A storage plugin "wedge" for Proxmox VE (PVE) that allows PVE to manage iSCSI LUNs on TrueNAS/FreeNAS via the TrueNAS REST API instead of the traditional SSH-based `iscsiadm` approach.
|
||||
A native `PVE::Storage::Custom` plugin for Proxmox VE (PVE) that manages TrueNAS ZFS volumes over iSCSI via the TrueNAS REST API. Each VM gets its own dedicated iSCSI target; QEMU drives the connection directly via `iscsi://` paths — no `iscsiadm`, no SSH keys, no patches to PVE core files.
|
||||
|
||||
The plugin installs as a Debian package and works by:
|
||||
1. Deploying a new Perl LunCmd handler (`FreeNAS.pm`) to `/usr/share/perl5/PVE/Storage/LunCmd/`
|
||||
2. Patching three Proxmox VE system files at install time via `dpkg triggers`
|
||||
1. Copying `TrueNAS.pm` to `/usr/share/perl5/PVE/Storage/Custom/` (auto-discovered by PVE)
|
||||
2. Copying `truenas-storage.js` to `/usr/share/pve-manager/js/` and injecting one `<script>` tag into `index.html.tpl`
|
||||
|
||||
## Repository Layout
|
||||
|
||||
```
|
||||
freenas-proxmox/
|
||||
├── perl5/PVE/Storage/
|
||||
│ ├── Custom/FreeNAS.pm # Old attempt at a full custom storage type (unfinished)
|
||||
│ ├── LunCmd/FreeNAS.pm # MAIN backend: iSCSI LunCmd via TrueNAS REST API
|
||||
│ ├── LunCmd/FreeNAS-ng.pm # Next-gen draft (not in use)
|
||||
│ └── ZFSPlugin-*.pm.patch # Per-PVE-version patches for ZFSPlugin.pm
|
||||
├── pve-manager/js/
|
||||
│ └── pvemanagerlib-*.js.patch # Per-PVE-version patches for the Proxmox UI JS
|
||||
├── pve-docs/api-viewer/
|
||||
│ └── apidoc-*.js.patch # Per-PVE-version patches for the API docs JS
|
||||
├── perl5/REST/Client.pm # Bundled REST::Client (also an apt dependency)
|
||||
├── stable-5/, stable-6/, stable-7/, stable-8/
|
||||
│ # Per-major-version snapshots of patches + originals
|
||||
└── .github/workflows/action.yml # Currently just dispatches to external packer repo
|
||||
├── perl5/PVE/Storage/Custom/TrueNAS.pm # MAIN plugin — PVE::Storage::Custom subclass
|
||||
├── ui/truenas-storage.js # Proxmox UI panel (storageSchema + input panel)
|
||||
├── packaging/
|
||||
│ └── DEBIAN/
|
||||
│ ├── control.j2 # Package metadata template (VERSION substituted at build time)
|
||||
│ ├── postinst # Install: copies .pm + .js, injects <script> tag, restarts PVE
|
||||
│ └── postrm # Remove/purge: removes files, strips <script> tag, restarts PVE
|
||||
└── .github/workflows/
|
||||
├── build.yml # Active CI: lint → build .deb → security scan → publish
|
||||
└── action.yml # Deprecated no-op (retained so old external links don't 404)
|
||||
```
|
||||
|
||||
## How the Current Build Works (Two-Repo Problem)
|
||||
## How the Build Works
|
||||
|
||||
1. A push to this repo triggers `action.yml` which fires a `repository_dispatch` event to `TheGrandWazoo/freenas-proxmox-packer`
|
||||
2. That separate repo holds the DEBIAN package structure (`DEBIAN/control`, `postinst`, `postrm`, `triggers`)
|
||||
3. Its CI builds the `.deb` with `dpkg-deb` and pushes to Cloudsmith
|
||||
Everything lives in this repo — there is no external packer repo.
|
||||
|
||||
The `postinst` script at install time **git-clones this repo** to `/usr/local/src/freenas-proxmox` and applies patches from there. This is the key fragility — internet access required at package install time.
|
||||
`.github/workflows/build.yml` runs on every push and tag:
|
||||
|
||||
## Three Files That Get Patched at Install Time
|
||||
| Job | What it does |
|
||||
|-----|-------------|
|
||||
| **lint** | `perl -c` syntax check + perlcritic + shellcheck on postinst/postrm |
|
||||
| **build** | Resolves version from `$VERSION` in `TrueNAS.pm`, assembles `dist/` staging dir, runs `dpkg-deb`, uploads `.deb` as a workflow artifact |
|
||||
| **security** | Trivy repo scan (secrets + misconfig) + Trivy scan of extracted `.deb` contents |
|
||||
| **publish** | Pushes `.deb` to Cloudsmith; on `v*.*.*` tags also creates a draft GitHub Release |
|
||||
|
||||
| File | What the patch adds |
|
||||
|------|---------------------|
|
||||
| `/usr/share/perl5/PVE/Storage/ZFSPlugin.pm` | Adds `freenas` as a valid iSCSI provider, routes `run_lun_command` to `FreeNAS.pm`, adds custom properties/options |
|
||||
| `/usr/share/pve-manager/js/pvemanagerlib.js` | Adds `FreeNAS/TrueNAS API` to the iSCSI provider dropdown; adds UI fields for API host, user, secret, SSL, token auth |
|
||||
| `/usr/share/pve-docs/api-viewer/apidoc.js` | Registers TrueNAS-specific properties in the API docs |
|
||||
### Version / Channel mapping
|
||||
|
||||
## Authentication Modes Supported
|
||||
| Trigger | Version format | Cloudsmith repo |
|
||||
|---------|---------------|-----------------|
|
||||
| `v*.*.*` tag | `X.Y.Z-1` | `truenas-proxmox` (stable) |
|
||||
| `release/3.x` or `master` branch | `X.Y.Z~beta+<sha>` | `truenas-proxmox-testing` |
|
||||
| other `release/*` branches | `X.Y.Z~alpha+<sha>` | `truenas-proxmox-snapshots` |
|
||||
| PRs / feature branches | `X.Y.Z~dev+<sha>` | not published |
|
||||
|
||||
- **Basic Auth**: `freenas_user` + `freenas_password` (deprecated but still works)
|
||||
- **Bearer Token**: `truenas_token_auth=true` + `truenas_secret` (preferred for TrueNAS SCALE)
|
||||
`$VERSION` in `TrueNAS.pm` is the single source of truth. A version tag that doesn't match emits a CI warning.
|
||||
|
||||
## TrueNAS API Version Detection
|
||||
## Authentication
|
||||
|
||||
The plugin auto-detects v1.0 vs v2.0 API based on the TrueNAS version string and HTTP response:
|
||||
- `>= 11.03.01.00` → uses v2.0 API
|
||||
- Older → uses v1.0 API
|
||||
- **Bearer Token** (required): `truenas_api_token` — set in PVE storage config
|
||||
|
||||
Basic auth (`freenas_user` / `freenas_password`) was removed in v3.0. Bearer token is the only supported mode.
|
||||
|
||||
## TrueNAS API
|
||||
|
||||
v3.0 uses the TrueNAS v2.0 REST API exclusively. Supported:
|
||||
- TrueNAS CORE 11.3+ (confirmed tested: CORE 13.0-U6)
|
||||
- TrueNAS SCALE (confirmed tested: SCALE 24.10 Electric Eel, SCALE 25.04, SCALE 25.10)
|
||||
|
||||
## Known Issues / Active Work
|
||||
|
||||
- Versioned patches are fragile — each PVE minor release may need a new patch
|
||||
- `postinst` clones the repo at install time (requires internet, fragile)
|
||||
- `Custom/FreeNAS.pm` is unfinished (duplicate `properties()` and `options()` subs)
|
||||
- REST::Client is both an apt dependency AND manually bundled
|
||||
- Everything is named `FreeNAS` internally but the product is now `TrueNAS`
|
||||
- SSH keys still required for ZFS pool listing (separate Proxmox code path via `ZFSPoolPlugin.pm`)
|
||||
- Max LUN limit bug (#150)
|
||||
- **#228** — v2.x→v3.0 migration: "Move Disk" confirmed working; in-place zvol rename script TBD
|
||||
- **#234** — Snapshot interface (PVE 9+): not started
|
||||
- **#243** — WebSocket API (SCALE 25.04+): future
|
||||
- **#249** — Per-variant dispatch: deferred to v3.1+
|
||||
- **#250** — alloc_image partial-failure rollback: narrow edge case, open
|
||||
- **#256** — Multipath support: deferred (no test hardware)
|
||||
- **#260** — TPM state disks incompatible with `iscsi://` paths: known limitation, store TPM state on local-lvm or NFS
|
||||
|
||||
## Key Perl Modules
|
||||
## Key Perl Module
|
||||
|
||||
- `PVE::Storage::LunCmd::FreeNAS` — the main plugin. Entry point: `run_lun_command()`
|
||||
- Functions: `freenas_api_connect`, `freenas_api_check`, `freenas_api_call`, `freenas_list_lu`, `run_create_lu`, `run_delete_lu`, `run_modify_lu`
|
||||
- `PVE::Storage::Custom::TrueNAS` (`perl5/PVE/Storage/Custom/TrueNAS.pm`) — the entire plugin
|
||||
- Entry points: `alloc_image`, `free_image`, `volume_size_info`, `path`, `activate_volume`, `deactivate_volume`
|
||||
- API helpers: `_api_call`, `_ensure_target`, `_running_vms_on_storage`
|
||||
|
||||
## Packaging / Deployment Notes
|
||||
|
||||
- Package name: `freenas-proxmox`
|
||||
- Apt repos: Cloudsmith (`ksatechnologies/truenas-proxmox` stable, `ksatechnologies/truenas-proxmox-testing` beta)
|
||||
- Install: `apt install freenas-proxmox` after adding the repo
|
||||
- The package uses dpkg `triggers` to re-apply patches when Proxmox VE packages are upgraded
|
||||
- Install: `apt install freenas-proxmox` after adding the Cloudsmith repo
|
||||
- No dpkg triggers — install/remove are handled entirely by `postinst` / `postrm`
|
||||
- PVE auto-discovers the plugin via `/usr/share/perl5/PVE/Storage/Custom/` on daemon restart
|
||||
|
||||
## ADRs / Plans / Runbooks
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Contributing to freenas-proxmox
|
||||
# Contributing to truenas-proxmox
|
||||
|
||||
Thank you for your interest in contributing. This document covers how to report bugs, request features, and submit code.
|
||||
|
||||
|
|
@ -22,16 +22,16 @@ Be respectful. This is a community project maintained in spare time. Constructiv
|
|||
|
||||
## Reporting Bugs
|
||||
|
||||
Use the [bug report issue template](https://github.com/TheGrandWazoo/freenas-proxmox/issues/new?template=bug_report.md).
|
||||
Use the [bug report issue template](https://github.com/TheGrandWazoo/truenas-proxmox/issues/new?template=bug_report.md).
|
||||
|
||||
Before filing:
|
||||
- Check existing [open and closed issues](https://github.com/TheGrandWazoo/freenas-proxmox/issues?q=is%3Aissue) for duplicates
|
||||
- Check existing [open and closed issues](https://github.com/TheGrandWazoo/truenas-proxmox/issues?q=is%3Aissue) for duplicates
|
||||
- Reproduce the issue on the latest release if possible
|
||||
|
||||
**Always include:**
|
||||
- Proxmox VE version (`proxmox-ve` package version)
|
||||
- TrueNAS version and type (CORE / SCALE)
|
||||
- Plugin version (`dpkg -l freenas-proxmox`)
|
||||
- Plugin version (`dpkg -l truenas-proxmox`)
|
||||
- Relevant log lines from syslog (`grep -i freenas /var/log/syslog`)
|
||||
- The storage configuration (redact passwords/tokens)
|
||||
|
||||
|
|
@ -39,7 +39,7 @@ Before filing:
|
|||
|
||||
## Requesting Features
|
||||
|
||||
Use the [feature request issue template](https://github.com/TheGrandWazoo/freenas-proxmox/issues/new?template=feature_request.md).
|
||||
Use the [feature request issue template](https://github.com/TheGrandWazoo/truenas-proxmox/issues/new?template=feature_request.md).
|
||||
|
||||
Feature requests are evaluated against the project roadmap. Large changes should be discussed in an issue before a pull request is opened.
|
||||
|
||||
|
|
@ -57,14 +57,14 @@ Feature requests are evaluated against the project roadmap. Large changes should
|
|||
### Local Build
|
||||
|
||||
```bash
|
||||
git clone https://github.com/TheGrandWazoo/freenas-proxmox.git
|
||||
cd freenas-proxmox
|
||||
git clone https://github.com/TheGrandWazoo/truenas-proxmox.git
|
||||
cd truenas-proxmox
|
||||
|
||||
# Build the package (once packaging/ directory exists in v3.x)
|
||||
dpkg-deb -Zgzip --build packaging freenas-proxmox_dev_all.deb
|
||||
dpkg-deb -Zgzip --build packaging truenas-proxmox_dev_all.deb
|
||||
|
||||
# Install locally for testing
|
||||
dpkg -i freenas-proxmox_dev_all.deb
|
||||
dpkg -i truenas-proxmox_dev_all.deb
|
||||
```
|
||||
|
||||
### Testing Changes to FreeNAS.pm
|
||||
|
|
|
|||
321
README.md
321
README.md
|
|
@ -2,10 +2,12 @@
|
|||
|
||||
[](https://www.gnu.org/licenses/agpl-3.0)
|
||||
[](https://github.com/TheGrandWazoo/freenas-proxmox/releases/latest)
|
||||
[](https://github.com/TheGrandWazoo/freenas-proxmox/actions/workflows/build.yml)
|
||||
[](https://github.com/TheGrandWazoo/freenas-proxmox/actions/workflows/build.yml)
|
||||
[](https://github.com/TheGrandWazoo/freenas-proxmox/issues)
|
||||
[](https://github.com/sponsors/TheGrandWazoo)
|
||||
|
||||
> This plugin is maintained by one person and has 4 million downloads — used in production at ISPs and MSPs worldwide. If it saves you time or headaches, [please consider sponsoring](https://github.com/sponsors/TheGrandWazoo) to keep it actively maintained and funded.
|
||||
|
||||
A Proxmox VE storage plugin that manages ZFS-over-iSCSI volumes on TrueNAS (CORE and SCALE) through the TrueNAS REST API — no SSH-based LUN management, no `iscsiadm` scripting.
|
||||
|
||||
---
|
||||
|
|
@ -28,14 +30,12 @@ A Proxmox VE storage plugin that manages ZFS-over-iSCSI volumes on TrueNAS (CORE
|
|||
|
||||
## How It Works
|
||||
|
||||
Proxmox VE's built-in ZFS-over-iSCSI storage type uses SSH to manage LUNs on the storage server. This plugin replaces that SSH-based management layer with direct calls to the **TrueNAS REST API**, giving you:
|
||||
This plugin is a native `PVE::Storage::Custom` type — Proxmox discovers it automatically and treats it like any other storage backend. When you create a VM disk, the plugin calls the **TrueNAS REST API** to provision a ZFS volume (zvol) and a dedicated per-VM iSCSI target, then hands Proxmox an `iscsi://` path. QEMU opens that path directly — no SSH, no `iscsiadm`, no kernel iSCSI sessions on the Proxmox host.
|
||||
|
||||
- API token (Bearer) or username/password authentication
|
||||
- Automatic TrueNAS API version detection (v1 and v2)
|
||||
- Support for both TrueNAS CORE and TrueNAS SCALE
|
||||
- Proper rollback when operations fail (no dangling iSCSI extents)
|
||||
|
||||
> **Note:** Proxmox still uses `iscsiadm` to connect and disconnect the iSCSI session on the Proxmox host itself — that part is handled by the core Proxmox code and does not require SSH. The SSH keys documented in the [Proxmox ZFS-over-iSCSI wiki](https://pve.proxmox.com/wiki/Storage:_ZFS_over_iSCSI) are still required for the ZFS pool listing step.
|
||||
- Bearer Token (API key) authentication — no username/password, no SSH keys
|
||||
- Per-VM iSCSI targets (`proxmox-vm-<vmid>`) — removing one disk never affects other VMs
|
||||
- Automatic rollback when operations fail (no dangling iSCSI extents)
|
||||
- Supports TrueNAS CORE 13.x and TrueNAS SCALE 24.10–25.10
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -45,94 +45,202 @@ Proxmox VE's built-in ZFS-over-iSCSI storage type uses SSH to manage LUNs on the
|
|||
|
||||
| Plugin Version | Proxmox VE | TrueNAS CORE | TrueNAS SCALE | Status |
|
||||
|:--------------:|:----------:|:------------:|:-------------:|:------:|
|
||||
| **3.x** *(upcoming)* | 8.x, 9.x | 13.0-U6+ | Cobia (23.10)+, Dragonfish (24.04)+ | In development |
|
||||
| **2.x** *(current)* | 7.x ⚠️, 8.0–8.3 ✅, 8.4.x ✅ | 11.3+ | 22.02+ | Active |
|
||||
| **3.x** *(stable)* | 8.4.x ✅, 9.x ✅ | 13.0-U6+ ✅ | Electric Eel (24.10) ✅, Fangtooth (25.04) ✅, Goldeye (25.10) ✅ | Stable |
|
||||
| **2.x** *(stable)* | 7.x ⚠️, 8.0–8.3 ✅, 8.4.x ✅ | 11.3+ | 22.02+ | Active |
|
||||
| **1.x** *(legacy)* | 5.x, 6.x | 11.x | — | Unsupported |
|
||||
|
||||
Tested combinations are marked ✅. Untested combinations may work but are not validated. Check the [Releases page](https://github.com/TheGrandWazoo/freenas-proxmox/releases) for the specific versions tested against each release.
|
||||
|
||||
### Important Version Notices
|
||||
|
||||
> **Proxmox VE 7 users**
|
||||
>
|
||||
> v2.x is the **last release series that supports PVE 7**. PVE 7 support is best-effort only — no new patches will be developed for it.
|
||||
>
|
||||
> **Do not upgrade to v3.0** when it releases — v3.0 requires Proxmox VE 8 or later. Stay on the latest v2.x release.
|
||||
> v3.0 requires Proxmox VE 8 or later. Stay on the latest v2.x release.
|
||||
|
||||
> **Proxmox VE 8 users**
|
||||
>
|
||||
> Proxmox VE 8 reaches **end-of-life on 2026-08-31**. Plan your upgrade to PVE 9 before that date.
|
||||
> v3.0 beta is tested and working on PVE 8.4.x. This is the recommended version for running v3.0 beta.
|
||||
>
|
||||
> v2.x works on PVE 8. When you upgrade to PVE 9, migrate to v3.0 (coming before the EOL date).
|
||||
> Proxmox VE 8 reaches **end-of-life on 2026-08-31**. PVE 9 support is targeted for v3.1.0, planned well before that date.
|
||||
|
||||
> **Proxmox VE 9+ users**
|
||||
>
|
||||
> v2.x is **not supported on PVE 9**. Use v3.0 when it is released. Do not install v2.x on a PVE 9 node.
|
||||
> v3.0 beta has a **known issue on PVE 9.x** — VMs may fail to start due to a LUN type mismatch in QEMU's blockdev layer ([#266](https://github.com/TheGrandWazoo/freenas-proxmox/issues/266)). PVE 9.x support is actively being investigated for **v3.1.0**.
|
||||
>
|
||||
> v2.x is not supported on PVE 9. Stay on PVE 8.x until v3.1.0 is available.
|
||||
|
||||
> **Proxmox VE 5 or 6 users**
|
||||
>
|
||||
> These versions are not supported. PVE 5 reached end-of-life in 2019, PVE 6 in 2022. Please upgrade your Proxmox VE installation.
|
||||
|
||||
Check the [Releases page](https://github.com/TheGrandWazoo/freenas-proxmox/releases) for the specific Proxmox and TrueNAS versions tested against each release.
|
||||
Check the [Releases page](https://github.com/TheGrandWazoo/truenas-proxmox/releases) for the specific Proxmox and TrueNAS versions tested against each release.
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Before installing, ensure the following are in place on your **Proxmox VE node**:
|
||||
### v3.0 (beta)
|
||||
|
||||
1. **SSH keys** configured between Proxmox and TrueNAS — required for ZFS pool listing by the Proxmox core (see the [Proxmox wiki](https://pve.proxmox.com/wiki/Storage:_ZFS_over_iSCSI), section starting with `mkdir /etc/pve/priv/zfs`).
|
||||
v3.0 is a fully API-driven custom storage plugin. No SSH keys required.
|
||||
|
||||
2. On **TrueNAS**, an iSCSI target and initiator group must exist and be configured. The plugin manages extents and target-to-extent mappings, but the target itself must be pre-created.
|
||||
1. On **TrueNAS**, ensure the iSCSI service is running and an iSCSI **portal** and **initiator group** are configured. The plugin creates per-VM iSCSI targets automatically — you do not need to pre-create a target.
|
||||
|
||||
3. On **TrueNAS SCALE** or **TrueNAS CORE 13+**, generate an API key:
|
||||
2. Generate a TrueNAS API key:
|
||||
- TrueNAS SCALE: *System Settings → API Keys → Add*
|
||||
- TrueNAS CORE 13: *gear icon (top-right) → API Keys → Add*
|
||||
|
||||
|
||||
Copy the key — you will need it during storage configuration in Proxmox.
|
||||
|
||||
> **Known limitation — TPM state disks**
|
||||
>
|
||||
> `tpmstate0` (virtual TPM) disks **cannot** be stored on v3.0 iSCSI storage. The `swtpm` backend requires a local filesystem path and cannot use the `iscsi://` URIs that v3.0 provides to QEMU.
|
||||
>
|
||||
> If your VM uses Secure Boot or TPM, store the `tpmstate0` disk on a separate storage (e.g. `local-lvm` or NFS). All other disk types — virtio, scsi, IDE, EFI — work normally.
|
||||
>
|
||||
> For live VM migration with TPM, the TPM state disk must also be on shared filesystem storage (NFS or CephFS), not on this plugin's storage.
|
||||
|
||||
### v2.x (legacy)
|
||||
|
||||
> v2.x is no longer the recommended version. See [Migrating from v2.x](docs/migrating-from-v2.md).
|
||||
|
||||
1. **SSH keys** configured between each Proxmox node and TrueNAS — required by the Proxmox core for ZFS pool listing (see the [Proxmox wiki](https://pve.proxmox.com/wiki/Storage:_ZFS_over_iSCSI), section starting with `mkdir /etc/pve/priv/zfs`).
|
||||
|
||||
2. On **TrueNAS**, a pre-created iSCSI **target** and **initiator group**.
|
||||
|
||||
3. A TrueNAS API key (SCALE: *System Settings → API Keys*; CORE 13: *gear icon → API Keys*).
|
||||
|
||||
---
|
||||
|
||||
## Installation
|
||||
|
||||
### Stable Release
|
||||
|
||||
Add the repository and install:
|
||||
### Stable Release (v3.x)
|
||||
|
||||
```bash
|
||||
# Import the GPG key
|
||||
curl -fsSL https://dl.cloudsmith.io/public/ksatechnologies/truenas-proxmox/gpg.284C106104A8CE6D.key \
|
||||
curl -fsSL https://thegrandwazoo.github.io/freenas-proxmox/public.gpg.key \
|
||||
| gpg --dearmor \
|
||||
| tee /usr/share/keyrings/ksatechnologies-truenas-proxmox-keyring.gpg > /dev/null
|
||||
| sudo tee /etc/apt/keyrings/truenas-proxmox.gpg > /dev/null
|
||||
|
||||
# Add the repository
|
||||
cat > /etc/apt/sources.list.d/ksatechnologies-repo.list << 'EOF'
|
||||
deb [signed-by=/usr/share/keyrings/ksatechnologies-truenas-proxmox-keyring.gpg] \
|
||||
https://dl.cloudsmith.io/public/ksatechnologies/truenas-proxmox/deb/debian any-version main
|
||||
EOF
|
||||
# Add the v3 repository track
|
||||
echo "deb [signed-by=/etc/apt/keyrings/truenas-proxmox.gpg] \
|
||||
https://thegrandwazoo.github.io/freenas-proxmox v3 main" \
|
||||
| sudo tee /etc/apt/sources.list.d/truenas-proxmox.list
|
||||
|
||||
# Install
|
||||
apt update && apt install freenas-proxmox
|
||||
sudo apt update && sudo apt install truenas-proxmox
|
||||
```
|
||||
|
||||
`apt upgrade` will deliver v3.x point releases automatically. You will never be
|
||||
automatically promoted to a future v4 — that requires changing the dist track in
|
||||
your `sources.list` to `v4`.
|
||||
|
||||
> **Existing users — one-time prompt after v3.2.2**
|
||||
> If `apt update` shows `changed its 'Codename' value from 'v3' to 'error'`, run:
|
||||
> ```bash
|
||||
> apt update --allow-releaseinfo-change
|
||||
> ```
|
||||
> This happened once when codename aliases were added in v3.2.2. It will not recur.
|
||||
> Both `v3` (Suite) and `error` (Codename) continue to work in `sources.list`.
|
||||
|
||||
### Upgrading from v2.x
|
||||
|
||||
**v3.0 is a breaking change.** Do not run `apt upgrade` until you have read the
|
||||
migration guide — your storage configuration will need to be updated.
|
||||
|
||||
See [docs/migrating-from-v2.md](docs/migrating-from-v2.md).
|
||||
|
||||
### Staying on v2.x
|
||||
|
||||
If you are on v2.x and want to continue receiving v2.x point releases without
|
||||
risking a v3 upgrade, switch to the `main` dist track (v2.x only):
|
||||
|
||||
```bash
|
||||
# Import the GPG key (if not already done)
|
||||
curl -fsSL https://thegrandwazoo.github.io/freenas-proxmox/public.gpg.key \
|
||||
| gpg --dearmor \
|
||||
| sudo tee /etc/apt/keyrings/truenas-proxmox.gpg > /dev/null
|
||||
|
||||
# Add the v2 repository track
|
||||
echo "deb [signed-by=/etc/apt/keyrings/truenas-proxmox.gpg] \
|
||||
https://thegrandwazoo.github.io/freenas-proxmox main main" \
|
||||
| sudo tee /etc/apt/sources.list.d/truenas-proxmox.list
|
||||
|
||||
sudo apt update
|
||||
```
|
||||
|
||||
### Migrating from Cloudsmith
|
||||
|
||||
> Cloudsmith is being phased out. GitHub Pages is the permanent home for this project's packages.
|
||||
|
||||
If you installed from the Cloudsmith apt repo, swap your source to GitHub Pages. Your installed package is not affected — this only changes where future updates come from.
|
||||
|
||||
**On v3.x (Cloudsmith stable):**
|
||||
|
||||
```bash
|
||||
# Remove the old Cloudsmith source
|
||||
sudo rm -f /etc/apt/sources.list.d/truenas-proxmox*.list
|
||||
sudo rm -f /usr/share/keyrings/ksatechnologies-truenas-proxmox*.gpg
|
||||
|
||||
# Import the GitHub Pages GPG key
|
||||
curl -fsSL https://thegrandwazoo.github.io/freenas-proxmox/public.gpg.key \
|
||||
| gpg --dearmor \
|
||||
| sudo tee /etc/apt/keyrings/truenas-proxmox.gpg > /dev/null
|
||||
|
||||
# Point to the v3 dist track
|
||||
echo "deb [signed-by=/etc/apt/keyrings/truenas-proxmox.gpg] \
|
||||
https://thegrandwazoo.github.io/freenas-proxmox v3 main" \
|
||||
| sudo tee /etc/apt/sources.list.d/truenas-proxmox.list
|
||||
|
||||
sudo apt update
|
||||
```
|
||||
|
||||
**On v2.x (Cloudsmith stable) — staying on v2:**
|
||||
|
||||
```bash
|
||||
# Remove the old Cloudsmith source
|
||||
sudo rm -f /etc/apt/sources.list.d/freenas-proxmox*.list /etc/apt/sources.list.d/truenas-proxmox*.list
|
||||
sudo rm -f /usr/share/keyrings/ksatechnologies-truenas-proxmox*.gpg
|
||||
|
||||
# Import the GitHub Pages GPG key
|
||||
curl -fsSL https://thegrandwazoo.github.io/freenas-proxmox/public.gpg.key \
|
||||
| gpg --dearmor \
|
||||
| sudo tee /etc/apt/keyrings/truenas-proxmox.gpg > /dev/null
|
||||
|
||||
# Point to the v2 dist track (v2.x only — you will never receive a v3 package)
|
||||
echo "deb [signed-by=/etc/apt/keyrings/truenas-proxmox.gpg] \
|
||||
https://thegrandwazoo.github.io/freenas-proxmox main main" \
|
||||
| sudo tee /etc/apt/sources.list.d/truenas-proxmox.list
|
||||
|
||||
sudo apt update
|
||||
```
|
||||
|
||||
For a full upgrade path matrix — including what to do when v4 ships — see [docs/upgrade-paths.md](docs/upgrade-paths.md).
|
||||
|
||||
---
|
||||
|
||||
### Testing / Beta Release
|
||||
|
||||
For early access to new features (may be unstable):
|
||||
For early access to new features (may be unstable). Beta builds are published on
|
||||
every push to `release/3.x` — always the latest build, not accumulated history.
|
||||
|
||||
```bash
|
||||
# Import the GPG key
|
||||
curl -fsSL https://dl.cloudsmith.io/public/ksatechnologies/truenas-proxmox-testing/gpg.CACC9EE03F2DFFCC.key \
|
||||
# Import the GPG key (skip if already done for the stable track)
|
||||
curl -fsSL https://thegrandwazoo.github.io/freenas-proxmox/public.gpg.key \
|
||||
| gpg --dearmor \
|
||||
| tee /usr/share/keyrings/ksatechnologies-truenas-proxmox-testing-keyring.gpg > /dev/null
|
||||
| sudo tee /etc/apt/keyrings/truenas-proxmox.gpg > /dev/null
|
||||
|
||||
# Add the repository
|
||||
cat > /etc/apt/sources.list.d/ksatechnologies-testing-repo.list << 'EOF'
|
||||
deb [signed-by=/usr/share/keyrings/ksatechnologies-truenas-proxmox-testing-keyring.gpg] \
|
||||
https://dl.cloudsmith.io/public/ksatechnologies/truenas-proxmox-testing/deb/debian any-version main
|
||||
EOF
|
||||
# Add the testing dist track
|
||||
echo "deb [signed-by=/etc/apt/keyrings/truenas-proxmox.gpg] \
|
||||
https://thegrandwazoo.github.io/freenas-proxmox testing main" \
|
||||
| sudo tee /etc/apt/sources.list.d/truenas-proxmox.list
|
||||
|
||||
# Install
|
||||
apt update && apt install freenas-proxmox
|
||||
sudo apt update && sudo apt install truenas-proxmox
|
||||
```
|
||||
|
||||
To switch back to stable, replace `testing` with `v3` in your sources.list and run `apt update`.
|
||||
|
||||
---
|
||||
|
||||
## Configuration
|
||||
|
|
@ -187,24 +295,53 @@ The disk is created successfully despite this warning, but the suboptimal block
|
|||
|
||||
Edit `/etc/pve/storage.cfg` on any cluster node and change `blocksize 8192` to `blocksize 16384` for your TrueNAS SCALE storage entry. No data migration is needed — only newly created zvols use the updated value. Existing zvols are unaffected.
|
||||
|
||||
> **Note:** Automatic blocksize detection based on TrueNAS version is planned for v2.4.0 (see [#241](https://github.com/TheGrandWazoo/freenas-proxmox/issues/241)).
|
||||
> **Note:** Automatic blocksize detection based on TrueNAS version is planned for v2.4.0 (see [#241](https://github.com/TheGrandWazoo/truenas-proxmox/issues/241)).
|
||||
|
||||
---
|
||||
|
||||
## Upgrading
|
||||
|
||||
The package integrates with Proxmox VE's standard upgrade mechanism. On `apt upgrade`, the package will automatically re-apply any patches needed after a Proxmox VE update:
|
||||
### v2.x → v2.x (patch / minor upgrade)
|
||||
|
||||
Standard apt upgrade — the package re-applies any patches needed after a Proxmox VE update automatically:
|
||||
|
||||
```bash
|
||||
apt update && apt full-upgrade
|
||||
```
|
||||
|
||||
### v2.x → v3.0 (migration)
|
||||
|
||||
v3.0 is a different storage plugin type (`PVE::Storage::Custom::TrueNAS`) and uses a different architecture (per-VM iSCSI targets, `iscsi://` paths). There is no in-place upgrade — disk data stays on TrueNAS and you move VM disks across using Proxmox's built-in **Move Disk** function.
|
||||
|
||||
> **Tested migration path (confirmed in lab):**
|
||||
> Migrating live VM disks from a v2.x storage to a v3.0 storage via Move Disk was validated on Proxmox VE 8.4 with TrueNAS CORE 13.0-U6 and TrueNAS SCALE 24.10. VMs remained running throughout the migration.
|
||||
|
||||
#### Step-by-step
|
||||
|
||||
1. **Install v3.0** on all Proxmox nodes (see [Installation](#installation) — use the testing channel until v3.0 is stable-released).
|
||||
|
||||
2. **Add a new v3.0 storage** in Proxmox (*Datacenter → Storage → Add → TrueNAS (ZFS/iSCSI)*). Use the same TrueNAS pool as your existing v2.x storage. Give it a distinct ID (e.g. `truenas-v3`).
|
||||
|
||||
3. **For each VM**, move its disks from the v2.x storage to the new v3.0 storage:
|
||||
- Select the VM → **Hardware** tab
|
||||
- Select the disk → **Move Disk**
|
||||
- Choose the new v3.0 storage as the target
|
||||
- Check **Delete source** if you want the old zvol removed after the move
|
||||
|
||||
Repeat for each disk (including EFI disk if present). VMs can remain running during Move Disk.
|
||||
|
||||
4. **Verify** the VM boots and its disks are accessible after migration.
|
||||
|
||||
5. **Remove the v2.x storage** from Proxmox once all VMs are migrated (*Datacenter → Storage → Remove*). The iSCSI targets and extents that belonged to the old storage will need to be cleaned up from TrueNAS manually if they were not auto-removed.
|
||||
|
||||
> **Note:** EFI disks (`efidisk0`) and data disks can be migrated with Move Disk. TPM state disks (`tpmstate0`) must stay on local-lvm or NFS — see the [TPM limitation](#v30-upcoming) in Prerequisites.
|
||||
|
||||
---
|
||||
|
||||
## Uninstalling
|
||||
|
||||
```bash
|
||||
apt remove freenas-proxmox
|
||||
apt remove truenas-proxmox
|
||||
```
|
||||
|
||||
This removes the plugin and reverses all patches, returning your Proxmox VE installation to its unmodified state. Any storage configurations using this plugin should be removed from Proxmox before uninstalling.
|
||||
|
|
@ -213,6 +350,64 @@ This removes the plugin and reverses all patches, returning your Proxmox VE inst
|
|||
|
||||
## Troubleshooting
|
||||
|
||||
### Understanding what this plugin does — and does not — do
|
||||
|
||||
Before reporting an issue, it helps to know which layer the error is coming from. This plugin is an **orchestrator**: it calls the TrueNAS REST API to provision resources (create zvols, iSCSI extents, and target mappings). It does not carry data. The actual disk I/O path — QEMU reading and writing blocks — runs directly between Proxmox and TrueNAS over iSCSI, with no involvement from this plugin.
|
||||
|
||||
There are three distinct layers where errors can appear:
|
||||
|
||||
**Layer 1 — Plugin (TrueNAS REST API)**
|
||||
|
||||
The plugin called the TrueNAS API and got an error, or the API was unreachable. These errors come from the plugin code and appear in the Proxmox task log with prefixes like `freenas-proxmox:` or `[TrueNAS::]`.
|
||||
|
||||
Common causes:
|
||||
- Wrong API host IP or hostname
|
||||
- HTTP vs HTTPS mismatch (toggle **Use SSL**)
|
||||
- API token expired, revoked, or not entered correctly
|
||||
- TrueNAS iSCSI service not running
|
||||
- TrueNAS API service not reachable from the Proxmox node
|
||||
|
||||
Example log line:
|
||||
```
|
||||
freenas-proxmox: Unable to connect to the TrueNAS API at '192.168.1.10' using HTTPS (500)
|
||||
```
|
||||
|
||||
**Layer 2 — iSCSI / QEMU data path**
|
||||
|
||||
The VM's QEMU process (v3.0) or the Proxmox kernel iSCSI stack (v2.x) failed to connect or lost its session to TrueNAS. These errors come from QEMU or `iscsiadm` — not from this plugin.
|
||||
|
||||
In **v3.0**, look for QEMU log lines referencing `iscsi://` paths. In **v2.x**, look for `iscsiadm` lines in syslog.
|
||||
|
||||
Common causes:
|
||||
- TrueNAS iSCSI service stopped while a VM was running
|
||||
- Initiator group does not permit the Proxmox node's IP
|
||||
- Network path to the iSCSI portal is down
|
||||
- CHAP authentication configured on the TrueNAS target but not in Proxmox (note: this plugin does not configure CHAP — it must be set to None or configured separately)
|
||||
|
||||
Example log line (v2.x):
|
||||
```
|
||||
iscsiadm: No active sessions.
|
||||
```
|
||||
|
||||
**Layer 3 — Proxmox core storage stack**
|
||||
|
||||
Errors from PVE's own storage subsystem — pvedaemon, pvestatd, or storage.cfg parsing.
|
||||
|
||||
Common causes:
|
||||
- `storage.cfg` syntax error or stale config keys (especially after migrating from v2.x — see [migration guide](docs/migrating-from-v2.md))
|
||||
- `pvedaemon` or `pvestatd` service crashed
|
||||
|
||||
**Quick triage:**
|
||||
|
||||
| Symptom | Likely layer | First check |
|
||||
|---------|-------------|-------------|
|
||||
| Disk creation fails, API error in task log | Plugin (Layer 1) | API key, SSL setting, TrueNAS API reachable |
|
||||
| Disk created on TrueNAS but Proxmox reports error | Plugin (Layer 1) | syslog for `truenas-proxmox:` lines |
|
||||
| VM won't start, iSCSI session error | iSCSI/QEMU (Layer 2) | TrueNAS iSCSI service, initiator group ACL |
|
||||
| Storage shows unavailable | Proxmox core (Layer 3) | `pvedaemon` service, storage.cfg |
|
||||
|
||||
---
|
||||
|
||||
### After install, the "FreeNAS/TrueNAS API" option is not visible
|
||||
|
||||
Refresh your browser (force-refresh with Ctrl+Shift+R or Cmd+Shift+R). The Proxmox UI JavaScript is cached aggressively.
|
||||
|
|
@ -246,7 +441,29 @@ TrueNAS SCALE 25.04 **revokes all existing API keys** that were created with whi
|
|||
|
||||
**Additionally**, TrueNAS SCALE 25.04 enforces HTTPS for API key authentication — keys transmitted over plain HTTP are automatically revoked. Ensure **Use SSL** is enabled in your Proxmox storage config when using token auth.
|
||||
|
||||
> **Note:** TrueNAS SCALE 25.04 also deprecates the REST API used by this plugin (v2.x). Full removal is planned for SCALE 26.x. Plugin v3.0.0 will add WebSocket JSON-RPC 2.0 support. See [issue #243](https://github.com/TheGrandWazoo/freenas-proxmox/issues/243).
|
||||
> **Note:** TrueNAS SCALE 25.04 also deprecates the REST API used by this plugin (v2.x). Full removal is planned for SCALE 26.x. Plugin v3.0.0 will add WebSocket JSON-RPC 2.0 support. See [issue #243](https://github.com/TheGrandWazoo/truenas-proxmox/issues/243).
|
||||
|
||||
### Disk size in Proxmox shows larger than what I entered
|
||||
|
||||
This is expected. Proxmox creates disks in **GiB** (gibibytes, base-2) but displays them in **GB** (gigabytes, base-10) in some views.
|
||||
|
||||
| Unit | Base | 1 unit = |
|
||||
|------|------|----------|
|
||||
| GiB (gibibyte) | 2¹⁰ = 1024 | 1,073,741,824 bytes |
|
||||
| GB (gigabyte) | 10³ = 1000 | 1,000,000,000 bytes |
|
||||
|
||||
When you enter **80 GiB** in the Proxmox disk creation dialog, the zvol is created as exactly 85,899,345,920 bytes. TrueNAS reports that in decimal: **85.90 GB**. The disk inside the VM is still exactly 80 GiB — nothing is lost or added.
|
||||
|
||||
A quick reference:
|
||||
|
||||
| Entered in Proxmox | TrueNAS / decimal display |
|
||||
|--------------------|---------------------------|
|
||||
| 10 GiB | 10.74 GB |
|
||||
| 32 GiB | 34.36 GB |
|
||||
| 80 GiB | 85.90 GB |
|
||||
| 100 GiB | 107.37 GB |
|
||||
| 500 GiB | 536.87 GB |
|
||||
| 1 TiB | 1,099.51 GB (≈ 1.10 TB) |
|
||||
|
||||
### Dangling extents on TrueNAS after a failed operation
|
||||
|
||||
|
|
@ -254,7 +471,7 @@ If you see iSCSI extents in TrueNAS that are not associated with any target, the
|
|||
|
||||
### Filing a Bug Report
|
||||
|
||||
Please use the [GitHub issue tracker](https://github.com/TheGrandWazoo/freenas-proxmox/issues) and include the [bug report template](.github/ISSUE_TEMPLATE/bug_report.md). Include relevant log lines from `syslog` (search for `FreeNAS::`).
|
||||
Please use the [GitHub issue tracker](https://github.com/TheGrandWazoo/truenas-proxmox/issues) and include the [bug report template](.github/ISSUE_TEMPLATE/bug_report.md). Include relevant log lines from `syslog` (search for `FreeNAS::`).
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -268,10 +485,14 @@ For significant changes, open an issue first to discuss the approach.
|
|||
|
||||
## Support the Project
|
||||
|
||||
If this plugin saves you time, consider supporting its development:
|
||||
This plugin is built and maintained by one person. Sponsorship directly funds the hardware lab used to test every release against real Proxmox and TrueNAS nodes before it ships to you.
|
||||
|
||||
- **GitHub Sponsors**: [github.com/sponsors/TheGrandWazoo](https://github.com/sponsors/TheGrandWazoo)
|
||||
- **PayPal**: [Donate via PayPal](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=TCLNEMBUYQUXN&source=url)
|
||||
**[Become a sponsor on GitHub](https://github.com/sponsors/TheGrandWazoo)** — GitHub Sponsors has no platform fee and goes straight to development.
|
||||
|
||||
Sponsor tiers:
|
||||
- **$5/month** — Supporter. You keep this maintained.
|
||||
- **$10/month** — Backer. Listed in DONORS.md.
|
||||
- **$25/month** — Sustainer. Priority issue responses.
|
||||
|
||||
Donor support has funded a 4-node Proxmox cluster and TrueNAS test lab used for development and validation. See [DONORS.md](DONORS.md) for a full list of donors.
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,182 @@
|
|||
# freenas-proxmox / truenas-proxmox — Roadmap
|
||||
|
||||
This file captures release scope, business decisions, and deferred items. It is updated in the same commit as any scope or decision change — not just in issues or memory.
|
||||
|
||||
---
|
||||
|
||||
## Released — v3.0.0 (TrueNAS Custom Plugin)
|
||||
|
||||
**Released:** 2026-05-31
|
||||
**Branch:** `release/3.x` (default branch)
|
||||
**GitHub Release:** https://github.com/TheGrandWazoo/freenas-proxmox/releases/tag/v3.0.0
|
||||
|
||||
Full rewrite as a native `PVE::Storage::Custom` plugin. No patching of PVE files, no SSH, full TrueNAS REST API, bearer token auth only.
|
||||
|
||||
### Confirmed tested
|
||||
|
||||
| Component | Versions |
|
||||
|-----------|---------|
|
||||
| Proxmox VE | 8.4.x, 9.2.x |
|
||||
| TrueNAS CORE | 13.0-U6 |
|
||||
| TrueNAS SCALE | 24.10 Electric Eel, 25.04, 25.10 |
|
||||
|
||||
### What shipped
|
||||
|
||||
| # | Fix |
|
||||
|---|-----|
|
||||
| [#266](https://github.com/TheGrandWazoo/freenas-proxmox/issues/266) | PVE 9: `lun` string vs integer in QEMU blockdev JSON |
|
||||
| [#269](https://github.com/TheGrandWazoo/freenas-proxmox/issues/269) | SCALE 25.10 strict Pydantic rejects volsize as string |
|
||||
| [#267](https://github.com/TheGrandWazoo/freenas-proxmox/issues/267) | free_image 422 on targetextent delete when VM is running |
|
||||
| [#265](https://github.com/TheGrandWazoo/freenas-proxmox/issues/265) | Loop over all targetextent rows in free_image |
|
||||
| [#264](https://github.com/TheGrandWazoo/freenas-proxmox/issues/264) | SCALE 25.04 integer type coercion + alias uniqueness |
|
||||
| [#261](https://github.com/TheGrandWazoo/freenas-proxmox/issues/261) | API token keyfile (`/etc/pve/priv/truenas-<id>.key`) |
|
||||
| [#262](https://github.com/TheGrandWazoo/freenas-proxmox/issues/262) | Package rename: `freenas-proxmox` → `truenas-proxmox` + transitional package |
|
||||
| [#263](https://github.com/TheGrandWazoo/freenas-proxmox/issues/263) | Fix `truenas_target` full-IQN match |
|
||||
| [#260](https://github.com/TheGrandWazoo/freenas-proxmox/issues/260) | TPM state disk limitation callout |
|
||||
| [#250](https://github.com/TheGrandWazoo/freenas-proxmox/issues/250) | Rollback orphaned TrueNAS resources on `alloc_image` partial failure |
|
||||
| [#228](https://github.com/TheGrandWazoo/freenas-proxmox/issues/228) | Migration docs: beginner, advanced, troubleshooting |
|
||||
|
||||
---
|
||||
|
||||
## Released — GitHub Pages APT Repository
|
||||
|
||||
**Completed:** 2026-06-06
|
||||
**Closed:** [#230](https://github.com/TheGrandWazoo/freenas-proxmox/issues/230)
|
||||
**URL:** https://thegrandwazoo.github.io/freenas-proxmox
|
||||
|
||||
Replaced Cloudsmith as the primary package distribution channel. Per-major-version dist tracks prevent silent cross-version upgrades (ADR-010). Optional feature packages distributed via apt components (ADR-011).
|
||||
|
||||
### Dist tracks
|
||||
|
||||
| Dist | Content | Sources.list |
|
||||
|------|---------|--------------|
|
||||
| `main` | v2.x — backward-compat alias | `... freenas-proxmox main main` |
|
||||
| `v2` | v2.x — explicit pin | `... freenas-proxmox v2 main` |
|
||||
| `v3` | v3.x | `... freenas-proxmox v3 main` |
|
||||
| `testing` | beta builds | Future — [#272](https://github.com/TheGrandWazoo/freenas-proxmox/issues/272) |
|
||||
|
||||
### Components (ADR-011)
|
||||
|
||||
| Component | Content |
|
||||
|-----------|---------|
|
||||
| `main` | Base plugin (all installs) |
|
||||
| `multipath` | `truenas-proxmox-multipath` — optional add-on, [#256](https://github.com/TheGrandWazoo/freenas-proxmox/issues/256) |
|
||||
|
||||
### Cloudsmith transition state (2026-06-06)
|
||||
|
||||
| Channel | State |
|
||||
|---------|-------|
|
||||
| Stable (`truenas-proxmox`) | v3.x+ no longer published here; v2.x still published |
|
||||
| Testing (`truenas-proxmox-testing`) | Still active until #272 ships |
|
||||
|
||||
### Migration tooling
|
||||
|
||||
`scripts/migrate-repo-to-github-pages.sh` — auto-detects installed version, finds Cloudsmith sources by URL content, switches to correct dist track. Run on each Proxmox node as root.
|
||||
|
||||
---
|
||||
|
||||
## Deferred Business Decisions
|
||||
|
||||
### GitHub Repo Rename: `freenas-proxmox` → `truenas-proxmox`
|
||||
|
||||
**Status:** Deferred — no timeline set
|
||||
**Decision date:** 2026-05-25
|
||||
**Tracked in:** [#229](https://github.com/TheGrandWazoo/freenas-proxmox/issues/229)
|
||||
|
||||
All code is ready. GitHub auto-redirects old URLs so there is no technical urgency.
|
||||
|
||||
```bash
|
||||
gh repo rename truenas-proxmox --repo TheGrandWazoo/freenas-proxmox
|
||||
```
|
||||
|
||||
**Note:** `FUNDING.yml` is tied to the user account, not the repo name. Rename has no effect on GitHub Sponsors.
|
||||
|
||||
---
|
||||
|
||||
## Released — v3.1.0 (PVE 9 + Snapshots)
|
||||
|
||||
**Released:** 2026-06-07
|
||||
**PVE support:** PVE 9.x only — PVE 8 support dropped
|
||||
**Decision date:** 2026-05-31 — see ADR-009
|
||||
**GitHub Release:** https://github.com/TheGrandWazoo/freenas-proxmox/releases/tag/v3.1.0
|
||||
|
||||
v3.1 is the first release that requires PVE 9. Dropping PVE 8 allows:
|
||||
- Bumping `api()` to PVE 9's `APIVER` (silences "older storage API" warning)
|
||||
- Implementing snapshot-as-volume-chains (PVE 9 feature)
|
||||
- Removing the `qemu_blockdev_options` override if Proxmox fixes `Plugin.pm` upstream
|
||||
|
||||
### Completed in v3.1.0
|
||||
|
||||
| # | Title | Commit |
|
||||
|---|-------|--------|
|
||||
| [#270](https://github.com/TheGrandWazoo/freenas-proxmox/issues/270) | Bump `api()` to 14 — silences PVE 9 "older storage API" warning | fc74a42 |
|
||||
| [#272](https://github.com/TheGrandWazoo/freenas-proxmox/issues/272) | GitHub Pages testing dist track (retires Cloudsmith testing) | 03fe20b |
|
||||
| [#273](https://github.com/TheGrandWazoo/freenas-proxmox/issues/273) | Debian changelog in package (`apt changelog` was failing) | 1c98591 |
|
||||
| [#274](https://github.com/TheGrandWazoo/freenas-proxmox/issues/274) | Filename: prefix stripped from Packages — packages not downloadable | 19a3ec2 |
|
||||
| [#275](https://github.com/TheGrandWazoo/freenas-proxmox/issues/275) | README badges pointed to non-existent repo | da0aaff |
|
||||
| [#276](https://github.com/TheGrandWazoo/freenas-proxmox/issues/276) | parse_volname state volume crash — RAM snapshots left orphans | 80a7889 |
|
||||
| [#234](https://github.com/TheGrandWazoo/freenas-proxmox/issues/234) | Snapshot interface — ZFS snapshots via TrueNAS REST API | 59d8a81 |
|
||||
|
||||
**Snapshot implementation (2026-06-07):** Verified on PVE 9.2.3 against TrueNAS CORE 13.0-U6 and SCALE 24.10. Disk-only and RAM snapshots both confirmed working end-to-end including rollback.
|
||||
|
||||
### Carried forward
|
||||
|
||||
| # | Title | Notes |
|
||||
|---|-------|-------|
|
||||
| [#277](https://github.com/TheGrandWazoo/freenas-proxmox/issues/277) | iSCSI GET_LBA_STATUS error on VM start | Log noise, non-fatal — open |
|
||||
|
||||
---
|
||||
|
||||
## Released — v3.2.0 (Multipath)
|
||||
|
||||
**Released:** 2026-06-20
|
||||
**GitHub Release:** https://github.com/TheGrandWazoo/freenas-proxmox/releases/tag/v3.2.0
|
||||
|
||||
Introduces the `truenas-proxmox-multipath` optional add-on package. Active-active iSCSI multipath via dm-multipath. Separate plugin (`TrueNASMultipath.pm`) in the `multipath` apt component — not part of the base plugin. See ADR-011.
|
||||
|
||||
### Confirmed tested
|
||||
|
||||
| Component | Versions |
|
||||
|-----------|---------|
|
||||
| Proxmox VE | 9.2.x |
|
||||
| TrueNAS CORE | 13.0-U6 (FreeBSD / ALUA) |
|
||||
| TrueNAS SCALE | 24.10 Electric Eel, 25.04 |
|
||||
|
||||
**Path mode:** Active-active (`multibus` + `service-time 0`) — both paths carry I/O simultaneously. Failover and path recovery confirmed on CORE and SCALE 25.04.
|
||||
|
||||
### What shipped
|
||||
|
||||
| # | Title | Commit |
|
||||
|---|-------|--------|
|
||||
| [#256](https://github.com/TheGrandWazoo/freenas-proxmox/issues/256) | Multipath plugin — `truenas-proxmox-multipath` package (ADR-011) | da82356 |
|
||||
| [#278](https://github.com/TheGrandWazoo/freenas-proxmox/issues/278) | Multipath UI panel (`truenas-multipath` in Datacenter → Storage → Add) | da82356 |
|
||||
| [#279](https://github.com/TheGrandWazoo/freenas-proxmox/issues/279) | postinst: strip pre-existing unmanaged devices block in multipath.conf | b7acb60 |
|
||||
|
||||
### Known differences: CORE vs SCALE multipath
|
||||
|
||||
| | TrueNAS CORE (FreeBSD) | TrueNAS SCALE (Linux) |
|
||||
|---|---|---|
|
||||
| iSCSI target | `ctld` | Linux LIO |
|
||||
| ALUA | Yes — `hwhandler='1 alua'`, `prio=50` | No — `hwhandler='0'`, `prio=1` |
|
||||
| Portal config | `0.0.0.0` (all interfaces) | Per-IP listen addresses required |
|
||||
| Failover time | ~2s (ALUA state query) | <0.1s |
|
||||
| SCALE 25.04 note | — | `port` field rejected in `PUT /api/v2.0/iscsi/portal` — omit it ([#280](https://github.com/TheGrandWazoo/freenas-proxmox/issues/280)) |
|
||||
|
||||
---
|
||||
|
||||
## Upcoming — v4.0.0 (WebSocket API, SCALE 25.x)
|
||||
|
||||
**Target:** After PoC testing on SCALE 25.04+
|
||||
**Why major version:** WebSocket JSON-RPC 2.0 is a new transport — genuine architectural change.
|
||||
|
||||
| # | Title |
|
||||
|---|-------|
|
||||
| [#243](https://github.com/TheGrandWazoo/freenas-proxmox/issues/243) | WebSocket JSON-RPC 2.0 API support (TrueNAS SCALE 25.04+) |
|
||||
|
||||
---
|
||||
|
||||
## Process Rules
|
||||
|
||||
- Every business decision (defer, hold, change scope) is recorded here **and** as a comment on the relevant GitHub issue — not only in conversation or AI memory.
|
||||
- ADRs (in `.claude/cos/adrs/`) capture architectural decisions.
|
||||
- This file captures timing, business rationale, and deferred actions.
|
||||
|
|
@ -0,0 +1,622 @@
|
|||
# freenas-proxmox v3.0 — Architecture & Developer Guide
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Architecture Overview](#1-architecture-overview)
|
||||
2. [Per-VM iSCSI Target Model](#2-per-vm-iscsi-target-model)
|
||||
3. [The `iscsi://` Path Model](#3-the-iscsi-path-model)
|
||||
4. [API Flow — TrueNAS REST v2.0](#4-api-flow--truenas-rest-v20)
|
||||
5. [alloc_image Deep Dive](#5-alloc_image-deep-dive)
|
||||
6. [free_image Deep Dive](#6-free_image-deep-dive)
|
||||
7. [Build Pipeline and Version/Channel Routing](#7-build-pipeline-and-versionchannel-routing)
|
||||
8. [Debugging Guide](#8-debugging-guide)
|
||||
9. [Contributing](#9-contributing)
|
||||
|
||||
---
|
||||
|
||||
## 1. Architecture Overview
|
||||
|
||||
### Where This Plugin Sits
|
||||
|
||||
Proxmox VE organizes storage backends through a plugin registry. Every storage type — directory, LVM, Ceph, ZFS-over-iSCSI — is a Perl module that subclasses `PVE::Storage::Plugin`. The `pvedaemon` process loads all registered storage plugins at startup and delegates storage operations (create volume, delete volume, list volumes, etc.) to the correct plugin based on the `type` field in `/etc/pve/storage.cfg`.
|
||||
|
||||
`PVE::Storage::Custom::TrueNAS` is loaded by PVE's auto-discovery mechanism. Any module installed under `/usr/share/perl5/PVE/Storage/Custom/` is automatically registered as a custom storage type. No patching of PVE's core `ZFSPlugin.pm`, `pvemanagerlib.js`, or `apidoc.js` is required — this is the fundamental architectural change from v2.x.
|
||||
|
||||
```
|
||||
Proxmox VE (pvedaemon)
|
||||
└─ PVE::Storage # core storage dispatch
|
||||
└─ PVE::Storage::Custom::TrueNAS # this plugin
|
||||
└─ TrueNAS REST API v2.0 # all state lives here
|
||||
├─ /pool/dataset # zvol create/delete/resize
|
||||
└─ /iscsi/* # extent, target, targetextent CRUD
|
||||
```
|
||||
|
||||
### What the Plugin Manages
|
||||
|
||||
The plugin manages the entire lifecycle of iSCSI-backed block devices on behalf of Proxmox VE:
|
||||
|
||||
- **ZFS volumes (zvols)** — the actual block storage, created and destroyed via `POST /pool/dataset` and `DELETE /pool/dataset/id/{id}`
|
||||
- **iSCSI extents** — TrueNAS's representation of a device to export over iSCSI, one per zvol
|
||||
- **iSCSI targets** — the access point a client connects to, one per VM (`proxmox-vm-<vmid>`)
|
||||
- **targetextent mappings** — the join record linking an extent to a target at a specific LUN ID
|
||||
|
||||
The plugin does NOT manage the iSCSI initiator side. QEMU's built-in libiscsi opens the `iscsi://` URI returned by `path()` directly, without any host-side session management via `iscsiadm`.
|
||||
|
||||
### What the Plugin Does Not Do
|
||||
|
||||
- **Pool name auto-discovery**: The user types the TrueNAS pool name directly in the Add Storage dialog. The plugin does not enumerate available pools. Pool capacity is returned via the TrueNAS API (`GET /pool/dataset`), not SSH.
|
||||
- **Snapshots on PVE 8.x**: The `volume_snapshot` family of methods is defined but will return an unsupported error on PVE 8. PVE 9.0's Snapshot-as-Volume-Chains feature is the target integration point (v3.1.0, ADR-008).
|
||||
- **TPM state disks**: swtpm (the virtual TPM backend) cannot use `iscsi://` URIs; it requires a local filesystem path. TPM disks must be placed on a different storage type.
|
||||
|
||||
### Installed File Locations
|
||||
|
||||
| File | Destination on Proxmox host |
|
||||
|---|---|
|
||||
| `perl5/PVE/Storage/Custom/TrueNAS.pm` | `/usr/share/perl5/PVE/Storage/Custom/TrueNAS.pm` |
|
||||
| `ui/truenas-storage.js` | `/usr/share/pve-manager/js/truenas-storage.js` |
|
||||
| One `<script>` tag | Injected into `/usr/share/pve-manager/index.html.tpl` |
|
||||
|
||||
`postinst` copies both files, injects the script tag into `index.html.tpl` (idempotent — skipped if already present), and restarts `pvedaemon` and `pveproxy`.
|
||||
|
||||
---
|
||||
|
||||
## 2. Per-VM iSCSI Target Model
|
||||
|
||||
### Why Per-VM Targets
|
||||
|
||||
The v2.x approach used a single shared iSCSI target for all VMs. Every disk from every VM was a different LUN on the same target IQN. This created several problems:
|
||||
|
||||
- Deleting one disk required stopping the iSCSI service (`force=true` on extent delete does not work cleanly when multiple active sessions are on the same target)
|
||||
- Live migration of a single disk while others on the same target are active caused session disruption
|
||||
- LUN exhaustion — TrueNAS has a practical limit of 256 LUNs per target
|
||||
|
||||
In v3.0, each VM gets its own iSCSI target: `proxmox-vm-<vmid>`. Disks belonging to that VM become LUN 0, LUN 1, LUN 2, etc. on that target.
|
||||
|
||||
### The TrueNAS Object Graph
|
||||
|
||||
For a VM with `vmid=100` and two disks, TrueNAS holds:
|
||||
|
||||
```
|
||||
ZFS pool "tank"
|
||||
└── tank/proxmox/vdisks/
|
||||
├── vm-100-disk-0 (zvol, 32 GiB)
|
||||
└── vm-100-disk-1 (zvol, 64 GiB)
|
||||
|
||||
iSCSI extents
|
||||
├── vm-100-disk-0 (type=DISK, disk=zvol/tank/proxmox/vdisks/vm-100-disk-0)
|
||||
└── vm-100-disk-1 (type=DISK, disk=zvol/tank/proxmox/vdisks/vm-100-disk-1)
|
||||
|
||||
iSCSI target
|
||||
└── proxmox-vm-100 (IQN: iqn.2005-10.org.freenas.ctl:proxmox-vm-100)
|
||||
|
||||
targetextent mappings
|
||||
├── target=proxmox-vm-100, extent=vm-100-disk-0, lunid=0
|
||||
└── target=proxmox-vm-100, extent=vm-100-disk-1, lunid=1
|
||||
```
|
||||
|
||||
### Target Lifecycle
|
||||
|
||||
**Creation**: `_resolve_vm_target($scfg, $vmid)` is called on the first `alloc_image` for a VM. It queries `GET /iscsi/target`, looks for an entry with `name eq "proxmox-vm-$vmid"`, and creates one via `POST /iscsi/target` if it does not exist. The new target is assigned the same portal group and initiator group settings as an existing reference target (the `truenas_target` config value, or any existing target on the configured portal IP, as a fallback).
|
||||
|
||||
**Portal group discovery** (`_portal_groups_for_new_target`): Queries `GET /iscsi/portal` and finds all portals listening on the configured `truenas_portal_ip` (or `0.0.0.0`/`::`). Then queries `GET /iscsi/target` and finds the groups from the base target that reference those portal IDs. These groups are cloned for the new per-VM target, preserving `portal`, `initiator`, `authmethod`, and `auth` settings.
|
||||
|
||||
**Deletion**: `_maybe_cleanup_vm_target($scfg, $vmid, $target_id)` is called after each disk is removed. It queries `GET /iscsi/targetextent?target=<id>` to check for remaining extents. If the target is now empty, it is deleted via `DELETE /iscsi/target/id/<id>`. This keeps TrueNAS clean — no accumulation of empty targets.
|
||||
|
||||
**Runtime caching**: Both the target lookup and the global iSCSI config (`GET /iscsi/global`) are cached in the module-level `$state` hashref, keyed by `truenas_host`. `deactivate_storage` clears the entire cache for a host.
|
||||
|
||||
---
|
||||
|
||||
## 3. The `iscsi://` Path Model
|
||||
|
||||
### How QEMU Connects
|
||||
|
||||
The `path()` method returns a URI of the form:
|
||||
|
||||
```
|
||||
iscsi://<portal_ip>/<iqn>/<lun_id>
|
||||
```
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
iscsi://192.168.1.50/iqn.2005-10.org.freenas.ctl:proxmox-vm-100/0
|
||||
```
|
||||
|
||||
QEMU implements libiscsi internally. When it receives an `iscsi://` path, it opens an iSCSI session directly from the QEMU process to the TrueNAS portal. No `iscsiadm` command is run, no kernel iSCSI session is created, and no `/dev/sdX` or `/dev/disk/by-path/` device node appears on the Proxmox host.
|
||||
|
||||
### How `path()` Works Internally
|
||||
|
||||
```perl
|
||||
sub path {
|
||||
my ($class, $scfg, $volname, $storeid, $snapname) = @_;
|
||||
|
||||
my $ext = _find_extent($scfg, $volname);
|
||||
my $t = _api($scfg, 'GET', "/iscsi/target/id/$ext->{target_id}");
|
||||
my $iqn = _basename($scfg) . ":$t->{name}";
|
||||
my $portal = _portal($scfg);
|
||||
return ("iscsi://$portal/$iqn/$ext->{lun_id}", $vmid, $vtype);
|
||||
}
|
||||
```
|
||||
|
||||
`_find_extent` calls `GET /iscsi/extent` to locate the extent by name, then `GET /iscsi/targetextent?extent=<id>` to find the LUN ID and target association. The IQN is assembled from the cached global basename (`GET /iscsi/global` → `basename` field) and the target name.
|
||||
|
||||
### activate_volume and deactivate_volume
|
||||
|
||||
Because QEMU manages the connection itself, `activate_volume` only verifies that the extent exists (catching configuration errors early) and logs readiness. It does not call `iscsiadm login` or any iSCSI session management command. `deactivate_volume` is a no-op.
|
||||
|
||||
### Constraints of the iscsi:// Model
|
||||
|
||||
- **No multipath**: libiscsi in QEMU does not do multipath. If the portal is unreachable, QEMU retries but does not fail over to a second portal. Multi-portal TrueNAS setups are not effective with this model (tracked for v3.1.0, #256).
|
||||
- **TPM disks excluded**: `swtpm` cannot use `iscsi://` URIs. Any disk assigned to a VM's TPM must be on a different storage type (e.g., local-lvm, directory).
|
||||
- **No host-visible device**: Because there is no kernel iSCSI session, tools like `lsscsi`, `iscsiadm --mode session`, or `ls /dev/disk/by-path` will not show the disk on the Proxmox host. This is expected and correct.
|
||||
|
||||
---
|
||||
|
||||
## 4. API Flow — TrueNAS REST v2.0
|
||||
|
||||
### Authentication
|
||||
|
||||
All requests use `Authorization: Bearer <token>` with the API key stored in `truenas_api_key` in storage.cfg. Basic auth was removed in v3.0 (ADR-005). The API key is generated in TrueNAS under:
|
||||
|
||||
- SCALE: System Settings → API Keys → Add
|
||||
- CORE 13: gear icon (top right) → API Keys → Add
|
||||
|
||||
### Transport
|
||||
|
||||
The plugin uses `LWP::UserAgent` with a 30-second timeout. A single `LWP::UserAgent` instance is created per `truenas_host` and cached in `$state->{$host}{ua}`. SSL verification is off by default (`truenas_ssl_verify => 0`) to accommodate self-signed certificates. When `truenas_ssl_verify` is false, `ssl_opts(verify_hostname => 0, SSL_verify_mode => 0)` is applied to the UA.
|
||||
|
||||
### The `_api` Function
|
||||
|
||||
All TrueNAS calls go through `_api($scfg, $method, $path, $data)`:
|
||||
|
||||
```perl
|
||||
sub _api {
|
||||
my ($scfg, $method, $path, $data) = @_;
|
||||
|
||||
my $url = "$scheme://$scfg->{truenas_host}/api/v2.0$path";
|
||||
my $req = HTTP::Request->new(uc($method) => $url);
|
||||
$req->header('Authorization' => "Bearer $scfg->{truenas_api_key}");
|
||||
$req->content(encode_json($data)) if defined $data;
|
||||
|
||||
my $res = _ua($scfg)->request($req);
|
||||
die "TrueNAS API $method $path: " . $res->status_line unless $res->is_success;
|
||||
return decode_json($res->content);
|
||||
}
|
||||
```
|
||||
|
||||
On HTTP error, the function extracts the `message` field from the JSON response body, logs it via `syslog('err', ...)`, and dies with a descriptive string. HTTP 204 (No Content) responses — returned by most DELETE calls — return `undef` cleanly.
|
||||
|
||||
### Endpoints Used
|
||||
|
||||
| Operation | Method | Endpoint |
|
||||
|---|---|---|
|
||||
| Global iSCSI config | GET | `/iscsi/global` |
|
||||
| List portals | GET | `/iscsi/portal` |
|
||||
| List targets | GET | `/iscsi/target` |
|
||||
| Get target by ID | GET | `/iscsi/target/id/{id}` |
|
||||
| Create target | POST | `/iscsi/target` |
|
||||
| Delete target | DELETE | `/iscsi/target/id/{id}` |
|
||||
| List extents | GET | `/iscsi/extent` |
|
||||
| Create extent | POST | `/iscsi/extent` |
|
||||
| Delete extent | DELETE | `/iscsi/extent/id/{id}` |
|
||||
| List targetextents (by target) | GET | `/iscsi/targetextent?target={id}` |
|
||||
| List targetextents (by extent) | GET | `/iscsi/targetextent?extent={id}` |
|
||||
| Create targetextent | POST | `/iscsi/targetextent` |
|
||||
| Delete targetextent | DELETE | `/iscsi/targetextent/id/{id}` |
|
||||
| Reload iSCSI service | POST | `/service/reload` |
|
||||
| Get pool dataset | GET | `/pool/dataset?id={pool}` |
|
||||
| Get zvol by ID | GET | `/pool/dataset/id/{encoded_path}` |
|
||||
| List all zvols | GET | `/pool/dataset?type=VOLUME` |
|
||||
| Create zvol | POST | `/pool/dataset` |
|
||||
| Delete zvol | DELETE | `/pool/dataset/id/{encoded_path}` |
|
||||
|
||||
### URL Encoding for Dataset Paths
|
||||
|
||||
TrueNAS uses the full ZFS dataset path as the resource identifier in DELETE and GET by-ID calls. Forward slashes must be URL-encoded. The plugin uses `URI::Escape::uri_escape($path, "^A-Za-z0-9\\-_.~")` which encodes `/` as `%2F`:
|
||||
|
||||
```perl
|
||||
my $enc = uri_escape("tank/proxmox/vdisks/vm-100-disk-0", "^A-Za-z0-9\\-_.~");
|
||||
# Result: tank%2Fproxmox%2Fvdisks%2Fvm-100-disk-0
|
||||
_api($scfg, 'DELETE', "/pool/dataset/id/$enc", { recursive => JSON::true });
|
||||
```
|
||||
|
||||
### iSCSI Service Reload
|
||||
|
||||
After creating a new extent and targetextent, the plugin calls:
|
||||
|
||||
```perl
|
||||
_api($scfg, 'POST', '/service/reload', { service => 'iscsitarget' });
|
||||
```
|
||||
|
||||
This signals TrueNAS to reload its iSCSI configuration so the new LUN is visible to connecting initiators. The reload is non-fatal — if it fails, a warning is logged but `alloc_image` still succeeds.
|
||||
|
||||
---
|
||||
|
||||
## 5. alloc_image Deep Dive
|
||||
|
||||
`alloc_image($class, $storeid, $scfg, $vmid, $fmt, $name, $size_kb)` is called by PVE when a new virtual disk is created.
|
||||
|
||||
### Step 1: Name Resolution
|
||||
|
||||
If `$name` is not supplied, `find_free_diskname` (base-class method) generates `vm-<vmid>-disk-<N>` where `N` is the lowest unused integer. Only `raw` format is accepted.
|
||||
|
||||
### Step 2: Zvol Creation
|
||||
|
||||
```perl
|
||||
_api($scfg, 'POST', '/pool/dataset', {
|
||||
name => "$prefix/$name", # e.g. tank/proxmox/vdisks/vm-100-disk-0
|
||||
type => 'VOLUME',
|
||||
volsize => $size_kb * 1024, # API takes bytes
|
||||
sparse => JSON::true,
|
||||
});
|
||||
```
|
||||
|
||||
`$prefix` is assembled by `_zvol_prefix`: `truenas_pool` + optionally `/ truenas_dataset`. Sparse provisioning is always on.
|
||||
|
||||
### Step 3: iSCSI Extent Creation
|
||||
|
||||
```perl
|
||||
_api($scfg, 'POST', '/iscsi/extent', {
|
||||
name => $name, # e.g. vm-100-disk-0
|
||||
type => 'DISK',
|
||||
disk => "zvol/$prefix/$name", # zvol/ prefix required by TrueNAS
|
||||
ro => JSON::false,
|
||||
});
|
||||
```
|
||||
|
||||
The extent name matches the volume name exactly. This 1:1 naming is load-bearing — `_find_extent` searches extents by name.
|
||||
|
||||
### Step 4: Per-VM Target Resolution
|
||||
|
||||
`_resolve_vm_target($scfg, $vmid)` returns `{ id, iqn }`. If the target does not yet exist, it is created here. LUN ID assignment (`_next_lun_id`): queries `GET /iscsi/targetextent?target=<id>` and returns the lowest unused non-negative integer.
|
||||
|
||||
### Step 5: targetextent Creation
|
||||
|
||||
```perl
|
||||
_api($scfg, 'POST', '/iscsi/targetextent', {
|
||||
target => $target_id,
|
||||
extent => $extent_id,
|
||||
lunid => $lun_id,
|
||||
});
|
||||
```
|
||||
|
||||
### Step 6: Service Reload and Return
|
||||
|
||||
`_reload_iscsi` triggers the TrueNAS iSCSI service reload. The function returns `$name`, which PVE stores in the VM config.
|
||||
|
||||
### Rollback on Failure
|
||||
|
||||
The current implementation does not explicitly roll back a partially-created extent if targetextent creation fails — the extent and zvol remain on TrueNAS. ADR-004 documents the intended `eval {}` rollback pattern. This is tracked as a known gap in issue #250.
|
||||
|
||||
---
|
||||
|
||||
## 6. free_image Deep Dive
|
||||
|
||||
`free_image($class, $storeid, $scfg, $volname, $isBase)` is called when a disk is deleted. The deletion order is deliberately sequenced to avoid dangling references.
|
||||
|
||||
### Step 1: Locate the Extent
|
||||
|
||||
`_find_extent($scfg, $volname)` calls `GET /iscsi/extent` (by name) then `GET /iscsi/targetextent?extent=<id>`. Returns `{ extent_id, targetextent_id, lun_id, target_id }`. If no extent exists, a warning is logged and extent removal is skipped.
|
||||
|
||||
### Step 2: Unmap the targetextent
|
||||
|
||||
```perl
|
||||
_api($scfg, 'DELETE', "/iscsi/targetextent/id/$ext->{targetextent_id}");
|
||||
```
|
||||
|
||||
This must happen before the extent DELETE. Removing the targetextent severs this LUN's association without affecting the target session or other LUNs on the same target — critical for the live-migration case.
|
||||
|
||||
### Step 3: Delete the Extent
|
||||
|
||||
```perl
|
||||
_api($scfg, 'DELETE', "/iscsi/extent/id/$ext->{extent_id}", { force => JSON::true });
|
||||
```
|
||||
|
||||
`force=true` is required to delete an extent that may still have a residual QEMU libiscsi session open (e.g., detached disk on a running VM).
|
||||
|
||||
### Step 4: Clean Up the Per-VM Target if Empty
|
||||
|
||||
`_maybe_cleanup_vm_target($scfg, $vmid, $target_id)` queries `GET /iscsi/targetextent?target=<id>` after the targetextent was removed. If no mappings remain, `DELETE /iscsi/target/id/<id>` removes the empty target.
|
||||
|
||||
### Step 5: Delete the Zvol
|
||||
|
||||
```perl
|
||||
my $enc = uri_escape($zvol, "^A-Za-z0-9\\-_.~");
|
||||
_api($scfg, 'DELETE', "/pool/dataset/id/$enc", { recursive => JSON::true });
|
||||
```
|
||||
|
||||
`recursive=true` handles any ZFS snapshots under the zvol. Zvol deletion is last — if it fails, the error propagates after extent and targetextent are already cleaned up.
|
||||
|
||||
---
|
||||
|
||||
## 7. Build Pipeline and Version/Channel Routing
|
||||
|
||||
### Single-Repo Architecture (ADR-001)
|
||||
|
||||
All build and packaging logic lives in `.github/workflows/build.yml`. The old two-repo dispatch approach (this repo → `freenas-proxmox-packer`) was eliminated. Files are embedded in the `.deb` at build time; no `git clone` or `patch` runs at install time.
|
||||
|
||||
### Four-Job Pipeline
|
||||
|
||||
```
|
||||
lint → build → security → publish
|
||||
```
|
||||
|
||||
**Job 1: Lint**
|
||||
- `perl -c -I/tmp/pve-stub perl5/PVE/Storage/Custom/TrueNAS.pm` — syntax check with PVE module stubs
|
||||
- `perlcritic --profile .perlcriticrc` — static analysis at severity 4
|
||||
- `shellcheck --severity=warning packaging/DEBIAN/postinst packaging/DEBIAN/postrm`
|
||||
|
||||
**Job 2: Build**
|
||||
- Reads `our $VERSION` from `TrueNAS.pm` as the base version (ADR-006)
|
||||
- Resolves version string and target channel based on `$GITHUB_REF`
|
||||
- Generates `dist/DEBIAN/control` from `packaging/DEBIAN/control.j2` via `sed`
|
||||
- Runs `dpkg-deb -Zgzip --build dist <deb_file>`
|
||||
- Uploads the `.deb` as a workflow artifact (30-day retention)
|
||||
|
||||
**Job 3: Security**
|
||||
- Trivy filesystem scan (secrets + misconfig) on the repository
|
||||
- Trivy scan of extracted `.deb` contents (vuln + secret)
|
||||
- Both scans use `exit-code: 1` — HIGH or CRITICAL findings fail the pipeline
|
||||
|
||||
**Job 4: Publish**
|
||||
- Skipped for PRs and branches with `channel=none`
|
||||
- Cloudsmith `deb` push to the resolved repo (parallel channel, transitional)
|
||||
- GitHub Pages apt repo update: `.deb` added to `pool/v{major}/`, `Packages.gz` and signed `InRelease` regenerated for the relevant dist tracks, committed to `gh-pages` branch
|
||||
- On tagged releases: creates a draft GitHub Release with the `.deb` attached
|
||||
|
||||
### Version String Logic
|
||||
|
||||
| Git ref | Debian version string | Channel | Cloudsmith repo | GitHub Pages dist |
|
||||
|---|---|---|---|---|
|
||||
| `refs/tags/v3.0.0` | `3.0.0-1` | stable | `truenas-proxmox` | `v3` |
|
||||
| `refs/heads/master` or `refs/heads/release/3.x` | `3.0.0~beta+abc1234` | testing | `truenas-proxmox-testing` | — |
|
||||
| `refs/heads/release/*` (other) | `3.0.0~alpha+abc1234` | development | `truenas-proxmox-snapshots` | — |
|
||||
| feature branches, PRs | `3.0.0~dev+abc1234` | none | not published |
|
||||
|
||||
The tilde (`~`) in the Debian version sorts below the base version in `dpkg`, guaranteeing pre-release builds never auto-upgrade over a stable release.
|
||||
|
||||
### Running the Build Locally
|
||||
|
||||
```bash
|
||||
# Install dependencies
|
||||
sudo apt-get install -y libperl-critic-perl shellcheck libwww-perl libjson-perl liburi-perl
|
||||
|
||||
# Create PVE stubs
|
||||
mkdir -p /tmp/pve-stub/PVE/Storage
|
||||
printf 'package PVE::SafeSyslog;\nuse Exporter "import";\nour @EXPORT = qw(syslog);\nsub syslog {}\n1;\n' \
|
||||
> /tmp/pve-stub/PVE/SafeSyslog.pm
|
||||
printf 'package PVE::Tools;\nuse Exporter "import";\nour @EXPORT_OK = qw(run_command);\nsub run_command {}\n1;\n' \
|
||||
> /tmp/pve-stub/PVE/Tools.pm
|
||||
printf 'package PVE::Storage::Plugin;\nsub new {}\nsub register {}\nsub lookup_types { [] }\nsub properties { {} }\nsub options { {} }\n1;\n' \
|
||||
> /tmp/pve-stub/PVE/Storage/Plugin.pm
|
||||
|
||||
# Syntax check + lint
|
||||
perl -c -I/tmp/pve-stub perl5/PVE/Storage/Custom/TrueNAS.pm
|
||||
perlcritic --profile .perlcriticrc perl5/PVE/Storage/Custom/TrueNAS.pm
|
||||
shellcheck --severity=warning packaging/DEBIAN/postinst packaging/DEBIAN/postrm
|
||||
|
||||
# Build the package
|
||||
VERSION="3.0.0-dev"
|
||||
mkdir -p dist/DEBIAN dist/usr/share/freenas-proxmox
|
||||
sed "s/\${VERSION}/$VERSION/" packaging/DEBIAN/control.j2 > dist/DEBIAN/control
|
||||
cp packaging/DEBIAN/postinst packaging/DEBIAN/postrm dist/DEBIAN/
|
||||
chmod 0755 dist/DEBIAN/postinst dist/DEBIAN/postrm
|
||||
cp perl5/PVE/Storage/Custom/TrueNAS.pm dist/usr/share/freenas-proxmox/TrueNAS.pm
|
||||
cp ui/truenas-storage.js dist/usr/share/freenas-proxmox/truenas-storage.js
|
||||
sudo dpkg-deb -Zgzip --build dist freenas-proxmox_${VERSION}_all.deb
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. Debugging Guide
|
||||
|
||||
### Layer Model
|
||||
|
||||
Errors fall into one of four distinct layers:
|
||||
|
||||
```
|
||||
PVE task / VM operation
|
||||
└─ Plugin (TrueNAS.pm) — syslog "TrueNASPlugin: ..."
|
||||
└─ TrueNAS REST API — HTTP errors, JSON responses
|
||||
└─ iSCSI / QEMU libiscsi — QEMU logs, dmesg
|
||||
└─ ZFS — TrueNAS ZFS pool logs
|
||||
```
|
||||
|
||||
### Plugin Logs (Layer 1)
|
||||
|
||||
```bash
|
||||
# All plugin messages
|
||||
grep -i 'TrueNASPlugin' /var/log/syslog
|
||||
|
||||
# Follow live
|
||||
tail -f /var/log/syslog | grep -i TrueNAS
|
||||
```
|
||||
|
||||
Log levels: `info` (normal operation), `warning` (non-fatal issues), `err` (fatal — operation dies).
|
||||
|
||||
Example sequence for a successful `alloc_image`:
|
||||
|
||||
```
|
||||
TrueNASPlugin: alloc_image: creating zvol tank/proxmox/vdisks/vm-100-disk-0 (34359738368 bytes) for VM 100
|
||||
TrueNASPlugin: Created per-VM target: iqn.2005-10.org.freenas.ctl:proxmox-vm-100 (id=42)
|
||||
TrueNASPlugin: alloc_image: vm-100-disk-0 ready at lun 0 on iqn.2005-10.org.freenas.ctl:proxmox-vm-100
|
||||
```
|
||||
|
||||
### PVE Task Logs (Layer 1)
|
||||
|
||||
```bash
|
||||
# Recent storage tasks
|
||||
pvesh get /nodes/<nodename>/tasks --limit 50 | grep -i storage
|
||||
|
||||
# Specific task log
|
||||
pvesh get /nodes/<nodename>/tasks/<upid>/log
|
||||
|
||||
# VM start failures
|
||||
tail -f /var/log/pve/qemu-server/<vmid>.log
|
||||
```
|
||||
|
||||
### TrueNAS API Errors (Layer 2)
|
||||
|
||||
Common errors and causes:
|
||||
|
||||
| Error | Likely cause |
|
||||
|---|---|
|
||||
| `401 Unauthorized` | API key missing, revoked, or wrong |
|
||||
| `404 Not Found` on extent DELETE | Extent already deleted or never created |
|
||||
| `422 Unprocessable Entity` | Payload validation error — check TrueNAS system log for detail |
|
||||
| `500 Internal Server Error` | TrueNAS-side error — check TrueNAS system log |
|
||||
| Connection refused / timeout | TrueNAS host unreachable; check `truenas_host` or `truenas_portal_ip` |
|
||||
|
||||
**Probe the API manually:**
|
||||
|
||||
```bash
|
||||
# Test connectivity and auth
|
||||
curl -sk -H "Authorization: Bearer <token>" \
|
||||
https://<truenas_host>/api/v2.0/iscsi/global | python3 -m json.tool
|
||||
|
||||
# List all extents
|
||||
curl -sk -H "Authorization: Bearer <token>" \
|
||||
https://<truenas_host>/api/v2.0/iscsi/extent | python3 -m json.tool
|
||||
|
||||
# List all targetextents
|
||||
curl -sk -H "Authorization: Bearer <token>" \
|
||||
https://<truenas_host>/api/v2.0/iscsi/targetextent | python3 -m json.tool
|
||||
```
|
||||
|
||||
### Dangling Resources (Layer 2)
|
||||
|
||||
If `alloc_image` fails partway through, orphaned extents may remain on TrueNAS. Identify unmapped extents:
|
||||
|
||||
```bash
|
||||
# Get all extent IDs
|
||||
curl -sk -H "Authorization: Bearer $TOKEN" \
|
||||
https://<truenas>/api/v2.0/iscsi/extent | python3 -c \
|
||||
"import json,sys; [print(e['id'],e['name']) for e in json.load(sys.stdin)]"
|
||||
|
||||
# Get all mapped extent IDs
|
||||
curl -sk -H "Authorization: Bearer $TOKEN" \
|
||||
https://<truenas>/api/v2.0/iscsi/targetextent | python3 -c \
|
||||
"import json,sys; [print(te['extent']) for te in json.load(sys.stdin)]"
|
||||
```
|
||||
|
||||
Manual cleanup of a dangling extent:
|
||||
|
||||
```bash
|
||||
curl -sk -X DELETE -H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"force": true}' \
|
||||
https://<truenas>/api/v2.0/iscsi/extent/id/<id>
|
||||
```
|
||||
|
||||
### iSCSI / QEMU Connection Issues (Layer 3)
|
||||
|
||||
```bash
|
||||
tail -f /var/log/pve/qemu-server/<vmid>.log
|
||||
```
|
||||
|
||||
Common libiscsi errors:
|
||||
|
||||
| Error | Meaning |
|
||||
|---|---|
|
||||
| `Failed to connect to iSCSI portal` | Portal IP wrong, TrueNAS iSCSI service not running, or firewall |
|
||||
| `Initiator has no access to this target` | Initiator group does not include the Proxmox node's IQN |
|
||||
| `LUN not found` | Extent not mapped, or iSCSI service not reloaded after extent creation |
|
||||
| `Target not found` | IQN mismatch — check `_basename()` vs TrueNAS's actual global basename |
|
||||
|
||||
Check the Proxmox node's initiator IQN:
|
||||
|
||||
```bash
|
||||
cat /etc/iscsi/initiatorname.iscsi
|
||||
```
|
||||
|
||||
### Plugin Not Loading
|
||||
|
||||
```bash
|
||||
grep -i 'TrueNAS\|Storage::Custom\|Can.t locate' /var/log/syslog
|
||||
|
||||
# Verify syntax directly
|
||||
perl -c /usr/share/perl5/PVE/Storage/Custom/TrueNAS.pm
|
||||
|
||||
# Check required Perl modules
|
||||
dpkg -l libwww-perl libjson-perl liburi-perl libio-socket-ssl-perl
|
||||
```
|
||||
|
||||
### Install / Removal Log
|
||||
|
||||
```bash
|
||||
cat /var/log/freenas-proxmox-install.log
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. Contributing
|
||||
|
||||
### Quick Iteration on a Live Proxmox Node
|
||||
|
||||
For changes to `TrueNAS.pm` only:
|
||||
|
||||
```bash
|
||||
scp perl5/PVE/Storage/Custom/TrueNAS.pm \
|
||||
root@<proxmox-node>:/usr/share/perl5/PVE/Storage/Custom/TrueNAS.pm
|
||||
ssh root@<proxmox-node> "pvedaemon restart && pveproxy restart"
|
||||
```
|
||||
|
||||
For UI changes (`truenas-storage.js`):
|
||||
|
||||
```bash
|
||||
scp ui/truenas-storage.js \
|
||||
root@<proxmox-node>:/usr/share/pve-manager/js/truenas-storage.js
|
||||
# Hard-refresh browser (Ctrl+Shift+R) — no service restart needed
|
||||
```
|
||||
|
||||
### Coding Standards
|
||||
|
||||
**Perl:**
|
||||
- `use strict` and `use warnings` — no exceptions
|
||||
- All TrueNAS API calls through `_api()` — never raw `LWP::UserAgent` outside that function
|
||||
- Multi-step operations must use `eval {}` rollback: if step N fails, undo steps 1 through N-1 in reverse before dying (ADR-004)
|
||||
- `JSON::true` / `JSON::false` for boolean JSON fields
|
||||
|
||||
**Shell (`postinst`/`postrm`):**
|
||||
- `set -e` at top
|
||||
- All output via the `log()` function (writes to `$LOG_FILE`)
|
||||
- Operations must be idempotent — check before acting
|
||||
- `shellcheck --severity=warning` must pass
|
||||
|
||||
### Branch and PR Workflow
|
||||
|
||||
1. Open a GitHub issue before writing code
|
||||
2. Branch from `master`: `git checkout -b feature/short-description`
|
||||
3. Run lint locally before pushing
|
||||
4. Open a PR against `master` — CI runs lint → build → security automatically
|
||||
5. Close the issue with commit SHA and version when the change ships
|
||||
|
||||
### Architecture Decision Records
|
||||
|
||||
Major design decisions live in `.claude/cos/adrs/`. Check before making changes that affect the plugin's architecture, auth model, build pipeline, or supported platform matrix.
|
||||
|
||||
| ADR | Decision |
|
||||
|---|---|
|
||||
| ADR-001 | Consolidate build pipeline into this repo; no install-time git clone |
|
||||
| ADR-002 | Full `PVE::Storage::Custom` plugin; no ZFSPlugin.pm or pvemanagerlib.js patches |
|
||||
| ADR-003 | Superseded by ADR-010 |
|
||||
| ADR-010 | GitHub Pages apt repo with per-major-version dist tracks (`v3`, `main`/`v2` for backward compat) |
|
||||
| ADR-004 | `eval {}` rollback pattern for all multi-step TrueNAS operations |
|
||||
| ADR-005 | Bearer token (API key) as primary auth; basic auth removed in v3.0 |
|
||||
| ADR-006 | `$VERSION` in `TrueNAS.pm` is the single source of truth for package versioning |
|
||||
| ADR-007 | v2.x keeps `FreeNAS` naming; v3.x uses `TrueNAS` everywhere |
|
||||
| ADR-008 | v3.0 supports PVE 8+ (core); PVE 9+ snapshot interface targeted for v3.1.0 |
|
||||
|
||||
---
|
||||
|
||||
## Key File Reference
|
||||
|
||||
| File | Purpose |
|
||||
|---|---|
|
||||
| [perl5/PVE/Storage/Custom/TrueNAS.pm](../perl5/PVE/Storage/Custom/TrueNAS.pm) | Main plugin — all storage operations |
|
||||
| [ui/truenas-storage.js](../ui/truenas-storage.js) | Ext.js UI panel for the `truenas` storage type |
|
||||
| [packaging/DEBIAN/postinst](../packaging/DEBIAN/postinst) | Install script — copies files, injects script tag, restarts PVE |
|
||||
| [packaging/DEBIAN/postrm](../packaging/DEBIAN/postrm) | Removal script — reverses postinst changes |
|
||||
| [packaging/DEBIAN/control.j2](../packaging/DEBIAN/control.j2) | Debian package control file template |
|
||||
| [.github/workflows/build.yml](../.github/workflows/build.yml) | Full CI/CD pipeline |
|
||||
| [.claude/cos/adrs/](../.claude/cos/adrs/) | Architecture Decision Records |
|
||||
|
|
@ -0,0 +1,364 @@
|
|||
# Getting Started with freenas-proxmox v3.0
|
||||
|
||||
## 1. What This Plugin Does
|
||||
|
||||
Proxmox VE is a hypervisor — it runs your virtual machines. Those VMs need somewhere to store their disk images. If you have a TrueNAS storage server on your network, you might want to store those disks on TrueNAS so they live on dedicated, reliable, ZFS-managed storage instead of inside the Proxmox host itself. This plugin makes that possible. It registers a new storage type called **TrueNAS (ZFS/iSCSI)** inside Proxmox. When you add a VM disk to that storage, Proxmox talks to your TrueNAS server through its REST API, creates a ZFS volume on TrueNAS, exposes it as an iSCSI block device, and gives your VM a direct high-speed path to it — all automatically. You do not need to touch the TrueNAS UI every time you create or delete a VM disk.
|
||||
|
||||
---
|
||||
|
||||
## 2. Prerequisites
|
||||
|
||||
Complete all of these before installing the plugin.
|
||||
|
||||
### 2.1 Proxmox VE Version
|
||||
|
||||
Your Proxmox VE node (or cluster) must be running **PVE 8.x or PVE 9.x**. Check with:
|
||||
|
||||
```bash
|
||||
pveversion
|
||||
```
|
||||
|
||||
If the output shows `pve-manager/7.x`, stay on plugin v2.x — v3.0 does not support PVE 7.
|
||||
|
||||
### 2.2 TrueNAS Version
|
||||
|
||||
| TrueNAS product | Minimum version |
|
||||
|:----------------|:----------------|
|
||||
| TrueNAS SCALE | Electric Eel (24.10) or later |
|
||||
| TrueNAS CORE | 13.0-U6 or later |
|
||||
|
||||
### 2.3 iSCSI Service on TrueNAS
|
||||
|
||||
The plugin creates per-VM iSCSI targets for you automatically. It does **not** create the iSCSI service infrastructure — you must set that up once.
|
||||
|
||||
**On TrueNAS SCALE:**
|
||||
1. Go to **Shares → iSCSI → Configure**
|
||||
2. Under **Portals**, add a portal that listens on the IP address your Proxmox nodes use to reach TrueNAS (or `0.0.0.0` to listen on all interfaces). Note the portal's IP address — you will need it.
|
||||
3. Under **Initiators**, add an initiator group. You can leave it open (allow all) for initial setup; lock it down to specific Proxmox node IPs later.
|
||||
4. Enable the iSCSI service and confirm it is running.
|
||||
|
||||
**On TrueNAS CORE:**
|
||||
1. Go to **Sharing → Block Shares (iSCSI)**
|
||||
2. Under **Portals**, add a portal (same as above)
|
||||
3. Under **Initiators**, add an initiator group
|
||||
4. Go to **Services** and enable **iSCSI**
|
||||
|
||||
You do **not** need to create any targets or extents by hand — the plugin creates those automatically when you allocate VM disks.
|
||||
|
||||
### 2.4 TrueNAS API Key
|
||||
|
||||
The plugin authenticates with TrueNAS using an API key (Bearer token). No username or password is used.
|
||||
|
||||
**On TrueNAS SCALE:**
|
||||
Go to **System Settings → API Keys → Add**. Give it a descriptive name such as `proxmox-plugin`. Copy the key — it is shown only once.
|
||||
|
||||
**On TrueNAS CORE:**
|
||||
Click the gear icon in the top-right corner → **API Keys → Add**. Copy the key.
|
||||
|
||||
Store the key somewhere safe temporarily. You will paste it into the Proxmox UI during storage configuration.
|
||||
|
||||
### 2.5 Network Reachability
|
||||
|
||||
- Every Proxmox node must be able to reach the TrueNAS host on **TCP 443** (HTTPS API) or **TCP 80** (HTTP, not recommended)
|
||||
- Every Proxmox node must be able to reach the iSCSI portal on **TCP 3260**
|
||||
|
||||
No SSH keys are required. No pre-created iSCSI targets are required.
|
||||
|
||||
### 2.6 What You Cannot Store on This Storage
|
||||
|
||||
TPM state disks (`tpmstate0`) cannot live on this storage type. If you plan to create VMs with Secure Boot / TPM enabled, configure a separate local-lvm or NFS storage for those disks. All other disk types (virtio, scsi, IDE, EFI) work normally.
|
||||
|
||||
---
|
||||
|
||||
## 3. Installation
|
||||
|
||||
Run all of these commands on **each Proxmox node** in your cluster, or on your single Proxmox host. Run them as root.
|
||||
|
||||
### Step 1 — Import the GPG Signing Key
|
||||
|
||||
```bash
|
||||
curl -fsSL https://thegrandwazoo.github.io/freenas-proxmox/public.gpg.key \
|
||||
| gpg --dearmor \
|
||||
| tee /etc/apt/keyrings/truenas-proxmox.gpg > /dev/null
|
||||
```
|
||||
|
||||
### Step 2 — Add the Package Repository
|
||||
|
||||
```bash
|
||||
echo "deb [signed-by=/etc/apt/keyrings/truenas-proxmox.gpg] \
|
||||
https://thegrandwazoo.github.io/freenas-proxmox v3 main" \
|
||||
| tee /etc/apt/sources.list.d/truenas-proxmox.list
|
||||
```
|
||||
|
||||
### Step 3 — Install the Package
|
||||
|
||||
```bash
|
||||
apt update && apt install truenas-proxmox
|
||||
```
|
||||
|
||||
The installer:
|
||||
- Copies `TrueNAS.pm` to `/usr/share/perl5/PVE/Storage/Custom/`
|
||||
- Copies `truenas-storage.js` to `/usr/share/pve-manager/js/`
|
||||
- Adds one `<script>` tag to `/usr/share/pve-manager/index.html.tpl` so the UI loads the new panel
|
||||
- Restarts `pvedaemon` and `pveproxy`
|
||||
|
||||
It does **not** patch any PVE system files.
|
||||
|
||||
### Step 4 — Refresh Your Browser
|
||||
|
||||
Open (or reload) the Proxmox web UI. Do a hard refresh: **Ctrl+Shift+R** (Windows/Linux) or **Cmd+Shift+R** (Mac). The TrueNAS storage type will not appear until the browser picks up the new JavaScript.
|
||||
|
||||
---
|
||||
|
||||
## 4. First Storage Configuration in the Proxmox UI
|
||||
|
||||
In the Proxmox web UI:
|
||||
|
||||
1. Click **Datacenter** in the left tree
|
||||
2. Click **Storage** in the top tabs
|
||||
3. Click **Add**
|
||||
4. Choose **TrueNAS (ZFS/iSCSI)** from the dropdown
|
||||
|
||||
If **TrueNAS (ZFS/iSCSI)** does not appear in the list, see [section 6.1](#61-truenas-zfsiscsi-does-not-appear-in-the-add-storage-dropdown) below.
|
||||
|
||||
### Field Reference
|
||||
|
||||
Fill in the form using the table below. Fields not listed can be left at their defaults.
|
||||
|
||||
| Field | What to enter | Notes |
|
||||
|-------|---------------|-------|
|
||||
| **ID** | A short name, e.g. `truenas-vms` | How Proxmox refers to the storage internally. Letters, numbers, and hyphens only. Cannot be changed later. |
|
||||
| **TrueNAS Host** | IP address or hostname of your TrueNAS server | Example: `192.168.10.50` or `truenas.local`. Also used as the iSCSI portal address unless you set Portal IP separately. |
|
||||
| **API Key** | Paste the API key you generated in step 2.4 | Treated as a password — hidden by default. Click the eye icon to show it while pasting. |
|
||||
| **Pool / Dataset Path** | The ZFS pool or dataset path where VM disks should be created | Use `tank` for the pool root, or `tank/proxmox/vdisks` for a specific dataset. |
|
||||
| **Use SSL** | Leave checked (on) | Recommended. Turn off only if TrueNAS has no HTTPS configured. |
|
||||
| **Verify SSL Certificate** | Leave unchecked | Turn on only if TrueNAS has a valid CA-signed certificate. Most homelab setups use self-signed certs — leave this off. |
|
||||
| **Shared** | Leave checked (on) | Required for clusters. Tells Proxmox all nodes can access this storage. |
|
||||
| **Portal IP** | Optional — leave blank | If your TrueNAS management interface and iSCSI data interface are on different IPs, put the iSCSI data IP here. Otherwise the TrueNAS Host IP is used for both. |
|
||||
| **Target IQN** | Optional — leave blank | The plugin auto-discovers the portal and initiator group from existing iSCSI targets. Leave blank unless you want to force it to copy settings from a specific target. |
|
||||
|
||||
Click **Add**. Proxmox contacts TrueNAS and confirms the pool is reachable. If it fails, check the error message — see [section 6](#6-common-first-run-problems) for common causes.
|
||||
|
||||
### 4.1 Securing the API Token (Recommended for Production)
|
||||
|
||||
By default the API token is stored in `/etc/pve/storage.cfg`, which is replicated in plaintext across all cluster nodes via the PVE cluster filesystem. For production deployments, move the token into a private keyfile that only root can read and that is not replicated.
|
||||
|
||||
Run these commands **on each Proxmox node** after adding the storage:
|
||||
|
||||
```bash
|
||||
# Replace 'truenas-vms' with your actual storage ID
|
||||
STORAGEID="truenas-vms"
|
||||
KEYFILE="/etc/pve/priv/truenas-${STORAGEID}.key"
|
||||
|
||||
# Write the token to the keyfile (replace the value with your actual token)
|
||||
echo -n "your-api-token-here" > "$KEYFILE"
|
||||
chmod 600 "$KEYFILE"
|
||||
|
||||
# Remove the token from storage.cfg now that the keyfile is in place
|
||||
pvesm set "$STORAGEID" --truenas_api_key ""
|
||||
```
|
||||
|
||||
The plugin automatically checks `/etc/pve/priv/truenas-<storeid>.key` at startup. If the file exists, it is used and `truenas_api_key` in storage.cfg is ignored.
|
||||
|
||||
> **Note:** The keyfile must exist on **every Proxmox node** in your cluster. The `/etc/pve/priv/` directory is not replicated via pmxcfs — copy the file to each node manually (use `scp` or your configuration management tool).
|
||||
|
||||
---
|
||||
|
||||
## 5. Creating Your First VM Disk on TrueNAS Storage
|
||||
|
||||
### During VM Creation
|
||||
|
||||
1. In the Proxmox UI, click **Create VM**
|
||||
2. On the **Disks** tab, change **Storage** from `local-lvm` to the storage ID you just created (e.g., `truenas-vms`)
|
||||
3. Set the disk size
|
||||
4. Complete the rest of the VM setup and click **Finish**
|
||||
|
||||
Proxmox will:
|
||||
- Create a ZFS volume (`zvol`) on TrueNAS sized to your request
|
||||
- Create an iSCSI extent pointing to that zvol
|
||||
- Create a dedicated iSCSI target named `proxmox-vm-<vmid>` on TrueNAS (e.g., `proxmox-vm-100`)
|
||||
- Map the extent to the target
|
||||
- Give QEMU an `iscsi://` path to connect directly
|
||||
|
||||
When the VM starts, QEMU opens an iSCSI connection directly to TrueNAS — no kernel session management (`iscsiadm`) is involved.
|
||||
|
||||
### Adding a Disk to an Existing VM
|
||||
|
||||
1. Select the VM in the left tree
|
||||
2. Click **Hardware**
|
||||
3. Click **Add → Hard Disk**
|
||||
4. Set **Storage** to your TrueNAS storage
|
||||
5. Set the disk size
|
||||
6. Click **Add**
|
||||
|
||||
### What You Will See on TrueNAS
|
||||
|
||||
After creating a VM disk, log into TrueNAS and check:
|
||||
- **Datasets** (SCALE) or **Storage → Pools** (CORE): a new zvol named `vm-100-disk-0` (or similar) will appear under your configured pool/dataset
|
||||
- **Shares → iSCSI → Targets**: a target named `proxmox-vm-100` will appear
|
||||
- **Shares → iSCSI → Extents**: an extent named `vm-100-disk-0` will appear
|
||||
|
||||
When you delete the disk from Proxmox, all three are removed automatically. If the deleted disk was the last disk on that VM's target, the target is removed too.
|
||||
|
||||
---
|
||||
|
||||
## 6. Common First-Run Problems
|
||||
|
||||
### 6.1 "TrueNAS (ZFS/iSCSI)" Does Not Appear in the Add Storage Dropdown
|
||||
|
||||
**Cause:** Browser has cached the old Proxmox JavaScript.
|
||||
|
||||
**Fix:** Hard-refresh the browser: **Ctrl+Shift+R** (Windows/Linux) or **Cmd+Shift+R** (Mac). If it still does not appear, check that the package installed correctly:
|
||||
|
||||
```bash
|
||||
ls /usr/share/perl5/PVE/Storage/Custom/TrueNAS.pm
|
||||
ls /usr/share/pve-manager/js/truenas-storage.js
|
||||
```
|
||||
|
||||
Both files should exist. If they are missing, re-run `apt install truenas-proxmox`. Also check whether `pvedaemon` and `pveproxy` restarted cleanly:
|
||||
|
||||
```bash
|
||||
systemctl status pvedaemon pveproxy
|
||||
```
|
||||
|
||||
### 6.2 Storage Shows as Unavailable or Add Dialog Returns an Error
|
||||
|
||||
**Cause:** The plugin cannot reach the TrueNAS API.
|
||||
|
||||
**Check the logs first:**
|
||||
|
||||
```bash
|
||||
journalctl -u pvedaemon --since "10 minutes ago" | grep -i truenas
|
||||
```
|
||||
|
||||
Common specific errors:
|
||||
|
||||
| Log message | Fix |
|
||||
|-------------|-----|
|
||||
| `TrueNAS API key is not configured` | API key field was left blank; edit the storage and paste the key |
|
||||
| `401 Unauthorized` | API key is wrong, expired, or revoked; generate a new one in TrueNAS |
|
||||
| `500 Internal Server Error` or connection refused | SSL mismatch — if TrueNAS uses a self-signed certificate and Use SSL is on, try toggling Verify SSL Certificate off. Or check that TrueNAS is reachable on HTTPS from the Proxmox node |
|
||||
| `Pool dataset 'tank' not found` | The pool name you typed does not match what TrueNAS has; check under **Storage → Pools** in TrueNAS |
|
||||
|
||||
**Quick connectivity test** from the Proxmox node shell:
|
||||
|
||||
```bash
|
||||
# Replace 192.168.10.50 and your-api-key with real values
|
||||
curl -sk -H "Authorization: Bearer your-api-key" \
|
||||
https://192.168.10.50/api/v2.0/iscsi/global | python3 -m json.tool
|
||||
```
|
||||
|
||||
If this returns JSON with a `basename` field, the API is working. If it returns an HTML login page or a TLS error, you have a connectivity or SSL problem.
|
||||
|
||||
### 6.3 Disk Creation Fails with "No iSCSI Portals Found"
|
||||
|
||||
**Cause:** The plugin could not match the TrueNAS Host IP (or Portal IP) to any portal configured in TrueNAS iSCSI settings.
|
||||
|
||||
**Fix:** In TrueNAS, go to **Shares → iSCSI → Portals** and check what IP the portal is listening on. Either:
|
||||
- Set the portal to listen on `0.0.0.0` (all interfaces), or
|
||||
- In Proxmox, edit the storage and set **Portal IP** to the exact IP the TrueNAS portal is listening on
|
||||
|
||||
### 6.4 VM Starts but Disk Is Not Accessible / VM Won't Boot
|
||||
|
||||
**Cause:** The iSCSI data path is blocked. Even if the plugin successfully created the disk (API path), the VM's QEMU process must open a separate iSCSI TCP connection to TrueNAS.
|
||||
|
||||
**Checks:**
|
||||
1. Confirm TrueNAS iSCSI service is running
|
||||
2. From the Proxmox node, test TCP connectivity to the iSCSI port:
|
||||
```bash
|
||||
nc -zv 192.168.10.50 3260
|
||||
```
|
||||
3. Check that the initiator group in TrueNAS does not block the Proxmox node's IP
|
||||
4. Check that the per-VM target in TrueNAS (`proxmox-vm-<vmid>`) has the extent associated with it under **iSCSI → Target/Extent**
|
||||
|
||||
### 6.5 API Key Stopped Working After Upgrading TrueNAS SCALE to 25.04
|
||||
|
||||
TrueNAS SCALE 25.04 may revoke API keys created under certain conditions during upgrade, and enforces HTTPS for API key authentication.
|
||||
|
||||
**Fix:**
|
||||
1. Log into TrueNAS SCALE → **Credentials → API Keys** → generate a new key
|
||||
2. In Proxmox: **Datacenter → Storage** → select your TrueNAS storage → **Edit**
|
||||
3. Paste the new API key into the **API Key** field
|
||||
4. Ensure **Use SSL** is checked
|
||||
|
||||
### 6.6 Disk Size Shown in TrueNAS Is Larger Than What I Entered in Proxmox
|
||||
|
||||
This is expected and not an error. Proxmox creates disks in GiB (base-2) but TrueNAS reports sizes in GB (base-10). An 80 GiB disk shows as approximately 85.90 GB in TrueNAS. The disk inside your VM is exactly what you asked for.
|
||||
|
||||
### 6.7 TPM State Disk Fails to Create on TrueNAS Storage
|
||||
|
||||
TPM state (`tpmstate0`) is a known limitation — this storage type cannot hold TPM state disks. When creating a VM with TPM/Secure Boot, configure a different storage (such as `local-lvm` or NFS) for the `tpmstate0` disk. All other disk types work normally.
|
||||
|
||||
### 6.8 `apt update` Says Codename Changed from `v3` to `error`
|
||||
|
||||
This is a one-time prompt from apt's security model. In v3.2.2, a codename alias (`error`) was added to the `v3` dist — apt flags metadata changes like this and requires explicit acceptance before continuing.
|
||||
|
||||
Run this once:
|
||||
|
||||
```bash
|
||||
apt update --allow-releaseinfo-change
|
||||
```
|
||||
|
||||
After that, `apt update` works normally and this prompt will not appear again for any future v3.x release. Both `v3` (Suite) and `error` (Codename) work in `sources.list` and point to the same packages.
|
||||
|
||||
### 6.9 `PUT /api/v2.0/iscsi/portal/id/<n>` Returns "Extra inputs are not permitted" on SCALE 25.04+
|
||||
|
||||
TrueNAS SCALE 25.04 tightened the portal PUT schema. The `port` field is no longer accepted in PUT requests — GET still returns it, but sending it back causes a validation error:
|
||||
|
||||
```json
|
||||
{"iscsi_portal_update.listen.0.port": [{"message": "Extra inputs are not permitted", "errno": 22}]}
|
||||
```
|
||||
|
||||
This only affects admins who manually call the TrueNAS REST API (e.g., via curl or scripts). The plugin does not create or modify portals.
|
||||
|
||||
**Fix:** Omit `port` from the `listen` array in PUT requests:
|
||||
|
||||
```bash
|
||||
# Correct — SCALE 25.04+
|
||||
curl -sk -X PUT -H "Authorization: Bearer <key>" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"listen": [{"ip": "0.0.0.0"}]}' \
|
||||
https://<truenas>/api/v2.0/iscsi/portal/id/1
|
||||
|
||||
# Wrong — causes error on SCALE 25.04+
|
||||
# -d '{"listen": [{"ip": "0.0.0.0", "port": 3260}]}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Uninstalling
|
||||
|
||||
Before uninstalling, remove any storage configurations that use this plugin from Proxmox, and migrate any VM disks off TrueNAS storage. Uninstalling while VMs are still using TrueNAS disks will not delete your data, but Proxmox will lose track of those disks.
|
||||
|
||||
### Step 1 — Remove Storage Configurations
|
||||
|
||||
In the Proxmox UI: **Datacenter → Storage** → select each TrueNAS storage entry → **Remove**.
|
||||
|
||||
### Step 2 — Uninstall the Package
|
||||
|
||||
```bash
|
||||
apt remove truenas-proxmox
|
||||
```
|
||||
|
||||
The removal script:
|
||||
- Deletes `/usr/share/perl5/PVE/Storage/Custom/TrueNAS.pm`
|
||||
- Deletes `/usr/share/pve-manager/js/truenas-storage.js`
|
||||
- Removes the `<script>` tag from `/usr/share/pve-manager/index.html.tpl`
|
||||
- Restarts `pvedaemon` and `pveproxy`
|
||||
|
||||
To also remove install logs:
|
||||
|
||||
```bash
|
||||
apt purge truenas-proxmox
|
||||
```
|
||||
|
||||
### Step 3 — Refresh Your Browser
|
||||
|
||||
Hard-refresh the browser after uninstalling. The TrueNAS option will disappear from the Add Storage dropdown.
|
||||
|
||||
---
|
||||
|
||||
## Getting Help
|
||||
|
||||
- GitHub Issues: https://github.com/TheGrandWazoo/truenas-proxmox/issues
|
||||
- When reporting a bug, include log lines from `journalctl -u pvedaemon | grep -i truenas` and the output of `pveversion`
|
||||
|
|
@ -0,0 +1,197 @@
|
|||
# Migrating from v2.x to v3.0
|
||||
|
||||
This guide covers moving from the old `freenas-proxmox` v2.x plugin to the new `truenas-proxmox` v3.0 plugin.
|
||||
|
||||
v3.0 is a clean break from v2.x — a completely rewritten native Proxmox storage plugin. You cannot upgrade in place. The migration involves installing the new plugin, adding a new storage entry, and moving your VM disks across using Proxmox's built-in Move Disk function. **VMs can stay running throughout.**
|
||||
|
||||
---
|
||||
|
||||
## What's a zvol?
|
||||
|
||||
A **zvol** (ZFS volume) is TrueNAS's name for a dedicated chunk of storage carved out of a pool — think of it as a virtual hard drive sitting inside TrueNAS. Each VM disk in this plugin is its own zvol. When you "move a disk," Proxmox creates a new zvol, copies the data to it, and deletes the old one.
|
||||
|
||||
---
|
||||
|
||||
## What changed from v2.x
|
||||
|
||||
**Authentication:** v2.x used a username and password (or a secret token). v3.0 uses a TrueNAS API key only — username/password is gone.
|
||||
|
||||
**iSCSI connection model:** v2.x ran `iscsiadm` on the Proxmox host to create kernel iSCSI sessions. You could see block devices like `/dev/disk/by-path/ip-...` on your Proxmox node. In v3.0, QEMU connects directly to TrueNAS via `iscsi://` — no kernel sessions, no block devices on the host. **This is expected and correct.** If you look in `/dev/` and don't see the disk, that's fine — QEMU holds the connection, not the host.
|
||||
|
||||
**Per-VM targets:** v2.x shared iSCSI targets across VMs. v3.0 creates a dedicated iSCSI target for each VM (`proxmox-vm-<vmid>`). This means removing one disk never affects other VMs.
|
||||
|
||||
**Package name:** `freenas-proxmox` is now a transitional stub that installs `truenas-proxmox`. Both can coexist during migration.
|
||||
|
||||
---
|
||||
|
||||
## Pre-migration checklist
|
||||
|
||||
Do these checks **before** you start:
|
||||
|
||||
```
|
||||
[ ] All Proxmox nodes running PVE 8.0 or newer
|
||||
pveversion | grep pve-manager
|
||||
|
||||
[ ] TrueNAS reachable from each Proxmox node (API + iSCSI)
|
||||
curl -k https://<truenas_host>/api/v2.0/system/info
|
||||
nc -zv <truenas_host> 3260
|
||||
|
||||
[ ] iSCSI service enabled on TrueNAS
|
||||
Sharing → iSCSI → Target Global Configuration: check "Enable"
|
||||
|
||||
[ ] TrueNAS pool is healthy (no resilvering, no scrub running)
|
||||
TrueNAS → Storage → Pools: all pools show "Healthy"
|
||||
|
||||
[ ] TrueNAS pool has free space ≥ your largest single disk
|
||||
(only one copy is needed at a time if you move one disk at a time)
|
||||
|
||||
[ ] Check each VM for tpmstate0 disks (see TPM section below)
|
||||
Proxmox → VM → Hardware: look for tpmstate0
|
||||
|
||||
[ ] Back up any VMs you're not comfortable losing (optional but recommended)
|
||||
```
|
||||
|
||||
**Ports required from Proxmox to TrueNAS:**
|
||||
|
||||
| Port | Protocol | Used for |
|
||||
|------|----------|---------|
|
||||
| 443 | HTTPS | Plugin API calls (REST v2.0) |
|
||||
| 3260 | TCP | iSCSI disk I/O (QEMU to TrueNAS) |
|
||||
| 22 | SSH | Not needed — removed in v3.0 |
|
||||
|
||||
---
|
||||
|
||||
## Check for TPM state disks first
|
||||
|
||||
Before you do anything else, check if any of your VMs have a TPM state disk. QEMU requires TPM state on a local filesystem path — it cannot use `iscsi://` URIs. You cannot migrate `tpmstate0` to v3.0 iSCSI storage.
|
||||
|
||||
**How to check:** In Proxmox, click each VM → **Hardware** tab. Look for a disk labeled `tpmstate0`. If you see one, you need to move it to `local-lvm` or NFS storage **before** migrating that VM's other disks.
|
||||
|
||||
> **Note for HA users:** If the VM is managed by Proxmox HA, `local-lvm` is not suitable for `tpmstate0` (it won't be accessible on other nodes if the VM is restarted). Use NFS or another shared filesystem storage for `tpmstate0` instead.
|
||||
|
||||
---
|
||||
|
||||
## Step 1 — Install v3.0 on every Proxmox node
|
||||
|
||||
Run this on **every node in the cluster** before adding the new storage:
|
||||
|
||||
```bash
|
||||
# Import the GPG key
|
||||
curl -fsSL https://thegrandwazoo.github.io/freenas-proxmox/public.gpg.key \
|
||||
| gpg --dearmor \
|
||||
| tee /etc/apt/keyrings/truenas-proxmox.gpg > /dev/null
|
||||
|
||||
# Add the v3 repository track
|
||||
echo "deb [signed-by=/etc/apt/keyrings/truenas-proxmox.gpg] \
|
||||
https://thegrandwazoo.github.io/freenas-proxmox v3 main" \
|
||||
| tee /etc/apt/sources.list.d/truenas-proxmox.list
|
||||
|
||||
# Install
|
||||
apt update && apt install truenas-proxmox
|
||||
```
|
||||
|
||||
Verify the install on each node:
|
||||
```bash
|
||||
dpkg -l truenas-proxmox
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 2 — Create an API key on TrueNAS
|
||||
|
||||
**TrueNAS SCALE:**
|
||||
1. Log in to the TrueNAS web UI
|
||||
2. Go to **System Settings → API Keys**
|
||||
3. Click **Add**, give it a name (e.g. `proxmox-v3`), click **Generate Key**
|
||||
4. Copy the key — you will only see it once
|
||||
|
||||
**TrueNAS CORE:**
|
||||
1. Log in to the TrueNAS web UI
|
||||
2. Click the gear icon (top right) → **API Keys**
|
||||
3. Click **Add**, give it a name, click **Submit**
|
||||
4. Copy the key
|
||||
|
||||
Keep the key somewhere safe — you will paste it into Proxmox next.
|
||||
|
||||
---
|
||||
|
||||
## Step 3 — Add the v3.0 storage in Proxmox
|
||||
|
||||
> Keep your existing v2.x storage entry active — **do not remove it yet**. Both entries can coexist while you migrate.
|
||||
|
||||
1. In the Proxmox web UI, go to **Datacenter → Storage → Add → TrueNAS**
|
||||
2. Fill in the fields:
|
||||
|
||||
| Field | What to enter |
|
||||
|-------|--------------|
|
||||
| ID | A new name, e.g. `truenas-v3` — must be different from your v2 storage ID |
|
||||
| TrueNAS Host | Same IP/hostname as your v2 storage |
|
||||
| API Key | The key you just created |
|
||||
| Pool | Same ZFS pool path as before (e.g. `tank/proxmox/disks`) |
|
||||
| Nodes | Select all cluster nodes |
|
||||
|
||||
3. Click **Add**. If the storage turns green and shows free/used space, you are connected.
|
||||
|
||||
**What is "Portal IP"?** Leave it blank for a typical setup with one TrueNAS box on one network. Only fill this in if your TrueNAS has separate management and iSCSI data network interfaces and you want iSCSI traffic on a specific IP.
|
||||
|
||||
---
|
||||
|
||||
## Step 4 — Move your VM disks
|
||||
|
||||
For each VM disk, use Proxmox's Move Disk function. VMs can stay powered on. Each move copies the disk data, then cuts over — you may notice a brief I/O pause (typically under a second) at the moment of cutover. For database or latency-sensitive workloads, consider scheduling moves during low-traffic windows.
|
||||
|
||||
1. Click the VM → **Hardware** tab
|
||||
2. Click a disk (e.g. `scsi0`) → **Move Disk** in the top toolbar
|
||||
3. Set **Target Storage** to `truenas-v3`
|
||||
4. **Delete source:** if checked, Proxmox will automatically delete the old zvol immediately after the copy finishes and the VM switches to the new disk. If unchecked, both zvols remain — you can delete the old one manually from TrueNAS later. When in doubt, leave unchecked and delete manually after verifying the VM works.
|
||||
5. Click **Move Disk** and wait for the task to complete before starting the next move
|
||||
|
||||
**Capacity note:** Each move requires free pool space equal to the size of the disk being moved. If you move one disk at a time and check "Delete source," you only need space for one disk. If you run moves in parallel, multiply accordingly.
|
||||
|
||||
**Timing:** A 100 GB disk over a 1 Gbps link takes roughly 10–15 minutes. Plan accordingly for large disks.
|
||||
|
||||
Repeat for every disk on every VM. EFI disks, CloudInit drives, and data disks all follow the same process. Skip `tpmstate0` disks — handle those separately as described above.
|
||||
|
||||
---
|
||||
|
||||
## Step 5 — Verify and clean up
|
||||
|
||||
After moving all disks for a VM:
|
||||
|
||||
1. **Check the Hardware tab** — all disks should reference `truenas-v3`
|
||||
2. **Restart the VM** (or stop/start) and confirm it boots normally
|
||||
3. **Check TrueNAS** — go to Sharing → iSCSI → Targets. You should see one target named `proxmox-vm-<vmid>` for each migrated VM, with the correct number of LUNs
|
||||
|
||||
Once all VMs are migrated:
|
||||
|
||||
1. Confirm no disks reference the old v2.x storage: run this on any Proxmox node and expect no output:
|
||||
```bash
|
||||
grep -r 'old-storage-id:' /etc/pve/nodes/*/qemu-server/ /etc/pve/nodes/*/lxc/ 2>/dev/null
|
||||
```
|
||||
*(Replace `old-storage-id` with your actual v2 storage ID)*
|
||||
|
||||
2. Go to **Datacenter → Storage**, select the old v2.x storage, click **Remove**
|
||||
|
||||
3. Clean up any leftover iSCSI extents or targets on TrueNAS that were not auto-removed (see [migration-troubleshooting.md](migration-troubleshooting.md))
|
||||
|
||||
---
|
||||
|
||||
## Frequently asked questions
|
||||
|
||||
**Can I run v2.x and v3.0 at the same time?**
|
||||
Yes — both storage entries coexist in Proxmox during migration. Remove the v2.x entry only after all disks are moved.
|
||||
|
||||
**What if something goes wrong — can I go back?**
|
||||
Yes. The original disk is not deleted until the move fully completes (and only if you checked "Delete source"). If a move fails, the VM is still running on its original disk. You can also move disks back from v3.0 to v2.x storage using the same Move Disk process in reverse.
|
||||
|
||||
**Will my VMs have downtime?**
|
||||
No shutdown required. Move Disk with a running VM uses live storage migration. Expect a brief I/O pause (typically under a second) at cutover.
|
||||
|
||||
**What happened to my block devices in `/dev/`?**
|
||||
In v3.0, QEMU talks to iSCSI directly — no kernel sessions are created, so no block devices appear on the Proxmox host. This is correct behavior. Tools like `iscsiadm` and `lsscsi` will not show your VM disks.
|
||||
|
||||
**What about snapshots on my old disks?**
|
||||
Proxmox-level snapshots taken via the v2.x plugin will not transfer. If you have important snapshot data, revert to a snapshot before migrating. ZFS-level snapshots on TrueNAS are unaffected.
|
||||
|
||||
**My old storage still shows orphaned extents on TrueNAS after removal.**
|
||||
See [Dangling resources after removal](migration-troubleshooting.md#dangling-resources-after-removal) in the troubleshooting guide.
|
||||
|
|
@ -0,0 +1,210 @@
|
|||
# Migration Advanced Reference: v2.x to v3.0
|
||||
|
||||
This document is for administrators who want a full understanding of what changes between v2.x and v3.0, how to plan large-scale migrations, and how to handle edge cases. For a step-by-step walkthrough, start with the [Migration Guide](migrating-from-v2.md).
|
||||
|
||||
---
|
||||
|
||||
## What changed architecturally
|
||||
|
||||
### v2.x: Host-managed iSCSI sessions
|
||||
|
||||
v2.x used `iscsiadm` on the Proxmox host to establish kernel-level iSCSI sessions. All VMs on a storage shared a small number of iSCSI targets, with each VM disk assigned to a different LUN number. Device nodes like `/dev/disk/by-path/ip-...-lun-0` appeared on the Proxmox host and were passed to QEMU as block devices.
|
||||
|
||||
**Consequences:**
|
||||
- iSCSI sessions visible on the host (`iscsiadm -m session`)
|
||||
- Block device nodes in `/dev/` on the Proxmox host
|
||||
- LUN limit per target (256 max)
|
||||
- Removing one disk from a running VM could disrupt other VMs on the same target
|
||||
- Depended on SSH keys from Proxmox nodes to TrueNAS for pool enumeration
|
||||
|
||||
### v3.0: Per-VM targets, QEMU-native iSCSI
|
||||
|
||||
v3.0 creates a dedicated iSCSI target for each VM (`proxmox-vm-<vmid>`) and passes an `iscsi://` URI directly to QEMU. QEMU's built-in libiscsi library opens the session — no kernel iSCSI daemon involved.
|
||||
|
||||
**Consequences:**
|
||||
- No iSCSI sessions visible on the Proxmox host
|
||||
- No block device nodes in `/dev/` — this is expected and correct
|
||||
- Each VM has exactly one target; all its disks are LUNs on that target
|
||||
- Removing a disk from one VM never affects another VM's connection
|
||||
- No SSH keys required — the plugin communicates only via TrueNAS REST API
|
||||
|
||||
The `iscsi://` URI format stored in the VM config:
|
||||
```
|
||||
iscsi://<portal_ip>/<iqn>:<target_name>/<lun_id>
|
||||
```
|
||||
Example:
|
||||
```
|
||||
iscsi://192.168.10.50/iqn.2005-10.org.freenas.ctl:proxmox-vm-100/0
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Configuration field mapping
|
||||
|
||||
| v2.x field | v3.0 field | Notes |
|
||||
|-----------|-----------|-------|
|
||||
| `type freenas` / `iscsiprovider freenas` | `type truenas` | New storage plugin type |
|
||||
| `freenas_user` | *(removed)* | Basic auth no longer supported |
|
||||
| `freenas_password` | *(removed)* | Basic auth no longer supported |
|
||||
| `truenas_secret` | `truenas_api_key` | Same concept; renamed |
|
||||
| `portal` | `truenas_portal_ip` | Optional; defaults to `truenas_host` |
|
||||
| `target` | `truenas_target` | Optional; base IQN for auto-discovery |
|
||||
| `pool` | `truenas_pool` | Same ZFS pool path |
|
||||
| — | `truenas_dataset` | New: optional sub-dataset within the pool |
|
||||
| `truenas_ssl` | `truenas_ssl` | Unchanged (default: 1) |
|
||||
| `truenas_ssl_verify` | `truenas_ssl_verify` | Unchanged (default: 0) |
|
||||
|
||||
A v2.x storage config block like:
|
||||
```
|
||||
freenas: old-storage
|
||||
freenas_user admin
|
||||
freenas_password secret
|
||||
portal 192.168.10.50
|
||||
pool tank/proxmox/disks
|
||||
truenas_ssl 1
|
||||
truenas_ssl_verify 0
|
||||
```
|
||||
|
||||
Becomes in v3.0:
|
||||
```
|
||||
truenas: truenas-v3
|
||||
truenas_host 192.168.10.50
|
||||
truenas_api_key 1-AbCdEf...
|
||||
truenas_pool tank/proxmox/disks
|
||||
truenas_ssl 1
|
||||
truenas_ssl_verify 0
|
||||
nodes pve01,pve02,pve03
|
||||
shared 1
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## HA (High Availability) considerations
|
||||
|
||||
If any VM is managed by Proxmox HA, disable HA for that VM before migrating its disks:
|
||||
|
||||
1. Datacenter → HA → Resources → select the VM → Remove (or set to disabled)
|
||||
2. Migrate the disks
|
||||
3. Re-enable HA after migration is complete
|
||||
|
||||
If a node fences (reboots unexpectedly) while Move Disk is in progress, the HA manager may attempt to restart the VM on another node. The VM config may still reference the old disk at that point. Check the VM config after recovery and re-run the migration if needed. The original disk is always preserved until Move Disk fully completes.
|
||||
|
||||
---
|
||||
|
||||
## Single portal — single point of failure
|
||||
|
||||
v3.0 does not support iSCSI multipath. QEMU's libiscsi opens one connection to the portal IP and retries on failure but does not fail over to a second IP.
|
||||
|
||||
If your TrueNAS has multiple network interfaces or portal IPs, `truenas_portal_ip` selects exactly one. **All Proxmox nodes must be able to reach that IP on port 3260.** A failure of that interface will cause all VMs on that storage to lose disk access.
|
||||
|
||||
Options for resilience:
|
||||
- Use a floating/virtual IP at the network layer (bond, VRRP/CARP)
|
||||
- Ensure the portal IP is on a highly available network segment
|
||||
- Multipath support is tracked for a future release
|
||||
|
||||
---
|
||||
|
||||
## Running v2.x and v3.0 in parallel
|
||||
|
||||
Both storage entries can coexist in `/etc/pve/storage.cfg`. Proxmox routes disk allocation to whichever storage is specified — the two plugins do not interfere with each other. This allows a rolling, VM-by-VM migration without any maintenance window.
|
||||
|
||||
Recommended approach for clusters with many VMs:
|
||||
1. Install v3.0 on all nodes first (required before adding the new storage type)
|
||||
2. Add the v3.0 storage entry
|
||||
3. Migrate VMs in batches — test one or two first before proceeding
|
||||
4. Remove the v2.x storage entry only after confirming the last VM is migrated
|
||||
|
||||
---
|
||||
|
||||
## Planning for large deployments
|
||||
|
||||
**Capacity:** Move Disk creates a full copy of the zvol before deleting the old one. You need free pool space equal to the size of one disk at a time (or however many moves run concurrently). If free space is tight, migrate and verify one disk at a time with "Delete source" checked before starting the next.
|
||||
|
||||
**Concurrency:** Proxmox allows multiple Move Disk operations in parallel, but each operation is I/O-intensive. Limit concurrency to 2–3 simultaneous moves and **stagger start times by 5–10 minutes** — if multiple moves reach the cutover phase at the same time, multiple VMs will see simultaneous I/O pauses. Monitor TrueNAS pool load during migrations: `zpool iostat 1`. If the pool is above 60–70% utilization, reduce concurrency.
|
||||
|
||||
**Network:** Move Disk transfers data through the Proxmox host, not directly on TrueNAS. Traffic path: TrueNAS → iSCSI → Proxmox host RAM → iSCSI → TrueNAS. For a 1 Gbps link, budget ~10–15 minutes per 100 GB.
|
||||
|
||||
**Verification checklist per VM:**
|
||||
|
||||
- [ ] All disks listed in Hardware tab reference the new storage
|
||||
- [ ] VM boots cleanly from the new storage
|
||||
- [ ] `qm config <vmid>` shows no references to the old storage ID
|
||||
- [ ] TrueNAS: one iSCSI target named `proxmox-vm-<vmid>` exists with the correct number of LUNs
|
||||
- [ ] TrueNAS: old zvols from v2.x storage are gone (if Delete source was checked)
|
||||
|
||||
**Verification for the whole cluster when done:**
|
||||
|
||||
```bash
|
||||
# Find any VM configs still referencing the old storage ID (replace 'old-storage' with your v2 ID)
|
||||
grep -r 'old-storage:' /etc/pve/nodes/*/qemu-server/ /etc/pve/nodes/*/lxc/ 2>/dev/null
|
||||
```
|
||||
|
||||
No output means all disks are migrated.
|
||||
|
||||
---
|
||||
|
||||
## Why there is no in-place rename path
|
||||
|
||||
An in-place rename would require:
|
||||
1. Renaming each zvol on TrueNAS to match the new naming convention
|
||||
2. Rebuilding the iSCSI extent, target, and target-extent association under v3.0's per-VM model
|
||||
3. Rewriting the disk path in the Proxmox VM config from a block device to an `iscsi://` URI
|
||||
|
||||
Steps 2 and 3 require the VM to be offline and involve several API calls that can fail partway through. The Move Disk path is safer because Proxmox manages the cutover atomically and the VM can stay running. An offline rename script may be provided in a future release for large deployments where copying data is impractical.
|
||||
|
||||
---
|
||||
|
||||
## Rollback
|
||||
|
||||
If a Move Disk operation fails partway through:
|
||||
|
||||
1. The original disk is untouched — it is not deleted until the move completes successfully and you confirm
|
||||
2. The new (partial) zvol and any iSCSI objects created on TrueNAS may be left behind — clean these up manually:
|
||||
- On TrueNAS: **Sharing → iSCSI → Extents** — delete the orphaned extent
|
||||
- On TrueNAS: **Sharing → iSCSI → Targets** — delete the orphaned target (if no other disks for that VM exist)
|
||||
- On TrueNAS: **Storage → Pools → Browse** — delete the orphaned zvol
|
||||
3. The VM's config in Proxmox still references the original disk — it is unaffected
|
||||
|
||||
If you decide to roll back entirely to v2.x after partially migrating:
|
||||
- Disks still on v2.x storage will continue to work on v2.x
|
||||
- Disks already moved to v3.0 storage must stay on v3.0 storage — moving back is the same Move Disk process in reverse
|
||||
|
||||
---
|
||||
|
||||
## TPM state disks
|
||||
|
||||
VMs with vTPM enabled have a `tpmstate0` disk. QEMU requires this to be a local filesystem path — the `iscsi://` URI model that v3.0 uses is incompatible.
|
||||
|
||||
**If your v2.x storage used the kernel iSCSI path model (block device), tpmstate0 may have worked there.** In v3.0 it will not. Before migrating a VM with TPM:
|
||||
|
||||
1. Move the `tpmstate0` disk to `local-lvm` or NFS storage first
|
||||
2. Then migrate the remaining disks to v3.0 storage as normal
|
||||
|
||||
For live VM migration across cluster nodes (if needed), the `tpmstate0` disk must also be on shared filesystem storage (NFS, CephFS) — local-lvm does not support live migration.
|
||||
|
||||
---
|
||||
|
||||
## API key security
|
||||
|
||||
v2.x stored credentials in `/etc/pve/storage.cfg` in plain text. v3.0 supports the same (`truenas_api_key` in the config file), but also supports keyfiles stored outside the cluster config:
|
||||
|
||||
```bash
|
||||
# Store on each node (not synced across the cluster)
|
||||
mkdir -p /etc/pve/priv/truenas
|
||||
echo "1-YourKeyHere" > /etc/pve/priv/truenas/truenas-v3.key
|
||||
chmod 600 /etc/pve/priv/truenas/truenas-v3.key
|
||||
```
|
||||
|
||||
The plugin looks for `/etc/pve/priv/truenas/<storeid>.key` and prefers it over the config file value. This keeps the API key out of the cluster config database (which is readable by all cluster members).
|
||||
|
||||
---
|
||||
|
||||
## Cluster node considerations
|
||||
|
||||
The storage config in `/etc/pve/storage.cfg` is cluster-wide. The plugin must be installed on every node that has the storage in its `nodes` list. If a node is missing the plugin, `pvesm status` will show an error for that storage on that node.
|
||||
|
||||
Install order:
|
||||
1. Install `truenas-proxmox` on all nodes first
|
||||
2. Then add the storage entry (or it may fail on nodes that don't have the plugin yet)
|
||||
|
||||
The API key and any keyfiles must be present on every node individually — keyfiles in `/etc/pve/priv/` are not synced across the cluster.
|
||||
|
|
@ -0,0 +1,187 @@
|
|||
# Migration Troubleshooting
|
||||
|
||||
This guide covers problems specific to migrating from v2.x to v3.0. For general post-migration errors, see the troubleshooting section in the [README](../README.md).
|
||||
|
||||
---
|
||||
|
||||
## Move Disk fails immediately
|
||||
|
||||
**Symptom:** Task errors within seconds of starting Move Disk.
|
||||
|
||||
**Check the task log** (click the failed task in Proxmox):
|
||||
|
||||
| Error | Cause | Fix |
|
||||
|-------|-------|-----|
|
||||
| `TrueNAS API token not configured` | API key missing from the v3.0 storage config | Edit the storage in Datacenter → Storage and add the API key |
|
||||
| `Pool dataset '...' not found` | `truenas_pool` path doesn't exist on TrueNAS | Verify the pool path in the storage config matches exactly what TrueNAS shows |
|
||||
| `422 Unprocessable Entity` | API field validation error | Check TrueNAS version compatibility; enable logging (`journalctl -u pvedaemon -f`) for detail |
|
||||
| `SSL: certificate verify failed` | `truenas_ssl_verify 1` but certificate is self-signed | Set `truenas_ssl_verify 0` in storage config, or install a valid certificate |
|
||||
| `Connection refused` | Wrong host/IP or TrueNAS API not reachable | Verify host from the Proxmox node: `curl -k https://<host>/api/v2.0/system/info` |
|
||||
|
||||
---
|
||||
|
||||
## Move Disk hangs or times out
|
||||
|
||||
**Symptom:** Task starts, progress bar moves, but eventually fails with a timeout or connection error.
|
||||
|
||||
- **Network interruption:** iSCSI traffic is sensitive to packet loss. Check switch/VLAN configuration between Proxmox and TrueNAS.
|
||||
- **TrueNAS pool under stress:** If the pool is rebuilding (resilver in progress) or highly loaded, I/O will be slow. Check TrueNAS → Storage → Pools for any active operations.
|
||||
- **Large disk:** A 500 GB disk at 1 Gbps takes ~70 minutes. Proxmox's default task timeout may trigger. Check if the task actually completed on TrueNAS despite the Proxmox error.
|
||||
|
||||
After a failed move, check TrueNAS for a partial zvol:
|
||||
```
|
||||
Sharing → iSCSI → Extents # look for an extent with the new disk name
|
||||
Storage → Pools → Browse # look for a partial zvol
|
||||
```
|
||||
Delete any partial resources before retrying.
|
||||
|
||||
---
|
||||
|
||||
## VM won't start after Move Disk
|
||||
|
||||
**Symptom:** VM was running before migration, fails to start after the disk move completes.
|
||||
|
||||
**Check the VM config:**
|
||||
```bash
|
||||
qm config <vmid>
|
||||
```
|
||||
Look for the moved disk's line. It should show `iscsi://...` as the path, not a block device path.
|
||||
|
||||
**Check the iSCSI target exists on TrueNAS:**
|
||||
1. TrueNAS → Sharing → iSCSI → Targets — confirm `proxmox-vm-<vmid>` is present
|
||||
2. TrueNAS → Sharing → iSCSI → Extents — confirm the extent for each disk is present
|
||||
3. TrueNAS → Sharing → iSCSI → Associated Targets — confirm each extent is linked to the target at the correct LUN
|
||||
|
||||
If any of these are missing, the disk cannot be accessed. Re-run a Move Disk operation to recreate the iSCSI objects, or use the Proxmox task log to identify which step failed.
|
||||
|
||||
**Check the iSCSI portal is reachable:**
|
||||
```bash
|
||||
# From the Proxmox node, using qm's built-in check:
|
||||
qm start <vmid>
|
||||
# Read the full error in the task log — it often includes the iscsi:// URI
|
||||
```
|
||||
|
||||
If the error is `Failed to open drive ... iscsi://...`: the QEMU process cannot reach the iSCSI portal. Check:
|
||||
- `truenas_portal_ip` in storage config — should be the IP that Proxmox can reach TrueNAS on
|
||||
- Firewall rules between Proxmox and TrueNAS on port 3260
|
||||
- iSCSI service is running on TrueNAS (Sharing → iSCSI → Target Global Configuration → Enable)
|
||||
|
||||
---
|
||||
|
||||
## Disk shows wrong size or "undefined" after move
|
||||
|
||||
**Symptom:** The disk appears in the Hardware tab but shows "undefined" or an incorrect size.
|
||||
|
||||
This is usually transient — the Proxmox UI polls before the iSCSI setup is fully complete. Wait 30–60 seconds and refresh.
|
||||
|
||||
If it persists:
|
||||
|
||||
1. Check `volume_size_info` is returning a value:
|
||||
```bash
|
||||
pvesm volume_info <storeid> <volid>
|
||||
```
|
||||
Example: `pvesm volume_info truenas-v3 truenas-v3:vm-100-disk-0`
|
||||
|
||||
2. If this returns an error, the plugin cannot find the zvol via the TrueNAS API. Verify the zvol exists:
|
||||
```bash
|
||||
curl -sk -H "Authorization: Bearer <apikey>" \
|
||||
https://<truenas_host>/api/v2.0/pool/dataset/id/<pool>%2F<volname>
|
||||
```
|
||||
|
||||
3. If the zvol exists but `volume_size_info` fails, check for SSL or API connectivity issues in the daemon log:
|
||||
```bash
|
||||
journalctl -u pvedaemon --since '5 minutes ago' | grep TrueNAS
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Dangling resources after removal
|
||||
|
||||
**Symptom:** After removing the v2.x storage from Proxmox, TrueNAS still has old iSCSI extents, targets, or zvols that weren't cleaned up.
|
||||
|
||||
The v2.x plugin may not have removed these automatically. They are harmless but waste space and clutter the iSCSI config.
|
||||
|
||||
**To clean up manually on TrueNAS:**
|
||||
|
||||
1. **Extents** (Sharing → iSCSI → Extents): Delete any extents whose name matches old VM disk names
|
||||
2. **Target-Extent associations** (Sharing → iSCSI → Associated Targets): Delete associations for removed targets
|
||||
3. **Targets** (Sharing → iSCSI → Targets): Delete targets that no longer have any Proxmox VMs using them
|
||||
4. **Zvols** (Storage → Pools → Browse): Delete zvols that were not cleaned up (confirm the VM config no longer references them first)
|
||||
|
||||
Always verify a zvol is truly unused before deleting it — check `qm config` for all VMs.
|
||||
|
||||
---
|
||||
|
||||
## Old v2.x storage can't be removed from Proxmox
|
||||
|
||||
**Symptom:** "Remove" is greyed out or fails with "storage is still in use."
|
||||
|
||||
Proxmox blocks storage removal if any VM config still references it. Find the VMs:
|
||||
```bash
|
||||
grep -r 'old-storage:' /etc/pve/nodes/*/qemu-server/ /etc/pve/nodes/*/lxc/ 2>/dev/null
|
||||
```
|
||||
|
||||
For each match, move or remove the disk first. Common leftovers:
|
||||
- **Unused disks** (detached but not deleted): VM → Hardware tab, look for `unused0`, `unused1` etc. referencing the old storage
|
||||
- **CloudInit drives**: Move or delete the CloudInit drive (it will be regenerated)
|
||||
- **EFI disks**: Move via Move Disk the same as any other disk
|
||||
|
||||
---
|
||||
|
||||
## iSCSI service stops working after migration
|
||||
|
||||
**Symptom:** VMs that were running fine start failing with iSCSI errors after migration.
|
||||
|
||||
This is not a migration problem — it means the iSCSI service on TrueNAS is unhealthy. Check:
|
||||
|
||||
1. **TrueNAS → Sharing → iSCSI** — confirm the service shows as running
|
||||
2. **TrueNAS System Logs** — look for any iSCSI service errors around the time of failure
|
||||
3. **Proxmox node logs:**
|
||||
```bash
|
||||
dmesg | grep -i iscsi
|
||||
journalctl -u pvedaemon --since '30 minutes ago' | grep TrueNAS
|
||||
```
|
||||
|
||||
If TrueNAS rebooted or the iSCSI service was restarted, running VMs with iSCSI disks will lose connectivity. QEMU will retry the connection — VMs typically recover within 30–60 seconds if the service comes back quickly. For production workloads, configure TrueNAS to start the iSCSI service automatically on boot.
|
||||
|
||||
---
|
||||
|
||||
## Move Disk failure recovery matrix
|
||||
|
||||
If a Move Disk operation fails partway through, use this table to determine what to clean up and whether it is safe to retry:
|
||||
|
||||
| What failed | State on TrueNAS | VM state | Action |
|
||||
|-------------|-----------------|----------|--------|
|
||||
| Zvol copy (mid-copy) | Partial zvol exists | Running on original disk | Delete partial zvol on TrueNAS, retry Move Disk |
|
||||
| iSCSI extent creation | New zvol exists, no extent | Running on original disk | Delete new zvol on TrueNAS, retry Move Disk |
|
||||
| iSCSI target creation | New zvol + extent exist | Running on original disk | Delete extent and new zvol, retry Move Disk |
|
||||
| targetextent association | All iSCSI objects exist | Running on original disk | Delete targetextent, extent, new zvol, retry Move Disk |
|
||||
| VM config rewrite | All iSCSI objects exist, config not updated | Running on original disk | Verify VM is still on old disk (`qm config`); delete new iSCSI objects and zvol, retry |
|
||||
| Zvol deletion (Delete source) | New disk active, old zvol still present | Running on new disk | Verify VM config references new disk; delete old zvol manually with `zfs destroy` |
|
||||
| Node crash mid-copy | Partial zvol may exist | Will recover to original disk | After node recovers, check TrueNAS for partial zvol; delete if found; retry Move Disk |
|
||||
|
||||
The original disk is never modified or deleted during the copy phase. If anything fails before the VM config is rewritten, the VM will continue running on its original disk.
|
||||
|
||||
---
|
||||
|
||||
## API key revoked or expired
|
||||
|
||||
**Symptom:** Move Disk or disk creation fails with `401 Unauthorized` or `TrueNAS API token not configured`.
|
||||
|
||||
TrueNAS API keys can be revoked manually or expire under rotation policies. If this happens:
|
||||
1. Generate a new API key on TrueNAS (same process as initial setup)
|
||||
2. In Proxmox: Datacenter → Storage → select the storage → Edit → update the API Key field
|
||||
3. Retry the failed operation
|
||||
|
||||
---
|
||||
|
||||
## Getting help
|
||||
|
||||
If you hit a problem not covered here:
|
||||
|
||||
1. Collect the relevant log output:
|
||||
```bash
|
||||
journalctl -u pvedaemon --since '1 hour ago' | grep -i truenas > /tmp/pvedaemon.log
|
||||
```
|
||||
2. Note your versions: `dpkg -l truenas-proxmox`, PVE version (`pveversion`), TrueNAS version
|
||||
3. Open an issue at https://github.com/TheGrandWazoo/freenas-proxmox/issues with the log and version info
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
# Upgrade Paths
|
||||
|
||||
Quick reference for every migration scenario. Find your current version and repo source in the table, then follow the link for step-by-step instructions.
|
||||
|
||||
---
|
||||
|
||||
## Where am I, and where do I go?
|
||||
|
||||
| Current version | Current repo | → Stay on current major | → Move to v3.x | → Move to v4.x |
|
||||
|---|---|---|---|---|
|
||||
| **v2.x** | Cloudsmith stable | [Swap repo to GitHub Pages `main`](#cloudsmith-to-github-pages) | [Swap repo + config migration](migrating-from-v2.md) | TBD when v4 ships — will require v3 config first |
|
||||
| **v2.x** | GitHub Pages `main` | Already on correct repo ✅ | [Config migration only](migrating-from-v2.md) | TBD when v4 ships — will require v3 config first |
|
||||
| **v3.x** | Cloudsmith stable | [Swap repo to GitHub Pages `v3`](#cloudsmith-to-github-pages) | Already on v3 — swap repo only | [Change dist track to `v4` + v3→v4 guide](#future-v4) |
|
||||
| **v3.x** | GitHub Pages `v3` | Already on correct repo ✅ | Already here ✅ | [Change dist track to `v4` + v3→v4 guide](#future-v4) |
|
||||
|
||||
**Not sure which version you have?** Run this on any Proxmox node:
|
||||
```bash
|
||||
dpkg -l truenas-proxmox freenas-proxmox 2>/dev/null | grep '^ii'
|
||||
```
|
||||
|
||||
**Not sure which repo you're using?**
|
||||
```bash
|
||||
cat /etc/apt/sources.list.d/truenas-proxmox*.list /etc/apt/sources.list.d/freenas-proxmox*.list 2>/dev/null
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Cloudsmith → GitHub Pages {#cloudsmith-to-github-pages}
|
||||
|
||||
> Cloudsmith is being phased out. GitHub Pages is the permanent home for this project's packages. Your installed package is not affected — this only changes where future updates come from.
|
||||
|
||||
Full instructions are in the [README — Migrating from Cloudsmith](../README.md#migrating-from-cloudsmith) section.
|
||||
|
||||
In short:
|
||||
1. Remove your existing Cloudsmith source file from `/etc/apt/sources.list.d/`
|
||||
2. Remove the old Cloudsmith keyring from `/usr/share/keyrings/`
|
||||
3. Import the GitHub Pages GPG key to `/etc/apt/keyrings/truenas-proxmox.gpg`
|
||||
4. Add the GitHub Pages source pointing at the right dist track (`main` for v2, `v3` for v3)
|
||||
5. Run `apt update`
|
||||
|
||||
---
|
||||
|
||||
## What changes between major versions
|
||||
|
||||
| | v2.x → v3.x | v3.x → v4.x (future) |
|
||||
|---|---|---|
|
||||
| **Breaking change?** | Yes — auth model, iSCSI model, package name | Yes — WebSocket API replaces REST |
|
||||
| **In-place upgrade?** | No — VM disks must be moved | TBD |
|
||||
| **Plugin restarts VMs?** | No — Move Disk is live | TBD |
|
||||
| **Config changes?** | Yes — API key replaces username/password | TBD |
|
||||
| **Full guide** | [migrating-from-v2.md](migrating-from-v2.md) | Will be added when v4 ships |
|
||||
|
||||
---
|
||||
|
||||
## v2.x → v3.x
|
||||
|
||||
See [migrating-from-v2.md](migrating-from-v2.md) for the full step-by-step guide.
|
||||
|
||||
High-level steps:
|
||||
1. Install `truenas-proxmox` on every Proxmox node (from GitHub Pages `v3` dist)
|
||||
2. Create a TrueNAS API key
|
||||
3. Add a new v3 storage entry in Proxmox (keep the old v2 entry running alongside)
|
||||
4. Move VM disks one at a time using Proxmox Move Disk (VMs stay running)
|
||||
5. Remove the old v2 storage entry once all disks are moved
|
||||
|
||||
**Do not run `apt upgrade` from a v2 Cloudsmith install without reading the migration guide first.** v3.0 is a breaking change — your storage will need reconfiguration.
|
||||
|
||||
---
|
||||
|
||||
## v3.x → v4.x (future) {#future-v4}
|
||||
|
||||
v4.0 will replace the TrueNAS REST API with the WebSocket JSON-RPC 2.0 API introduced in TrueNAS SCALE 25.04. This is an architectural change and will be a major version with its own migration guide.
|
||||
|
||||
When v4.0 ships, this section will be updated with:
|
||||
- What changes in v4.0 (API model, config fields, compatibility matrix)
|
||||
- Whether an in-place upgrade is possible
|
||||
- Step-by-step migration from v3.x
|
||||
- Step-by-step migration from v2.x (if a direct v2→v4 path is supported)
|
||||
|
||||
**To receive v4.x packages when they ship**, you will need to change your `sources.list` dist track from `v3` to `v4`:
|
||||
|
||||
```bash
|
||||
# When v4.0 is released — do NOT run this until the v4 migration guide is published
|
||||
echo "deb [signed-by=/etc/apt/keyrings/truenas-proxmox.gpg] \
|
||||
https://thegrandwazoo.github.io/freenas-proxmox v4 main" \
|
||||
| sudo tee /etc/apt/sources.list.d/truenas-proxmox.list
|
||||
sudo apt update
|
||||
```
|
||||
|
||||
`apt upgrade` on the `v3` dist track will never pull in a v4 package — you must explicitly change the dist track. This is intentional.
|
||||
|
||||
---
|
||||
|
||||
## Dist track reference
|
||||
|
||||
| Dist track | Codename alias | Contains | Who should use it |
|
||||
|---|---|---|---|
|
||||
| `main` | `limelight` | v2.x packages only | Users staying on v2.x |
|
||||
| `v2` | `limelight` | v2.x packages (alias for `main`) | Same as `main` — explicit pin |
|
||||
| `v3` | `error` | v3.x packages only | All current v3 installs |
|
||||
| `v4` | `rivendell` | v4.x packages only | Added when v4.0 ships |
|
||||
|
||||
Both the dist track (`v3`) and its codename alias (`error`) work in `sources.list` — they resolve to the same packages.
|
||||
|
||||
Packages on one dist track are never promoted to another. Changing major versions always requires an explicit `sources.list` edit.
|
||||
|
||||
> **Existing v3 users — one-time prompt after v3.2.2**
|
||||
> If `apt update` reports `changed its 'Codename' value from 'v3' to 'error'`, run:
|
||||
> ```bash
|
||||
> apt update --allow-releaseinfo-change
|
||||
> ```
|
||||
> This is a one-time acknowledgment of the codename being added in v3.2.2. It will not recur.
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
truenas-proxmox-multipath (3.2.2-1) bookworm; urgency=low
|
||||
|
||||
* Version bump to match truenas-proxmox 3.2.2; api() fix inherited from TrueNAS.pm
|
||||
|
||||
-- KSA Technologies, LLC <theprofessor@ksatechnologies.com> Mon, 23 Jun 2026 00:00:00 +0000
|
||||
|
||||
truenas-proxmox-multipath (3.2.1-1) bookworm; urgency=low
|
||||
|
||||
* Version bump to match truenas-proxmox 3.2.1; no functional changes
|
||||
|
||||
-- KSA Technologies, LLC <theprofessor@ksatechnologies.com> Sun, 22 Jun 2026 00:00:00 +0000
|
||||
|
||||
truenas-proxmox-multipath (3.1.0-1) bookworm; urgency=low
|
||||
|
||||
* Initial release — kernel iSCSI multipath via iscsiadm + dm-multipath
|
||||
* Registers 'truenas-multipath' PVE storage type
|
||||
* Inherits all REST API and iSCSI resource management from truenas-proxmox
|
||||
* path() returns /dev/mapper/<wwid> for QEMU block device access
|
||||
* activate_volume() logs in via all configured portals
|
||||
* Distributed via the 'multipath' apt component
|
||||
|
||||
-- KSA Technologies, LLC <theprofessor@ksatechnologies.com> Sun, 07 Jun 2026 00:00:00 +0000
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
Package: truenas-proxmox-multipath
|
||||
Version: ${VERSION}
|
||||
Architecture: all
|
||||
Maintainer: KSA Technologies, LLC <theprofessor@ksatechnologies.com>
|
||||
Depends: truenas-proxmox (>= 3.1.0), open-iscsi, multipath-tools
|
||||
Section: perl
|
||||
Priority: optional
|
||||
Homepage: https://github.com/TheGrandWazoo/truenas-proxmox
|
||||
Description: TrueNAS Multipath Storage Plugin for Proxmox VE
|
||||
Extends truenas-proxmox with kernel iSCSI multipath support via iscsiadm
|
||||
and dm-multipath. Registers a separate 'truenas-multipath' storage type
|
||||
that presents /dev/mapper/<wwid> block devices to QEMU instead of
|
||||
iscsi:// URIs, enabling active/active multipath with failover.
|
||||
.
|
||||
Requires two or more network paths between Proxmox and TrueNAS and
|
||||
TrueNAS iSCSI portals configured on each path.
|
||||
.
|
||||
Licensed under the GNU Affero General Public License v3 (AGPL-3.0).
|
||||
Copyright (c) 2020 KSA Technologies, LLC.
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
# /etc/multipath.conf — reference for truenas-proxmox-multipath
|
||||
#
|
||||
# postinst installs this automatically if /etc/multipath.conf does not exist.
|
||||
# If you already have a multipath.conf, postinst appends the tagged block below
|
||||
# instead of overwriting your file.
|
||||
#
|
||||
# The BEGIN/END markers let postinst update the stanza on upgrade and postrm
|
||||
# strip it on remove without touching the rest of your config.
|
||||
|
||||
defaults {
|
||||
find_multipaths yes
|
||||
}
|
||||
|
||||
# BEGIN truenas-proxmox-multipath
|
||||
# Managed by truenas-proxmox-multipath. Do not edit between these markers.
|
||||
# To customise, remove the package tags and maintain the block yourself.
|
||||
devices {
|
||||
device {
|
||||
vendor "TrueNAS"
|
||||
product "iSCSI Disk"
|
||||
path_grouping_policy multibus
|
||||
path_checker tur
|
||||
failback immediate
|
||||
rr_weight uniform
|
||||
no_path_retry 5
|
||||
}
|
||||
}
|
||||
# END truenas-proxmox-multipath
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
#!/bin/bash
|
||||
# postinst: truenas-proxmox-multipath install/upgrade script
|
||||
set -e
|
||||
|
||||
INSTALL_DIR="/usr/share/truenas-proxmox-multipath"
|
||||
PLUGIN_DST="/usr/share/perl5/PVE/Storage/Custom/TrueNASMultipath.pm"
|
||||
JS_DST="/usr/share/pve-manager/js/truenas-multipath.js"
|
||||
TPL="/usr/share/pve-manager/index.html.tpl"
|
||||
SCRIPT_TAG=' <script type="text/javascript" src="/pve2/js/truenas-multipath.js?ver=[% version %]"></script>'
|
||||
MULTIPATH_CONF="/etc/multipath.conf"
|
||||
LOG_FILE="/var/log/truenas-proxmox-install.log"
|
||||
|
||||
log() {
|
||||
echo "[truenas-proxmox-multipath] $*" | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
install_plugin() {
|
||||
log "Installing ${PLUGIN_DST}"
|
||||
mkdir -p "$(dirname "$PLUGIN_DST")"
|
||||
cp "${INSTALL_DIR}/TrueNASMultipath.pm" "$PLUGIN_DST"
|
||||
}
|
||||
|
||||
install_ui() {
|
||||
log "Installing ${JS_DST}"
|
||||
cp "${INSTALL_DIR}/truenas-multipath.js" "$JS_DST"
|
||||
|
||||
if grep -qF 'truenas-multipath.js' "$TPL" 2>/dev/null; then
|
||||
log "index.html.tpl already has truenas-multipath.js — skipping"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log "Adding <script> tag to ${TPL}"
|
||||
sed -i "/pvemanagerlib\.js/a\\${SCRIPT_TAG}" "$TPL"
|
||||
}
|
||||
|
||||
BLOCK_BEGIN="# BEGIN truenas-proxmox-multipath"
|
||||
BLOCK_END="# END truenas-proxmox-multipath"
|
||||
|
||||
# Appends or replaces the tagged TrueNAS device stanza in multipath.conf.
|
||||
# Safe to call on upgrade — idempotent via BEGIN/END markers.
|
||||
install_multipath_conf() {
|
||||
local stanza
|
||||
stanza=$(cat <<STANZA
|
||||
${BLOCK_BEGIN}
|
||||
# Managed by truenas-proxmox-multipath. Do not edit between these markers.
|
||||
# To customise, remove the package tags and maintain the block yourself.
|
||||
devices {
|
||||
device {
|
||||
vendor "TrueNAS"
|
||||
product "iSCSI Disk"
|
||||
path_grouping_policy multibus
|
||||
path_checker tur
|
||||
failback immediate
|
||||
rr_weight uniform
|
||||
no_path_retry 5
|
||||
}
|
||||
}
|
||||
${BLOCK_END}
|
||||
STANZA
|
||||
)
|
||||
|
||||
if [ ! -f "$MULTIPATH_CONF" ]; then
|
||||
log "Creating ${MULTIPATH_CONF} with defaults + TrueNAS device stanza"
|
||||
printf 'defaults {\n find_multipaths yes\n}\n\n%s\n' "$stanza" > "$MULTIPATH_CONF"
|
||||
elif grep -qF "$BLOCK_BEGIN" "$MULTIPATH_CONF"; then
|
||||
log "Updating existing TrueNAS stanza in ${MULTIPATH_CONF}"
|
||||
# Replace the block between markers (inclusive) with the new stanza
|
||||
perl -i -0777 -pe \
|
||||
"s|\Q${BLOCK_BEGIN}\E.*?\Q${BLOCK_END}\E|${stanza}|s" \
|
||||
"$MULTIPATH_CONF"
|
||||
else
|
||||
log "Appending TrueNAS device stanza to existing ${MULTIPATH_CONF}"
|
||||
# Strip any pre-existing unmanaged devices block to prevent duplicate
|
||||
# keyword warnings in multipathd (e.g. user had a manual block before install).
|
||||
if grep -qE '^\s*devices\s*\{' "$MULTIPATH_CONF"; then
|
||||
log "Removing pre-existing unmanaged devices block from ${MULTIPATH_CONF}"
|
||||
perl -i -0777 -pe \
|
||||
's/\n*\ndevices\s*\{(?:[^{}]*|\{[^{}]*\})*\}\n*//gs' \
|
||||
"$MULTIPATH_CONF"
|
||||
fi
|
||||
printf '\n%s\n' "$stanza" >> "$MULTIPATH_CONF"
|
||||
fi
|
||||
|
||||
systemctl enable multipathd 2>/dev/null || true
|
||||
systemctl start multipathd 2>/dev/null || true
|
||||
log "multipathd enabled and started"
|
||||
}
|
||||
|
||||
restart_pve_services() {
|
||||
log "Restarting Proxmox VE services ..."
|
||||
pvedaemon restart && log "pvedaemon restarted"
|
||||
pveproxy restart && log "pveproxy restarted"
|
||||
log "Done. Refresh your Proxmox browser tab."
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
configure)
|
||||
log "Configuring truenas-proxmox-multipath (previous version: ${2:-none})"
|
||||
install_plugin
|
||||
install_ui
|
||||
install_multipath_conf
|
||||
restart_pve_services
|
||||
;;
|
||||
abort-upgrade|abort-remove|abort-deconfigure)
|
||||
;;
|
||||
*)
|
||||
echo "$0: called with unknown argument '$1'" >&2
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
|
||||
exit 0
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
#!/bin/bash
|
||||
# postrm: truenas-proxmox-multipath remove/purge script
|
||||
set -e
|
||||
|
||||
PLUGIN_DST="/usr/share/perl5/PVE/Storage/Custom/TrueNASMultipath.pm"
|
||||
JS_DST="/usr/share/pve-manager/js/truenas-multipath.js"
|
||||
TPL="/usr/share/pve-manager/index.html.tpl"
|
||||
MULTIPATH_CONF="/etc/multipath.conf"
|
||||
BLOCK_BEGIN="# BEGIN truenas-proxmox-multipath"
|
||||
BLOCK_END="# END truenas-proxmox-multipath"
|
||||
LOG_FILE="/var/log/truenas-proxmox-install.log"
|
||||
|
||||
log() {
|
||||
echo "[truenas-proxmox-multipath] $*" | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
remove|purge)
|
||||
if [ -f "$PLUGIN_DST" ]; then
|
||||
log "Removing ${PLUGIN_DST}"
|
||||
rm -f "$PLUGIN_DST"
|
||||
fi
|
||||
|
||||
if [ -f "$JS_DST" ]; then
|
||||
log "Removing ${JS_DST}"
|
||||
rm -f "$JS_DST"
|
||||
fi
|
||||
|
||||
if [ -f "$TPL" ] && grep -qF 'truenas-multipath.js' "$TPL" 2>/dev/null; then
|
||||
log "Removing <script> tag from ${TPL}"
|
||||
sed -i '/truenas-multipath\.js/d' "$TPL"
|
||||
fi
|
||||
|
||||
if [ -f "$MULTIPATH_CONF" ] && grep -qF "$BLOCK_BEGIN" "$MULTIPATH_CONF"; then
|
||||
log "Removing TrueNAS stanza from ${MULTIPATH_CONF}"
|
||||
perl -i -0777 -pe \
|
||||
"s|\n?\Q${BLOCK_BEGIN}\E.*?\Q${BLOCK_END}\E\n?||s" \
|
||||
"$MULTIPATH_CONF"
|
||||
fi
|
||||
|
||||
log "Restarting Proxmox VE services ..."
|
||||
if pvedaemon restart 2>/dev/null; then log "pvedaemon restarted"; fi
|
||||
if pveproxy restart 2>/dev/null; then log "pveproxy restarted"; fi
|
||||
log "Done."
|
||||
;;
|
||||
upgrade|failed-upgrade|abort-install|abort-upgrade|disappear)
|
||||
;;
|
||||
*)
|
||||
echo "$0: called with unknown argument '$1'" >&2
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
|
||||
exit 0
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
freenas-proxmox (3.2.2-1) bookworm; urgency=low
|
||||
|
||||
* Version bump to match truenas-proxmox 3.2.2; no functional changes
|
||||
|
||||
-- KSA Technologies, LLC <theprofessor@ksatechnologies.com> Mon, 23 Jun 2026 00:00:00 +0000
|
||||
|
||||
freenas-proxmox (3.2.1-1) bookworm; urgency=low
|
||||
|
||||
* Version bump to match truenas-proxmox 3.2.1; no functional changes
|
||||
|
||||
-- KSA Technologies, LLC <theprofessor@ksatechnologies.com> Sun, 22 Jun 2026 00:00:00 +0000
|
||||
|
||||
freenas-proxmox (3.0.0-1) bookworm; urgency=low
|
||||
|
||||
* Transitional package — installs truenas-proxmox automatically
|
||||
* The freenas-proxmox package name is retired; truenas-proxmox is the successor
|
||||
|
||||
-- KSA Technologies, LLC <theprofessor@ksatechnologies.com> Sun, 31 May 2026 00:00:00 +0000
|
||||
|
||||
freenas-proxmox (2.4.0-1) bookworm; urgency=low
|
||||
|
||||
* Quality of life improvements and minor fixes
|
||||
|
||||
-- KSA Technologies, LLC <theprofessor@ksatechnologies.com> Fri, 22 May 2026 00:00:00 +0000
|
||||
|
||||
freenas-proxmox (2.3.0-1) bookworm; urgency=medium
|
||||
|
||||
* Hardening and stability improvements
|
||||
* Bearer token authentication support
|
||||
|
||||
-- KSA Technologies, LLC <theprofessor@ksatechnologies.com> Mon, 19 May 2026 00:00:00 +0000
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
Package: freenas-proxmox
|
||||
Version: ${VERSION}
|
||||
Architecture: all
|
||||
Maintainer: KSA Technologies, LLC <theprofessor@ksatechnologies.com>
|
||||
Depends: truenas-proxmox (>= 3.0.0)
|
||||
Section: perl
|
||||
Priority: optional
|
||||
Homepage: https://github.com/TheGrandWazoo/truenas-proxmox
|
||||
Description: Transitional package — replaced by truenas-proxmox
|
||||
This is an empty transitional package. The plugin has been renamed from
|
||||
freenas-proxmox to truenas-proxmox. Installing this package pulls in the
|
||||
real package automatically.
|
||||
.
|
||||
To fully complete migration: apt install truenas-proxmox && apt purge freenas-proxmox
|
||||
.
|
||||
The freenas-proxmox package name will not exist in v4.0.
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
#!/bin/bash
|
||||
# postinst: freenas-proxmox transitional package
|
||||
# This package is empty — the real plugin is in truenas-proxmox.
|
||||
set -e
|
||||
|
||||
LOG_FILE="/var/log/truenas-proxmox-install.log"
|
||||
|
||||
log() {
|
||||
echo "[freenas-proxmox] $*" | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
configure)
|
||||
log "freenas-proxmox is now a transitional package."
|
||||
log "The plugin has moved to: truenas-proxmox"
|
||||
log "To complete migration: apt install truenas-proxmox && apt purge freenas-proxmox"
|
||||
log "Note: freenas-proxmox will not exist in v4.0."
|
||||
;;
|
||||
abort-upgrade|abort-remove|abort-deconfigure)
|
||||
;;
|
||||
*)
|
||||
echo "$0: called with unknown argument '$1'" >&2
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
|
||||
exit 0
|
||||
|
|
@ -1,15 +1,18 @@
|
|||
Package: freenas-proxmox
|
||||
Package: truenas-proxmox
|
||||
Version: ${VERSION}
|
||||
Architecture: all
|
||||
Maintainer: KSA Technologies, LLC <theprofessor@ksatechnologies.com>
|
||||
Depends: librest-client-perl, patch
|
||||
Depends: librest-client-perl, open-iscsi
|
||||
Replaces: freenas-proxmox
|
||||
Breaks: freenas-proxmox (<< 3.0.0)
|
||||
Section: perl
|
||||
Priority: optional
|
||||
Homepage: https://github.com/TheGrandWazoo/freenas-proxmox
|
||||
Description: TrueNAS ZFS-over-iSCSI Plugin for Proxmox VE
|
||||
Manages iSCSI LUNs on TrueNAS (CORE and SCALE) via the TrueNAS REST API.
|
||||
Supports Bearer Token and username/password authentication.
|
||||
No SSH-based LUN management required.
|
||||
Homepage: https://github.com/TheGrandWazoo/truenas-proxmox
|
||||
Description: TrueNAS Custom Storage Plugin for Proxmox VE
|
||||
Native PVE::Storage::Custom plugin for managing TrueNAS ZFS volumes over iSCSI.
|
||||
Discovered automatically by Proxmox VE — no patches to PVE system files required.
|
||||
Supports TrueNAS CORE and SCALE via the TrueNAS REST API with Bearer Token auth.
|
||||
No SSH keys required.
|
||||
.
|
||||
Licensed under the GNU Affero General Public License v3 (AGPL-3.0).
|
||||
Copyright (c) 2020 KSA Technologies, LLC.
|
||||
|
|
|
|||
|
|
@ -1,212 +1,63 @@
|
|||
#!/bin/bash
|
||||
# postinst: freenas-proxmox install/upgrade/trigger script
|
||||
# Applies patches to Proxmox VE system files and installs the TrueNAS API plugin.
|
||||
# No internet access required — all files are bundled in the package.
|
||||
# postinst: truenas-proxmox v3.x install/upgrade script
|
||||
# Installs the TrueNAS custom storage plugin and UI panel for Proxmox VE.
|
||||
# No patches to PVE core files — only adds our own files and one <script> line.
|
||||
set -e
|
||||
|
||||
INSTALL_DIR="/usr/share/freenas-proxmox"
|
||||
LIB_PATH="/usr/share"
|
||||
LOG_FILE="/var/log/freenas-proxmox-install.log"
|
||||
|
||||
ZFSPLUGIN_PATH="/perl5/PVE/Storage/ZFSPlugin.pm"
|
||||
PVEMANAGER_PATH="/pve-manager/js/pvemanagerlib.js"
|
||||
APIDOC_PATH="/pve-docs/api-viewer/apidoc.js"
|
||||
FREENAS_PM_PATH="/perl5/PVE/Storage/LunCmd/FreeNAS.pm"
|
||||
REST_CLIENT_PATH="/perl5/REST/Client.pm"
|
||||
INSTALL_DIR="/usr/share/truenas-proxmox"
|
||||
PLUGIN_DST="/usr/share/perl5/PVE/Storage/Custom/TrueNAS.pm"
|
||||
JS_DST="/usr/share/pve-manager/js/truenas-storage.js"
|
||||
HELP_DST="/usr/share/pve-docs/truenas-storage.html"
|
||||
TPL="/usr/share/pve-manager/index.html.tpl"
|
||||
SCRIPT_TAG=' <script type="text/javascript" src="/pve2/js/truenas-storage.js?ver=[% version %]"></script>'
|
||||
LOG_FILE="/var/log/truenas-proxmox-install.log"
|
||||
|
||||
log() {
|
||||
echo "[freenas-proxmox] $*" | tee -a "$LOG_FILE"
|
||||
echo "[truenas-proxmox] $*" | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
# Detect installed Proxmox VE major version (e.g. "8" from "8.4.19")
|
||||
detect_pve_major() {
|
||||
local ver
|
||||
ver=$(dpkg-query --showformat='${Version}' --show proxmox-ve 2>/dev/null || echo "0")
|
||||
echo "${ver%%.*}"
|
||||
install_plugin() {
|
||||
log "Installing ${PLUGIN_DST}"
|
||||
mkdir -p "$(dirname "$PLUGIN_DST")"
|
||||
cp "${INSTALL_DIR}/TrueNAS.pm" "$PLUGIN_DST"
|
||||
}
|
||||
|
||||
# Detect pve-manager major.minor (e.g. "8.4" from "8.4.19").
|
||||
# Used to select minor-version-specific patches within a major series.
|
||||
detect_pve_manager_minor() {
|
||||
local ver
|
||||
ver=$(dpkg-query -W -f '${Version}' pve-manager 2>/dev/null || echo "0.0.0")
|
||||
echo "${ver%.*}"
|
||||
}
|
||||
install_ui() {
|
||||
log "Installing ${JS_DST}"
|
||||
cp "${INSTALL_DIR}/truenas-storage.js" "$JS_DST"
|
||||
|
||||
# Find the best bundled patch for a given component and PVE version.
|
||||
# Tries {minor}.patch (e.g. 8.4.patch) first, then walks down {major}.patch,
|
||||
# {major-1}.patch, ... until a match is found.
|
||||
find_patch() {
|
||||
local component="$1"
|
||||
local major="$2"
|
||||
local minor="$3" # e.g. "8.4" — may be empty
|
||||
local patch_dir="${INSTALL_DIR}/patches/${component}"
|
||||
log "Installing ${HELP_DST}"
|
||||
cp "${INSTALL_DIR}/truenas-storage-help.html" "$HELP_DST"
|
||||
|
||||
# Minor-version-specific patch takes priority (e.g. pvemanagerlib/8.4.patch)
|
||||
if [ -n "$minor" ] && [ -f "${patch_dir}/${minor}.patch" ]; then
|
||||
echo "${patch_dir}/${minor}.patch"
|
||||
if grep -qF 'truenas-storage.js' "$TPL" 2>/dev/null; then
|
||||
log "index.html.tpl already has truenas-storage.js — skipping"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Fall back to major-version patch, walking down until one is found
|
||||
local ver="$major"
|
||||
while [ "$ver" -ge 5 ]; do
|
||||
if [ -f "${patch_dir}/${ver}.patch" ]; then
|
||||
echo "${patch_dir}/${ver}.patch"
|
||||
return 0
|
||||
fi
|
||||
ver=$(( ver - 1 ))
|
||||
done
|
||||
|
||||
log "WARNING: No bundled patch found for ${component} on Proxmox VE ${major}.x"
|
||||
return 1
|
||||
}
|
||||
|
||||
# Apply a patch to a target file idempotently.
|
||||
# Creates a .orig backup (via patch --backup) on first application.
|
||||
apply_patch() {
|
||||
local target_rel="$1"
|
||||
local patch_file="$2"
|
||||
local label="$3"
|
||||
local target="${LIB_PATH}${target_rel}"
|
||||
|
||||
[ -f "$target" ] || { log "ERROR: Target not found: ${target}"; return 1; }
|
||||
[ -f "$patch_file" ] || { log "Skipping ${label}: patch file missing"; return 0; }
|
||||
|
||||
if grep -q "freenas" "$target" 2>/dev/null; then
|
||||
log "${label} is already patched — skipping"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log "Patching ${target} ..."
|
||||
if patch --backup --ignore-whitespace "$target" < "$patch_file" >> "$LOG_FILE" 2>&1; then
|
||||
log "${label} patched successfully"
|
||||
else
|
||||
log "ERROR: Failed to patch ${label} — see ${LOG_FILE} for details"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
install_files() {
|
||||
log "Installing ${LIB_PATH}${FREENAS_PM_PATH}"
|
||||
mkdir -p "$(dirname "${LIB_PATH}${FREENAS_PM_PATH}")"
|
||||
cp "${INSTALL_DIR}/FreeNAS.pm" "${LIB_PATH}${FREENAS_PM_PATH}"
|
||||
|
||||
log "Installing ${LIB_PATH}${REST_CLIENT_PATH}"
|
||||
mkdir -p "$(dirname "${LIB_PATH}${REST_CLIENT_PATH}")"
|
||||
cp "${INSTALL_DIR}/REST-Client.pm" "${LIB_PATH}${REST_CLIENT_PATH}"
|
||||
log "Adding <script> tag to ${TPL}"
|
||||
sed -i "/pvemanagerlib\.js/a\\${SCRIPT_TAG}" "$TPL"
|
||||
}
|
||||
|
||||
restart_pve_services() {
|
||||
log "Restarting Proxmox VE services ..."
|
||||
pvedaemon restart && log "pvedaemon restarted"
|
||||
pveproxy restart && log "pveproxy restarted"
|
||||
pvestatd restart && log "pvestatd restarted"
|
||||
systemctl restart pvescheduler.service && log "pvescheduler restarted"
|
||||
log "Done. Refresh your Proxmox browser tab to load the updated UI."
|
||||
pvedaemon restart && log "pvedaemon restarted"
|
||||
pveproxy restart && log "pveproxy restarted"
|
||||
log "Done. Refresh your Proxmox browser tab."
|
||||
}
|
||||
|
||||
# Warn the user if their PVE version is outside the supported range for this
|
||||
# plugin version. Exits non-zero (aborting the install) only for versions so
|
||||
# old that no patches exist (< 7).
|
||||
check_pve_version() {
|
||||
local major="$1"
|
||||
|
||||
if [ "$major" -lt 7 ]; then
|
||||
log "ERROR: Proxmox VE ${major}.x is not supported by this plugin."
|
||||
log " PVE 5 and 6 reached end-of-life in 2019 and 2022 respectively."
|
||||
log " Please upgrade your Proxmox VE installation before installing this plugin."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$major" -eq 7 ]; then
|
||||
log "WARNING: *** Proxmox VE 7 — Important Notice ***"
|
||||
log "WARNING: v2.x is the LAST release series that supports Proxmox VE 7."
|
||||
log "WARNING: PVE 7 support is best-effort only; no new patches will be"
|
||||
log "WARNING: developed for it."
|
||||
log "WARNING: When v3.0 is released, DO NOT upgrade to it — v3.0 requires"
|
||||
log "WARNING: Proxmox VE 8 or later. Stay on the latest v2.x release."
|
||||
fi
|
||||
|
||||
if [ "$major" -eq 8 ]; then
|
||||
log "INFO: Proxmox VE 8 reaches end-of-life on 2026-08-31."
|
||||
log "INFO: Plan your upgrade to PVE 9 and migration to v3.0 before that date."
|
||||
fi
|
||||
|
||||
if [ "$major" -ge 9 ]; then
|
||||
log "WARNING: *** Proxmox VE ${major}.x — Not Supported by v2.x ***"
|
||||
log "WARNING: This plugin (v2.x) is not supported on Proxmox VE 9 or later."
|
||||
log "WARNING: Patches may fail to apply or the UI may not work correctly."
|
||||
log "WARNING: Please use v3.0 of this plugin, which is designed for PVE 9+."
|
||||
log "WARNING: See: https://github.com/TheGrandWazoo/freenas-proxmox/releases"
|
||||
log "WARNING: Continuing anyway — but expect problems."
|
||||
fi
|
||||
}
|
||||
|
||||
# ── Entry point ──────────────────────────────────────────────────────────────
|
||||
|
||||
major=$(detect_pve_major)
|
||||
pve_mgr_minor=$(detect_pve_manager_minor)
|
||||
log "Proxmox VE major version: ${major}, pve-manager: ${pve_mgr_minor}"
|
||||
check_pve_version "$major"
|
||||
|
||||
case "$1" in
|
||||
|
||||
triggered)
|
||||
# dpkg fired us because one of the watched PVE files was updated.
|
||||
log "Triggered by package update — re-applying patches for: $2"
|
||||
|
||||
for fullpath in $2; do
|
||||
filename=$(basename "$fullpath")
|
||||
filename="${filename%.*}" # strip extension
|
||||
case "$filename" in
|
||||
ZFSPlugin)
|
||||
patch_file=$(find_patch "ZFSPlugin" "$major" "$pve_mgr_minor" || true)
|
||||
apply_patch "$ZFSPLUGIN_PATH" "$patch_file" "ZFSPlugin.pm"
|
||||
;;
|
||||
pvemanagerlib)
|
||||
patch_file=$(find_patch "pvemanagerlib" "$major" "$pve_mgr_minor" || true)
|
||||
apply_patch "$PVEMANAGER_PATH" "$patch_file" "pvemanagerlib.js"
|
||||
;;
|
||||
apidoc)
|
||||
patch_file=$(find_patch "apidoc" "$major" "$pve_mgr_minor" || true)
|
||||
apply_patch "$APIDOC_PATH" "$patch_file" "apidoc.js"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
install_files
|
||||
exit 0
|
||||
;;
|
||||
|
||||
configure)
|
||||
log "Configuring freenas-proxmox (previous version: ${2:-none})"
|
||||
CHANGED="no"
|
||||
|
||||
patch_file=$(find_patch "ZFSPlugin" "$major" "$pve_mgr_minor" || true)
|
||||
apply_patch "$ZFSPLUGIN_PATH" "$patch_file" "ZFSPlugin.pm" && CHANGED="yes"
|
||||
|
||||
patch_file=$(find_patch "pvemanagerlib" "$major" "$pve_mgr_minor" || true)
|
||||
apply_patch "$PVEMANAGER_PATH" "$patch_file" "pvemanagerlib.js" && CHANGED="yes"
|
||||
|
||||
patch_file=$(find_patch "apidoc" "$major" "$pve_mgr_minor" || true)
|
||||
apply_patch "$APIDOC_PATH" "$patch_file" "apidoc.js" && CHANGED="yes"
|
||||
|
||||
install_files
|
||||
CHANGED="yes"
|
||||
|
||||
[ "$CHANGED" = "yes" ] && restart_pve_services
|
||||
exit 0
|
||||
log "Configuring truenas-proxmox (previous version: ${2:-none})"
|
||||
install_plugin
|
||||
install_ui
|
||||
restart_pve_services
|
||||
;;
|
||||
|
||||
abort-upgrade|abort-remove|abort-deconfigure)
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "$0: called with unknown argument '$1'" >&2
|
||||
exit 0
|
||||
;;
|
||||
|
||||
esac
|
||||
|
||||
exit 0
|
||||
|
|
|
|||
|
|
@ -1,104 +1,52 @@
|
|||
#!/bin/bash
|
||||
# postrm: freenas-proxmox removal script
|
||||
# Reverses patches applied to Proxmox VE system files and removes installed plugin files.
|
||||
# postrm: truenas-proxmox v3.x removal script
|
||||
set -e
|
||||
|
||||
LIB_PATH="/usr/share"
|
||||
LOG_FILE="/var/log/freenas-proxmox-install.log"
|
||||
|
||||
ZFSPLUGIN_PATH="/perl5/PVE/Storage/ZFSPlugin.pm"
|
||||
PVEMANAGER_PATH="/pve-manager/js/pvemanagerlib.js"
|
||||
APIDOC_PATH="/pve-docs/api-viewer/apidoc.js"
|
||||
FREENAS_PM_PATH="/perl5/PVE/Storage/LunCmd/FreeNAS.pm"
|
||||
INSTALL_DIR="/usr/share/freenas-proxmox"
|
||||
PLUGIN_DST="/usr/share/perl5/PVE/Storage/Custom/TrueNAS.pm"
|
||||
JS_DST="/usr/share/pve-manager/js/truenas-storage.js"
|
||||
HELP_DST="/usr/share/pve-docs/truenas-storage.html"
|
||||
TPL="/usr/share/pve-manager/index.html.tpl"
|
||||
LOG_FILE="/var/log/truenas-proxmox-install.log"
|
||||
|
||||
log() {
|
||||
echo "[freenas-proxmox] $*" | tee -a "$LOG_FILE"
|
||||
echo "[truenas-proxmox] $*" | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
# Restore a file from the .orig backup that patch --backup created.
|
||||
restore_orig() {
|
||||
local target_rel="$1"
|
||||
local label="$2"
|
||||
local target="${LIB_PATH}${target_rel}"
|
||||
local orig="${target}.orig"
|
||||
remove_ui() {
|
||||
rm -f "$JS_DST" && log "Removed ${JS_DST}"
|
||||
rm -f "$HELP_DST" && log "Removed ${HELP_DST}"
|
||||
|
||||
if ! grep -q "freenas" "$target" 2>/dev/null; then
|
||||
log "${label} does not appear to be patched — skipping restore"
|
||||
return 0
|
||||
if [ -f "$TPL" ] && grep -qF 'truenas-storage.js' "$TPL"; then
|
||||
log "Removing <script> tag from ${TPL}"
|
||||
sed -i '/truenas-storage\.js/d' "$TPL"
|
||||
fi
|
||||
|
||||
if [ -f "$orig" ]; then
|
||||
log "Restoring ${label} from .orig backup"
|
||||
cp "$orig" "$target" && rm -f "$orig"
|
||||
log "${label} restored"
|
||||
else
|
||||
log "WARNING: No .orig backup found for ${label} — Proxmox VE reinstall may be needed"
|
||||
log " Run: apt install --reinstall pve-manager libpve-storage-perl pve-docs"
|
||||
fi
|
||||
}
|
||||
|
||||
remove_plugin_files() {
|
||||
if [ -f "${LIB_PATH}${FREENAS_PM_PATH}" ]; then
|
||||
log "Removing ${LIB_PATH}${FREENAS_PM_PATH}"
|
||||
rm -f "${LIB_PATH}${FREENAS_PM_PATH}"
|
||||
fi
|
||||
# REST/Client.pm is owned by librest-client-perl; do not remove it here.
|
||||
}
|
||||
|
||||
restart_pve_services() {
|
||||
log "Restarting Proxmox VE services ..."
|
||||
pvedaemon restart && log "pvedaemon restarted"
|
||||
pveproxy restart && log "pveproxy restarted"
|
||||
pvestatd restart && log "pvestatd restarted"
|
||||
systemctl restart pvescheduler.service && log "pvescheduler restarted"
|
||||
pvedaemon restart && log "pvedaemon restarted"
|
||||
pveproxy restart && log "pveproxy restarted"
|
||||
}
|
||||
|
||||
# ── Entry point ──────────────────────────────────────────────────────────────
|
||||
|
||||
case "$1" in
|
||||
|
||||
remove)
|
||||
log "Removing freenas-proxmox — restoring Proxmox VE files ..."
|
||||
|
||||
restore_orig "$ZFSPLUGIN_PATH" "ZFSPlugin.pm"
|
||||
restore_orig "$PVEMANAGER_PATH" "pvemanagerlib.js"
|
||||
restore_orig "$APIDOC_PATH" "apidoc.js"
|
||||
remove_plugin_files
|
||||
log "Removing truenas-proxmox ..."
|
||||
rm -f "$PLUGIN_DST" && log "Removed ${PLUGIN_DST}"
|
||||
remove_ui
|
||||
restart_pve_services
|
||||
|
||||
log "freenas-proxmox removed. Refresh your Proxmox browser tab."
|
||||
exit 0
|
||||
log "truenas-proxmox removed. Refresh your Proxmox browser tab."
|
||||
;;
|
||||
|
||||
upgrade)
|
||||
# On upgrade the new package's postinst will re-apply everything.
|
||||
# Just restore the originals cleanly so the new patch applies to a fresh file.
|
||||
log "Preparing for upgrade — restoring unpatched Proxmox VE files ..."
|
||||
|
||||
restore_orig "$ZFSPLUGIN_PATH" "ZFSPlugin.pm"
|
||||
restore_orig "$PVEMANAGER_PATH" "pvemanagerlib.js"
|
||||
restore_orig "$APIDOC_PATH" "apidoc.js"
|
||||
remove_plugin_files
|
||||
|
||||
exit 0
|
||||
;;
|
||||
|
||||
purge)
|
||||
log "Purging freenas-proxmox ..."
|
||||
rm -rf "$INSTALL_DIR"
|
||||
rm -f "$LOG_FILE"
|
||||
exit 0
|
||||
log "Purging truenas-proxmox ..."
|
||||
rm -f "$PLUGIN_DST" "$JS_DST" "$HELP_DST" "$LOG_FILE"
|
||||
[ -f "$TPL" ] && sed -i '/truenas-storage\.js/d' "$TPL"
|
||||
;;
|
||||
|
||||
failed-upgrade|disappear|abort-upgrade|abort-remove|abort-deconfigure)
|
||||
upgrade|failed-upgrade|disappear|abort-upgrade|abort-remove|abort-deconfigure)
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "$0: called with unknown argument '$1'" >&2
|
||||
exit 0
|
||||
;;
|
||||
|
||||
esac
|
||||
|
||||
exit 0
|
||||
|
|
|
|||
|
|
@ -1,7 +0,0 @@
|
|||
# dpkg trigger file for freenas-proxmox
|
||||
#
|
||||
# When Proxmox VE updates replace any of these three files, dpkg automatically
|
||||
# re-fires the postinst script so patches are re-applied without manual action.
|
||||
interest /usr/share/perl5/PVE/Storage/ZFSPlugin.pm
|
||||
interest /usr/share/pve-manager/js/pvemanagerlib.js
|
||||
interest /usr/share/pve-docs/api-viewer/apidoc.js
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
truenas-proxmox (3.2.2-1) bookworm; urgency=low
|
||||
|
||||
* Bump api() return value from 14 to 15 to match PVE 9 APIVER — silences
|
||||
"implementing an older storage API" warning on every daemon restart
|
||||
|
||||
-- KSA Technologies, LLC <theprofessor@ksatechnologies.com> Mon, 23 Jun 2026 00:00:00 +0000
|
||||
|
||||
truenas-proxmox (3.2.1-1) bookworm; urgency=low
|
||||
|
||||
* Fix apt codename alias 'error' — dists/error was never created because
|
||||
codename alias code was added after v3.2.0 tag; also fix cp -r overwrite
|
||||
bug that would corrupt dists/error on second+ stable releases
|
||||
|
||||
-- KSA Technologies, LLC <theprofessor@ksatechnologies.com> Sun, 22 Jun 2026 00:00:00 +0000
|
||||
|
||||
truenas-proxmox (3.0.0-1) bookworm; urgency=medium
|
||||
|
||||
* Full rewrite as native PVE::Storage::Custom plugin — no PVE core patches
|
||||
* Bearer token authentication only; basic auth (freenas_user/password) removed
|
||||
* Per-VM dedicated iSCSI targets via TrueNAS REST API v2.0
|
||||
* QEMU connects via iscsi:// directly — no iscsiadm, no kernel iSCSI sessions
|
||||
* Supports TrueNAS CORE 13.0-U6 and SCALE 24.10, 25.04, 25.10
|
||||
* Supports Proxmox VE 8.x and 9.x
|
||||
* Package renamed from freenas-proxmox to truenas-proxmox; transitional package provided
|
||||
* alloc_image partial-failure rollback (cleanup orphaned TrueNAS resources)
|
||||
* Migration docs: beginner, advanced, troubleshooting guides
|
||||
|
||||
-- KSA Technologies, LLC <theprofessor@ksatechnologies.com> Sun, 31 May 2026 00:00:00 +0000
|
||||
|
||||
truenas-proxmox (2.4.0-1) bookworm; urgency=low
|
||||
|
||||
* Quality of life improvements and minor fixes
|
||||
|
||||
-- KSA Technologies, LLC <theprofessor@ksatechnologies.com> Fri, 22 May 2026 00:00:00 +0000
|
||||
|
||||
truenas-proxmox (2.3.0-1) bookworm; urgency=medium
|
||||
|
||||
* Hardening and stability improvements
|
||||
* Bearer token authentication support
|
||||
* Build and CI infrastructure improvements
|
||||
|
||||
-- KSA Technologies, LLC <theprofessor@ksatechnologies.com> Mon, 19 May 2026 00:00:00 +0000
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
/ZFSPlugin.pm
|
||||
/ZFSPoolPlugin.pm
|
||||
/ZFSPlugin-new.pm
|
||||
/ZFSPlugin-newver.patch
|
||||
|
|
@ -0,0 +1,813 @@
|
|||
package PVE::Storage::Custom::TrueNAS;
|
||||
|
||||
# TrueNAS Custom Storage Plugin for Proxmox VE
|
||||
#
|
||||
# Manages ZFS volumes over iSCSI via the TrueNAS REST API.
|
||||
# Discovered automatically by PVE — no patches to system files required.
|
||||
#
|
||||
# Supports: TrueNAS CORE 13.x, TrueNAS SCALE <= 24.10 (REST API v2.0)
|
||||
# Auth: Bearer token (API key) only — basic auth removed in v3.0
|
||||
# iSCSI: QEMU libiscsi (iscsi:// paths) — no iscsiadm session management
|
||||
#
|
||||
# Per-VM target architecture:
|
||||
# Each VM gets its own iSCSI target (proxmox-vm-<vmid>).
|
||||
# path() returns iscsi://portal/iqn:proxmox-vm-<vmid>/lun — QEMU connects via
|
||||
# libiscsi. Deleting a disk from a running VM (e.g. removing an unused disk)
|
||||
# requires force=true on both the targetextent and extent DELETEs — TrueNAS
|
||||
# sees the target session as active even when the specific LUN is not in use.
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use JSON qw(encode_json decode_json);
|
||||
use LWP::UserAgent ();
|
||||
use HTTP::Request ();
|
||||
use URI::Escape qw(uri_escape);
|
||||
use Sys::Syslog qw(syslog);
|
||||
|
||||
use PVE::Storage::Plugin;
|
||||
|
||||
use base qw(PVE::Storage::Plugin);
|
||||
|
||||
our $VERSION = '3.2.3';
|
||||
|
||||
# Per-host runtime state cache
|
||||
my $state = {};
|
||||
|
||||
# ── Plugin identity ───────────────────────────────────────────────────────────
|
||||
|
||||
sub api { return 15; }
|
||||
sub type { return 'truenas'; }
|
||||
|
||||
sub plugindata {
|
||||
return {
|
||||
content => [ { images => 1 }, { images => 1 } ],
|
||||
format => [ { raw => 1 }, 'raw' ],
|
||||
};
|
||||
}
|
||||
|
||||
sub properties {
|
||||
return {
|
||||
truenas_host => {
|
||||
description => "TrueNAS hostname or IP address",
|
||||
type => 'string',
|
||||
},
|
||||
truenas_api_key => {
|
||||
description => "TrueNAS API key (Bearer token). "
|
||||
. "Leave blank to use /etc/pve/priv/truenas-<storeid>.key instead.",
|
||||
type => 'string',
|
||||
},
|
||||
truenas_ssl => {
|
||||
description => "Use HTTPS for TrueNAS API (recommended)",
|
||||
type => 'boolean',
|
||||
default => 1,
|
||||
},
|
||||
truenas_ssl_verify => {
|
||||
description => "Verify TrueNAS SSL certificate (disable for self-signed certs)",
|
||||
type => 'boolean',
|
||||
default => 0,
|
||||
},
|
||||
truenas_pool => {
|
||||
description => "ZFS pool or dataset path where PVE volumes are created "
|
||||
. "(e.g. 'tank' or 'tank/proxmox/vdisks'). "
|
||||
. "Matches the 'pool' field from the v2.x plugin.",
|
||||
type => 'string',
|
||||
},
|
||||
truenas_dataset => {
|
||||
description => "Optional additional sub-dataset appended to Pool path. "
|
||||
. "Leave blank — put the full path in Pool instead.",
|
||||
type => 'string',
|
||||
},
|
||||
truenas_portal_ip => {
|
||||
description => "iSCSI portal IP address. Defaults to truenas_host if not set.",
|
||||
type => 'string',
|
||||
},
|
||||
truenas_target => {
|
||||
description => "Base iSCSI target name (used to look up portal and initiator "
|
||||
. "group settings for auto-created per-VM targets). "
|
||||
. "Leave blank to auto-discover from the portal IP.",
|
||||
type => 'string',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
sub options {
|
||||
return {
|
||||
nodes => { optional => 1 },
|
||||
disable => { optional => 1 },
|
||||
content => { optional => 1 },
|
||||
bwlimit => { optional => 1 },
|
||||
shared => { optional => 1 },
|
||||
truenas_host => { fixed => 1 },
|
||||
truenas_api_key => { optional => 1 },
|
||||
truenas_ssl => { optional => 1 },
|
||||
truenas_ssl_verify => { optional => 1 },
|
||||
truenas_pool => { fixed => 1 },
|
||||
truenas_dataset => { optional => 1 },
|
||||
truenas_portal_ip => { optional => 1 },
|
||||
truenas_target => { optional => 1 },
|
||||
};
|
||||
}
|
||||
|
||||
# ── Private: logging ──────────────────────────────────────────────────────────
|
||||
|
||||
sub _log {
|
||||
my ($level, $msg) = @_;
|
||||
syslog($level, "TrueNASPlugin: $msg");
|
||||
return;
|
||||
}
|
||||
|
||||
# ── Private: HTTP/API helpers ─────────────────────────────────────────────────
|
||||
|
||||
sub _ua {
|
||||
my ($scfg) = @_;
|
||||
my $host = $scfg->{truenas_host};
|
||||
unless ($state->{$host}{ua}) {
|
||||
my $ua = LWP::UserAgent->new(timeout => 30);
|
||||
if ($scfg->{truenas_ssl} // 1) {
|
||||
unless ($scfg->{truenas_ssl_verify} // 0) {
|
||||
$ua->ssl_opts(verify_hostname => 0, SSL_verify_mode => 0);
|
||||
}
|
||||
}
|
||||
$state->{$host}{ua} = $ua;
|
||||
}
|
||||
return $state->{$host}{ua};
|
||||
}
|
||||
|
||||
# Resolves the API token from /etc/pve/priv/truenas-<storeid>.key (preferred)
|
||||
# or truenas_api_key in storage.cfg (backwards compat). Caches in $state so
|
||||
# the keyfile is read at most once per daemon session per host.
|
||||
sub _resolve_token {
|
||||
my ($storeid, $scfg) = @_;
|
||||
my $host = $scfg->{truenas_host};
|
||||
return if $state->{$host}{api_token};
|
||||
|
||||
my $keyfile = "/etc/pve/priv/truenas-$storeid.key";
|
||||
if (-r $keyfile) {
|
||||
open(my $fh, '<', $keyfile)
|
||||
or die "Cannot read TrueNAS token file '$keyfile': $!\n";
|
||||
my $token = do { local $/; <$fh> };
|
||||
close $fh;
|
||||
$token =~ s/\s+\z//;
|
||||
die "TrueNAS token file '$keyfile' is empty\n" unless length $token;
|
||||
$state->{$host}{api_token} = $token;
|
||||
return;
|
||||
}
|
||||
|
||||
die "TrueNAS API token not configured for storage '$storeid'. "
|
||||
. "Set truenas_api_key in storage.cfg or create $keyfile\n"
|
||||
unless $scfg->{truenas_api_key};
|
||||
|
||||
$state->{$host}{api_token} = $scfg->{truenas_api_key};
|
||||
return;
|
||||
}
|
||||
|
||||
# Make a TrueNAS REST API v2.0 call.
|
||||
# Dies with a descriptive message on HTTP error.
|
||||
# Returns decoded JSON hashref/arrayref, or undef for empty 204 responses.
|
||||
sub _api {
|
||||
my ($scfg, $method, $path, $data) = @_;
|
||||
|
||||
my $host = $scfg->{truenas_host};
|
||||
my $token = $state->{$host}{api_token}
|
||||
or die "TrueNAS API token not resolved for '$host'\n";
|
||||
|
||||
my $scheme = ($scfg->{truenas_ssl} // 1) ? 'https' : 'http';
|
||||
my $url = "$scheme://$host/api/v2.0$path";
|
||||
|
||||
my $req = HTTP::Request->new(uc($method) => $url);
|
||||
$req->header('Content-Type' => 'application/json');
|
||||
$req->header('Accept' => 'application/json');
|
||||
$req->header('Authorization' => "Bearer $token");
|
||||
$req->content(encode_json($data)) if defined $data;
|
||||
|
||||
my $res = _ua($scfg)->request($req);
|
||||
|
||||
unless ($res->is_success) {
|
||||
my $detail = '';
|
||||
eval {
|
||||
my $body = decode_json($res->content);
|
||||
if (ref $body eq 'HASH' && $body->{message}) {
|
||||
$detail = " — $body->{message}";
|
||||
} elsif (ref $body eq 'ARRAY' && @$body) {
|
||||
# SCALE 25.04+ returns Pydantic validation errors as an array
|
||||
my @msgs = map { $_->{message} // $_->{msg} // '' } @$body;
|
||||
$detail = " — " . join('; ', grep { length } @msgs);
|
||||
} elsif (ref $body eq 'HASH') {
|
||||
# SCALE 25.10+ returns field-keyed validation errors: { "field": [{message}...] }
|
||||
my @msgs;
|
||||
for my $field (sort keys %$body) {
|
||||
my $errs = $body->{$field};
|
||||
next unless ref $errs eq 'ARRAY';
|
||||
push @msgs, map { "$field: " . ($_->{message} // '') } @$errs;
|
||||
}
|
||||
$detail = " — " . join('; ', grep { length } @msgs) if @msgs;
|
||||
}
|
||||
};
|
||||
my $msg = "TrueNAS API $method $path: " . $res->status_line . $detail;
|
||||
_log('err', $msg);
|
||||
die "$msg\n";
|
||||
}
|
||||
|
||||
my $content = $res->content // '';
|
||||
return unless length($content);
|
||||
return decode_json($content);
|
||||
}
|
||||
|
||||
# Returns (and caches) the iSCSI global config { basename, ... }
|
||||
sub _api_global {
|
||||
my ($scfg) = @_;
|
||||
my $host = $scfg->{truenas_host};
|
||||
$state->{$host}{global} //= _api($scfg, 'GET', '/iscsi/global') // {};
|
||||
return $state->{$host}{global};
|
||||
}
|
||||
|
||||
# ── Private: TrueNAS domain helpers ──────────────────────────────────────────
|
||||
|
||||
# Returns the zvol parent dataset path: "pool" or "pool/dataset"
|
||||
sub _zvol_prefix {
|
||||
my ($scfg) = @_;
|
||||
my $prefix = $scfg->{truenas_pool};
|
||||
$prefix .= "/$scfg->{truenas_dataset}" if $scfg->{truenas_dataset};
|
||||
return $prefix;
|
||||
}
|
||||
|
||||
# Returns the iSCSI portal IP/hostname to use
|
||||
sub _portal {
|
||||
my ($scfg) = @_;
|
||||
return $scfg->{truenas_portal_ip} // $scfg->{truenas_host};
|
||||
}
|
||||
|
||||
# Returns the iSCSI global basename (e.g. "iqn.2005-10.org.freenas.ctl")
|
||||
sub _basename {
|
||||
my ($scfg) = @_;
|
||||
return _api_global($scfg)->{basename} // 'iqn.2005-10.org.freenas.ctl';
|
||||
}
|
||||
|
||||
# Finds the [ { portal, initiator, authmethod, auth } ] group list to use when
|
||||
# creating new per-VM targets. Copies the first group found on an existing
|
||||
# target that uses our portal IP, so security settings (initiator group, auth)
|
||||
# are inherited automatically.
|
||||
sub _portal_groups_for_new_target {
|
||||
my ($scfg) = @_;
|
||||
|
||||
my $portal_ip = _portal($scfg);
|
||||
my $portals = _api($scfg, 'GET', '/iscsi/portal') // [];
|
||||
|
||||
my %our_portal_ids;
|
||||
for my $p (@$portals) {
|
||||
for my $listen (@{$p->{listen} // []}) {
|
||||
my $ip = $listen->{ip} // '';
|
||||
if ($ip eq $portal_ip || $ip eq '0.0.0.0' || $ip eq '::') {
|
||||
$our_portal_ids{$p->{id}} = 1;
|
||||
last;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
die "No iSCSI portals found listening on $portal_ip — "
|
||||
. "cannot create per-VM targets. Check truenas_portal_ip.\n"
|
||||
unless %our_portal_ids;
|
||||
|
||||
my $targets = _api($scfg, 'GET', '/iscsi/target') // [];
|
||||
|
||||
# Prefer copying groups from the configured base target
|
||||
if (my $base = $scfg->{truenas_target}) {
|
||||
my ($bt) = grep { $_->{name} eq $base || $base =~ /:\Q$_->{name}\E$/ } @$targets;
|
||||
if ($bt) {
|
||||
my @gs = grep { $our_portal_ids{$_->{portal}} } @{$bt->{groups} // []};
|
||||
return _clean_groups(\@gs) if @gs;
|
||||
}
|
||||
}
|
||||
|
||||
# Fall back to any existing target that uses our portal
|
||||
for my $t (@$targets) {
|
||||
my @gs = grep { $our_portal_ids{$_->{portal}} } @{$t->{groups} // []};
|
||||
return _clean_groups(\@gs) if @gs;
|
||||
}
|
||||
|
||||
# No existing targets — use first matching portal, no initiator restriction.
|
||||
# Force int(): hash keys are always Perl strings; encode_json must see an IV
|
||||
# to emit "portal":1 rather than "portal":"1" (rejected by Pydantic v2).
|
||||
my ($pid) = keys %our_portal_ids;
|
||||
return [ { portal => int($pid), authmethod => 'NONE' } ];
|
||||
}
|
||||
|
||||
sub _clean_groups {
|
||||
my ($gs) = @_;
|
||||
return [ map {
|
||||
my %g = (
|
||||
portal => int($_->{portal}),
|
||||
authmethod => $_->{authmethod} // 'NONE',
|
||||
);
|
||||
# Omit null integer fields — Pydantic v2 (SCALE 25.04+) is strict about types
|
||||
$g{initiator} = int($_->{initiator}) if defined $_->{initiator};
|
||||
$g{auth} = int($_->{auth}) if defined $_->{auth};
|
||||
\%g
|
||||
} @$gs ];
|
||||
}
|
||||
|
||||
# Finds or creates the per-VM iSCSI target for $vmid.
|
||||
# Returns { id, iqn }.
|
||||
sub _resolve_vm_target {
|
||||
my ($scfg, $vmid) = @_;
|
||||
my $host = $scfg->{truenas_host};
|
||||
|
||||
return $state->{$host}{vm_targets}{$vmid}
|
||||
if $state->{$host}{vm_targets}{$vmid};
|
||||
|
||||
my $target_name = "proxmox-vm-$vmid";
|
||||
my $targets = _api($scfg, 'GET', '/iscsi/target') // [];
|
||||
my ($existing) = grep { $_->{name} eq $target_name } @$targets;
|
||||
|
||||
my ($t_id, $iqn);
|
||||
if ($existing) {
|
||||
$t_id = $existing->{id};
|
||||
$iqn = _basename($scfg) . ":$target_name";
|
||||
_log('info', "Using existing per-VM target: $iqn (id=$t_id)");
|
||||
} else {
|
||||
my $groups = _portal_groups_for_new_target($scfg);
|
||||
my $new = _api($scfg, 'POST', '/iscsi/target', {
|
||||
name => $target_name,
|
||||
alias => "Proxmox VM $vmid",
|
||||
mode => 'ISCSI',
|
||||
groups => $groups,
|
||||
});
|
||||
$t_id = $new->{id};
|
||||
$iqn = _basename($scfg) . ":$target_name";
|
||||
_log('info', "Created per-VM target: $iqn (id=$t_id)");
|
||||
}
|
||||
|
||||
# int() after _log interpolation: string-interpolating $t_id sets its PV flag,
|
||||
# turning it into a dual-var that encode_json encodes as a string.
|
||||
$state->{$host}{vm_targets}{$vmid} = { id => int($t_id), iqn => $iqn };
|
||||
return $state->{$host}{vm_targets}{$vmid};
|
||||
}
|
||||
|
||||
# Deletes the per-VM target if it has no remaining targetextent associations.
|
||||
sub _maybe_cleanup_vm_target {
|
||||
my ($scfg, $vmid, $target_id) = @_;
|
||||
return unless defined $target_id;
|
||||
|
||||
my $tes = _api($scfg, 'GET', "/iscsi/targetextent?target=$target_id") // [];
|
||||
return if @$tes; # still has extents — don't remove
|
||||
|
||||
eval { _api($scfg, 'DELETE', "/iscsi/target/id/$target_id") };
|
||||
if ($@) {
|
||||
_log('warning', "could not remove empty per-VM target id=$target_id (vm=$vmid): $@");
|
||||
} else {
|
||||
_log('info', "removed empty per-VM target id=$target_id (vm=$vmid)");
|
||||
my $host = $scfg->{truenas_host};
|
||||
delete $state->{$host}{vm_targets}{$vmid};
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
# Returns true if the QEMU process for $vmid is alive.
|
||||
# Returns the next unused LUN ID on the given target
|
||||
sub _next_lun_id {
|
||||
my ($scfg, $target_id) = @_;
|
||||
my $tes = _api($scfg, 'GET', "/iscsi/targetextent?target=$target_id") // [];
|
||||
my %used = map { $_->{lunid} => 1 } @$tes;
|
||||
my $lun = 0;
|
||||
$lun++ while $used{$lun}; # hash-key lookup stringifies $lun; int() restores IV type
|
||||
return int($lun);
|
||||
}
|
||||
|
||||
# Returns { extent_id, targetextents, targetextent_id, lun_id, target_id } for a volname,
|
||||
# or undef if no extent exists. targetextents is the full array of all associations
|
||||
# (normally one, but may be >1 if duplicate rows exist from a prior failed alloc_image).
|
||||
sub _find_extent {
|
||||
my ($scfg, $volname) = @_;
|
||||
|
||||
my $extents = _api($scfg, 'GET', '/iscsi/extent') // [];
|
||||
my ($ext) = grep { $_->{name} eq $volname } @$extents;
|
||||
return unless $ext;
|
||||
|
||||
my $tes = _api($scfg, 'GET', "/iscsi/targetextent?extent=$ext->{id}") // [];
|
||||
my ($te) = @$tes;
|
||||
|
||||
return {
|
||||
extent_id => $ext->{id},
|
||||
naa => $ext->{naa},
|
||||
targetextents => $tes,
|
||||
targetextent_id => defined $te ? $te->{id} : undef,
|
||||
lun_id => defined $te ? $te->{lunid} : 0,
|
||||
target_id => defined $te ? $te->{target} : undef,
|
||||
};
|
||||
}
|
||||
|
||||
# Signal TrueNAS to reload the iSCSI service so new extents are visible
|
||||
sub _reload_iscsi {
|
||||
my ($scfg) = @_;
|
||||
eval { _api($scfg, 'POST', '/service/reload', { service => 'iscsitarget' }) };
|
||||
_log('warning', "iSCSI reload failed (non-fatal): $@") if $@;
|
||||
return;
|
||||
}
|
||||
|
||||
# ── PVE::Storage::Plugin interface ───────────────────────────────────────────
|
||||
|
||||
sub parse_volname {
|
||||
my ($class, $volname) = @_;
|
||||
|
||||
if ($volname =~ /^(vm|base)-(\d+)-disk-\d+$/) {
|
||||
my ($prefix, $vmid) = ($1, $2);
|
||||
my $isBase = $prefix eq 'base' ? 1 : 0;
|
||||
return ('images', $volname, $vmid, undef, undef, $isBase, 'raw');
|
||||
}
|
||||
|
||||
# RAM snapshot state volume: vm-<vmid>-state-<snapname>
|
||||
# PVE allocates one of these per snapshot when "Include RAM" is selected.
|
||||
if ($volname =~ /^vm-(\d+)-state-.+$/) {
|
||||
return ('images', $volname, $1, undef, undef, 0, 'raw');
|
||||
}
|
||||
|
||||
die "unable to parse TrueNAS volume name '$volname'\n";
|
||||
}
|
||||
|
||||
sub status {
|
||||
my ($class, $storeid, $scfg, $cache) = @_;
|
||||
_resolve_token($storeid, $scfg);
|
||||
|
||||
my $pool_name = (split m{/}, $scfg->{truenas_pool})[0];
|
||||
my $datasets = _api($scfg, 'GET', "/pool/dataset?id=$pool_name") // [];
|
||||
my ($ds) = grep { $_->{name} eq $pool_name } @$datasets;
|
||||
die "Pool dataset '$pool_name' not found on $scfg->{truenas_host}\n" unless $ds;
|
||||
|
||||
my $free = $ds->{available}{parsed} // 0;
|
||||
my $used = $ds->{used}{parsed} // 0;
|
||||
my $total = $free + $used;
|
||||
|
||||
return ($total, $free, $used, 1);
|
||||
}
|
||||
|
||||
sub alloc_image {
|
||||
my ($class, $storeid, $scfg, $vmid, $fmt, $name, $size_kb) = @_;
|
||||
_resolve_token($storeid, $scfg);
|
||||
|
||||
die "Unsupported format '$fmt' — only raw is supported\n" if $fmt && $fmt ne 'raw';
|
||||
|
||||
$name //= $class->find_free_diskname($storeid, $scfg, $vmid, 'raw');
|
||||
|
||||
my $prefix = _zvol_prefix($scfg);
|
||||
my $zvol = "$prefix/$name";
|
||||
my $size_b = int($size_kb) * 1024;
|
||||
|
||||
_log('info', "alloc_image: creating zvol $zvol ($size_b bytes) for VM $vmid");
|
||||
|
||||
# 1. Create the zvol
|
||||
# int() here produces a fresh IV — string interpolation in the log call above
|
||||
# sets Perl's POK flag on $size_b, which causes JSON::XS to encode it as a
|
||||
# string. SCALE 25.10 strict Pydantic rejects string-typed integers.
|
||||
_api($scfg, 'POST', '/pool/dataset', {
|
||||
name => $zvol,
|
||||
type => 'VOLUME',
|
||||
volsize => int($size_b),
|
||||
sparse => JSON::true,
|
||||
});
|
||||
|
||||
# Steps 2-4 wrapped in eval so the zvol is cleaned up on any failure.
|
||||
my ($extent, $target, $lun_id);
|
||||
eval {
|
||||
# 2. Create the iSCSI extent
|
||||
$extent = _api($scfg, 'POST', '/iscsi/extent', {
|
||||
name => $name,
|
||||
type => 'DISK',
|
||||
disk => "zvol/$zvol",
|
||||
ro => JSON::false,
|
||||
});
|
||||
|
||||
# 3. Associate extent with the per-VM target
|
||||
$target = _resolve_vm_target($scfg, $vmid);
|
||||
$lun_id = _next_lun_id($scfg, $target->{id});
|
||||
|
||||
# int() forces fresh IV scalars — decode_json IDs can become dual-vars
|
||||
# (string+int) after log interpolation or hash-key lookup, which causes
|
||||
# encode_json to emit "7" instead of 7, rejected by Pydantic v2.
|
||||
_api($scfg, 'POST', '/iscsi/targetextent', {
|
||||
target => int($target->{id}),
|
||||
extent => int($extent->{id}),
|
||||
lunid => int($lun_id),
|
||||
});
|
||||
};
|
||||
|
||||
if (my $err = $@) {
|
||||
_log('warning', "alloc_image: partial failure — rolling back: $err");
|
||||
|
||||
# Reverse order, best-effort. Target: only removed if it has no
|
||||
# remaining extents (i.e. it was just created for this disk).
|
||||
if ($target) {
|
||||
eval { _maybe_cleanup_vm_target($scfg, $vmid, $target->{id}) };
|
||||
}
|
||||
if ($extent) {
|
||||
eval { _api($scfg, 'DELETE', "/iscsi/extent/id/$extent->{id}",
|
||||
{ force => JSON::true }) };
|
||||
_log('warning', "alloc_image rollback: extent delete failed: $@") if $@;
|
||||
}
|
||||
my $zvol_id = uri_escape($zvol, "^A-Za-z0-9\\-_.~");
|
||||
eval { _api($scfg, 'DELETE', "/pool/dataset/id/$zvol_id",
|
||||
{ recursive => JSON::true }) };
|
||||
_log('warning', "alloc_image rollback: zvol delete failed: $@") if $@;
|
||||
|
||||
die $err;
|
||||
}
|
||||
|
||||
_reload_iscsi($scfg);
|
||||
|
||||
_log('info', "alloc_image: $name ready at lun $lun_id on $target->{iqn}");
|
||||
return $name;
|
||||
}
|
||||
|
||||
sub free_image {
|
||||
my ($class, $storeid, $scfg, $volname, $isBase) = @_;
|
||||
_resolve_token($storeid, $scfg);
|
||||
|
||||
_log('info', "free_image: removing $volname");
|
||||
|
||||
my ($vmid) = $volname =~ /^(?:vm|base)-(\d+)-/;
|
||||
|
||||
my $ext = _find_extent($scfg, $volname);
|
||||
|
||||
if ($ext) {
|
||||
# Step 1: unmap ALL LUN associations for this extent. Normally there is
|
||||
# exactly one targetextent row, but duplicate rows can exist if a prior
|
||||
# alloc_image failed partway through and left an orphan. force=true
|
||||
# (bare boolean) is required by SCALE 24.10 when the target has an active
|
||||
# session — e.g. removing an unused disk from a running VM.
|
||||
for my $te (@{ $ext->{targetextents} // [] }) {
|
||||
eval { _api($scfg, 'DELETE', "/iscsi/targetextent/id/$te->{id}", JSON::true) };
|
||||
_log('warning', "free_image: could not remove targetextent $te->{id}: $@") if $@;
|
||||
}
|
||||
|
||||
# Step 2: delete the extent. force=true handles any residual session
|
||||
# on a single-disk per-VM target (e.g. deleting a detached disk).
|
||||
eval {
|
||||
_api($scfg, 'DELETE', "/iscsi/extent/id/$ext->{extent_id}",
|
||||
{ force => JSON::true });
|
||||
};
|
||||
die "free_image: could not delete iSCSI extent for '$volname': $@\n" if $@;
|
||||
|
||||
# Remove the per-VM target if this was its last disk
|
||||
_maybe_cleanup_vm_target($scfg, $vmid, $ext->{target_id})
|
||||
if $vmid && defined $ext->{target_id};
|
||||
} else {
|
||||
_log('warning', "free_image: no iSCSI extent found for $volname — skipping extent removal");
|
||||
}
|
||||
|
||||
# Delete the zvol; recursive handles any leftover snapshots
|
||||
my $zvol = _zvol_prefix($scfg) . "/$volname";
|
||||
my $zvol_id = uri_escape($zvol, "^A-Za-z0-9\\-_.~");
|
||||
_api($scfg, 'DELETE', "/pool/dataset/id/$zvol_id", { recursive => JSON::true });
|
||||
|
||||
_log('info', "free_image: $volname removed");
|
||||
return;
|
||||
}
|
||||
|
||||
sub list_images {
|
||||
my ($class, $storeid, $scfg, $vmid, $fmt, $ids) = @_;
|
||||
_resolve_token($storeid, $scfg);
|
||||
|
||||
my $prefix = _zvol_prefix($scfg);
|
||||
my $datasets = _api($scfg, 'GET', '/pool/dataset?type=VOLUME') // [];
|
||||
|
||||
my @vols;
|
||||
for my $ds (@$datasets) {
|
||||
my $name = $ds->{name} // '';
|
||||
|
||||
next unless $name =~ s{^\Q$prefix\E/}{};
|
||||
next if $name =~ m{/};
|
||||
|
||||
next unless $name =~ /^(?:vm|base|subvol)-(\d+)-/;
|
||||
my $ds_vmid = $1 + 0;
|
||||
|
||||
next if defined $vmid && $ds_vmid != $vmid;
|
||||
|
||||
my $volid = "$storeid:$name";
|
||||
next if $ids && !$ids->{$volid};
|
||||
|
||||
my $size = 0;
|
||||
eval { $size = $ds->{volsize}{parsed} // 0 };
|
||||
|
||||
push @vols, {
|
||||
volid => $volid,
|
||||
name => $name,
|
||||
size => $size,
|
||||
vmid => $ds_vmid,
|
||||
format => 'raw',
|
||||
};
|
||||
}
|
||||
|
||||
return \@vols;
|
||||
}
|
||||
|
||||
sub volume_size_info {
|
||||
my ($class, $scfg, $storeid, $volname, $timeout) = @_;
|
||||
_resolve_token($storeid, $scfg);
|
||||
|
||||
my $zvol = _zvol_prefix($scfg) . "/$volname";
|
||||
my $enc = uri_escape($zvol, "^A-Za-z0-9\\-_.~");
|
||||
my $ds = _api($scfg, 'GET', "/pool/dataset/id/$enc") // {};
|
||||
return $ds->{volsize}{parsed} // 0;
|
||||
}
|
||||
|
||||
sub path {
|
||||
my ($class, $scfg, $volname, $storeid, $snapname) = @_;
|
||||
_resolve_token($storeid, $scfg);
|
||||
|
||||
my $ext = _find_extent($scfg, $volname);
|
||||
die "Volume '$volname' has no iSCSI extent on $scfg->{truenas_host}. "
|
||||
. "Was it created via this plugin?\n" unless $ext;
|
||||
|
||||
die "Volume '$volname' is not mapped to any iSCSI target.\n"
|
||||
unless defined $ext->{target_id};
|
||||
|
||||
# Look up the target that owns this extent (per-VM or legacy shared)
|
||||
my $t = _api($scfg, 'GET', "/iscsi/target/id/$ext->{target_id}") // {};
|
||||
my $iqn = _basename($scfg) . ":$t->{name}";
|
||||
my $portal = _portal($scfg);
|
||||
my $dev_path = "iscsi://$portal/$iqn/$ext->{lun_id}";
|
||||
|
||||
my ($vtype, undef, $ds_vmid) = $class->parse_volname($volname);
|
||||
return ($dev_path, $ds_vmid, $vtype);
|
||||
}
|
||||
|
||||
# Override base-class qemu_blockdev_options to ensure lun is encoded as a JSON
|
||||
# integer. PVE::Storage::Plugin does lun => "$3" (string capture from the
|
||||
# iscsi:// URL regex) which QEMU 9's strict blockdev schema rejects. We build
|
||||
# the hash directly from _find_extent so the value is always an IV. (#266)
|
||||
# REMOVE this override once Proxmox fixes Plugin.pm to use int($3) — verify by
|
||||
# checking pve-storage changelog for an iscsi lun integer fix, then confirm
|
||||
# "grep 'lun =>' /usr/share/perl5/PVE/Storage/Plugin.pm" no longer quotes $3.
|
||||
sub qemu_blockdev_options {
|
||||
my ($class, $scfg, $storeid, $volname, $machine_version, $options) = @_;
|
||||
_resolve_token($storeid, $scfg);
|
||||
|
||||
my $ext = _find_extent($scfg, $volname);
|
||||
die "Volume '$volname' has no iSCSI extent on $scfg->{truenas_host}.\n" unless $ext;
|
||||
die "Volume '$volname' is not mapped to any iSCSI target.\n"
|
||||
unless defined $ext->{target_id};
|
||||
|
||||
my $t = _api($scfg, 'GET', "/iscsi/target/id/$ext->{target_id}") // {};
|
||||
my $iqn = _basename($scfg) . ":$t->{name}";
|
||||
|
||||
return {
|
||||
driver => 'iscsi',
|
||||
portal => _portal($scfg),
|
||||
target => $iqn,
|
||||
lun => int($ext->{lun_id}),
|
||||
transport => 'tcp',
|
||||
};
|
||||
}
|
||||
|
||||
sub activate_storage {
|
||||
my ($class, $storeid, $scfg, $cache) = @_;
|
||||
_resolve_token($storeid, $scfg);
|
||||
# Verify API reachability and warm up the global config cache
|
||||
_api_global($scfg);
|
||||
_log('info', "activate_storage: $storeid online");
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub deactivate_storage {
|
||||
my ($class, $storeid, $scfg, $cache) = @_;
|
||||
delete $state->{$scfg->{truenas_host}};
|
||||
_log('info', "deactivate_storage: $storeid offline");
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub activate_volume {
|
||||
my ($class, $storeid, $scfg, $volname, $snapname, $cache) = @_;
|
||||
_resolve_token($storeid, $scfg);
|
||||
# QEMU connects via iscsi:// (libiscsi) — no iscsiadm session needed here.
|
||||
# Just verify the extent exists so we catch config errors early.
|
||||
my $ext = _find_extent($scfg, $volname);
|
||||
die "Volume '$volname' has no iSCSI extent — was it created via this plugin?\n"
|
||||
unless $ext;
|
||||
_log('info', "activate_volume: $volname (lun $ext->{lun_id}) ready");
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub deactivate_volume {
|
||||
my ($class, $storeid, $scfg, $volname, $snapname, $cache) = @_;
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub volume_has_feature {
|
||||
my ($class, $scfg, $feature, $storeid, $volname, $snapname, $running) = @_;
|
||||
|
||||
my $features = {
|
||||
copy => { base => 1, current => 1 },
|
||||
snapshot => { current => 1, snap => 1 },
|
||||
};
|
||||
|
||||
my ($vtype, undef, undef, undef, undef, $isBase) = $class->parse_volname($volname);
|
||||
my $key = $snapname ? 'snap' : ($isBase ? 'base' : 'current');
|
||||
|
||||
return 1 if $features->{$feature} && $features->{$feature}{$key};
|
||||
return;
|
||||
}
|
||||
|
||||
# ── Snapshot support ──────────────────────────────────────────────────────────
|
||||
|
||||
# Request a guest filesystem freeze before snapping a running VM's disk.
|
||||
# Returning 1 causes PVE to ask the QEMU guest agent to freeze IO before the
|
||||
# TrueNAS API snapshot call, ensuring write-consistency.
|
||||
sub volume_snapshot_needs_fsfreeze { return 1; }
|
||||
|
||||
# Returns the full ZFS path for a volume: "pool[/dataset]/volname"
|
||||
sub _snap_dataset {
|
||||
my ($scfg, $volname) = @_;
|
||||
return _zvol_prefix($scfg) . "/$volname";
|
||||
}
|
||||
|
||||
# Returns the full ZFS snapshot ID: "pool[/dataset]/volname@snapname"
|
||||
sub _snap_id {
|
||||
my ($scfg, $volname, $snap) = @_;
|
||||
return _zvol_prefix($scfg) . "/$volname\@$snap";
|
||||
}
|
||||
|
||||
sub volume_snapshot {
|
||||
my ($class, $scfg, $storeid, $volname, $snap) = @_;
|
||||
|
||||
_resolve_token($storeid, $scfg);
|
||||
my $dataset = _snap_dataset($scfg, $volname);
|
||||
_log('info', "snapshot: creating $dataset\@$snap");
|
||||
_api($scfg, 'POST', '/zfs/snapshot', { dataset => $dataset, name => $snap });
|
||||
return;
|
||||
}
|
||||
|
||||
sub volume_snapshot_delete {
|
||||
my ($class, $scfg, $storeid, $volname, $snap, $running) = @_;
|
||||
|
||||
_resolve_token($storeid, $scfg);
|
||||
my $id = _snap_id($scfg, $volname, $snap);
|
||||
_log('info', "snapshot: deleting $id");
|
||||
|
||||
# The snapshot API requires full URI encoding of the id path segment —
|
||||
# unlike the dataset endpoint, it does not accept bare slashes in the path.
|
||||
_api($scfg, 'DELETE', "/zfs/snapshot/id/" . uri_escape($id));
|
||||
return;
|
||||
}
|
||||
|
||||
sub volume_snapshot_rollback {
|
||||
my ($class, $scfg, $storeid, $volname, $snap) = @_;
|
||||
|
||||
_resolve_token($storeid, $scfg);
|
||||
my $id = _snap_id($scfg, $volname, $snap);
|
||||
_log('info', "snapshot: rolling back to $id");
|
||||
_api($scfg, 'POST', '/zfs/snapshot/rollback', {
|
||||
id => $id,
|
||||
options => { force => JSON::false, recursive_clones => JSON::false },
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
# ZFS only permits rollback to the most recent snapshot. If $snap is not the
|
||||
# newest, list the blocking (newer) snapshots so PVE can report them.
|
||||
sub volume_rollback_is_possible {
|
||||
my ($class, $scfg, $storeid, $volname, $snap, $blockers) = @_;
|
||||
|
||||
my $snaps = $class->volume_snapshot_info($scfg, $storeid, $volname);
|
||||
$blockers //= [];
|
||||
my $found;
|
||||
|
||||
for my $name (sort { $snaps->{$a}{creation_time} <=> $snaps->{$b}{creation_time} }
|
||||
keys %$snaps) {
|
||||
if ($name eq $snap) {
|
||||
$found = 1;
|
||||
} elsif ($found) {
|
||||
push @$blockers, $name;
|
||||
}
|
||||
}
|
||||
|
||||
die "snapshot '$snap' does not exist on '$volname'\n" if !$found;
|
||||
die "can't rollback '$snap' on '$volname' — newer snapshots exist: "
|
||||
. join(', ', @$blockers) . "\n"
|
||||
if @$blockers;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
# Returns a hashref of snapshots for $volname, keyed by snapshot name:
|
||||
# { 'snapname' => { id => 'pool/vol@snapname', creation_time => $epoch }, ... }
|
||||
sub volume_snapshot_info {
|
||||
my ($class, $scfg, $storeid, $volname) = @_;
|
||||
|
||||
_resolve_token($storeid, $scfg);
|
||||
my $dataset = _snap_dataset($scfg, $volname);
|
||||
my $enc_ds = uri_escape($dataset);
|
||||
my $snaps = _api($scfg, 'GET', "/zfs/snapshot?dataset=$enc_ds&limit=500") // [];
|
||||
|
||||
my %result;
|
||||
for my $s (@$snaps) {
|
||||
# snapshot_name is the part after @; id is the full "dataset@name" string
|
||||
my $name = $s->{snapshot_name}
|
||||
// do { (my $n = $s->{id} // '') =~ s/^.*@//; $n };
|
||||
my $ctime = $s->{properties}{creation}{rawvalue} // 0;
|
||||
$result{$name} = { id => $s->{id}, creation_time => int($ctime) };
|
||||
}
|
||||
|
||||
return \%result;
|
||||
}
|
||||
|
||||
1;
|
||||
|
|
@ -0,0 +1,218 @@
|
|||
package PVE::Storage::Custom::TrueNASMultipath;
|
||||
|
||||
# TrueNAS Multipath Storage Plugin for Proxmox VE
|
||||
#
|
||||
# Extends TrueNAS.pm with kernel iSCSI multipath support (iscsiadm + dm-multipath).
|
||||
# Registers as PVE storage type 'truenas-multipath'.
|
||||
#
|
||||
# Requires: truenas-proxmox >= 3.1.0, open-iscsi, multipath-tools
|
||||
# Auth: Same Bearer token model as TrueNAS.pm
|
||||
# Path: /dev/mapper/<wwid> instead of iscsi:// — QEMU uses the block device directly
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use PVE::Storage::Custom::TrueNAS;
|
||||
use base qw(PVE::Storage::Custom::TrueNAS);
|
||||
|
||||
# ── Plugin identity ───────────────────────────────────────────────────────────
|
||||
|
||||
sub type { return 'truenas-multipath'; }
|
||||
|
||||
sub plugindata {
|
||||
return {
|
||||
content => [ { images => 1 }, { images => 1 } ],
|
||||
format => [ { raw => 1 }, 'raw' ],
|
||||
};
|
||||
}
|
||||
|
||||
# ── Config properties (extends parent) ───────────────────────────────────────
|
||||
|
||||
# Only declare NEW properties here. Inherited TrueNAS.pm properties (truenas_host,
|
||||
# truenas_pool, etc.) are already registered globally by TrueNAS.pm — re-declaring
|
||||
# them would cause a "duplicate property" error in PVE::SectionConfig.
|
||||
sub properties {
|
||||
return {
|
||||
truenas_portals => {
|
||||
description => "Comma-separated iSCSI portal IP addresses for multipath "
|
||||
. "(e.g. '172.31.69.91,192.168.69.91'). Each portal is logged "
|
||||
. "into separately; dm-multipath aggregates the sessions.",
|
||||
type => 'string',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
sub options {
|
||||
my $opts = PVE::Storage::Custom::TrueNAS::options();
|
||||
$opts->{truenas_portals} = { optional => 0 };
|
||||
return $opts;
|
||||
}
|
||||
|
||||
# ── Private helpers ───────────────────────────────────────────────────────────
|
||||
|
||||
# Parse truenas_portals into a list of IP strings.
|
||||
sub _portals {
|
||||
my ($scfg) = @_;
|
||||
return grep { length } split /\s*,\s*/, ($scfg->{truenas_portals} // '');
|
||||
}
|
||||
|
||||
# Convert TrueNAS NAA string (e.g. "0x6589cfc...") to the dm-multipath WWID
|
||||
# used in /dev/mapper/ (e.g. "36589cfc...").
|
||||
sub _naa_to_wwid {
|
||||
my ($naa) = @_;
|
||||
$naa =~ s/^0x//i;
|
||||
return "3$naa";
|
||||
}
|
||||
|
||||
# Run an iscsiadm command, returning (exit_code, output).
|
||||
# Suppresses errors that indicate a session already exists.
|
||||
sub _iscsiadm {
|
||||
my (@args) = @_;
|
||||
my $out = '';
|
||||
open(my $fh, '-|', 'iscsiadm', @args, '2>&1')
|
||||
or return (1, "failed to exec iscsiadm: $!");
|
||||
$out = do { local $/; <$fh> };
|
||||
close $fh;
|
||||
my $rc = $? >> 8;
|
||||
return ($rc, $out);
|
||||
}
|
||||
|
||||
# Wait up to $timeout seconds for a block device path to appear.
|
||||
sub _wait_for_device {
|
||||
my ($path, $timeout) = @_;
|
||||
$timeout //= 30;
|
||||
while ($timeout-- > 0) {
|
||||
return 1 if -b $path;
|
||||
sleep 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
# ── Path — block device via dm-multipath ─────────────────────────────────────
|
||||
|
||||
sub path {
|
||||
my ($class, $scfg, $volname, $storeid, $snapname) = @_;
|
||||
|
||||
PVE::Storage::Custom::TrueNAS::_resolve_token($storeid, $scfg);
|
||||
|
||||
my $ext = PVE::Storage::Custom::TrueNAS::_find_extent($scfg, $volname);
|
||||
die "Volume '$volname' has no iSCSI extent on $scfg->{truenas_host}.\n" unless $ext;
|
||||
|
||||
my $wwid = _naa_to_wwid($ext->{naa})
|
||||
or die "Volume '$volname' has no NAA/WWID on $scfg->{truenas_host}.\n";
|
||||
|
||||
return ("/dev/mapper/$wwid", $ext->{lun_id}, $storeid);
|
||||
}
|
||||
|
||||
# ── qemu_blockdev_options — delegate to Plugin.pm (host_device for /dev/mapper) ─
|
||||
|
||||
# TrueNAS.pm overrides qemu_blockdev_options to return an iscsi:// blockdev —
|
||||
# that's wrong for multipath where path() returns /dev/mapper/<wwid>.
|
||||
# We skip the parent and call the grandparent (PVE::Storage::Plugin) directly.
|
||||
# Plugin.pm calls path(), sees /dev/mapper/<wwid> starts with '/', stats it as a
|
||||
# block device (S_ISBLK), and returns { driver => 'host_device', filename => ... }.
|
||||
# QEMU then accesses the dm-multipath block device directly.
|
||||
sub qemu_blockdev_options {
|
||||
my ($class, $scfg, $storeid, $volname, $machine_version, $options) = @_;
|
||||
return PVE::Storage::Plugin::qemu_blockdev_options(
|
||||
$class, $scfg, $storeid, $volname, $machine_version, $options // {});
|
||||
}
|
||||
|
||||
# ── activate_volume — login via all portals, wait for mapper device ───────────
|
||||
|
||||
sub activate_volume {
|
||||
my ($class, $storeid, $scfg, $volname, $snapname, $cache) = @_;
|
||||
|
||||
PVE::Storage::Custom::TrueNAS::_resolve_token($storeid, $scfg);
|
||||
|
||||
my $ext = PVE::Storage::Custom::TrueNAS::_find_extent($scfg, $volname);
|
||||
die "Volume '$volname' has no iSCSI extent on $scfg->{truenas_host}.\n" unless $ext;
|
||||
die "Volume '$volname' is not mapped to any iSCSI target.\n"
|
||||
unless defined $ext->{target_id};
|
||||
|
||||
my $t = PVE::Storage::Custom::TrueNAS::_api($scfg, 'GET',
|
||||
"/iscsi/target/id/$ext->{target_id}") // {};
|
||||
my $iqn = PVE::Storage::Custom::TrueNAS::_basename($scfg) . ":$t->{name}";
|
||||
|
||||
my $wwid = _naa_to_wwid($ext->{naa})
|
||||
or die "Volume '$volname' has no NAA/WWID.\n";
|
||||
my $mapper = "/dev/mapper/$wwid";
|
||||
|
||||
return if -b $mapper; # already active
|
||||
|
||||
my @portals = _portals($scfg);
|
||||
die "No portals configured — set truenas_portals in storage config.\n" unless @portals;
|
||||
|
||||
PVE::Storage::Custom::TrueNAS::_log('info',
|
||||
"multipath activate: $volname iqn=$iqn wwid=$wwid portals=" . join(',', @portals));
|
||||
|
||||
for my $ip (@portals) {
|
||||
my $portal = "$ip:3260";
|
||||
|
||||
# Ensure node entry exists
|
||||
_iscsiadm('-m', 'node', '-T', $iqn, '-p', $portal, '-o', 'new');
|
||||
|
||||
# Login — ISCSI_ERR_SESS_EXISTS (15) is fine
|
||||
my ($rc, $out) = _iscsiadm('-m', 'node', '-T', $iqn, '-p', $portal, '--login');
|
||||
if ($rc && $rc != 15) {
|
||||
die "iscsiadm login to $portal failed (rc=$rc): $out\n";
|
||||
}
|
||||
}
|
||||
|
||||
# Signal multipathd to pick up the new sessions. Don't call multipath(8)
|
||||
# directly — that conflicts with a running multipathd daemon.
|
||||
if (-x '/usr/sbin/multipathd') {
|
||||
system('multipathd', 'reconfigure');
|
||||
} elsif (-x '/sbin/multipathd') {
|
||||
system('/sbin/multipathd', 'reconfigure');
|
||||
}
|
||||
|
||||
# multipathd may take a moment to create the dm device after sessions appear
|
||||
unless (_wait_for_device($mapper, 30)) {
|
||||
# Last resort: force a manual scan
|
||||
system('multipath', '-v0') if !-b $mapper;
|
||||
unless (_wait_for_device($mapper, 10)) {
|
||||
my $found = `multipath -l 2>/dev/null` // '';
|
||||
die "Multipath device $mapper did not appear after 40s. "
|
||||
. "Check that multipathd is running and multipath.conf includes TrueNAS devices.\n"
|
||||
. "Current multipath map:\n$found\n";
|
||||
}
|
||||
}
|
||||
|
||||
PVE::Storage::Custom::TrueNAS::_log('info', "multipath activate: $mapper ready");
|
||||
return;
|
||||
}
|
||||
|
||||
# ── deactivate_volume — logout from all portals, flush mapper device ──────────
|
||||
|
||||
sub deactivate_volume {
|
||||
my ($class, $storeid, $scfg, $volname, $snapname, $cache) = @_;
|
||||
|
||||
PVE::Storage::Custom::TrueNAS::_resolve_token($storeid, $scfg);
|
||||
|
||||
my $ext = PVE::Storage::Custom::TrueNAS::_find_extent($scfg, $volname);
|
||||
return unless $ext && defined $ext->{target_id};
|
||||
|
||||
my $t = PVE::Storage::Custom::TrueNAS::_api($scfg, 'GET',
|
||||
"/iscsi/target/id/$ext->{target_id}") // {};
|
||||
my $iqn = PVE::Storage::Custom::TrueNAS::_basename($scfg) . ":$t->{name}";
|
||||
|
||||
my $wwid = _naa_to_wwid($ext->{naa} // '');
|
||||
my $mapper = $wwid ? "/dev/mapper/$wwid" : undef;
|
||||
|
||||
# Flush multipath device first
|
||||
if ($mapper && -b $mapper) {
|
||||
system('multipath', '-f', $wwid);
|
||||
}
|
||||
|
||||
for my $ip (_portals($scfg)) {
|
||||
my $portal = "$ip:3260";
|
||||
_iscsiadm('-m', 'node', '-T', $iqn, '-p', $portal, '--logout');
|
||||
_iscsiadm('-m', 'node', '-T', $iqn, '-p', $portal, '-o', 'delete');
|
||||
}
|
||||
|
||||
PVE::Storage::Custom::TrueNAS::_log('info', "multipath deactivate: $volname done");
|
||||
return;
|
||||
}
|
||||
|
||||
1;
|
||||
|
|
@ -1,905 +0,0 @@
|
|||
package PVE::Storage::LunCmd::FreeNAS;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
our $VERSION = '2.4.0';
|
||||
use Data::Dumper;
|
||||
use PVE::SafeSyslog;
|
||||
use IO::Socket::SSL;
|
||||
|
||||
use LWP::UserAgent;
|
||||
use HTTP::Request;
|
||||
use REST::Client;
|
||||
use MIME::Base64;
|
||||
use JSON;
|
||||
|
||||
# Global variable definitions
|
||||
my $MAX_LUNS = 255; # Max LUNS per target on the iSCSI server
|
||||
my $freenas_server_list = {}; # Per-host state: {client, product_name, api_version, dev_prefix, runaway_count}
|
||||
my $freenas_rest_connection = undef; # REST::Client for the current active host
|
||||
my $freenas_global_config_list = {}; # Per-host iSCSI global config cache
|
||||
my $freenas_global_config = undef; # Pointer to entry in $freenas_global_config_list
|
||||
my $dev_prefix = "";
|
||||
my $product_name = undef;
|
||||
|
||||
# FreeNAS API definitions
|
||||
my $freenas_api_version = "v1.0"; # Default to v1.0 of the API's
|
||||
my $freenas_api_methods = undef; # API Methods Nested HASH Ref
|
||||
my $freenas_api_variables = undef; # API Variable Nested HASH Ref
|
||||
my $truenas_version = undef;
|
||||
my $truenas_release_type = "Production";
|
||||
|
||||
# FreeNAS/TrueNAS (CORE) API Versioning HashRef Matrix
|
||||
my $freenas_api_version_matrix = {
|
||||
"v1.0" => {
|
||||
"methods" => {
|
||||
"config" => {
|
||||
"resource" => "/api/v1.0/services/iscsi/globalconfiguration/",
|
||||
},
|
||||
"target" => {
|
||||
"resource" => "/api/v1.0/services/iscsi/target/",
|
||||
},
|
||||
"extent" => {
|
||||
"resource" => "/api/v1.0/services/iscsi/extent/",
|
||||
"post_body" => {
|
||||
"iscsi_target_extent_type" => "Disk",
|
||||
"iscsi_target_extent_name" => "\$name",
|
||||
"iscsi_target_extent_disk" => "\$device",
|
||||
},
|
||||
},
|
||||
"targetextent" => {
|
||||
"resource" => "/api/v1.0/services/iscsi/targettoextent/",
|
||||
"post_body" => {
|
||||
"iscsi_target" => "\$target_id",
|
||||
"iscsi_extent" => "\$extent_id",
|
||||
"iscsi_lunid" => "\$lun_id",
|
||||
},
|
||||
},
|
||||
},
|
||||
"variables" => {
|
||||
"basename" => "iscsi_basename",
|
||||
"lunid" => "iscsi_lunid",
|
||||
"extentid" => "iscsi_extent",
|
||||
"targetid" => "iscsi_target",
|
||||
"extentpath" => "iscsi_target_extent_path",
|
||||
"extentnaa" => "iscsi_target_extent_naa",
|
||||
"targetname" => "iscsi_target_name",
|
||||
}
|
||||
},
|
||||
"v2.0" => {
|
||||
"methods" => {
|
||||
"config" => {
|
||||
"resource" => "/api/v2.0/iscsi/global",
|
||||
},
|
||||
"target" => {
|
||||
"resource" => "/api/v2.0/iscsi/target/",
|
||||
},
|
||||
"extent" => {
|
||||
"resource" => "/api/v2.0/iscsi/extent/",
|
||||
"delete_body" => {
|
||||
"remove" => \1,
|
||||
"force" => \1,
|
||||
},
|
||||
"post_body" => {
|
||||
"type" => "DISK",
|
||||
"name" => "\$name",
|
||||
"disk" => "\$device",
|
||||
},
|
||||
},
|
||||
"targetextent" => {
|
||||
"resource" => "/api/v2.0/iscsi/targetextent/",
|
||||
"delete_body" => {
|
||||
"force" => \1,
|
||||
},
|
||||
"post_body" => {
|
||||
"target" => "\$target_id",
|
||||
"extent" => "\$extent_id",
|
||||
"lunid" => "\$lun_id",
|
||||
},
|
||||
},
|
||||
},
|
||||
"variables" => {
|
||||
"basename" => "basename",
|
||||
"lunid" => "lunid",
|
||||
"extentid" => "extent",
|
||||
"targetid" => "target",
|
||||
"extentpath" => "path",
|
||||
"extentnaa" => "naa",
|
||||
"targetname" => "name",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
#
|
||||
#
|
||||
#
|
||||
sub get_base {
|
||||
return '/dev/zvol';
|
||||
}
|
||||
|
||||
|
||||
#
|
||||
# Subroutine called from ZFSPlugin.pm
|
||||
#
|
||||
sub run_lun_command {
|
||||
my ($scfg, $timeout, $method, @params) = @_;
|
||||
|
||||
syslog("info",(caller(0))[3] . " : $method(@params)");
|
||||
|
||||
if (defined($scfg->{'truenas_token_auth'}) && $scfg->{'truenas_token_auth'}) {
|
||||
if (!defined($scfg->{'truenas_secret'})) {
|
||||
die "Undefined `truenas_secret` variable.";
|
||||
}
|
||||
} elsif (!defined($scfg->{'freenas_user'}) || !defined($scfg->{'freenas_password'})) {
|
||||
die "Undefined `freenas_user` and/or `freenas_password` variables.";
|
||||
}
|
||||
if (!defined $freenas_server_list->{defined($scfg->{freenas_apiv4_host}) ? $scfg->{freenas_apiv4_host} : $scfg->{portal}}) {
|
||||
freenas_api_check($scfg);
|
||||
}
|
||||
|
||||
if($method eq "create_lu") {
|
||||
return run_create_lu($scfg, $timeout, $method, @params);
|
||||
}
|
||||
if($method eq "delete_lu") {
|
||||
return run_delete_lu($scfg, $timeout, $method, @params);
|
||||
}
|
||||
if($method eq "import_lu") {
|
||||
return run_create_lu($scfg, $timeout, $method, @params);
|
||||
}
|
||||
if($method eq "modify_lu") {
|
||||
return run_modify_lu($scfg, $timeout, $method, @params);
|
||||
}
|
||||
if($method eq "add_view") {
|
||||
return run_add_view($scfg, $timeout, $method, @params);
|
||||
}
|
||||
if($method eq "list_view") {
|
||||
return run_list_view($scfg, $timeout, $method, @params);
|
||||
}
|
||||
if($method eq "list_extent") {
|
||||
return run_list_extent($scfg, $timeout, $method, @params);
|
||||
}
|
||||
if($method eq "list_lu") {
|
||||
return run_list_lu($scfg, $timeout, $method, "name", @params);
|
||||
}
|
||||
|
||||
syslog("error",(caller(0))[3] . " : unknown method $method");
|
||||
return;
|
||||
}
|
||||
|
||||
#
|
||||
#
|
||||
#
|
||||
sub run_add_view {
|
||||
return '';
|
||||
}
|
||||
|
||||
#
|
||||
# a modify_lu occur by example on a zvol resize. we just need to destroy and recreate the lun with the same zvol.
|
||||
# Be careful, the first param is the new size of the zvol, we must shift params
|
||||
#
|
||||
sub run_modify_lu {
|
||||
my ($scfg, $timeout, $method, @params) = @_;
|
||||
|
||||
syslog("info", (caller(0))[3] . " : called");
|
||||
|
||||
shift(@params);
|
||||
run_delete_lu($scfg, $timeout, $method, @params);
|
||||
return run_create_lu($scfg, $timeout, $method, @params);
|
||||
}
|
||||
|
||||
#
|
||||
#
|
||||
#
|
||||
sub run_list_view {
|
||||
my ($scfg, $timeout, $method, @params) = @_;
|
||||
|
||||
syslog("info", (caller(0))[3] . " : called");
|
||||
|
||||
return run_list_lu($scfg, $timeout, $method, "lun-id", @params);
|
||||
}
|
||||
|
||||
#
|
||||
#
|
||||
# Optimized
|
||||
sub run_list_lu {
|
||||
my ($scfg, $timeout, $method, $result_value_type, @params) = @_;
|
||||
my $object = $params[0];
|
||||
my $result = undef;
|
||||
my $luns = freenas_list_lu($scfg);
|
||||
syslog("info", (caller(0))[3] . " : called with (method: '$method'; result_value_type: '$result_value_type'; param[0]: '$object')");
|
||||
|
||||
$object =~ s/^\Q$dev_prefix//;
|
||||
syslog("info", (caller(0))[3] . " : TrueNAS object to find: '$object'");
|
||||
if (defined($luns->{$object})) {
|
||||
my $lu_object = $luns->{$object};
|
||||
$result = $result_value_type eq "lun-id" ? $lu_object->{$freenas_api_variables->{'lunid'}} : $dev_prefix . $lu_object->{$freenas_api_variables->{'extentpath'}};
|
||||
syslog("info",(caller(0))[3] . " '$object' with key '$result_value_type' found with value: '$result'");
|
||||
} else {
|
||||
syslog("info", (caller(0))[3] . " '$object' with key '$result_value_type' was not found");
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
#
|
||||
#
|
||||
# Optimzed
|
||||
sub run_list_extent {
|
||||
my ($scfg, $timeout, $method, @params) = @_;
|
||||
my $object = $params[0];
|
||||
syslog("info", (caller(0))[3] . " : called with (method: '$method'; params[0]: '$object')");
|
||||
my $result = undef;
|
||||
my $luns = freenas_list_lu($scfg);
|
||||
|
||||
$object =~ s/^\Q$dev_prefix//;
|
||||
syslog("info", (caller(0))[3] . " TrueNAS object to find: '$object'");
|
||||
if (defined($luns->{$object})) {
|
||||
my $lu_object = $luns->{$object};
|
||||
$result = $lu_object->{$freenas_api_variables->{'extentnaa'}};
|
||||
syslog("info",(caller(0))[3] . " '$object' with key '$freenas_api_variables->{'extentnaa'}' found with value: '$result'");
|
||||
} else {
|
||||
syslog("info",(caller(0))[3] . " '$object' with key '$freenas_api_variables->{'extentnaa'}' was not found");
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
#
|
||||
#
|
||||
#
|
||||
sub run_create_lu {
|
||||
my ($scfg, $timeout, $method, @params) = @_;
|
||||
my $lun_path = $params[0];
|
||||
|
||||
syslog("info", (caller(0))[3] . " : called with (method=$method; param[0]=$lun_path)");
|
||||
|
||||
my $lun_id = freenas_get_first_available_lunid($scfg);
|
||||
|
||||
die "Maximum number of LUNs per target is $MAX_LUNS" if scalar $lun_id >= $MAX_LUNS;
|
||||
die "$params[0]: LUN $lun_path exists" if defined(run_list_lu($scfg, $timeout, $method, "name", @params));
|
||||
|
||||
my $target_id = freenas_get_targetid($scfg);
|
||||
die "Unable to find the target id for $scfg->{target}" if !defined($target_id);
|
||||
|
||||
my ($extent_id, $link_id);
|
||||
eval {
|
||||
my $extent = freenas_iscsi_create_extent($scfg, $lun_path);
|
||||
die "Unable to create extent for $lun_path" if !defined($extent);
|
||||
$extent_id = $extent->{'id'};
|
||||
|
||||
my $link = freenas_iscsi_create_target_to_extent($scfg, $target_id, $extent_id, $lun_id);
|
||||
die "Unable to link extent to target for $lun_path" if !defined($link);
|
||||
$link_id = $link->{'id'};
|
||||
|
||||
# Set FREENAS_TEST_ROLLBACK=1 in pvedaemon environment to exercise
|
||||
# rollback without a real failure (#239)
|
||||
die "TEST: forced rollback for #239 verification" if $ENV{FREENAS_TEST_ROLLBACK};
|
||||
};
|
||||
if (my $err = $@) {
|
||||
syslog("err", (caller(0))[3] . " : rolling back after error for $lun_path: $err");
|
||||
freenas_iscsi_remove_target_to_extent($scfg, $link_id) if defined $link_id;
|
||||
freenas_iscsi_remove_extent($scfg, $extent_id) if defined $extent_id;
|
||||
freenas_delete_zvol($scfg, $lun_path) if defined $extent_id;
|
||||
die "Unable to create lun $lun_path (rolled back): $err";
|
||||
}
|
||||
|
||||
syslog("info", (caller(0))[3] . "(lun_path=$lun_path, lun_id=$lun_id) : successful");
|
||||
return "";
|
||||
}
|
||||
|
||||
#
|
||||
#
|
||||
# Optimzied
|
||||
sub run_delete_lu {
|
||||
my ($scfg, $timeout, $method, @params) = @_;
|
||||
my $lun_path = $params[0];
|
||||
|
||||
syslog("info", (caller(0))[3] . " : called with (method: '$method'; param[0]: '$lun_path')");
|
||||
|
||||
my $luns = freenas_list_lu($scfg);
|
||||
my $lun = undef;
|
||||
my $link = undef;
|
||||
$lun_path =~ s/^\Q$dev_prefix//;
|
||||
|
||||
if (defined($luns->{$lun_path})) {
|
||||
$lun = $luns->{$lun_path};
|
||||
syslog("info",(caller(0))[3] . " lun: '$lun_path' found");
|
||||
} else {
|
||||
die "Unable to find the lun $lun_path for $scfg->{target}";
|
||||
}
|
||||
|
||||
my $target_id = freenas_get_targetid($scfg);
|
||||
die "Unable to find the target id for $scfg->{target}" if !defined($target_id);
|
||||
|
||||
# find the target to extent
|
||||
my $target2extents = freenas_iscsi_get_target_to_extent($scfg);
|
||||
|
||||
syslog("info", (caller(0))[3] . " : searching for 'targetextent' with (target_id=$target_id; lun_id=$lun->{$freenas_api_variables->{'lunid'}}; extent_id=$lun->{id})");
|
||||
foreach my $item (@$target2extents) {
|
||||
if($item->{$freenas_api_variables->{'targetid'}} == $target_id &&
|
||||
$item->{$freenas_api_variables->{'lunid'}} == $lun->{$freenas_api_variables->{'lunid'}} &&
|
||||
$item->{$freenas_api_variables->{'extentid'}} == $lun->{'id'}) {
|
||||
$link = $item;
|
||||
syslog("info", (caller(0))[3] . " : found 'targetextent'(target_id=$item->{$freenas_api_variables->{'targetid'}}; lun_id=$item->{$freenas_api_variables->{'lunid'}}; extent_id=$item->{$freenas_api_variables->{'extentid'}})");
|
||||
last;
|
||||
}
|
||||
}
|
||||
die "Unable to find the link for the lun $lun_path for $scfg->{target}" if !defined($link);
|
||||
|
||||
# Remove the extent
|
||||
my $remove_extent = freenas_iscsi_remove_extent($scfg, $lun->{'id'});
|
||||
|
||||
# Remove the link
|
||||
my $remove_link = freenas_iscsi_remove_target_to_extent($scfg, $link->{'id'});
|
||||
|
||||
# NOTE: zvol deletion is intentionally left to ZFSPlugin's SSH path (zfs_delete_zvol).
|
||||
# Deleting it here caused a double-delete: SSH would fail, ZFSPlugin's error recovery
|
||||
# would call run_create_lu on a missing zvol, producing a false "Unable to create lun"
|
||||
# error in the task log (#240). freenas_delete_zvol belongs only in run_create_lu's
|
||||
# rollback path (#239).
|
||||
|
||||
if($remove_link == 1 && $remove_extent == 1) {
|
||||
syslog("info", (caller(0))[3] . "(lun_path=$lun_path) : successful");
|
||||
} else {
|
||||
die "Unable to delete lun $lun_path";
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
|
||||
sub freenas_api_connect {
|
||||
my ($scfg, $ping) = @_;
|
||||
$ping //= '/api/v1.0/system/version/';
|
||||
|
||||
syslog("info", (caller(0))[3] . " : called");
|
||||
|
||||
my $scheme = $scfg->{freenas_use_ssl} ? "https" : "http";
|
||||
my $apihost = defined($scfg->{freenas_apiv4_host}) ? $scfg->{freenas_apiv4_host} : $scfg->{portal};
|
||||
|
||||
if (!defined $freenas_server_list->{$apihost} || !defined $freenas_server_list->{$apihost}{client}) {
|
||||
$freenas_server_list->{$apihost} = {
|
||||
client => REST::Client->new(),
|
||||
product_name => undef,
|
||||
api_version => 'v1.0',
|
||||
dev_prefix => '',
|
||||
runaway_count => 0,
|
||||
};
|
||||
}
|
||||
my $host = $freenas_server_list->{$apihost};
|
||||
$host->{client}->setHost($scheme . '://' . $apihost);
|
||||
$host->{client}->addHeader('Content-Type', 'application/json');
|
||||
if (defined($scfg->{'truenas_token_auth'}) && $scfg->{'truenas_token_auth'}) {
|
||||
syslog("info", (caller(0))[3] . " : Authentication using Bearer Token Auth");
|
||||
if (!$scfg->{freenas_use_ssl}) {
|
||||
syslog("warn", (caller(0))[3] . " : Bearer Token used without SSL — TrueNAS 25.04+ revokes API keys sent over plain HTTP; enable 'Use SSL' in storage config");
|
||||
}
|
||||
$host->{client}->addHeader('Authorization', 'Bearer ' . $scfg->{truenas_secret});
|
||||
} else {
|
||||
syslog("warn", (caller(0))[3] . " : Authentication using Basic Auth (username/password) -- DEPRECATED. Generate an API key in TrueNAS and switch to token auth. Basic auth will be removed in v3.0.0.");
|
||||
$host->{client}->addHeader('Authorization', 'Basic ' . encode_base64($scfg->{freenas_user} . ':' . $scfg->{freenas_password}));
|
||||
}
|
||||
# If using SSL, don't verify SSL certs
|
||||
if ($scfg->{freenas_use_ssl}) {
|
||||
$host->{client}->getUseragent()->ssl_opts(verify_hostname => 0);
|
||||
$host->{client}->getUseragent()->ssl_opts(SSL_verify_mode => SSL_VERIFY_NONE);
|
||||
}
|
||||
# Check if the APIs are accessible via the selected host and scheme
|
||||
my $api_response = $host->{client}->request('GET', $ping);
|
||||
my $code = $api_response->responseCode();
|
||||
my $type = $api_response->responseHeader('Content-Type');
|
||||
syslog("info", (caller(0))[3] . " : REST connection header Content-Type:'" . $type . "'");
|
||||
|
||||
# Make sure we are not recursion calling.
|
||||
if ($host->{runaway_count} > 2) {
|
||||
freenas_api_log_error($host->{client});
|
||||
die "Loop recursion prevention";
|
||||
# Successful connection
|
||||
} elsif ($code == 200 && ($type =~ /^text\/plain/ || $type =~ /^application\/json/)) {
|
||||
syslog("info", (caller(0))[3] . " : REST connection successful to '" . $apihost . "' using the '" . $scheme . "' protocol");
|
||||
$host->{runaway_count} = 0;
|
||||
# A 302 or 200 (wrong content-type) means v1.0 is redirecting — upgrade to v2.0.
|
||||
# TrueNAS 25.04+ removed v1.0 entirely and returns 404 instead of 302; treat
|
||||
# that the same way so long as we are still probing the v1.0 endpoint.
|
||||
} elsif ($code == 302 || $code == 200 || ($code == 404 && $ping =~ /v1\.0/)) {
|
||||
syslog("info", (caller(0))[3] . " : v1.0 API unavailable (HTTP $code) — upgrading to v2.0");
|
||||
$host->{runaway_count}++;
|
||||
$ping =~ s/v1\.0/v2\.0/;
|
||||
freenas_api_connect($scfg, $ping);
|
||||
# A 307 from FreeNAS means redirect http to https.
|
||||
} elsif ($code == 307) {
|
||||
syslog("info", (caller(0))[3] . " : Redirecting to HTTPS protocol");
|
||||
$host->{runaway_count}++;
|
||||
$scfg->{freenas_use_ssl} = 1;
|
||||
freenas_api_connect($scfg, $ping);
|
||||
# For now, any other code we fail.
|
||||
} else {
|
||||
freenas_api_log_error($host->{client});
|
||||
die "Unable to connect to the FreeNAS API service at '" . $apihost . "' using the '" . $scheme . "' protocol";
|
||||
}
|
||||
$freenas_rest_connection = $host->{client};
|
||||
return;
|
||||
}
|
||||
|
||||
#
|
||||
# Check to see what FreeNAS version we are running and set
|
||||
# the FreeNAS.pm to use the correct API version of FreeNAS
|
||||
#
|
||||
sub freenas_api_check {
|
||||
my ($scfg, $timeout) = @_;
|
||||
my $result = {};
|
||||
my $apihost = defined($scfg->{freenas_apiv4_host}) ? $scfg->{freenas_apiv4_host} : $scfg->{portal};
|
||||
|
||||
syslog("info", (caller(0))[3] . " : called");
|
||||
|
||||
if (!defined $freenas_server_list->{$apihost}{client}) {
|
||||
freenas_api_connect($scfg);
|
||||
$result = $freenas_rest_connection->responseContent();
|
||||
$result =~ s/"//g;
|
||||
syslog("info", (caller(0))[3] . " : successful : Server version: " . $result);
|
||||
if ($result =~ /^(TrueNAS|FreeNAS)-(\d+)\.(\d+)\-U(\d+)(?(?=\.)\.(\d+))$/) {
|
||||
$product_name = $1;
|
||||
$truenas_version = sprintf("%02d%02d%02d%02d", $2, $3 || 0, $4 || 0, $5 || 0);
|
||||
} elsif ($result =~ /^(TrueNAS)-(\d+)\.(\d+)(?(?=\-U\d+)-U(\d+)|-\w+)(?(?=\.).(\d+))$/) {
|
||||
$product_name = $1;
|
||||
$truenas_version = sprintf("%02d%02d%02d%02d", $2, $3 || 0, $4 || 0, $6 || 0);
|
||||
$truenas_release_type = $5 || "Production";
|
||||
} elsif ($result =~ /^(TrueNAS-SCALE)-(\d+)\.(\d+)(?(?=\-)-(\w+))\.(\d+)(?(?=\.)\.(\d+))(?(?=\-)-(\d+))$/) {
|
||||
$product_name = $1;
|
||||
$truenas_version = sprintf("%02d%02d%02d%02d", $2, $3 || 0, $5 || 0, $7 || 0);
|
||||
$truenas_release_type = $4 || "Production";
|
||||
} else {
|
||||
$product_name = "Unknown";
|
||||
$truenas_release_type = "Unknown";
|
||||
syslog("error", (caller(0))[3] . " : Could not parse the version of TrueNAS.");
|
||||
}
|
||||
syslog("info", (caller(0))[3] . " : ". $product_name . " Unformatted Version: " . $truenas_version);
|
||||
if ($truenas_version >= 11030100) {
|
||||
$freenas_api_version = "v2.0";
|
||||
$dev_prefix = "/dev/";
|
||||
}
|
||||
if ($truenas_release_type ne "Production") {
|
||||
syslog("warn", (caller(0))[3] . " : The '" . $product_name . "' release type of '" . $truenas_release_type . "' may not worked due to unsupported changes.");
|
||||
}
|
||||
# Save per-host metadata so subsequent calls restore state correctly
|
||||
$freenas_server_list->{$apihost}{product_name} = $product_name;
|
||||
$freenas_server_list->{$apihost}{api_version} = $freenas_api_version;
|
||||
$freenas_server_list->{$apihost}{dev_prefix} = $dev_prefix;
|
||||
} else {
|
||||
syslog("info", (caller(0))[3] . " : REST Client already initialized");
|
||||
}
|
||||
syslog("info", (caller(0))[3] . " : Using " . $product_name . " API version " . $freenas_api_version);
|
||||
$freenas_api_methods = $freenas_api_version_matrix->{$freenas_api_version}->{'methods'};
|
||||
$freenas_api_variables = $freenas_api_version_matrix->{$freenas_api_version}->{'variables'};
|
||||
$freenas_global_config = $freenas_global_config_list->{$apihost} = (!defined($freenas_global_config_list->{$apihost})) ? freenas_iscsi_get_globalconfiguration($scfg) : $freenas_global_config_list->{$apihost};
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
#
|
||||
### FREENAS API CALLING ROUTINE ###
|
||||
#
|
||||
sub freenas_api_call {
|
||||
my ($scfg, $method, $path, $data) = @_;
|
||||
my $apihost = defined($scfg->{freenas_apiv4_host}) ? $scfg->{freenas_apiv4_host} : $scfg->{portal};
|
||||
|
||||
syslog("info", (caller(0))[3] . " : called for host '" . $apihost . "'");
|
||||
|
||||
$method = uc($method);
|
||||
if ($method !~ /^(?:GET|DELETE|POST)$/) {
|
||||
syslog("info", (caller(0))[3] . " : Invalid HTTP RESTful service method '$method'");
|
||||
die "Invalid HTTP RESTful service method '$method' used.";
|
||||
}
|
||||
|
||||
if (!defined $freenas_server_list->{$apihost}{client}) {
|
||||
freenas_api_check($scfg);
|
||||
}
|
||||
my $host = $freenas_server_list->{$apihost};
|
||||
$freenas_rest_connection = $host->{client};
|
||||
$freenas_global_config = $freenas_global_config_list->{$apihost};
|
||||
$product_name = $host->{product_name};
|
||||
$dev_prefix = $host->{dev_prefix};
|
||||
$freenas_api_methods = $freenas_api_version_matrix->{$host->{api_version}}{methods};
|
||||
$freenas_api_variables = $freenas_api_version_matrix->{$host->{api_version}}{variables};
|
||||
my $json_data = (defined $data) ? encode_json($data) : undef;
|
||||
$freenas_rest_connection->request($method, $path, $json_data);
|
||||
syslog("info", (caller(0))[3] . " : successful");
|
||||
return;
|
||||
}
|
||||
|
||||
#
|
||||
# Writes the Response and Content to SysLog
|
||||
#
|
||||
sub freenas_api_log_error {
|
||||
my ($rest_connection) = @_;
|
||||
my $connection = ((defined $rest_connection) ? $rest_connection : $freenas_rest_connection);
|
||||
syslog("info","[ERROR]FreeNAS::API::" . (caller(1))[3] . " : Response code: " . $connection->responseCode());
|
||||
syslog("info","[ERROR]FreeNAS::API::" . (caller(1))[3] . " : Response content: " . $connection->responseContent());
|
||||
return 1;
|
||||
}
|
||||
|
||||
#
|
||||
#
|
||||
#
|
||||
sub freenas_iscsi_get_globalconfiguration {
|
||||
my ($scfg) = @_;
|
||||
|
||||
syslog("info", (caller(0))[3] . " : called");
|
||||
|
||||
freenas_api_call($scfg, 'GET', $freenas_api_methods->{'config'}->{'resource'}, $freenas_api_methods->{'config'}->{'get'});
|
||||
my $code = $freenas_rest_connection->responseCode();
|
||||
if ($code == 200) {
|
||||
my $result = decode_json($freenas_rest_connection->responseContent());
|
||||
syslog("info", (caller(0))[3] . " : target_basename=" . $result->{$freenas_api_variables->{'basename'}});
|
||||
return $result;
|
||||
} else {
|
||||
freenas_api_log_error();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
#
|
||||
# Returns a list of all extents.
|
||||
# http://api.freenas.org/resources/iscsi/index.html#get--api-v1.0-services-iscsi-extent-
|
||||
#
|
||||
sub freenas_iscsi_get_extent {
|
||||
my ($scfg) = @_;
|
||||
|
||||
syslog("info", (caller(0))[3] . " : called");
|
||||
|
||||
freenas_api_call($scfg, 'GET', $freenas_api_methods->{'extent'}->{'resource'} . "?limit=0", $freenas_api_methods->{'extent'}->{'get'});
|
||||
my $code = $freenas_rest_connection->responseCode();
|
||||
if ($code == 200) {
|
||||
my $result = decode_json($freenas_rest_connection->responseContent());
|
||||
syslog("info", (caller(0))[3] . " : successful");
|
||||
return $result;
|
||||
} else {
|
||||
freenas_api_log_error();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
#
|
||||
# Create an extent on FreeNas
|
||||
# http://api.freenas.org/resources/iscsi/index.html#create-resource
|
||||
# Parameters:
|
||||
# - target config (scfg)
|
||||
# - lun_path
|
||||
#
|
||||
sub freenas_iscsi_create_extent {
|
||||
my ($scfg, $lun_path) = @_;
|
||||
|
||||
syslog("info", (caller(0))[3] . " : called with (lun_path=$lun_path)");
|
||||
|
||||
my $name = $lun_path;
|
||||
$name =~ s/^.*\///; # all from last /
|
||||
|
||||
my $pool = $scfg->{'pool'};
|
||||
# If TrueNAS-SCALE the slashes (/) need to be converted to dashes (-)
|
||||
if ($product_name eq "TrueNAS-SCALE") {
|
||||
$pool =~ s/\//-/g;
|
||||
syslog("info", (caller(0))[3] . " : TrueNAS-SCALE slash to dash conversion '" . $pool ."'");
|
||||
}
|
||||
$name = $pool . ($product_name eq "TrueNAS-SCALE" ? '-' : '/') . $name;
|
||||
syslog("info", (caller(0))[3] . " : " . $product_name . " extent '". $name . "'");
|
||||
|
||||
my $device = $lun_path;
|
||||
$device =~ s/^\/dev\///; # strip /dev/
|
||||
|
||||
my %extent_vars = ('$name' => $name, '$device' => $device);
|
||||
my $post_body = {};
|
||||
while ((my $key, my $value) = each %{$freenas_api_methods->{'extent'}->{'post_body'}}) {
|
||||
$post_body->{$key} = exists $extent_vars{$value} ? $extent_vars{$value} : $value;
|
||||
}
|
||||
|
||||
freenas_api_call($scfg, 'POST', $freenas_api_methods->{'extent'}->{'resource'}, $post_body);
|
||||
my $code = $freenas_rest_connection->responseCode();
|
||||
if ($code == 200 || $code == 201) {
|
||||
my $result = decode_json($freenas_rest_connection->responseContent());
|
||||
syslog("info", "FreeNAS::API::create_extent(lun_path=" . $result->{$freenas_api_variables->{'extentpath'}} . ") : successful");
|
||||
return $result;
|
||||
} else {
|
||||
freenas_api_log_error();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
#
|
||||
# Explicitly delete the underlying ZFS zvol via the pool/dataset API.
|
||||
# The extent DELETE with remove:true is unreliable on TrueNAS SCALE 24.10+
|
||||
# (extent config is removed but the zvol is left behind). This function
|
||||
# is a belt-and-suspenders call after every extent removal.
|
||||
# Only runs on v2.0; v1.0 hosts are expected to handle it via remove:true.
|
||||
# Parameters:
|
||||
# - scfg
|
||||
# - lun_path (either /dev/zvol/tank/... or zvol/tank/... form)
|
||||
#
|
||||
sub freenas_delete_zvol {
|
||||
my ($scfg, $lun_path) = @_;
|
||||
|
||||
my $apihost = defined($scfg->{freenas_apiv4_host}) ? $scfg->{freenas_apiv4_host} : $scfg->{portal};
|
||||
my $api_version = ($freenas_server_list->{$apihost}{api_version} // $freenas_api_version) // '';
|
||||
return 1 unless $api_version eq 'v2.0';
|
||||
|
||||
my $dataset = $lun_path;
|
||||
$dataset =~ s{^/dev/zvol/}{}; # strip /dev/zvol/ prefix (run_create_lu form)
|
||||
$dataset =~ s{^zvol/}{}; # strip zvol/ prefix (run_delete_lu form after $dev_prefix strip)
|
||||
$dataset =~ s{/}{%2F}g; # URL-encode path separators for REST endpoint
|
||||
|
||||
syslog("info", (caller(0))[3] . " : explicitly deleting zvol dataset '$dataset'");
|
||||
freenas_api_call($scfg, 'DELETE', "/api/v2.0/pool/dataset/id/$dataset/", {"recursive" => \0});
|
||||
my $code = $freenas_rest_connection->responseCode();
|
||||
if ($code == 200 || $code == 204) {
|
||||
syslog("info", (caller(0))[3] . " : zvol '$dataset' deleted successfully");
|
||||
return 1;
|
||||
} else {
|
||||
syslog("err", (caller(0))[3] . " : WARNING: could not delete zvol '$dataset' (HTTP $code) — manual cleanup may be needed");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
# Parse a blocksize string (e.g. "8k", "16k", "8192") to bytes.
|
||||
sub freenas_parse_blocksize {
|
||||
my ($val) = @_;
|
||||
return 0 unless defined $val && $val ne '';
|
||||
return $1 * 1024 if $val =~ /^(\d+)\s*[kK]$/;
|
||||
return $1 * 1024 * 1024 if $val =~ /^(\d+)\s*[mM]$/;
|
||||
return $val + 0;
|
||||
}
|
||||
|
||||
# Returns the recommended ZFS volblocksize for this TrueNAS host based on its
|
||||
# product type. TrueNAS SCALE ships a newer ZFS that requires >= 16k; CORE is
|
||||
# fine with 8k. Returns undef if the API is unreachable so callers can fall
|
||||
# back to the user-configured value or the ZFS default.
|
||||
sub freenas_get_recommended_blocksize {
|
||||
my ($scfg) = @_;
|
||||
|
||||
my $apihost = defined($scfg->{freenas_apiv4_host}) ? $scfg->{freenas_apiv4_host} : $scfg->{portal};
|
||||
|
||||
# freenas_api_check (not freenas_api_connect) must be used here because
|
||||
# product_name is only parsed and cached by freenas_api_check.
|
||||
# freenas_api_connect only sets up the HTTP client.
|
||||
eval { freenas_api_check($scfg) };
|
||||
if ($@) {
|
||||
syslog("warn", (caller(0))[3] . " : unable to connect to TrueNAS API for blocksize detection: $@");
|
||||
return;
|
||||
}
|
||||
|
||||
my $product = $freenas_server_list->{$apihost}{product_name} // '';
|
||||
if ($product =~ /SCALE/i) {
|
||||
syslog("info", (caller(0))[3] . " : detected TrueNAS SCALE — recommending blocksize 16384");
|
||||
return 16384;
|
||||
}
|
||||
|
||||
syslog("info", (caller(0))[3] . " : detected '$product' — recommending blocksize 8192");
|
||||
return 8192;
|
||||
}
|
||||
|
||||
#
|
||||
# Remove an extent by it's id
|
||||
# http://api.freenas.org/resources/iscsi/index.html#delete-resource
|
||||
# Parameters:
|
||||
# - scfg
|
||||
# - extent_id
|
||||
#
|
||||
sub freenas_iscsi_remove_extent {
|
||||
my ($scfg, $extent_id) = @_;
|
||||
|
||||
syslog("info", (caller(0))[3] . " : called with (extent_id=$extent_id)");
|
||||
freenas_api_call($scfg, 'DELETE', $freenas_api_methods->{'extent'}->{'resource'} . (($freenas_api_version eq "v2.0") ? "id/" : "") . "$extent_id/", $freenas_api_methods->{'extent'}->{'delete_body'});
|
||||
my $code = $freenas_rest_connection->responseCode();
|
||||
if ($code == 200 || $code == 204) {
|
||||
syslog("info", (caller(0))[3] . "(extent_id=$extent_id) : successful");
|
||||
return 1;
|
||||
} else {
|
||||
freenas_api_log_error();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
#
|
||||
# Returns a list of all targets
|
||||
# http://api.freenas.org/resources/iscsi/index.html#get--api-v1.0-services-iscsi-target-
|
||||
#
|
||||
sub freenas_iscsi_get_target {
|
||||
my ($scfg) = @_;
|
||||
|
||||
syslog("info", (caller(0))[3] . " : called");
|
||||
|
||||
freenas_api_call($scfg, 'GET', $freenas_api_methods->{'target'}->{'resource'} . "?limit=0", $freenas_api_methods->{'target'}->{'get'});
|
||||
my $code = $freenas_rest_connection->responseCode();
|
||||
if ($code == 200) {
|
||||
my $result = decode_json($freenas_rest_connection->responseContent());
|
||||
syslog("info", (caller(0))[3] . " : successful");
|
||||
return $result;
|
||||
} else {
|
||||
freenas_api_log_error();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
#
|
||||
# Returns a list of associated extents to targets
|
||||
# http://api.freenas.org/resources/iscsi/index.html#get--api-v1.0-services-iscsi-targettoextent-
|
||||
#
|
||||
sub freenas_iscsi_get_target_to_extent {
|
||||
my ($scfg) = @_;
|
||||
|
||||
syslog("info", (caller(0))[3] . " : called");
|
||||
|
||||
freenas_api_call($scfg, 'GET', $freenas_api_methods->{'targetextent'}->{'resource'} . "?limit=0", $freenas_api_methods->{'targetextent'}->{'get'});
|
||||
my $code = $freenas_rest_connection->responseCode();
|
||||
if ($code == 200) {
|
||||
my $result = decode_json($freenas_rest_connection->responseContent());
|
||||
syslog("info", (caller(0))[3] . " : successful");
|
||||
# If 'iscsi_lunid' is undef then it is set to 'Auto' in FreeNAS
|
||||
# which should be '0' in our eyes.
|
||||
# This gave Proxmox 5.x and FreeNAS 11.1 a few issues.
|
||||
foreach my $item (@$result) {
|
||||
if (!defined($item->{$freenas_api_variables->{'lunid'}})) {
|
||||
$item->{$freenas_api_variables->{'lunid'}} = 0;
|
||||
syslog("info", (caller(0))[3] . " : change undef iscsi_lunid to 0");
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
} else {
|
||||
freenas_api_log_error();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
#
|
||||
# Associate a FreeNas extent to a FreeNas Target
|
||||
# http://api.freenas.org/resources/iscsi/index.html#post--api-v1.0-services-iscsi-targettoextent-
|
||||
# Parameters:
|
||||
# - target config (scfg)
|
||||
# - FreeNas Target ID
|
||||
# - FreeNas Extent ID
|
||||
# - Lun ID
|
||||
#
|
||||
sub freenas_iscsi_create_target_to_extent {
|
||||
my ($scfg, $target_id, $extent_id, $lun_id) = @_;
|
||||
|
||||
syslog("info", (caller(0))[3] . " : called with (target_id=$target_id, extent_id=$extent_id, lun_id=$lun_id)");
|
||||
|
||||
my %tte_vars = ('$target_id' => $target_id, '$extent_id' => $extent_id, '$lun_id' => $lun_id);
|
||||
my $post_body = {};
|
||||
while ((my $key, my $value) = each %{$freenas_api_methods->{'targetextent'}->{'post_body'}}) {
|
||||
$post_body->{$key} = exists $tte_vars{$value} ? $tte_vars{$value} : $value;
|
||||
}
|
||||
|
||||
freenas_api_call($scfg, 'POST', $freenas_api_methods->{'targetextent'}->{'resource'}, $post_body);
|
||||
my $code = $freenas_rest_connection->responseCode();
|
||||
if ($code == 200 || $code == 201) {
|
||||
my $result = decode_json($freenas_rest_connection->responseContent());
|
||||
syslog("info", (caller(0))[3] . "(target_id=$target_id, extent_id=$extent_id, lun_id=$lun_id) : successful");
|
||||
return $result;
|
||||
} else {
|
||||
freenas_api_log_error();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
#
|
||||
# Remove a Target to extent by it's id
|
||||
# http://api.freenas.org/resources/iscsi/index.html#delete--api-v1.0-services-iscsi-targettoextent-(int-id)-
|
||||
# Parameters:
|
||||
# - scfg
|
||||
# - link_id
|
||||
#
|
||||
sub freenas_iscsi_remove_target_to_extent {
|
||||
my ($scfg, $link_id) = @_;
|
||||
|
||||
syslog("info", (caller(0))[3] . " : called with (link_id=$link_id)");
|
||||
|
||||
if ($freenas_api_version eq "v2.0") {
|
||||
syslog("info", (caller(0))[3] . "(link_id=$link_id) : V2.0 API's so NOT Needed...successful");
|
||||
return 1;
|
||||
}
|
||||
|
||||
freenas_api_call($scfg, 'DELETE', $freenas_api_methods->{'targetextent'}->{'resource'} . (($freenas_api_version eq "v2.0") ? "id/" : "") . "$link_id/", $freenas_api_methods->{'targetextent'}->{'delete_body'});
|
||||
my $code = $freenas_rest_connection->responseCode();
|
||||
if ($code == 200 || $code == 204) {
|
||||
syslog("info", (caller(0))[3] . "(link_id=$link_id) : successful");
|
||||
return 1;
|
||||
} else {
|
||||
freenas_api_log_error();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
#
|
||||
# Returns all luns associated to the current target defined by $scfg->{target}
|
||||
# This method returns an array reference like "freenas_iscsi_get_extent" do
|
||||
# but with an additionnal hash entry "iscsi_lunid" retrieved from "freenas_iscsi_get_target_to_extent"
|
||||
#
|
||||
sub freenas_list_lu {
|
||||
my ($scfg) = @_;
|
||||
|
||||
syslog("info", (caller(0))[3] . " : called");
|
||||
|
||||
my $targets = freenas_iscsi_get_target($scfg);
|
||||
my $target_id = freenas_get_targetid($scfg);
|
||||
|
||||
my %lun_hash;
|
||||
my $iscsi_lunid = undef;
|
||||
|
||||
if(defined($target_id)) {
|
||||
my $target2extents = freenas_iscsi_get_target_to_extent($scfg);
|
||||
my $extents = freenas_iscsi_get_extent($scfg);
|
||||
|
||||
foreach my $item (@$target2extents) {
|
||||
if($item->{$freenas_api_variables->{'targetid'}} == $target_id) {
|
||||
foreach my $node (@$extents) {
|
||||
if($node->{'id'} == $item->{$freenas_api_variables->{'extentid'}}) {
|
||||
if ($item->{$freenas_api_variables->{'lunid'}} =~ /(\d+)/) {
|
||||
if (defined($node)) {
|
||||
$node->{$freenas_api_variables->{'lunid'}} .= "$1";
|
||||
$lun_hash{$node->{$freenas_api_variables->{'extentpath'}}} = $node;
|
||||
}
|
||||
last;
|
||||
} else {
|
||||
syslog("warn", (caller(0))[3] . " : iscsi_lunid did not pass tainted testing");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
syslog("info", (caller(0))[3] . " : successful");
|
||||
return \%lun_hash;
|
||||
}
|
||||
|
||||
#
|
||||
# Returns the first available "lunid" (in all targets namespaces)
|
||||
#
|
||||
sub freenas_get_first_available_lunid {
|
||||
my ($scfg) = @_;
|
||||
|
||||
syslog("info", (caller(0))[3] . " : called");
|
||||
|
||||
my $target_id = freenas_get_targetid($scfg);
|
||||
my $target2extents = freenas_iscsi_get_target_to_extent($scfg);
|
||||
my @luns = ();
|
||||
|
||||
foreach my $item (@$target2extents) {
|
||||
push(@luns, $item->{$freenas_api_variables->{'lunid'}}) if ($item->{$freenas_api_variables->{'targetid'}} == $target_id);
|
||||
}
|
||||
|
||||
my @sorted_luns = sort {$a <=> $b} @luns;
|
||||
my $lun_id = 0;
|
||||
|
||||
# find the first hole, if not, give the +1 of the last lun
|
||||
foreach my $lun (@sorted_luns) {
|
||||
last if $lun != $lun_id;
|
||||
$lun_id = $lun_id + 1;
|
||||
}
|
||||
|
||||
syslog("info", (caller(0))[3] . " : $lun_id");
|
||||
return $lun_id;
|
||||
}
|
||||
|
||||
#
|
||||
# Returns the target id on FreeNas of the currently configured target of this PVE storage
|
||||
#
|
||||
sub freenas_get_targetid {
|
||||
my ($scfg) = @_;
|
||||
|
||||
syslog("info", (caller(0))[3] . " : called");
|
||||
|
||||
my $targets = freenas_iscsi_get_target($scfg);
|
||||
my $target_id = undef;
|
||||
|
||||
foreach my $target (@$targets) {
|
||||
my $iqn = $freenas_global_config->{$freenas_api_variables->{'basename'}} . ':' . $target->{$freenas_api_variables->{'targetname'}};
|
||||
if($iqn eq $scfg->{target}) {
|
||||
$target_id = $target->{'id'};
|
||||
last;
|
||||
}
|
||||
}
|
||||
syslog("info", (caller(0))[3] . " : successful : $target_id");
|
||||
return $target_id;
|
||||
}
|
||||
|
||||
|
||||
1;
|
||||
|
|
@ -1,140 +0,0 @@
|
|||
--- ZFSPlugin.pm.orig 2018-10-03 11:02:46.000000000 -0400
|
||||
+++ ZFSPlugin.pm 2018-10-28 21:27:44.794732848 -0400
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
use base qw(PVE::Storage::ZFSPoolPlugin);
|
||||
use PVE::Storage::LunCmd::Comstar;
|
||||
+use PVE::Storage::LunCmd::FreeNAS;
|
||||
use PVE::Storage::LunCmd::Istgt;
|
||||
use PVE::Storage::LunCmd::Iet;
|
||||
use PVE::Storage::LunCmd::LIO;
|
||||
@@ -32,7 +33,7 @@
|
||||
my $zfs_unknown_scsi_provider = sub {
|
||||
my ($provider) = @_;
|
||||
|
||||
- die "$provider: unknown iscsi provider. Available [comstar, istgt, iet, LIO]";
|
||||
+ die "$provider: unknown iscsi provider. Available [comstar, freenas, istgt, iet, LIO]";
|
||||
};
|
||||
|
||||
my $zfs_get_base = sub {
|
||||
@@ -40,6 +41,8 @@
|
||||
|
||||
if ($scfg->{iscsiprovider} eq 'comstar') {
|
||||
return PVE::Storage::LunCmd::Comstar::get_base;
|
||||
+ } elsif ($scfg->{iscsiprovider} eq 'freenas') {
|
||||
+ return PVE::Storage::LunCmd::FreeNAS::get_base;
|
||||
} elsif ($scfg->{iscsiprovider} eq 'istgt') {
|
||||
return PVE::Storage::LunCmd::Istgt::get_base;
|
||||
} elsif ($scfg->{iscsiprovider} eq 'iet') {
|
||||
@@ -62,6 +65,8 @@
|
||||
if ($lun_cmds->{$method}) {
|
||||
if ($scfg->{iscsiprovider} eq 'comstar') {
|
||||
$msg = PVE::Storage::LunCmd::Comstar::run_lun_command($scfg, $timeout, $method, @params);
|
||||
+ } elsif ($scfg->{iscsiprovider} eq 'freenas') {
|
||||
+ $msg = PVE::Storage::LunCmd::FreeNAS::run_lun_command($scfg, $timeout, $method, @params);
|
||||
} elsif ($scfg->{iscsiprovider} eq 'istgt') {
|
||||
$msg = PVE::Storage::LunCmd::Istgt::run_lun_command($scfg, $timeout, $method, @params);
|
||||
} elsif ($scfg->{iscsiprovider} eq 'iet') {
|
||||
@@ -160,6 +165,15 @@
|
||||
return $class->zfs_request($scfg, undef, 'list_view', $guid);
|
||||
}
|
||||
|
||||
+# Part of the multipath enhancement
|
||||
+sub zfs_get_wwid_number {
|
||||
+ my ($class, $scfg, $guid) = @_;
|
||||
+
|
||||
+ die "could not find lun_number for guid $guid" if !$guid;
|
||||
+
|
||||
+ return $class->zfs_request($scfg, undef, 'list_extent', $guid);
|
||||
+}
|
||||
+
|
||||
# Configuration
|
||||
|
||||
sub type {
|
||||
@@ -178,6 +192,24 @@
|
||||
description => "iscsi provider",
|
||||
type => 'string',
|
||||
},
|
||||
+ # This is for FreeNAS iscsi and API intergration
|
||||
+ # And some enhancements asked by the community
|
||||
+ freenas_user => {
|
||||
+ description => "FreeNAS API Username",
|
||||
+ type => 'string',
|
||||
+ },
|
||||
+ freenas_password => {
|
||||
+ description => "FreeNAS API Password",
|
||||
+ type => 'string',
|
||||
+ },
|
||||
+ freenas_use_ssl => {
|
||||
+ description => "FreeNAS API access via SSL",
|
||||
+ type => 'boolean',
|
||||
+ },
|
||||
+ freenas_apiv4_host => {
|
||||
+ description => "FreeNAS API Host",
|
||||
+ type => 'string',
|
||||
+ },
|
||||
# this will disable write caching on comstar and istgt.
|
||||
# it is not implemented for iet. iet blockio always operates with
|
||||
# writethrough caching when not in readonly mode
|
||||
@@ -205,14 +237,18 @@
|
||||
nodes => { optional => 1 },
|
||||
disable => { optional => 1 },
|
||||
portal => { fixed => 1 },
|
||||
- target => { fixed => 1 },
|
||||
- pool => { fixed => 1 },
|
||||
+ target => { fixed => 0 },
|
||||
+ pool => { fixed => 0 },
|
||||
blocksize => { fixed => 1 },
|
||||
iscsiprovider => { fixed => 1 },
|
||||
nowritecache => { optional => 1 },
|
||||
sparse => { optional => 1 },
|
||||
comstar_hg => { optional => 1 },
|
||||
comstar_tg => { optional => 1 },
|
||||
+ freenas_user => { optional => 1 },
|
||||
+ freenas_password => { optional => 1 },
|
||||
+ freenas_use_ssl => { optional => 1 },
|
||||
+ freenas_apiv4_host => { optional => 1 },
|
||||
lio_tpg => { optional => 1 },
|
||||
content => { optional => 1 },
|
||||
bwlimit => { optional => 1 },
|
||||
@@ -237,6 +273,40 @@
|
||||
|
||||
my $path = "iscsi://$portal/$target/$lun";
|
||||
|
||||
+ # Multipath enhancement
|
||||
+ eval {
|
||||
+ my $wwid = $class->zfs_get_wwid_number($scfg, $guid);
|
||||
+# syslog(info,"JD: path get_lun_number guid $guid");
|
||||
+
|
||||
+ if ($wwid =~ /^([-\@\w.]+)$/) {
|
||||
+ $wwid = $1; # $data now untainted
|
||||
+ } else {
|
||||
+ die "Bad data in '$wwid'"; # log this somewhere
|
||||
+ }
|
||||
+ my $wwid_end = substr $wwid, 16;
|
||||
+
|
||||
+ my $mapper = '';
|
||||
+ sleep 3;
|
||||
+ run_command("iscsiadm -m session --rescan");
|
||||
+ sleep 3;
|
||||
+ my $line = `multipath -ll | grep \"$wwid_end\"`;
|
||||
+ my ($mapper_device) = split(' ', $line);
|
||||
+ $mapper_device = "" unless $mapper_device;
|
||||
+ $mapper .= $mapper_device;
|
||||
+
|
||||
+ if ($mapper =~ /^([-\@\w.]+)$/) {
|
||||
+ $mapper = $1; # $data now untainted
|
||||
+ } else {
|
||||
+ $mapper = '';
|
||||
+ }
|
||||
+
|
||||
+# syslog(info,"Multipath mapper found: $mapper\n");
|
||||
+ if ($mapper ne "") {
|
||||
+ $path = "/dev/mapper/$mapper";
|
||||
+ sleep 5;
|
||||
+ }
|
||||
+ };
|
||||
+
|
||||
return ($path, $vmid, $vtype);
|
||||
}
|
||||
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
--- ZFSPlugin-5.0-33.pm 2018-12-15 09:29:56.586101170 -0500
|
||||
+++ ZFSPlugin.pm 2018-12-15 09:24:08.393984276 -0500
|
||||
@@ -27,6 +27,7 @@
|
||||
modify_lu => 1,
|
||||
add_view => 1,
|
||||
list_view => 1,
|
||||
+ list_extent => 1,
|
||||
list_lu => 1,
|
||||
};
|
||||
|
||||
|
|
@ -1,147 +0,0 @@
|
|||
--- ZFSPlugin.pm.orig 2018-11-27 07:23:15.000000000 -0500
|
||||
+++ ZFSPlugin.pm 2018-12-15 09:24:08.393984276 -0500
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
use base qw(PVE::Storage::ZFSPoolPlugin);
|
||||
use PVE::Storage::LunCmd::Comstar;
|
||||
+use PVE::Storage::LunCmd::FreeNAS;
|
||||
use PVE::Storage::LunCmd::Istgt;
|
||||
use PVE::Storage::LunCmd::Iet;
|
||||
use PVE::Storage::LunCmd::LIO;
|
||||
@@ -26,13 +27,14 @@
|
||||
modify_lu => 1,
|
||||
add_view => 1,
|
||||
list_view => 1,
|
||||
+ list_extent => 1,
|
||||
list_lu => 1,
|
||||
};
|
||||
|
||||
my $zfs_unknown_scsi_provider = sub {
|
||||
my ($provider) = @_;
|
||||
|
||||
- die "$provider: unknown iscsi provider. Available [comstar, istgt, iet, LIO]";
|
||||
+ die "$provider: unknown iscsi provider. Available [comstar, freenas, istgt, iet, LIO]";
|
||||
};
|
||||
|
||||
my $zfs_get_base = sub {
|
||||
@@ -40,6 +42,8 @@
|
||||
|
||||
if ($scfg->{iscsiprovider} eq 'comstar') {
|
||||
return PVE::Storage::LunCmd::Comstar::get_base;
|
||||
+ } elsif ($scfg->{iscsiprovider} eq 'freenas') {
|
||||
+ return PVE::Storage::LunCmd::FreeNAS::get_base;
|
||||
} elsif ($scfg->{iscsiprovider} eq 'istgt') {
|
||||
return PVE::Storage::LunCmd::Istgt::get_base;
|
||||
} elsif ($scfg->{iscsiprovider} eq 'iet') {
|
||||
@@ -62,6 +66,8 @@
|
||||
if ($lun_cmds->{$method}) {
|
||||
if ($scfg->{iscsiprovider} eq 'comstar') {
|
||||
$msg = PVE::Storage::LunCmd::Comstar::run_lun_command($scfg, $timeout, $method, @params);
|
||||
+ } elsif ($scfg->{iscsiprovider} eq 'freenas') {
|
||||
+ $msg = PVE::Storage::LunCmd::FreeNAS::run_lun_command($scfg, $timeout, $method, @params);
|
||||
} elsif ($scfg->{iscsiprovider} eq 'istgt') {
|
||||
$msg = PVE::Storage::LunCmd::Istgt::run_lun_command($scfg, $timeout, $method, @params);
|
||||
} elsif ($scfg->{iscsiprovider} eq 'iet') {
|
||||
@@ -160,6 +166,15 @@
|
||||
return $class->zfs_request($scfg, undef, 'list_view', $guid);
|
||||
}
|
||||
|
||||
+# Part of the multipath enhancement
|
||||
+sub zfs_get_wwid_number {
|
||||
+ my ($class, $scfg, $guid) = @_;
|
||||
+
|
||||
+ die "could not find lun_number for guid $guid" if !$guid;
|
||||
+
|
||||
+ return $class->zfs_request($scfg, undef, 'list_extent', $guid);
|
||||
+}
|
||||
+
|
||||
# Configuration
|
||||
|
||||
sub type {
|
||||
@@ -178,6 +193,24 @@
|
||||
description => "iscsi provider",
|
||||
type => 'string',
|
||||
},
|
||||
+ # This is for FreeNAS iscsi and API intergration
|
||||
+ # And some enhancements asked by the community
|
||||
+ freenas_user => {
|
||||
+ description => "FreeNAS API Username",
|
||||
+ type => 'string',
|
||||
+ },
|
||||
+ freenas_password => {
|
||||
+ description => "FreeNAS API Password",
|
||||
+ type => 'string',
|
||||
+ },
|
||||
+ freenas_use_ssl => {
|
||||
+ description => "FreeNAS API access via SSL",
|
||||
+ type => 'boolean',
|
||||
+ },
|
||||
+ freenas_apiv4_host => {
|
||||
+ description => "FreeNAS API Host",
|
||||
+ type => 'string',
|
||||
+ },
|
||||
# this will disable write caching on comstar and istgt.
|
||||
# it is not implemented for iet. iet blockio always operates with
|
||||
# writethrough caching when not in readonly mode
|
||||
@@ -205,14 +238,18 @@
|
||||
nodes => { optional => 1 },
|
||||
disable => { optional => 1 },
|
||||
portal => { fixed => 1 },
|
||||
- target => { fixed => 1 },
|
||||
- pool => { fixed => 1 },
|
||||
+ target => { fixed => 0 },
|
||||
+ pool => { fixed => 0 },
|
||||
blocksize => { fixed => 1 },
|
||||
iscsiprovider => { fixed => 1 },
|
||||
nowritecache => { optional => 1 },
|
||||
sparse => { optional => 1 },
|
||||
comstar_hg => { optional => 1 },
|
||||
comstar_tg => { optional => 1 },
|
||||
+ freenas_user => { optional => 1 },
|
||||
+ freenas_password => { optional => 1 },
|
||||
+ freenas_use_ssl => { optional => 1 },
|
||||
+ freenas_apiv4_host => { optional => 1 },
|
||||
lio_tpg => { optional => 1 },
|
||||
content => { optional => 1 },
|
||||
bwlimit => { optional => 1 },
|
||||
@@ -237,6 +274,40 @@
|
||||
|
||||
my $path = "iscsi://$portal/$target/$lun";
|
||||
|
||||
+ # Multipath enhancement
|
||||
+ eval {
|
||||
+ my $wwid = $class->zfs_get_wwid_number($scfg, $guid);
|
||||
+# syslog(info,"JD: path get_lun_number guid $guid");
|
||||
+
|
||||
+ if ($wwid =~ /^([-\@\w.]+)$/) {
|
||||
+ $wwid = $1; # $data now untainted
|
||||
+ } else {
|
||||
+ die "Bad data in '$wwid'"; # log this somewhere
|
||||
+ }
|
||||
+ my $wwid_end = substr $wwid, 16;
|
||||
+
|
||||
+ my $mapper = '';
|
||||
+ sleep 3;
|
||||
+ run_command("iscsiadm -m session --rescan");
|
||||
+ sleep 3;
|
||||
+ my $line = `multipath -ll | grep \"$wwid_end\"`;
|
||||
+ my ($mapper_device) = split(' ', $line);
|
||||
+ $mapper_device = "" unless $mapper_device;
|
||||
+ $mapper .= $mapper_device;
|
||||
+
|
||||
+ if ($mapper =~ /^([-\@\w.]+)$/) {
|
||||
+ $mapper = $1; # $data now untainted
|
||||
+ } else {
|
||||
+ $mapper = '';
|
||||
+ }
|
||||
+
|
||||
+# syslog(info,"Multipath mapper found: $mapper\n");
|
||||
+ if ($mapper ne "") {
|
||||
+ $path = "/dev/mapper/$mapper";
|
||||
+ sleep 5;
|
||||
+ }
|
||||
+ };
|
||||
+
|
||||
return ($path, $vmid, $vtype);
|
||||
}
|
||||
|
||||
|
|
@ -1,147 +0,0 @@
|
|||
--- ZFSPlugin-5.0-34.pm 2018-12-21 12:52:21.764194866 -0500
|
||||
+++ ZFSPlugin.pm 2018-12-21 12:53:08.663665081 -0500
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
use base qw(PVE::Storage::ZFSPoolPlugin);
|
||||
use PVE::Storage::LunCmd::Comstar;
|
||||
+use PVE::Storage::LunCmd::FreeNAS;
|
||||
use PVE::Storage::LunCmd::Istgt;
|
||||
use PVE::Storage::LunCmd::Iet;
|
||||
use PVE::Storage::LunCmd::LIO;
|
||||
@@ -26,13 +27,14 @@
|
||||
modify_lu => 1,
|
||||
add_view => 1,
|
||||
list_view => 1,
|
||||
+ list_extent => 1,
|
||||
list_lu => 1,
|
||||
};
|
||||
|
||||
my $zfs_unknown_scsi_provider = sub {
|
||||
my ($provider) = @_;
|
||||
|
||||
- die "$provider: unknown iscsi provider. Available [comstar, istgt, iet, LIO]";
|
||||
+ die "$provider: unknown iscsi provider. Available [comstar, freenas, istgt, iet, LIO]";
|
||||
};
|
||||
|
||||
my $zfs_get_base = sub {
|
||||
@@ -40,6 +42,8 @@
|
||||
|
||||
if ($scfg->{iscsiprovider} eq 'comstar') {
|
||||
return PVE::Storage::LunCmd::Comstar::get_base;
|
||||
+ } elsif ($scfg->{iscsiprovider} eq 'freenas') {
|
||||
+ return PVE::Storage::LunCmd::FreeNAS::get_base;
|
||||
} elsif ($scfg->{iscsiprovider} eq 'istgt') {
|
||||
return PVE::Storage::LunCmd::Istgt::get_base;
|
||||
} elsif ($scfg->{iscsiprovider} eq 'iet') {
|
||||
@@ -62,6 +66,8 @@
|
||||
if ($lun_cmds->{$method}) {
|
||||
if ($scfg->{iscsiprovider} eq 'comstar') {
|
||||
$msg = PVE::Storage::LunCmd::Comstar::run_lun_command($scfg, $timeout, $method, @params);
|
||||
+ } elsif ($scfg->{iscsiprovider} eq 'freenas') {
|
||||
+ $msg = PVE::Storage::LunCmd::FreeNAS::run_lun_command($scfg, $timeout, $method, @params);
|
||||
} elsif ($scfg->{iscsiprovider} eq 'istgt') {
|
||||
$msg = PVE::Storage::LunCmd::Istgt::run_lun_command($scfg, $timeout, $method, @params);
|
||||
} elsif ($scfg->{iscsiprovider} eq 'iet') {
|
||||
@@ -160,6 +166,15 @@
|
||||
return $class->zfs_request($scfg, undef, 'list_view', $guid);
|
||||
}
|
||||
|
||||
+# Part of the multipath enhancement
|
||||
+sub zfs_get_wwid_number {
|
||||
+ my ($class, $scfg, $guid) = @_;
|
||||
+
|
||||
+ die "could not find lun_number for guid $guid" if !$guid;
|
||||
+
|
||||
+ return $class->zfs_request($scfg, undef, 'list_extent', $guid);
|
||||
+}
|
||||
+
|
||||
# Configuration
|
||||
|
||||
sub type {
|
||||
@@ -178,6 +193,24 @@
|
||||
description => "iscsi provider",
|
||||
type => 'string',
|
||||
},
|
||||
+ # This is for FreeNAS iscsi and API intergration
|
||||
+ # And some enhancements asked by the community
|
||||
+ freenas_user => {
|
||||
+ description => "FreeNAS API Username",
|
||||
+ type => 'string',
|
||||
+ },
|
||||
+ freenas_password => {
|
||||
+ description => "FreeNAS API Password",
|
||||
+ type => 'string',
|
||||
+ },
|
||||
+ freenas_use_ssl => {
|
||||
+ description => "FreeNAS API access via SSL",
|
||||
+ type => 'boolean',
|
||||
+ },
|
||||
+ freenas_apiv4_host => {
|
||||
+ description => "FreeNAS API Host",
|
||||
+ type => 'string',
|
||||
+ },
|
||||
# this will disable write caching on comstar and istgt.
|
||||
# it is not implemented for iet. iet blockio always operates with
|
||||
# writethrough caching when not in readonly mode
|
||||
@@ -205,14 +238,18 @@
|
||||
nodes => { optional => 1 },
|
||||
disable => { optional => 1 },
|
||||
portal => { fixed => 1 },
|
||||
- target => { fixed => 1 },
|
||||
- pool => { fixed => 1 },
|
||||
+ target => { fixed => 0 },
|
||||
+ pool => { fixed => 0 },
|
||||
blocksize => { fixed => 1 },
|
||||
iscsiprovider => { fixed => 1 },
|
||||
nowritecache => { optional => 1 },
|
||||
sparse => { optional => 1 },
|
||||
comstar_hg => { optional => 1 },
|
||||
comstar_tg => { optional => 1 },
|
||||
+ freenas_user => { optional => 1 },
|
||||
+ freenas_password => { optional => 1 },
|
||||
+ freenas_use_ssl => { optional => 1 },
|
||||
+ freenas_apiv4_host => { optional => 1 },
|
||||
lio_tpg => { optional => 1 },
|
||||
content => { optional => 1 },
|
||||
bwlimit => { optional => 1 },
|
||||
@@ -237,6 +274,40 @@
|
||||
|
||||
my $path = "iscsi://$portal/$target/$lun";
|
||||
|
||||
+ # Multipath enhancement
|
||||
+ eval {
|
||||
+ my $wwid = $class->zfs_get_wwid_number($scfg, $guid);
|
||||
+# syslog(info,"JD: path get_lun_number guid $guid");
|
||||
+
|
||||
+ if ($wwid =~ /^([-\@\w.]+)$/) {
|
||||
+ $wwid = $1; # $data now untainted
|
||||
+ } else {
|
||||
+ die "Bad data in '$wwid'"; # log this somewhere
|
||||
+ }
|
||||
+ my $wwid_end = substr $wwid, 16;
|
||||
+
|
||||
+ my $mapper = '';
|
||||
+ sleep 3;
|
||||
+ run_command("iscsiadm -m session --rescan");
|
||||
+ sleep 3;
|
||||
+ my $line = `multipath -ll | grep \"$wwid_end\"`;
|
||||
+ my ($mapper_device) = split(' ', $line);
|
||||
+ $mapper_device = "" unless $mapper_device;
|
||||
+ $mapper .= $mapper_device;
|
||||
+
|
||||
+ if ($mapper =~ /^([-\@\w.]+)$/) {
|
||||
+ $mapper = $1; # $data now untainted
|
||||
+ } else {
|
||||
+ $mapper = '';
|
||||
+ }
|
||||
+
|
||||
+# syslog(info,"Multipath mapper found: $mapper\n");
|
||||
+ if ($mapper ne "") {
|
||||
+ $path = "/dev/mapper/$mapper";
|
||||
+ sleep 5;
|
||||
+ }
|
||||
+ };
|
||||
+
|
||||
return ($path, $vmid, $vtype);
|
||||
}
|
||||
|
||||
|
|
@ -1,147 +0,0 @@
|
|||
--- ZFSPlugin.pm.orig 2019-02-07 09:14:12.000000000 -0500
|
||||
+++ ZFSPlugin.pm 2019-02-26 20:53:29.096598801 -0500
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
use base qw(PVE::Storage::ZFSPoolPlugin);
|
||||
use PVE::Storage::LunCmd::Comstar;
|
||||
+use PVE::Storage::LunCmd::FreeNAS;
|
||||
use PVE::Storage::LunCmd::Istgt;
|
||||
use PVE::Storage::LunCmd::Iet;
|
||||
use PVE::Storage::LunCmd::LIO;
|
||||
@@ -26,13 +27,14 @@
|
||||
modify_lu => 1,
|
||||
add_view => 1,
|
||||
list_view => 1,
|
||||
+ list_extent => 1,
|
||||
list_lu => 1,
|
||||
};
|
||||
|
||||
my $zfs_unknown_scsi_provider = sub {
|
||||
my ($provider) = @_;
|
||||
|
||||
- die "$provider: unknown iscsi provider. Available [comstar, istgt, iet, LIO]";
|
||||
+ die "$provider: unknown iscsi provider. Available [comstar, freenas, istgt, iet, LIO]";
|
||||
};
|
||||
|
||||
my $zfs_get_base = sub {
|
||||
@@ -40,6 +42,8 @@
|
||||
|
||||
if ($scfg->{iscsiprovider} eq 'comstar') {
|
||||
return PVE::Storage::LunCmd::Comstar::get_base;
|
||||
+ } elsif ($scfg->{iscsiprovider} eq 'freenas') {
|
||||
+ return PVE::Storage::LunCmd::FreeNAS::get_base;
|
||||
} elsif ($scfg->{iscsiprovider} eq 'istgt') {
|
||||
return PVE::Storage::LunCmd::Istgt::get_base;
|
||||
} elsif ($scfg->{iscsiprovider} eq 'iet') {
|
||||
@@ -62,6 +66,8 @@
|
||||
if ($lun_cmds->{$method}) {
|
||||
if ($scfg->{iscsiprovider} eq 'comstar') {
|
||||
$msg = PVE::Storage::LunCmd::Comstar::run_lun_command($scfg, $timeout, $method, @params);
|
||||
+ } elsif ($scfg->{iscsiprovider} eq 'freenas') {
|
||||
+ $msg = PVE::Storage::LunCmd::FreeNAS::run_lun_command($scfg, $timeout, $method, @params);
|
||||
} elsif ($scfg->{iscsiprovider} eq 'istgt') {
|
||||
$msg = PVE::Storage::LunCmd::Istgt::run_lun_command($scfg, $timeout, $method, @params);
|
||||
} elsif ($scfg->{iscsiprovider} eq 'iet') {
|
||||
@@ -160,6 +166,15 @@
|
||||
return $class->zfs_request($scfg, undef, 'list_view', $guid);
|
||||
}
|
||||
|
||||
+# Part of the multipath enhancement
|
||||
+sub zfs_get_wwid_number {
|
||||
+ my ($class, $scfg, $guid) = @_;
|
||||
+
|
||||
+ die "could not find lun_number for guid $guid" if !$guid;
|
||||
+
|
||||
+ return $class->zfs_request($scfg, undef, 'list_extent', $guid);
|
||||
+}
|
||||
+
|
||||
# Configuration
|
||||
|
||||
sub type {
|
||||
@@ -178,6 +193,24 @@
|
||||
description => "iscsi provider",
|
||||
type => 'string',
|
||||
},
|
||||
+ # This is for FreeNAS iscsi and API intergration
|
||||
+ # And some enhancements asked by the community
|
||||
+ freenas_user => {
|
||||
+ description => "FreeNAS API Username",
|
||||
+ type => 'string',
|
||||
+ },
|
||||
+ freenas_password => {
|
||||
+ description => "FreeNAS API Password",
|
||||
+ type => 'string',
|
||||
+ },
|
||||
+ freenas_use_ssl => {
|
||||
+ description => "FreeNAS API access via SSL",
|
||||
+ type => 'boolean',
|
||||
+ },
|
||||
+ freenas_apiv4_host => {
|
||||
+ description => "FreeNAS API Host",
|
||||
+ type => 'string',
|
||||
+ },
|
||||
# this will disable write caching on comstar and istgt.
|
||||
# it is not implemented for iet. iet blockio always operates with
|
||||
# writethrough caching when not in readonly mode
|
||||
@@ -205,14 +238,18 @@
|
||||
nodes => { optional => 1 },
|
||||
disable => { optional => 1 },
|
||||
portal => { fixed => 1 },
|
||||
- target => { fixed => 1 },
|
||||
- pool => { fixed => 1 },
|
||||
+ target => { fixed => 0 },
|
||||
+ pool => { fixed => 0 },
|
||||
blocksize => { fixed => 1 },
|
||||
iscsiprovider => { fixed => 1 },
|
||||
nowritecache => { optional => 1 },
|
||||
sparse => { optional => 1 },
|
||||
comstar_hg => { optional => 1 },
|
||||
comstar_tg => { optional => 1 },
|
||||
+ freenas_user => { optional => 1 },
|
||||
+ freenas_password => { optional => 1 },
|
||||
+ freenas_use_ssl => { optional => 1 },
|
||||
+ freenas_apiv4_host => { optional => 1 },
|
||||
lio_tpg => { optional => 1 },
|
||||
content => { optional => 1 },
|
||||
bwlimit => { optional => 1 },
|
||||
@@ -237,6 +274,40 @@
|
||||
|
||||
my $path = "iscsi://$portal/$target/$lun";
|
||||
|
||||
+ # Multipath enhancement
|
||||
+ eval {
|
||||
+ my $wwid = $class->zfs_get_wwid_number($scfg, $guid);
|
||||
+# syslog(info,"JD: path get_lun_number guid $guid");
|
||||
+
|
||||
+ if ($wwid =~ /^([-\@\w.]+)$/) {
|
||||
+ $wwid = $1; # $data now untainted
|
||||
+ } else {
|
||||
+ die "Bad data in '$wwid'"; # log this somewhere
|
||||
+ }
|
||||
+ my $wwid_end = substr $wwid, 16;
|
||||
+
|
||||
+ my $mapper = '';
|
||||
+ sleep 3;
|
||||
+ run_command("iscsiadm -m session --rescan");
|
||||
+ sleep 3;
|
||||
+ my $line = `multipath -ll | grep \"$wwid_end\"`;
|
||||
+ my ($mapper_device) = split(' ', $line);
|
||||
+ $mapper_device = "" unless $mapper_device;
|
||||
+ $mapper .= $mapper_device;
|
||||
+
|
||||
+ if ($mapper =~ /^([-\@\w.]+)$/) {
|
||||
+ $mapper = $1; # $data now untainted
|
||||
+ } else {
|
||||
+ $mapper = '';
|
||||
+ }
|
||||
+
|
||||
+# syslog(info,"Multipath mapper found: $mapper\n");
|
||||
+ if ($mapper ne "") {
|
||||
+ $path = "/dev/mapper/$mapper";
|
||||
+ sleep 5;
|
||||
+ }
|
||||
+ };
|
||||
+
|
||||
return ($path, $vmid, $vtype);
|
||||
}
|
||||
|
||||
|
|
@ -1,147 +0,0 @@
|
|||
--- ZFSPlugin.pm.orig 2019-04-30 09:54:55.000000000 -0400
|
||||
+++ ZFSPlugin.pm 2019-05-11 10:32:36.326734352 -0400
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
use base qw(PVE::Storage::ZFSPoolPlugin);
|
||||
use PVE::Storage::LunCmd::Comstar;
|
||||
+use PVE::Storage::LunCmd::FreeNAS;
|
||||
use PVE::Storage::LunCmd::Istgt;
|
||||
use PVE::Storage::LunCmd::Iet;
|
||||
use PVE::Storage::LunCmd::LIO;
|
||||
@@ -26,13 +27,14 @@
|
||||
modify_lu => 1,
|
||||
add_view => 1,
|
||||
list_view => 1,
|
||||
+ list_extent => 1,
|
||||
list_lu => 1,
|
||||
};
|
||||
|
||||
my $zfs_unknown_scsi_provider = sub {
|
||||
my ($provider) = @_;
|
||||
|
||||
- die "$provider: unknown iscsi provider. Available [comstar, istgt, iet, LIO]";
|
||||
+ die "$provider: unknown iscsi provider. Available [comstar, freenas, istgt, iet, LIO]";
|
||||
};
|
||||
|
||||
my $zfs_get_base = sub {
|
||||
@@ -40,6 +42,8 @@
|
||||
|
||||
if ($scfg->{iscsiprovider} eq 'comstar') {
|
||||
return PVE::Storage::LunCmd::Comstar::get_base;
|
||||
+ } elsif ($scfg->{iscsiprovider} eq 'freenas') {
|
||||
+ return PVE::Storage::LunCmd::FreeNAS::get_base;
|
||||
} elsif ($scfg->{iscsiprovider} eq 'istgt') {
|
||||
return PVE::Storage::LunCmd::Istgt::get_base;
|
||||
} elsif ($scfg->{iscsiprovider} eq 'iet') {
|
||||
@@ -62,6 +66,8 @@
|
||||
if ($lun_cmds->{$method}) {
|
||||
if ($scfg->{iscsiprovider} eq 'comstar') {
|
||||
$msg = PVE::Storage::LunCmd::Comstar::run_lun_command($scfg, $timeout, $method, @params);
|
||||
+ } elsif ($scfg->{iscsiprovider} eq 'freenas') {
|
||||
+ $msg = PVE::Storage::LunCmd::FreeNAS::run_lun_command($scfg, $timeout, $method, @params);
|
||||
} elsif ($scfg->{iscsiprovider} eq 'istgt') {
|
||||
$msg = PVE::Storage::LunCmd::Istgt::run_lun_command($scfg, $timeout, $method, @params);
|
||||
} elsif ($scfg->{iscsiprovider} eq 'iet') {
|
||||
@@ -160,6 +166,15 @@
|
||||
return $class->zfs_request($scfg, undef, 'list_view', $guid);
|
||||
}
|
||||
|
||||
+# Part of the multipath enhancement
|
||||
+sub zfs_get_wwid_number {
|
||||
+ my ($class, $scfg, $guid) = @_;
|
||||
+
|
||||
+ die "could not find lun_number for guid $guid" if !$guid;
|
||||
+
|
||||
+ return $class->zfs_request($scfg, undef, 'list_extent', $guid);
|
||||
+}
|
||||
+
|
||||
# Configuration
|
||||
|
||||
sub type {
|
||||
@@ -178,6 +193,24 @@
|
||||
description => "iscsi provider",
|
||||
type => 'string',
|
||||
},
|
||||
+ # This is for FreeNAS iscsi and API intergration
|
||||
+ # And some enhancements asked by the community
|
||||
+ freenas_user => {
|
||||
+ description => "FreeNAS API Username",
|
||||
+ type => 'string',
|
||||
+ },
|
||||
+ freenas_password => {
|
||||
+ description => "FreeNAS API Password",
|
||||
+ type => 'string',
|
||||
+ },
|
||||
+ freenas_use_ssl => {
|
||||
+ description => "FreeNAS API access via SSL",
|
||||
+ type => 'boolean',
|
||||
+ },
|
||||
+ freenas_apiv4_host => {
|
||||
+ description => "FreeNAS API Host",
|
||||
+ type => 'string',
|
||||
+ },
|
||||
# this will disable write caching on comstar and istgt.
|
||||
# it is not implemented for iet. iet blockio always operates with
|
||||
# writethrough caching when not in readonly mode
|
||||
@@ -205,14 +238,18 @@
|
||||
nodes => { optional => 1 },
|
||||
disable => { optional => 1 },
|
||||
portal => { fixed => 1 },
|
||||
- target => { fixed => 1 },
|
||||
- pool => { fixed => 1 },
|
||||
+ target => { fixed => 0 },
|
||||
+ pool => { fixed => 0 },
|
||||
blocksize => { fixed => 1 },
|
||||
iscsiprovider => { fixed => 1 },
|
||||
nowritecache => { optional => 1 },
|
||||
sparse => { optional => 1 },
|
||||
comstar_hg => { optional => 1 },
|
||||
comstar_tg => { optional => 1 },
|
||||
+ freenas_user => { optional => 1 },
|
||||
+ freenas_password => { optional => 1 },
|
||||
+ freenas_use_ssl => { optional => 1 },
|
||||
+ freenas_apiv4_host => { optional => 1 },
|
||||
lio_tpg => { optional => 1 },
|
||||
content => { optional => 1 },
|
||||
bwlimit => { optional => 1 },
|
||||
@@ -237,6 +274,40 @@
|
||||
|
||||
my $path = "iscsi://$portal/$target/$lun";
|
||||
|
||||
+ # Multipath enhancement
|
||||
+ eval {
|
||||
+ my $wwid = $class->zfs_get_wwid_number($scfg, $guid);
|
||||
+# syslog(info,"JD: path get_lun_number guid $guid");
|
||||
+
|
||||
+ if ($wwid =~ /^([-\@\w.]+)$/) {
|
||||
+ $wwid = $1; # $data now untainted
|
||||
+ } else {
|
||||
+ die "Bad data in '$wwid'"; # log this somewhere
|
||||
+ }
|
||||
+ my $wwid_end = substr $wwid, 16;
|
||||
+
|
||||
+ my $mapper = '';
|
||||
+ sleep 3;
|
||||
+ run_command("iscsiadm -m session --rescan");
|
||||
+ sleep 3;
|
||||
+ my $line = `multipath -ll | grep \"$wwid_end\"`;
|
||||
+ my ($mapper_device) = split(' ', $line);
|
||||
+ $mapper_device = "" unless $mapper_device;
|
||||
+ $mapper .= $mapper_device;
|
||||
+
|
||||
+ if ($mapper =~ /^([-\@\w.]+)$/) {
|
||||
+ $mapper = $1; # $data now untainted
|
||||
+ } else {
|
||||
+ $mapper = '';
|
||||
+ }
|
||||
+
|
||||
+# syslog(info,"Multipath mapper found: $mapper\n");
|
||||
+ if ($mapper ne "") {
|
||||
+ $path = "/dev/mapper/$mapper";
|
||||
+ sleep 5;
|
||||
+ }
|
||||
+ };
|
||||
+
|
||||
return ($path, $vmid, $vtype);
|
||||
}
|
||||
|
||||
|
|
@ -1,147 +0,0 @@
|
|||
--- ZFSPlugin.pm.orig 2019-06-25 06:54:50.000000000 -0400
|
||||
+++ ZFSPlugin.pm 2019-07-16 13:36:48.817831174 -0400
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
use base qw(PVE::Storage::ZFSPoolPlugin);
|
||||
use PVE::Storage::LunCmd::Comstar;
|
||||
+use PVE::Storage::LunCmd::FreeNAS;
|
||||
use PVE::Storage::LunCmd::Istgt;
|
||||
use PVE::Storage::LunCmd::Iet;
|
||||
use PVE::Storage::LunCmd::LIO;
|
||||
@@ -26,13 +27,14 @@
|
||||
modify_lu => 1,
|
||||
add_view => 1,
|
||||
list_view => 1,
|
||||
+ list_extent => 1,
|
||||
list_lu => 1,
|
||||
};
|
||||
|
||||
my $zfs_unknown_scsi_provider = sub {
|
||||
my ($provider) = @_;
|
||||
|
||||
- die "$provider: unknown iscsi provider. Available [comstar, istgt, iet, LIO]";
|
||||
+ die "$provider: unknown iscsi provider. Available [comstar, freenas, istgt, iet, LIO]";
|
||||
};
|
||||
|
||||
my $zfs_get_base = sub {
|
||||
@@ -40,6 +42,8 @@
|
||||
|
||||
if ($scfg->{iscsiprovider} eq 'comstar') {
|
||||
return PVE::Storage::LunCmd::Comstar::get_base;
|
||||
+ } elsif ($scfg->{iscsiprovider} eq 'freenas') {
|
||||
+ return PVE::Storage::LunCmd::FreeNAS::get_base;
|
||||
} elsif ($scfg->{iscsiprovider} eq 'istgt') {
|
||||
return PVE::Storage::LunCmd::Istgt::get_base;
|
||||
} elsif ($scfg->{iscsiprovider} eq 'iet') {
|
||||
@@ -62,6 +66,8 @@
|
||||
if ($lun_cmds->{$method}) {
|
||||
if ($scfg->{iscsiprovider} eq 'comstar') {
|
||||
$msg = PVE::Storage::LunCmd::Comstar::run_lun_command($scfg, $timeout, $method, @params);
|
||||
+ } elsif ($scfg->{iscsiprovider} eq 'freenas') {
|
||||
+ $msg = PVE::Storage::LunCmd::FreeNAS::run_lun_command($scfg, $timeout, $method, @params);
|
||||
} elsif ($scfg->{iscsiprovider} eq 'istgt') {
|
||||
$msg = PVE::Storage::LunCmd::Istgt::run_lun_command($scfg, $timeout, $method, @params);
|
||||
} elsif ($scfg->{iscsiprovider} eq 'iet') {
|
||||
@@ -160,6 +166,15 @@
|
||||
return $class->zfs_request($scfg, undef, 'list_view', $guid);
|
||||
}
|
||||
|
||||
+# Part of the multipath enhancement
|
||||
+sub zfs_get_wwid_number {
|
||||
+ my ($class, $scfg, $guid) = @_;
|
||||
+
|
||||
+ die "could not find lun_number for guid $guid" if !$guid;
|
||||
+
|
||||
+ return $class->zfs_request($scfg, undef, 'list_extent', $guid);
|
||||
+}
|
||||
+
|
||||
# Configuration
|
||||
|
||||
sub type {
|
||||
@@ -178,6 +193,24 @@
|
||||
description => "iscsi provider",
|
||||
type => 'string',
|
||||
},
|
||||
+ # This is for FreeNAS iscsi and API intergration
|
||||
+ # And some enhancements asked by the community
|
||||
+ freenas_user => {
|
||||
+ description => "FreeNAS API Username",
|
||||
+ type => 'string',
|
||||
+ },
|
||||
+ freenas_password => {
|
||||
+ description => "FreeNAS API Password",
|
||||
+ type => 'string',
|
||||
+ },
|
||||
+ freenas_use_ssl => {
|
||||
+ description => "FreeNAS API access via SSL",
|
||||
+ type => 'boolean',
|
||||
+ },
|
||||
+ freenas_apiv4_host => {
|
||||
+ description => "FreeNAS API Host",
|
||||
+ type => 'string',
|
||||
+ },
|
||||
# this will disable write caching on comstar and istgt.
|
||||
# it is not implemented for iet. iet blockio always operates with
|
||||
# writethrough caching when not in readonly mode
|
||||
@@ -205,14 +238,18 @@
|
||||
nodes => { optional => 1 },
|
||||
disable => { optional => 1 },
|
||||
portal => { fixed => 1 },
|
||||
- target => { fixed => 1 },
|
||||
- pool => { fixed => 1 },
|
||||
+ target => { fixed => 0 },
|
||||
+ pool => { fixed => 0 },
|
||||
blocksize => { fixed => 1 },
|
||||
iscsiprovider => { fixed => 1 },
|
||||
nowritecache => { optional => 1 },
|
||||
sparse => { optional => 1 },
|
||||
comstar_hg => { optional => 1 },
|
||||
comstar_tg => { optional => 1 },
|
||||
+ freenas_user => { optional => 1 },
|
||||
+ freenas_password => { optional => 1 },
|
||||
+ freenas_use_ssl => { optional => 1 },
|
||||
+ freenas_apiv4_host => { optional => 1 },
|
||||
lio_tpg => { optional => 1 },
|
||||
content => { optional => 1 },
|
||||
bwlimit => { optional => 1 },
|
||||
@@ -237,6 +274,40 @@
|
||||
|
||||
my $path = "iscsi://$portal/$target/$lun";
|
||||
|
||||
+ # Multipath enhancement
|
||||
+ eval {
|
||||
+ my $wwid = $class->zfs_get_wwid_number($scfg, $guid);
|
||||
+# syslog(info,"JD: path get_lun_number guid $guid");
|
||||
+
|
||||
+ if ($wwid =~ /^([-\@\w.]+)$/) {
|
||||
+ $wwid = $1; # $data now untainted
|
||||
+ } else {
|
||||
+ die "Bad data in '$wwid'"; # log this somewhere
|
||||
+ }
|
||||
+ my $wwid_end = substr $wwid, 16;
|
||||
+
|
||||
+ my $mapper = '';
|
||||
+ sleep 3;
|
||||
+ run_command("iscsiadm -m session --rescan");
|
||||
+ sleep 3;
|
||||
+ my $line = `multipath -ll | grep \"$wwid_end\"`;
|
||||
+ my ($mapper_device) = split(' ', $line);
|
||||
+ $mapper_device = "" unless $mapper_device;
|
||||
+ $mapper .= $mapper_device;
|
||||
+
|
||||
+ if ($mapper =~ /^([-\@\w.]+)$/) {
|
||||
+ $mapper = $1; # $data now untainted
|
||||
+ } else {
|
||||
+ $mapper = '';
|
||||
+ }
|
||||
+
|
||||
+# syslog(info,"Multipath mapper found: $mapper\n");
|
||||
+ if ($mapper ne "") {
|
||||
+ $path = "/dev/mapper/$mapper";
|
||||
+ sleep 5;
|
||||
+ }
|
||||
+ };
|
||||
+
|
||||
return ($path, $vmid, $vtype);
|
||||
}
|
||||
|
||||
|
|
@ -1,147 +0,0 @@
|
|||
--- ZFSPlugin.pm.orig 2019-07-25 07:34:52.000000000 -0400
|
||||
+++ ZFSPlugin.pm 2019-08-07 13:28:29.268383007 -0400
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
use base qw(PVE::Storage::ZFSPoolPlugin);
|
||||
use PVE::Storage::LunCmd::Comstar;
|
||||
+use PVE::Storage::LunCmd::FreeNAS;
|
||||
use PVE::Storage::LunCmd::Istgt;
|
||||
use PVE::Storage::LunCmd::Iet;
|
||||
use PVE::Storage::LunCmd::LIO;
|
||||
@@ -26,13 +27,14 @@
|
||||
modify_lu => 1,
|
||||
add_view => 1,
|
||||
list_view => 1,
|
||||
+ list_extent => 1,
|
||||
list_lu => 1,
|
||||
};
|
||||
|
||||
my $zfs_unknown_scsi_provider = sub {
|
||||
my ($provider) = @_;
|
||||
|
||||
- die "$provider: unknown iscsi provider. Available [comstar, istgt, iet, LIO]";
|
||||
+ die "$provider: unknown iscsi provider. Available [comstar, freenas, istgt, iet, LIO]";
|
||||
};
|
||||
|
||||
my $zfs_get_base = sub {
|
||||
@@ -40,6 +42,8 @@
|
||||
|
||||
if ($scfg->{iscsiprovider} eq 'comstar') {
|
||||
return PVE::Storage::LunCmd::Comstar::get_base;
|
||||
+ } elsif ($scfg->{iscsiprovider} eq 'freenas') {
|
||||
+ return PVE::Storage::LunCmd::FreeNAS::get_base;
|
||||
} elsif ($scfg->{iscsiprovider} eq 'istgt') {
|
||||
return PVE::Storage::LunCmd::Istgt::get_base;
|
||||
} elsif ($scfg->{iscsiprovider} eq 'iet') {
|
||||
@@ -62,6 +66,8 @@
|
||||
if ($lun_cmds->{$method}) {
|
||||
if ($scfg->{iscsiprovider} eq 'comstar') {
|
||||
$msg = PVE::Storage::LunCmd::Comstar::run_lun_command($scfg, $timeout, $method, @params);
|
||||
+ } elsif ($scfg->{iscsiprovider} eq 'freenas') {
|
||||
+ $msg = PVE::Storage::LunCmd::FreeNAS::run_lun_command($scfg, $timeout, $method, @params);
|
||||
} elsif ($scfg->{iscsiprovider} eq 'istgt') {
|
||||
$msg = PVE::Storage::LunCmd::Istgt::run_lun_command($scfg, $timeout, $method, @params);
|
||||
} elsif ($scfg->{iscsiprovider} eq 'iet') {
|
||||
@@ -160,6 +166,15 @@
|
||||
return $class->zfs_request($scfg, undef, 'list_view', $guid);
|
||||
}
|
||||
|
||||
+# Part of the multipath enhancement
|
||||
+sub zfs_get_wwid_number {
|
||||
+ my ($class, $scfg, $guid) = @_;
|
||||
+
|
||||
+ die "could not find lun_number for guid $guid" if !$guid;
|
||||
+
|
||||
+ return $class->zfs_request($scfg, undef, 'list_extent', $guid);
|
||||
+}
|
||||
+
|
||||
# Configuration
|
||||
|
||||
sub type {
|
||||
@@ -178,6 +193,24 @@
|
||||
description => "iscsi provider",
|
||||
type => 'string',
|
||||
},
|
||||
+ # This is for FreeNAS iscsi and API intergration
|
||||
+ # And some enhancements asked by the community
|
||||
+ freenas_user => {
|
||||
+ description => "FreeNAS API Username",
|
||||
+ type => 'string',
|
||||
+ },
|
||||
+ freenas_password => {
|
||||
+ description => "FreeNAS API Password",
|
||||
+ type => 'string',
|
||||
+ },
|
||||
+ freenas_use_ssl => {
|
||||
+ description => "FreeNAS API access via SSL",
|
||||
+ type => 'boolean',
|
||||
+ },
|
||||
+ freenas_apiv4_host => {
|
||||
+ description => "FreeNAS API Host",
|
||||
+ type => 'string',
|
||||
+ },
|
||||
# this will disable write caching on comstar and istgt.
|
||||
# it is not implemented for iet. iet blockio always operates with
|
||||
# writethrough caching when not in readonly mode
|
||||
@@ -205,14 +238,18 @@
|
||||
nodes => { optional => 1 },
|
||||
disable => { optional => 1 },
|
||||
portal => { fixed => 1 },
|
||||
- target => { fixed => 1 },
|
||||
- pool => { fixed => 1 },
|
||||
+ target => { fixed => 0 },
|
||||
+ pool => { fixed => 0 },
|
||||
blocksize => { fixed => 1 },
|
||||
iscsiprovider => { fixed => 1 },
|
||||
nowritecache => { optional => 1 },
|
||||
sparse => { optional => 1 },
|
||||
comstar_hg => { optional => 1 },
|
||||
comstar_tg => { optional => 1 },
|
||||
+ freenas_user => { optional => 1 },
|
||||
+ freenas_password => { optional => 1 },
|
||||
+ freenas_use_ssl => { optional => 1 },
|
||||
+ freenas_apiv4_host => { optional => 1 },
|
||||
lio_tpg => { optional => 1 },
|
||||
content => { optional => 1 },
|
||||
bwlimit => { optional => 1 },
|
||||
@@ -237,6 +274,40 @@
|
||||
|
||||
my $path = "iscsi://$portal/$target/$lun";
|
||||
|
||||
+ # Multipath enhancement
|
||||
+ eval {
|
||||
+ my $wwid = $class->zfs_get_wwid_number($scfg, $guid);
|
||||
+# syslog(info,"JD: path get_lun_number guid $guid");
|
||||
+
|
||||
+ if ($wwid =~ /^([-\@\w.]+)$/) {
|
||||
+ $wwid = $1; # $data now untainted
|
||||
+ } else {
|
||||
+ die "Bad data in '$wwid'"; # log this somewhere
|
||||
+ }
|
||||
+ my $wwid_end = substr $wwid, 16;
|
||||
+
|
||||
+ my $mapper = '';
|
||||
+ sleep 3;
|
||||
+ run_command("iscsiadm -m session --rescan");
|
||||
+ sleep 3;
|
||||
+ my $line = `multipath -ll | grep \"$wwid_end\"`;
|
||||
+ my ($mapper_device) = split(' ', $line);
|
||||
+ $mapper_device = "" unless $mapper_device;
|
||||
+ $mapper .= $mapper_device;
|
||||
+
|
||||
+ if ($mapper =~ /^([-\@\w.]+)$/) {
|
||||
+ $mapper = $1; # $data now untainted
|
||||
+ } else {
|
||||
+ $mapper = '';
|
||||
+ }
|
||||
+
|
||||
+# syslog(info,"Multipath mapper found: $mapper\n");
|
||||
+ if ($mapper ne "") {
|
||||
+ $path = "/dev/mapper/$mapper";
|
||||
+ sleep 5;
|
||||
+ }
|
||||
+ };
|
||||
+
|
||||
return ($path, $vmid, $vtype);
|
||||
}
|
||||
|
||||
|
|
@ -1,147 +0,0 @@
|
|||
--- ZFSPlugin.pm.orig 2019-09-03 04:24:37.000000000 -0400
|
||||
+++ ZFSPlugin.pm 2019-09-22 13:54:51.570048336 -0400
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
use base qw(PVE::Storage::ZFSPoolPlugin);
|
||||
use PVE::Storage::LunCmd::Comstar;
|
||||
+use PVE::Storage::LunCmd::FreeNAS;
|
||||
use PVE::Storage::LunCmd::Istgt;
|
||||
use PVE::Storage::LunCmd::Iet;
|
||||
use PVE::Storage::LunCmd::LIO;
|
||||
@@ -26,13 +27,14 @@
|
||||
modify_lu => 1,
|
||||
add_view => 1,
|
||||
list_view => 1,
|
||||
+ list_extent => 1,
|
||||
list_lu => 1,
|
||||
};
|
||||
|
||||
my $zfs_unknown_scsi_provider = sub {
|
||||
my ($provider) = @_;
|
||||
|
||||
- die "$provider: unknown iscsi provider. Available [comstar, istgt, iet, LIO]";
|
||||
+ die "$provider: unknown iscsi provider. Available [comstar, freenas, istgt, iet, LIO]";
|
||||
};
|
||||
|
||||
my $zfs_get_base = sub {
|
||||
@@ -40,6 +42,8 @@
|
||||
|
||||
if ($scfg->{iscsiprovider} eq 'comstar') {
|
||||
return PVE::Storage::LunCmd::Comstar::get_base;
|
||||
+ } elsif ($scfg->{iscsiprovider} eq 'freenas') {
|
||||
+ return PVE::Storage::LunCmd::FreeNAS::get_base;
|
||||
} elsif ($scfg->{iscsiprovider} eq 'istgt') {
|
||||
return PVE::Storage::LunCmd::Istgt::get_base;
|
||||
} elsif ($scfg->{iscsiprovider} eq 'iet') {
|
||||
@@ -62,6 +66,8 @@
|
||||
if ($lun_cmds->{$method}) {
|
||||
if ($scfg->{iscsiprovider} eq 'comstar') {
|
||||
$msg = PVE::Storage::LunCmd::Comstar::run_lun_command($scfg, $timeout, $method, @params);
|
||||
+ } elsif ($scfg->{iscsiprovider} eq 'freenas') {
|
||||
+ $msg = PVE::Storage::LunCmd::FreeNAS::run_lun_command($scfg, $timeout, $method, @params);
|
||||
} elsif ($scfg->{iscsiprovider} eq 'istgt') {
|
||||
$msg = PVE::Storage::LunCmd::Istgt::run_lun_command($scfg, $timeout, $method, @params);
|
||||
} elsif ($scfg->{iscsiprovider} eq 'iet') {
|
||||
@@ -160,6 +166,15 @@
|
||||
return $class->zfs_request($scfg, undef, 'list_view', $guid);
|
||||
}
|
||||
|
||||
+# Part of the multipath enhancement
|
||||
+sub zfs_get_wwid_number {
|
||||
+ my ($class, $scfg, $guid) = @_;
|
||||
+
|
||||
+ die "could not find lun_number for guid $guid" if !$guid;
|
||||
+
|
||||
+ return $class->zfs_request($scfg, undef, 'list_extent', $guid);
|
||||
+}
|
||||
+
|
||||
# Configuration
|
||||
|
||||
sub type {
|
||||
@@ -178,6 +193,24 @@
|
||||
description => "iscsi provider",
|
||||
type => 'string',
|
||||
},
|
||||
+ # This is for FreeNAS iscsi and API intergration
|
||||
+ # And some enhancements asked by the community
|
||||
+ freenas_user => {
|
||||
+ description => "FreeNAS API Username",
|
||||
+ type => 'string',
|
||||
+ },
|
||||
+ freenas_password => {
|
||||
+ description => "FreeNAS API Password",
|
||||
+ type => 'string',
|
||||
+ },
|
||||
+ freenas_use_ssl => {
|
||||
+ description => "FreeNAS API access via SSL",
|
||||
+ type => 'boolean',
|
||||
+ },
|
||||
+ freenas_apiv4_host => {
|
||||
+ description => "FreeNAS API Host",
|
||||
+ type => 'string',
|
||||
+ },
|
||||
# this will disable write caching on comstar and istgt.
|
||||
# it is not implemented for iet. iet blockio always operates with
|
||||
# writethrough caching when not in readonly mode
|
||||
@@ -205,14 +238,18 @@
|
||||
nodes => { optional => 1 },
|
||||
disable => { optional => 1 },
|
||||
portal => { fixed => 1 },
|
||||
- target => { fixed => 1 },
|
||||
- pool => { fixed => 1 },
|
||||
+ target => { fixed => 0 },
|
||||
+ pool => { fixed => 0 },
|
||||
blocksize => { fixed => 1 },
|
||||
iscsiprovider => { fixed => 1 },
|
||||
nowritecache => { optional => 1 },
|
||||
sparse => { optional => 1 },
|
||||
comstar_hg => { optional => 1 },
|
||||
comstar_tg => { optional => 1 },
|
||||
+ freenas_user => { optional => 1 },
|
||||
+ freenas_password => { optional => 1 },
|
||||
+ freenas_use_ssl => { optional => 1 },
|
||||
+ freenas_apiv4_host => { optional => 1 },
|
||||
lio_tpg => { optional => 1 },
|
||||
content => { optional => 1 },
|
||||
bwlimit => { optional => 1 },
|
||||
@@ -237,6 +274,40 @@
|
||||
|
||||
my $path = "iscsi://$portal/$target/$lun";
|
||||
|
||||
+ # Multipath enhancement
|
||||
+ eval {
|
||||
+ my $wwid = $class->zfs_get_wwid_number($scfg, $guid);
|
||||
+# syslog(info,"JD: path get_lun_number guid $guid");
|
||||
+
|
||||
+ if ($wwid =~ /^([-\@\w.]+)$/) {
|
||||
+ $wwid = $1; # $data now untainted
|
||||
+ } else {
|
||||
+ die "Bad data in '$wwid'"; # log this somewhere
|
||||
+ }
|
||||
+ my $wwid_end = substr $wwid, 16;
|
||||
+
|
||||
+ my $mapper = '';
|
||||
+ sleep 3;
|
||||
+ run_command("iscsiadm -m session --rescan");
|
||||
+ sleep 3;
|
||||
+ my $line = `multipath -ll | grep \"$wwid_end\"`;
|
||||
+ my ($mapper_device) = split(' ', $line);
|
||||
+ $mapper_device = "" unless $mapper_device;
|
||||
+ $mapper .= $mapper_device;
|
||||
+
|
||||
+ if ($mapper =~ /^([-\@\w.]+)$/) {
|
||||
+ $mapper = $1; # $data now untainted
|
||||
+ } else {
|
||||
+ $mapper = '';
|
||||
+ }
|
||||
+
|
||||
+# syslog(info,"Multipath mapper found: $mapper\n");
|
||||
+ if ($mapper ne "") {
|
||||
+ $path = "/dev/mapper/$mapper";
|
||||
+ sleep 5;
|
||||
+ }
|
||||
+ };
|
||||
+
|
||||
return ($path, $vmid, $vtype);
|
||||
}
|
||||
|
||||
|
|
@ -1,147 +0,0 @@
|
|||
--- ZFSPlugin.pm.orig 2019-09-23 12:17:37.000000000 -0400
|
||||
+++ ZFSPlugin.pm 2019-10-13 09:31:58.780554103 -0400
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
use base qw(PVE::Storage::ZFSPoolPlugin);
|
||||
use PVE::Storage::LunCmd::Comstar;
|
||||
+use PVE::Storage::LunCmd::FreeNAS;
|
||||
use PVE::Storage::LunCmd::Istgt;
|
||||
use PVE::Storage::LunCmd::Iet;
|
||||
use PVE::Storage::LunCmd::LIO;
|
||||
@@ -26,13 +27,14 @@
|
||||
modify_lu => 1,
|
||||
add_view => 1,
|
||||
list_view => 1,
|
||||
+ list_extent => 1,
|
||||
list_lu => 1,
|
||||
};
|
||||
|
||||
my $zfs_unknown_scsi_provider = sub {
|
||||
my ($provider) = @_;
|
||||
|
||||
- die "$provider: unknown iscsi provider. Available [comstar, istgt, iet, LIO]";
|
||||
+ die "$provider: unknown iscsi provider. Available [comstar, freenas, istgt, iet, LIO]";
|
||||
};
|
||||
|
||||
my $zfs_get_base = sub {
|
||||
@@ -40,6 +42,8 @@
|
||||
|
||||
if ($scfg->{iscsiprovider} eq 'comstar') {
|
||||
return PVE::Storage::LunCmd::Comstar::get_base;
|
||||
+ } elsif ($scfg->{iscsiprovider} eq 'freenas') {
|
||||
+ return PVE::Storage::LunCmd::FreeNAS::get_base;
|
||||
} elsif ($scfg->{iscsiprovider} eq 'istgt') {
|
||||
return PVE::Storage::LunCmd::Istgt::get_base;
|
||||
} elsif ($scfg->{iscsiprovider} eq 'iet') {
|
||||
@@ -62,6 +66,8 @@
|
||||
if ($lun_cmds->{$method}) {
|
||||
if ($scfg->{iscsiprovider} eq 'comstar') {
|
||||
$msg = PVE::Storage::LunCmd::Comstar::run_lun_command($scfg, $timeout, $method, @params);
|
||||
+ } elsif ($scfg->{iscsiprovider} eq 'freenas') {
|
||||
+ $msg = PVE::Storage::LunCmd::FreeNAS::run_lun_command($scfg, $timeout, $method, @params);
|
||||
} elsif ($scfg->{iscsiprovider} eq 'istgt') {
|
||||
$msg = PVE::Storage::LunCmd::Istgt::run_lun_command($scfg, $timeout, $method, @params);
|
||||
} elsif ($scfg->{iscsiprovider} eq 'iet') {
|
||||
@@ -162,6 +168,15 @@
|
||||
return $class->zfs_request($scfg, undef, 'list_view', $guid);
|
||||
}
|
||||
|
||||
+# Part of the multipath enhancement
|
||||
+sub zfs_get_wwid_number {
|
||||
+ my ($class, $scfg, $guid) = @_;
|
||||
+
|
||||
+ die "could not find lun_number for guid $guid" if !$guid;
|
||||
+
|
||||
+ return $class->zfs_request($scfg, undef, 'list_extent', $guid);
|
||||
+}
|
||||
+
|
||||
# Configuration
|
||||
|
||||
sub type {
|
||||
@@ -180,6 +195,24 @@
|
||||
description => "iscsi provider",
|
||||
type => 'string',
|
||||
},
|
||||
+ # This is for FreeNAS iscsi and API intergration
|
||||
+ # And some enhancements asked by the community
|
||||
+ freenas_user => {
|
||||
+ description => "FreeNAS API Username",
|
||||
+ type => 'string',
|
||||
+ },
|
||||
+ freenas_password => {
|
||||
+ description => "FreeNAS API Password",
|
||||
+ type => 'string',
|
||||
+ },
|
||||
+ freenas_use_ssl => {
|
||||
+ description => "FreeNAS API access via SSL",
|
||||
+ type => 'boolean',
|
||||
+ },
|
||||
+ freenas_apiv4_host => {
|
||||
+ description => "FreeNAS API Host",
|
||||
+ type => 'string',
|
||||
+ },
|
||||
# this will disable write caching on comstar and istgt.
|
||||
# it is not implemented for iet. iet blockio always operates with
|
||||
# writethrough caching when not in readonly mode
|
||||
@@ -207,14 +240,18 @@
|
||||
nodes => { optional => 1 },
|
||||
disable => { optional => 1 },
|
||||
portal => { fixed => 1 },
|
||||
- target => { fixed => 1 },
|
||||
- pool => { fixed => 1 },
|
||||
+ target => { fixed => 0 },
|
||||
+ pool => { fixed => 0 },
|
||||
blocksize => { fixed => 1 },
|
||||
iscsiprovider => { fixed => 1 },
|
||||
nowritecache => { optional => 1 },
|
||||
sparse => { optional => 1 },
|
||||
comstar_hg => { optional => 1 },
|
||||
comstar_tg => { optional => 1 },
|
||||
+ freenas_user => { optional => 1 },
|
||||
+ freenas_password => { optional => 1 },
|
||||
+ freenas_use_ssl => { optional => 1 },
|
||||
+ freenas_apiv4_host => { optional => 1 },
|
||||
lio_tpg => { optional => 1 },
|
||||
content => { optional => 1 },
|
||||
bwlimit => { optional => 1 },
|
||||
@@ -239,6 +276,40 @@
|
||||
|
||||
my $path = "iscsi://$portal/$target/$lun";
|
||||
|
||||
+ # Multipath enhancement
|
||||
+ eval {
|
||||
+ my $wwid = $class->zfs_get_wwid_number($scfg, $guid);
|
||||
+# syslog(info,"JD: path get_lun_number guid $guid");
|
||||
+
|
||||
+ if ($wwid =~ /^([-\@\w.]+)$/) {
|
||||
+ $wwid = $1; # $data now untainted
|
||||
+ } else {
|
||||
+ die "Bad data in '$wwid'"; # log this somewhere
|
||||
+ }
|
||||
+ my $wwid_end = substr $wwid, 16;
|
||||
+
|
||||
+ my $mapper = '';
|
||||
+ sleep 3;
|
||||
+ run_command("iscsiadm -m session --rescan");
|
||||
+ sleep 3;
|
||||
+ my $line = `multipath -ll | grep \"$wwid_end\"`;
|
||||
+ my ($mapper_device) = split(' ', $line);
|
||||
+ $mapper_device = "" unless $mapper_device;
|
||||
+ $mapper .= $mapper_device;
|
||||
+
|
||||
+ if ($mapper =~ /^([-\@\w.]+)$/) {
|
||||
+ $mapper = $1; # $data now untainted
|
||||
+ } else {
|
||||
+ $mapper = '';
|
||||
+ }
|
||||
+
|
||||
+# syslog(info,"Multipath mapper found: $mapper\n");
|
||||
+ if ($mapper ne "") {
|
||||
+ $path = "/dev/mapper/$mapper";
|
||||
+ sleep 5;
|
||||
+ }
|
||||
+ };
|
||||
+
|
||||
return ($path, $vmid, $vtype);
|
||||
}
|
||||
|
||||
|
|
@ -1,147 +0,0 @@
|
|||
--- ZFSPlugin.pm.orig 2022-02-04 12:08:01.000000000 -0500
|
||||
+++ ZFSPlugin.pm 2022-03-26 13:51:40.660068908 -0400
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
use base qw(PVE::Storage::ZFSPoolPlugin);
|
||||
use PVE::Storage::LunCmd::Comstar;
|
||||
+use PVE::Storage::LunCmd::FreeNAS;
|
||||
use PVE::Storage::LunCmd::Istgt;
|
||||
use PVE::Storage::LunCmd::Iet;
|
||||
use PVE::Storage::LunCmd::LIO;
|
||||
@@ -26,13 +27,14 @@
|
||||
modify_lu => 1,
|
||||
add_view => 1,
|
||||
list_view => 1,
|
||||
+ list_extent => 1,
|
||||
list_lu => 1,
|
||||
};
|
||||
|
||||
my $zfs_unknown_scsi_provider = sub {
|
||||
my ($provider) = @_;
|
||||
|
||||
- die "$provider: unknown iscsi provider. Available [comstar, istgt, iet, LIO]";
|
||||
+ die "$provider: unknown iscsi provider. Available [comstar, freenas, istgt, iet, LIO]";
|
||||
};
|
||||
|
||||
my $zfs_get_base = sub {
|
||||
@@ -40,6 +42,8 @@
|
||||
|
||||
if ($scfg->{iscsiprovider} eq 'comstar') {
|
||||
return PVE::Storage::LunCmd::Comstar::get_base;
|
||||
+ } elsif ($scfg->{iscsiprovider} eq 'freenas') {
|
||||
+ return PVE::Storage::LunCmd::FreeNAS::get_base;
|
||||
} elsif ($scfg->{iscsiprovider} eq 'istgt') {
|
||||
return PVE::Storage::LunCmd::Istgt::get_base;
|
||||
} elsif ($scfg->{iscsiprovider} eq 'iet') {
|
||||
@@ -62,6 +66,8 @@
|
||||
if ($lun_cmds->{$method}) {
|
||||
if ($scfg->{iscsiprovider} eq 'comstar') {
|
||||
$msg = PVE::Storage::LunCmd::Comstar::run_lun_command($scfg, $timeout, $method, @params);
|
||||
+ } elsif ($scfg->{iscsiprovider} eq 'freenas') {
|
||||
+ $msg = PVE::Storage::LunCmd::FreeNAS::run_lun_command($scfg, $timeout, $method, @params);
|
||||
} elsif ($scfg->{iscsiprovider} eq 'istgt') {
|
||||
$msg = PVE::Storage::LunCmd::Istgt::run_lun_command($scfg, $timeout, $method, @params);
|
||||
} elsif ($scfg->{iscsiprovider} eq 'iet') {
|
||||
@@ -166,6 +172,15 @@
|
||||
die "lun_number for guid $guid is not a number";
|
||||
}
|
||||
|
||||
+# Part of the multipath enhancement
|
||||
+sub zfs_get_wwid_number {
|
||||
+ my ($class, $scfg, $guid) = @_;
|
||||
+
|
||||
+ die "could not find lun_number for guid $guid" if !$guid;
|
||||
+
|
||||
+ return $class->zfs_request($scfg, undef, 'list_extent', $guid);
|
||||
+}
|
||||
+
|
||||
# Configuration
|
||||
|
||||
sub type {
|
||||
@@ -184,6 +199,24 @@
|
||||
description => "iscsi provider",
|
||||
type => 'string',
|
||||
},
|
||||
+ # This is for FreeNAS iscsi and API intergration
|
||||
+ # And some enhancements asked by the community
|
||||
+ freenas_user => {
|
||||
+ description => "FreeNAS API Username",
|
||||
+ type => 'string',
|
||||
+ },
|
||||
+ freenas_password => {
|
||||
+ description => "FreeNAS API Password",
|
||||
+ type => 'string',
|
||||
+ },
|
||||
+ freenas_use_ssl => {
|
||||
+ description => "FreeNAS API access via SSL",
|
||||
+ type => 'boolean',
|
||||
+ },
|
||||
+ freenas_apiv4_host => {
|
||||
+ description => "FreeNAS API Host",
|
||||
+ type => 'string',
|
||||
+ },
|
||||
# this will disable write caching on comstar and istgt.
|
||||
# it is not implemented for iet. iet blockio always operates with
|
||||
# writethrough caching when not in readonly mode
|
||||
@@ -211,14 +244,18 @@
|
||||
nodes => { optional => 1 },
|
||||
disable => { optional => 1 },
|
||||
portal => { fixed => 1 },
|
||||
- target => { fixed => 1 },
|
||||
- pool => { fixed => 1 },
|
||||
+ target => { fixed => 0 },
|
||||
+ pool => { fixed => 0 },
|
||||
blocksize => { fixed => 1 },
|
||||
iscsiprovider => { fixed => 1 },
|
||||
nowritecache => { optional => 1 },
|
||||
sparse => { optional => 1 },
|
||||
comstar_hg => { optional => 1 },
|
||||
comstar_tg => { optional => 1 },
|
||||
+ freenas_user => { optional => 1 },
|
||||
+ freenas_password => { optional => 1 },
|
||||
+ freenas_use_ssl => { optional => 1 },
|
||||
+ freenas_apiv4_host => { optional => 1 },
|
||||
lio_tpg => { optional => 1 },
|
||||
content => { optional => 1 },
|
||||
bwlimit => { optional => 1 },
|
||||
@@ -243,6 +280,40 @@
|
||||
|
||||
my $path = "iscsi://$portal/$target/$lun";
|
||||
|
||||
+ # Multipath enhancement
|
||||
+ eval {
|
||||
+ my $wwid = $class->zfs_get_wwid_number($scfg, $guid);
|
||||
+# syslog(info,"JD: path get_lun_number guid $guid");
|
||||
+
|
||||
+ if ($wwid =~ /^([-\@\w.]+)$/) {
|
||||
+ $wwid = $1; # $data now untainted
|
||||
+ } else {
|
||||
+ die "Bad data in '$wwid'"; # log this somewhere
|
||||
+ }
|
||||
+ my $wwid_end = substr $wwid, 16;
|
||||
+
|
||||
+ my $mapper = '';
|
||||
+ sleep 3;
|
||||
+ run_command("iscsiadm -m session --rescan");
|
||||
+ sleep 3;
|
||||
+ my $line = `/usr/sbin/multipath -ll | grep \"$wwid_end\"`;
|
||||
+ my ($mapper_device) = split(' ', $line);
|
||||
+ $mapper_device = "" unless $mapper_device;
|
||||
+ $mapper .= $mapper_device;
|
||||
+
|
||||
+ if ($mapper =~ /^([-\@\w.]+)$/) {
|
||||
+ $mapper = $1; # $data now untainted
|
||||
+ } else {
|
||||
+ $mapper = '';
|
||||
+ }
|
||||
+
|
||||
+# syslog(info,"Multipath mapper found: $mapper\n");
|
||||
+ if ($mapper ne "") {
|
||||
+ $path = "/dev/mapper/$mapper";
|
||||
+ sleep 5;
|
||||
+ }
|
||||
+ };
|
||||
+
|
||||
return ($path, $vmid, $vtype);
|
||||
}
|
||||
|
||||
|
|
@ -1,157 +0,0 @@
|
|||
--- ZFSPlugin.pm.orig 2023-12-31 09:56:18.895228853 -0500
|
||||
+++ ZFSPlugin.pm 2023-12-31 09:57:08.830488875 -0500
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
use base qw(PVE::Storage::ZFSPoolPlugin);
|
||||
use PVE::Storage::LunCmd::Comstar;
|
||||
+use PVE::Storage::LunCmd::FreeNAS;
|
||||
use PVE::Storage::LunCmd::Istgt;
|
||||
use PVE::Storage::LunCmd::Iet;
|
||||
use PVE::Storage::LunCmd::LIO;
|
||||
@@ -26,13 +27,14 @@
|
||||
modify_lu => 1,
|
||||
add_view => 1,
|
||||
list_view => 1,
|
||||
+ list_extent => 1,
|
||||
list_lu => 1,
|
||||
};
|
||||
|
||||
my $zfs_unknown_scsi_provider = sub {
|
||||
my ($provider) = @_;
|
||||
|
||||
- die "$provider: unknown iscsi provider. Available [comstar, istgt, iet, LIO]";
|
||||
+ die "$provider: unknown iscsi provider. Available [comstar, freenas, istgt, iet, LIO]";
|
||||
};
|
||||
|
||||
my $zfs_get_base = sub {
|
||||
@@ -40,6 +42,8 @@
|
||||
|
||||
if ($scfg->{iscsiprovider} eq 'comstar') {
|
||||
return PVE::Storage::LunCmd::Comstar::get_base;
|
||||
+ } elsif ($scfg->{iscsiprovider} eq 'freenas') {
|
||||
+ return PVE::Storage::LunCmd::FreeNAS::get_base;
|
||||
} elsif ($scfg->{iscsiprovider} eq 'istgt') {
|
||||
return PVE::Storage::LunCmd::Istgt::get_base;
|
||||
} elsif ($scfg->{iscsiprovider} eq 'iet') {
|
||||
@@ -62,6 +66,8 @@
|
||||
if ($lun_cmds->{$method}) {
|
||||
if ($scfg->{iscsiprovider} eq 'comstar') {
|
||||
$msg = PVE::Storage::LunCmd::Comstar::run_lun_command($scfg, $timeout, $method, @params);
|
||||
+ } elsif ($scfg->{iscsiprovider} eq 'freenas') {
|
||||
+ $msg = PVE::Storage::LunCmd::FreeNAS::run_lun_command($scfg, $timeout, $method, @params);
|
||||
} elsif ($scfg->{iscsiprovider} eq 'istgt') {
|
||||
$msg = PVE::Storage::LunCmd::Istgt::run_lun_command($scfg, $timeout, $method, @params);
|
||||
} elsif ($scfg->{iscsiprovider} eq 'iet') {
|
||||
@@ -166,6 +172,15 @@
|
||||
die "lun_number for guid $guid is not a number";
|
||||
}
|
||||
|
||||
+# Part of the multipath enhancement
|
||||
+sub zfs_get_wwid_number {
|
||||
+ my ($class, $scfg, $guid) = @_;
|
||||
+
|
||||
+ die "could not find lun_number for guid $guid" if !$guid;
|
||||
+
|
||||
+ return $class->zfs_request($scfg, undef, 'list_extent', $guid);
|
||||
+}
|
||||
+
|
||||
# Configuration
|
||||
|
||||
sub type {
|
||||
@@ -184,6 +199,32 @@
|
||||
description => "iscsi provider",
|
||||
type => 'string',
|
||||
},
|
||||
+ # This is for FreeNAS iscsi and API intergration
|
||||
+ # And some enhancements asked by the community
|
||||
+ freenas_user => {
|
||||
+ description => "FreeNAS API Username",
|
||||
+ type => 'string',
|
||||
+ },
|
||||
+ freenas_password => {
|
||||
+ description => "FreeNAS API Password (Deprecated)",
|
||||
+ type => 'string',
|
||||
+ },
|
||||
+ truenas_secret => {
|
||||
+ description => "TrueNAS API Secret",
|
||||
+ type => 'string',
|
||||
+ },
|
||||
+ truenas_token_auth => {
|
||||
+ description => "TrueNAS API Authentication with Token",
|
||||
+ type => 'boolean',
|
||||
+ },
|
||||
+ freenas_use_ssl => {
|
||||
+ description => "FreeNAS API access via SSL",
|
||||
+ type => 'boolean',
|
||||
+ },
|
||||
+ freenas_apiv4_host => {
|
||||
+ description => "FreeNAS API Host",
|
||||
+ type => 'string',
|
||||
+ },
|
||||
# this will disable write caching on comstar and istgt.
|
||||
# it is not implemented for iet. iet blockio always operates with
|
||||
# writethrough caching when not in readonly mode
|
||||
@@ -211,14 +252,20 @@
|
||||
nodes => { optional => 1 },
|
||||
disable => { optional => 1 },
|
||||
portal => { fixed => 1 },
|
||||
- target => { fixed => 1 },
|
||||
- pool => { fixed => 1 },
|
||||
+ target => { fixed => 0 },
|
||||
+ pool => { fixed => 0 },
|
||||
blocksize => { fixed => 1 },
|
||||
iscsiprovider => { fixed => 1 },
|
||||
nowritecache => { optional => 1 },
|
||||
sparse => { optional => 1 },
|
||||
comstar_hg => { optional => 1 },
|
||||
comstar_tg => { optional => 1 },
|
||||
+ freenas_user => { optional => 1 },
|
||||
+ freenas_password => { optional => 1 },
|
||||
+ truenas_secret => { optional => 1 },
|
||||
+ truenas_token_auth => { optional => 1 },
|
||||
+ freenas_use_ssl => { optional => 1 },
|
||||
+ freenas_apiv4_host => { optional => 1 },
|
||||
lio_tpg => { optional => 1 },
|
||||
content => { optional => 1 },
|
||||
bwlimit => { optional => 1 },
|
||||
@@ -243,6 +290,40 @@
|
||||
|
||||
my $path = "iscsi://$portal/$target/$lun";
|
||||
|
||||
+ # Multipath enhancement
|
||||
+ eval {
|
||||
+ my $wwid = $class->zfs_get_wwid_number($scfg, $guid);
|
||||
+# syslog(info,"JD: path get_lun_number guid $guid");
|
||||
+
|
||||
+ if ($wwid =~ /^([-\@\w.]+)$/) {
|
||||
+ $wwid = $1; # $data now untainted
|
||||
+ } else {
|
||||
+ die "Bad data in '$wwid'"; # log this somewhere
|
||||
+ }
|
||||
+ my $wwid_end = substr $wwid, 16;
|
||||
+
|
||||
+ my $mapper = '';
|
||||
+ sleep 3;
|
||||
+ run_command("iscsiadm -m session --rescan");
|
||||
+ sleep 3;
|
||||
+ my $line = `/usr/sbin/multipath -ll | grep \"$wwid_end\"`;
|
||||
+ my ($mapper_device) = split(' ', $line);
|
||||
+ $mapper_device = "" unless $mapper_device;
|
||||
+ $mapper .= $mapper_device;
|
||||
+
|
||||
+ if ($mapper =~ /^([-\@\w.]+)$/) {
|
||||
+ $mapper = $1; # $data now untainted
|
||||
+ } else {
|
||||
+ $mapper = '';
|
||||
+ }
|
||||
+
|
||||
+# syslog(info,"Multipath mapper found: $mapper\n");
|
||||
+ if ($mapper ne "") {
|
||||
+ $path = "/dev/mapper/$mapper";
|
||||
+ sleep 5;
|
||||
+ }
|
||||
+ };
|
||||
+
|
||||
return ($path, $vmid, $vtype);
|
||||
}
|
||||
|
||||
|
|
@ -1,157 +0,0 @@
|
|||
--- ZFSPlugin.pm.orig 2023-12-31 09:56:18.895228853 -0500
|
||||
+++ ZFSPlugin.pm 2023-12-31 09:57:08.830488875 -0500
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
use base qw(PVE::Storage::ZFSPoolPlugin);
|
||||
use PVE::Storage::LunCmd::Comstar;
|
||||
+use PVE::Storage::LunCmd::FreeNAS;
|
||||
use PVE::Storage::LunCmd::Istgt;
|
||||
use PVE::Storage::LunCmd::Iet;
|
||||
use PVE::Storage::LunCmd::LIO;
|
||||
@@ -26,13 +27,14 @@
|
||||
modify_lu => 1,
|
||||
add_view => 1,
|
||||
list_view => 1,
|
||||
+ list_extent => 1,
|
||||
list_lu => 1,
|
||||
};
|
||||
|
||||
my $zfs_unknown_scsi_provider = sub {
|
||||
my ($provider) = @_;
|
||||
|
||||
- die "$provider: unknown iscsi provider. Available [comstar, istgt, iet, LIO]";
|
||||
+ die "$provider: unknown iscsi provider. Available [comstar, freenas, istgt, iet, LIO]";
|
||||
};
|
||||
|
||||
my $zfs_get_base = sub {
|
||||
@@ -40,6 +42,8 @@
|
||||
|
||||
if ($scfg->{iscsiprovider} eq 'comstar') {
|
||||
return PVE::Storage::LunCmd::Comstar::get_base;
|
||||
+ } elsif ($scfg->{iscsiprovider} eq 'freenas') {
|
||||
+ return PVE::Storage::LunCmd::FreeNAS::get_base;
|
||||
} elsif ($scfg->{iscsiprovider} eq 'istgt') {
|
||||
return PVE::Storage::LunCmd::Istgt::get_base;
|
||||
} elsif ($scfg->{iscsiprovider} eq 'iet') {
|
||||
@@ -62,6 +66,8 @@
|
||||
if ($lun_cmds->{$method}) {
|
||||
if ($scfg->{iscsiprovider} eq 'comstar') {
|
||||
$msg = PVE::Storage::LunCmd::Comstar::run_lun_command($scfg, $timeout, $method, @params);
|
||||
+ } elsif ($scfg->{iscsiprovider} eq 'freenas') {
|
||||
+ $msg = PVE::Storage::LunCmd::FreeNAS::run_lun_command($scfg, $timeout, $method, @params);
|
||||
} elsif ($scfg->{iscsiprovider} eq 'istgt') {
|
||||
$msg = PVE::Storage::LunCmd::Istgt::run_lun_command($scfg, $timeout, $method, @params);
|
||||
} elsif ($scfg->{iscsiprovider} eq 'iet') {
|
||||
@@ -166,6 +172,15 @@
|
||||
die "lun_number for guid $guid is not a number";
|
||||
}
|
||||
|
||||
+# Part of the multipath enhancement
|
||||
+sub zfs_get_wwid_number {
|
||||
+ my ($class, $scfg, $guid) = @_;
|
||||
+
|
||||
+ die "could not find lun_number for guid $guid" if !$guid;
|
||||
+
|
||||
+ return $class->zfs_request($scfg, undef, 'list_extent', $guid);
|
||||
+}
|
||||
+
|
||||
# Configuration
|
||||
|
||||
sub type {
|
||||
@@ -184,6 +199,32 @@
|
||||
description => "iscsi provider",
|
||||
type => 'string',
|
||||
},
|
||||
+ # This is for FreeNAS iscsi and API intergration
|
||||
+ # And some enhancements asked by the community
|
||||
+ freenas_user => {
|
||||
+ description => "FreeNAS API Username",
|
||||
+ type => 'string',
|
||||
+ },
|
||||
+ freenas_password => {
|
||||
+ description => "FreeNAS API Password (Deprecated)",
|
||||
+ type => 'string',
|
||||
+ },
|
||||
+ truenas_secret => {
|
||||
+ description => "TrueNAS API Secret",
|
||||
+ type => 'string',
|
||||
+ },
|
||||
+ truenas_token_auth => {
|
||||
+ description => "TrueNAS API Authentication with Token",
|
||||
+ type => 'boolean',
|
||||
+ },
|
||||
+ freenas_use_ssl => {
|
||||
+ description => "FreeNAS API access via SSL",
|
||||
+ type => 'boolean',
|
||||
+ },
|
||||
+ freenas_apiv4_host => {
|
||||
+ description => "FreeNAS API Host",
|
||||
+ type => 'string',
|
||||
+ },
|
||||
# this will disable write caching on comstar and istgt.
|
||||
# it is not implemented for iet. iet blockio always operates with
|
||||
# writethrough caching when not in readonly mode
|
||||
@@ -211,14 +252,20 @@
|
||||
nodes => { optional => 1 },
|
||||
disable => { optional => 1 },
|
||||
portal => { fixed => 1 },
|
||||
- target => { fixed => 1 },
|
||||
- pool => { fixed => 1 },
|
||||
+ target => { fixed => 0 },
|
||||
+ pool => { fixed => 0 },
|
||||
blocksize => { fixed => 1 },
|
||||
iscsiprovider => { fixed => 1 },
|
||||
nowritecache => { optional => 1 },
|
||||
sparse => { optional => 1 },
|
||||
comstar_hg => { optional => 1 },
|
||||
comstar_tg => { optional => 1 },
|
||||
+ freenas_user => { optional => 1 },
|
||||
+ freenas_password => { optional => 1 },
|
||||
+ truenas_secret => { optional => 1 },
|
||||
+ truenas_token_auth => { optional => 1 },
|
||||
+ freenas_use_ssl => { optional => 1 },
|
||||
+ freenas_apiv4_host => { optional => 1 },
|
||||
lio_tpg => { optional => 1 },
|
||||
content => { optional => 1 },
|
||||
bwlimit => { optional => 1 },
|
||||
@@ -243,6 +290,40 @@
|
||||
|
||||
my $path = "iscsi://$portal/$target/$lun";
|
||||
|
||||
+ # Multipath enhancement
|
||||
+ eval {
|
||||
+ my $wwid = $class->zfs_get_wwid_number($scfg, $guid);
|
||||
+# syslog(info,"JD: path get_lun_number guid $guid");
|
||||
+
|
||||
+ if ($wwid =~ /^([-\@\w.]+)$/) {
|
||||
+ $wwid = $1; # $data now untainted
|
||||
+ } else {
|
||||
+ die "Bad data in '$wwid'"; # log this somewhere
|
||||
+ }
|
||||
+ my $wwid_end = substr $wwid, 16;
|
||||
+
|
||||
+ my $mapper = '';
|
||||
+ sleep 3;
|
||||
+ run_command("iscsiadm -m session --rescan");
|
||||
+ sleep 3;
|
||||
+ my $line = `/usr/sbin/multipath -ll | grep \"$wwid_end\"`;
|
||||
+ my ($mapper_device) = split(' ', $line);
|
||||
+ $mapper_device = "" unless $mapper_device;
|
||||
+ $mapper .= $mapper_device;
|
||||
+
|
||||
+ if ($mapper =~ /^([-\@\w.]+)$/) {
|
||||
+ $mapper = $1; # $data now untainted
|
||||
+ } else {
|
||||
+ $mapper = '';
|
||||
+ }
|
||||
+
|
||||
+# syslog(info,"Multipath mapper found: $mapper\n");
|
||||
+ if ($mapper ne "") {
|
||||
+ $path = "/dev/mapper/$mapper";
|
||||
+ sleep 5;
|
||||
+ }
|
||||
+ };
|
||||
+
|
||||
return ($path, $vmid, $vtype);
|
||||
}
|
||||
|
||||
|
|
@ -1,549 +0,0 @@
|
|||
package REST::Client;
|
||||
|
||||
=head1 NAME
|
||||
|
||||
REST::Client - A simple client for interacting with RESTful http/https resources
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use REST::Client;
|
||||
|
||||
#The basic use case
|
||||
my $client = REST::Client->new();
|
||||
$client->GET('http://example.com/dir/file.xml');
|
||||
print $client->responseContent();
|
||||
|
||||
#A host can be set for convienience
|
||||
$client->setHost('http://example.com');
|
||||
$client->PUT('/dir/file.xml', '<example>new content</example>');
|
||||
if( $client->responseCode() eq '200' ){
|
||||
print "Updated\n";
|
||||
}
|
||||
|
||||
#custom request headers may be added
|
||||
$client->addHeader('CustomHeader', 'Value');
|
||||
|
||||
#response headers may be gathered
|
||||
print $client->responseHeader('ResponseHeader');
|
||||
|
||||
#X509 client authentication
|
||||
$client->setCert('/path/to/ssl.crt');
|
||||
$client->setKey('/path/to/ssl.key');
|
||||
|
||||
#add a CA to verify server certificates
|
||||
$client->setCa('/path/to/ca.file');
|
||||
|
||||
#you may set a timeout on requests, in seconds
|
||||
$client->setTimeout(10);
|
||||
|
||||
#options may be passed as well as set
|
||||
$client = REST::Client->new({
|
||||
host => 'https://example.com',
|
||||
cert => '/path/to/ssl.crt',
|
||||
key => '/path/to/ssl.key',
|
||||
ca => '/path/to/ca.file',
|
||||
timeout => 10,
|
||||
});
|
||||
$client->GET('/dir/file', {CustomHeader => 'Value'});
|
||||
|
||||
# Requests can be specificed directly as well
|
||||
$client->request('GET', '/dir/file', 'request body content', {CustomHeader => 'Value'});
|
||||
|
||||
# Requests can optionally automatically follow redirects and auth, defaults to
|
||||
# false
|
||||
$client->setFollow(1);
|
||||
|
||||
#It is possible to access the L<LWP::UserAgent> object REST::Client is using to
|
||||
#make requests, and set advanced options on it, for instance:
|
||||
$client->getUseragent()->proxy(['http'], 'http://proxy.example.com/');
|
||||
|
||||
# request responses can be written directly to a file
|
||||
$client->setContentFile( "FileName" );
|
||||
|
||||
# or call back method
|
||||
$client->setContentFile( \&callback_method );
|
||||
# see LWP::UserAgent for how to define callback methods
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
REST::Client provides a simple way to interact with HTTP RESTful resources.
|
||||
|
||||
=cut
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=cut
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use 5.008_000;
|
||||
|
||||
use constant TRUE => 1;
|
||||
use constant FALSE => 0;
|
||||
|
||||
our ($VERSION) = ('$Rev: 273 $' =~ /(\d+)/);
|
||||
|
||||
use URI;
|
||||
use LWP::UserAgent;
|
||||
use Carp qw(croak carp);
|
||||
|
||||
=head2 Construction and setup
|
||||
|
||||
=head3 new ( [%$config] )
|
||||
|
||||
Construct a new REST::Client. Takes an optional hash or hash reference or
|
||||
config flags. Each config flag also has get/set accessors of the form
|
||||
getHost/setHost, getUseragent/setUseragent, etc. These can be called on the
|
||||
instantiated object to change or check values.
|
||||
|
||||
The config flags are:
|
||||
|
||||
=over 4
|
||||
|
||||
=item host
|
||||
|
||||
A default host that will be prepended to all requests. Allows you to just
|
||||
specify the path when making requests.
|
||||
|
||||
The default is undef - you must include the host in your requests.
|
||||
|
||||
=item timeout
|
||||
|
||||
A timeout in seconds for requests made with the client. After the timeout the
|
||||
client will return a 500.
|
||||
|
||||
The default is 5 minutes.
|
||||
|
||||
=item cert
|
||||
|
||||
The path to a X509 certificate file to be used for client authentication.
|
||||
|
||||
The default is to not use a certificate/key pair.
|
||||
|
||||
=item key
|
||||
|
||||
The path to a X509 key file to be used for client authentication.
|
||||
|
||||
The default is to not use a certificate/key pair.
|
||||
|
||||
=item ca
|
||||
|
||||
The path to a certificate authority file to be used to verify host
|
||||
certificates.
|
||||
|
||||
The default is to not use a certificates authority.
|
||||
|
||||
=item pkcs12
|
||||
|
||||
The path to a PKCS12 certificate to be used for client authentication.
|
||||
|
||||
=item pkcs12password
|
||||
|
||||
The password for the PKCS12 certificate specified with 'pkcs12'.
|
||||
|
||||
=item follow
|
||||
|
||||
Boolean that determins whether REST::Client attempts to automatically follow
|
||||
redirects/authentication.
|
||||
|
||||
The default is false.
|
||||
|
||||
=item useragent
|
||||
|
||||
An L<LWP::UserAgent> object, ready to make http requests.
|
||||
|
||||
REST::Client will provide a default for you if you do not set this.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my $config;
|
||||
|
||||
$class->_buildAccessors();
|
||||
|
||||
if(ref $_[0] eq 'HASH'){
|
||||
$config = shift;
|
||||
}elsif(scalar @_ && scalar @_ % 2 == 0){
|
||||
$config = {@_};
|
||||
}else{
|
||||
$config = {};
|
||||
}
|
||||
|
||||
my $self = bless({}, $class);
|
||||
$self->{'_config'} = $config;
|
||||
|
||||
$self->_buildUseragent();
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
=head3 addHeader ( $header_name, $value )
|
||||
|
||||
Add a custom header to any requests made by this client.
|
||||
|
||||
=cut
|
||||
|
||||
sub addHeader {
|
||||
my $self = shift;
|
||||
my $header = shift;
|
||||
my $value = shift;
|
||||
|
||||
my $headers = $self->{'_headers'} || {};
|
||||
$headers->{$header} = $value;
|
||||
$self->{'_headers'} = $headers;
|
||||
return;
|
||||
}
|
||||
|
||||
=head3 buildQuery ( [...] )
|
||||
|
||||
A convienience wrapper around URI::query_form for building query strings from a
|
||||
variety of data structures. See L<URI>
|
||||
|
||||
Returns a scalar query string for use in URLs.
|
||||
|
||||
=cut
|
||||
|
||||
sub buildQuery {
|
||||
my $self = shift;
|
||||
|
||||
my $uri = URI->new();
|
||||
$uri->query_form(@_);
|
||||
return $uri->as_string();
|
||||
}
|
||||
|
||||
|
||||
|
||||
=head2 Request Methods
|
||||
|
||||
Each of these methods makes an HTTP request, sets the internal state of the
|
||||
object, and returns the object.
|
||||
|
||||
They can be combined with the response methods, such as:
|
||||
|
||||
print $client->GET('/search/?q=foobar')->responseContent();
|
||||
|
||||
=head3 GET ( $url, [$body_content, %$headers] )
|
||||
|
||||
Preform an HTTP GET to the resource specified. Takes an optional hashref of custom request headers.
|
||||
|
||||
=cut
|
||||
|
||||
sub GET {
|
||||
my $self = shift;
|
||||
return $self->request('GET', @_);
|
||||
}
|
||||
|
||||
=head3 PUT ($url, [$body_content, %$headers] )
|
||||
|
||||
Preform an HTTP PUT to the resource specified. Takes an optional body content and hashref of custom request headers.
|
||||
|
||||
=cut
|
||||
|
||||
sub PUT {
|
||||
my $self = shift;
|
||||
return $self->request('PUT', @_);
|
||||
}
|
||||
|
||||
=head3 PATCH ( $url, [$body_content, %$headers] )
|
||||
|
||||
Preform an HTTP PATCH to the resource specified. Takes an optional body content and hashref of custom request headers.
|
||||
|
||||
=cut
|
||||
|
||||
sub PATCH {
|
||||
my $self = shift;
|
||||
return $self->request('PATCH', @_);
|
||||
}
|
||||
|
||||
=head3 POST ( $url, [$body_content, %$headers] )
|
||||
|
||||
Preform an HTTP POST to the resource specified. Takes an optional body content and hashref of custom request headers.
|
||||
|
||||
=cut
|
||||
|
||||
sub POST {
|
||||
my $self = shift;
|
||||
return $self->request('POST', @_);
|
||||
}
|
||||
|
||||
=head3 DELETE ( $url, [$body_content, %$headers] )
|
||||
|
||||
Preform an HTTP DELETE to the resource specified. Takes an optional hashref of custom request headers.
|
||||
|
||||
=cut
|
||||
|
||||
sub DELETE {
|
||||
my $self = shift;
|
||||
return $self->request('DELETE', @_);
|
||||
}
|
||||
|
||||
=head3 OPTIONS ( $url, [%$headers] )
|
||||
|
||||
Preform an HTTP OPTIONS to the resource specified. Takes an optional hashref of custom request headers.
|
||||
|
||||
=cut
|
||||
|
||||
sub OPTIONS {
|
||||
my $self = shift;
|
||||
my $url = shift;
|
||||
my $headers = shift;
|
||||
return $self->request('OPTIONS', $url, undef, $headers);
|
||||
}
|
||||
|
||||
=head3 HEAD ( $url, [%$headers] )
|
||||
|
||||
Preform an HTTP HEAD to the resource specified. Takes an optional hashref of custom request headers.
|
||||
|
||||
=cut
|
||||
|
||||
sub HEAD {
|
||||
my $self = shift;
|
||||
my $url = shift;
|
||||
my $headers = shift;
|
||||
return $self->request('HEAD', $url, undef, $headers);
|
||||
}
|
||||
|
||||
=head3 request ( $method, $url, [$body_content, %$headers] )
|
||||
|
||||
Issue a custom request, providing all possible values.
|
||||
|
||||
=cut
|
||||
|
||||
sub request {
|
||||
my $self = shift;
|
||||
my $method = shift;
|
||||
my $url = shift;
|
||||
my $content = shift;
|
||||
my $headers = shift;
|
||||
|
||||
$self->{'_res'} = undef;
|
||||
$self->_buildUseragent();
|
||||
|
||||
|
||||
#error check
|
||||
croak "REST::Client exception: First argument to request must be one of GET, PATCH, PUT, POST, DELETE, OPTIONS, HEAD" unless $method =~ /^(get|patch|put|post|delete|options|head)$/i;
|
||||
croak "REST::Client exception: Must provide a url to $method" unless $url;
|
||||
croak "REST::Client exception: headers must be presented as a hashref" if $headers && ref $headers ne 'HASH';
|
||||
|
||||
|
||||
$url = $self->_prepareURL($url);
|
||||
|
||||
my $ua = $self->getUseragent();
|
||||
if(defined $self->getTimeout()){
|
||||
$ua->timeout($self->getTimeout);
|
||||
}else{
|
||||
$ua->timeout(300);
|
||||
}
|
||||
my $req = HTTP::Request->new( $method => $url );
|
||||
|
||||
#build headers
|
||||
if(defined $content && length($content)){
|
||||
$req->content($content);
|
||||
$req->header('Content-Length', length($content));
|
||||
}else{
|
||||
$req->header('Content-Length', 0);
|
||||
}
|
||||
|
||||
my $custom_headers = $self->{'_headers'} || {};
|
||||
for my $header (keys %$custom_headers){
|
||||
$req->header($header, $custom_headers->{$header});
|
||||
}
|
||||
|
||||
for my $header (keys %$headers){
|
||||
$req->header($header, $headers->{$header});
|
||||
}
|
||||
|
||||
|
||||
#prime LWP with ssl certfile if we have values
|
||||
if($self->getCert){
|
||||
carp "REST::Client exception: Certs defined but not using https" unless $url =~ /^https/;
|
||||
croak "REST::Client exception: Cannot read cert and key file" unless -f $self->getCert && -f $self->getKey;
|
||||
|
||||
$ua->ssl_opts(SSL_cert_file => $self->getCert);
|
||||
$ua->ssl_opts(SSL_key_file => $self->getKey);
|
||||
}
|
||||
|
||||
#prime LWP with CA file if we have one
|
||||
if(my $ca = $self->getCa){
|
||||
croak "REST::Client exception: Cannot read CA file" unless -f $ca;
|
||||
$ua->ssl_opts(SSL_ca_file => $ca);
|
||||
}
|
||||
|
||||
#prime LWP with PKCS12 certificate if we have one
|
||||
if($self->getPkcs12){
|
||||
carp "REST::Client exception: PKCS12 cert defined but not using https" unless $url =~ /^https/;
|
||||
croak "REST::Client exception: Cannot read PKCS12 cert" unless -f $self->getPkcs12;
|
||||
|
||||
$ENV{HTTPS_PKCS12_FILE} = $self->getPkcs12;
|
||||
if($self->getPkcs12password){
|
||||
$ENV{HTTPS_PKCS12_PASSWORD} = $self->getPkcs12password;
|
||||
}
|
||||
}
|
||||
|
||||
my $res = $self->getFollow ?
|
||||
$ua->request( $req, $self->getContentFile ) :
|
||||
$ua->simple_request( $req, $self->getContentFile );
|
||||
|
||||
$self->{_res} = $res;
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
=head2 Response Methods
|
||||
|
||||
Use these methods to gather information about the last requset
|
||||
performed.
|
||||
|
||||
=head3 responseCode ()
|
||||
|
||||
Return the HTTP response code of the last request
|
||||
|
||||
=cut
|
||||
|
||||
sub responseCode {
|
||||
my $self = shift;
|
||||
return $self->{_res}->code;
|
||||
}
|
||||
|
||||
=head3 responseContent ()
|
||||
|
||||
Return the response body content of the last request
|
||||
|
||||
=cut
|
||||
|
||||
sub responseContent {
|
||||
my $self = shift;
|
||||
return $self->{_res}->content;
|
||||
}
|
||||
|
||||
=head3 responseHeaders()
|
||||
|
||||
Returns a list of HTTP header names from the last response
|
||||
|
||||
=cut
|
||||
|
||||
sub responseHeaders {
|
||||
my $self = shift;
|
||||
return $self->{_res}->headers()->header_field_names();
|
||||
}
|
||||
|
||||
|
||||
|
||||
=head3 responseHeader ( $header )
|
||||
|
||||
Return a HTTP header from the last response
|
||||
|
||||
=cut
|
||||
|
||||
sub responseHeader {
|
||||
my $self = shift;
|
||||
my $header = shift;
|
||||
croak "REST::Client exception: no header provided to responseHeader" unless $header;
|
||||
return $self->{_res}->header($header);
|
||||
}
|
||||
|
||||
=head3 responseXpath ()
|
||||
|
||||
A convienience wrapper that returns a L<XML::LibXML> xpath context for the body content. Assumes the content is XML.
|
||||
|
||||
=cut
|
||||
|
||||
sub responseXpath {
|
||||
my $self = shift;
|
||||
|
||||
require XML::LibXML;
|
||||
|
||||
my $xml= XML::LibXML->new();
|
||||
$xml->load_ext_dtd(0);
|
||||
|
||||
if($self->responseHeader('Content-type') =~ /html/){
|
||||
return XML::LibXML::XPathContext->new($xml->parse_html_string( $self->responseContent() ));
|
||||
}else{
|
||||
return XML::LibXML::XPathContext->new($xml->parse_string( $self->responseContent() ));
|
||||
}
|
||||
}
|
||||
|
||||
# Private methods
|
||||
|
||||
sub _prepareURL {
|
||||
my $self = shift;
|
||||
my $url = shift;
|
||||
|
||||
my $host = $self->getHost;
|
||||
if($host){
|
||||
$url = '/'.$url unless $url =~ /^\//;
|
||||
$url = $host . $url;
|
||||
}
|
||||
unless($url =~ /^\w+:\/\//){
|
||||
$url = ($self->getCert ? 'https://' : 'http://') . $url;
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
sub _buildUseragent {
|
||||
my $self = shift;
|
||||
|
||||
return if $self->getUseragent();
|
||||
|
||||
my $ua = LWP::UserAgent->new;
|
||||
$ua->agent("REST::Client/$VERSION");
|
||||
$self->setUseragent($ua);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
sub _buildAccessors {
|
||||
my $self = shift;
|
||||
|
||||
return if $self->can('setHost');
|
||||
|
||||
my @attributes = qw(Host Key Cert Ca Timeout Follow Useragent Pkcs12 Pkcs12password ContentFile);
|
||||
|
||||
for my $attribute (@attributes){
|
||||
my $set_method = "
|
||||
sub {
|
||||
my \$self = shift;
|
||||
\$self->{'_config'}{lc('$attribute')} = shift;
|
||||
return \$self->{'_config'}{lc('$attribute')};
|
||||
}";
|
||||
|
||||
my $get_method = "
|
||||
sub {
|
||||
my \$self = shift;
|
||||
return \$self->{'_config'}{lc('$attribute')};
|
||||
}";
|
||||
|
||||
|
||||
{
|
||||
no strict 'refs';
|
||||
*{'REST::Client::set'.$attribute} = eval $set_method ;
|
||||
*{'REST::Client::get'.$attribute} = eval $get_method ;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
|
||||
=head1 TODO
|
||||
|
||||
Caching, content-type negotiation, readable handles for body content.
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Miles Crawford, E<lt>mcrawfor@cpan.orgE<gt>
|
||||
|
||||
=head1 COPYRIGHT
|
||||
|
||||
Copyright 2008 - 2010 by Miles Crawford
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
|
||||
|
||||
=cut
|
||||
|
|
@ -1 +0,0 @@
|
|||
/apidoc.js
|
||||
|
|
@ -1,66 +0,0 @@
|
|||
--- apidoc.js.orig 2018-08-19 10:18:11.715767285 -0400
|
||||
+++ apidoc.js.new 2018-08-19 10:17:09.364282178 -0400
|
||||
@@ -27584,6 +27584,31 @@
|
||||
"type" : "string",
|
||||
"typetext" : "<string>"
|
||||
},
|
||||
+ "freenas_user" : {
|
||||
+ "description" : "FreeNAS user for API access",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
+ },
|
||||
+ "freenas_password" : {
|
||||
+ "description" : "FreeNAS password for API access",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
+ },
|
||||
+ "freenas_use_ssl" : {
|
||||
+ "description" : "FreeNAS API access via SSL",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "boolean",
|
||||
+ "typetext" : "<boolean>"
|
||||
+ },
|
||||
+ "freenas_apiv4_host" : {
|
||||
+ "description" : "FreeNAS API Host via IPv4",
|
||||
+ "format" : "address",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
+ },
|
||||
"content" : {
|
||||
"description" : "Allowed content types.\n\nNOTE: the value 'rootdir' is used for Containers, and value 'images' for VMs.\n",
|
||||
"format" : "pve-storage-content-list",
|
||||
@@ -27948,6 +27973,31 @@
|
||||
"optional" : 1,
|
||||
"type" : "string",
|
||||
"typetext" : "<string>"
|
||||
+ },
|
||||
+ "freenas_user" : {
|
||||
+ "description" : "FreeNAS user for API access",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
+ },
|
||||
+ "freenas_password" : {
|
||||
+ "description" : "FreeNAS password for API access",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
+ },
|
||||
+ "freenas_use_ssl" : {
|
||||
+ "description" : "FreeNAS API access via SSL",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "boolean",
|
||||
+ "typetext" : "<boolean>"
|
||||
+ },
|
||||
+ "freenas_apiv4_host" : {
|
||||
+ "description" : "FreeNAS API Host via IPv4",
|
||||
+ "format" : "address",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
},
|
||||
"content" : {
|
||||
"description" : "Allowed content types.\n\nNOTE: the value 'rootdir' is used for Containers, and value 'images' for VMs.\n",
|
||||
|
|
@ -1,79 +0,0 @@
|
|||
--- apidoc-5.2-9.js 2018-11-10 14:28:44.022355849 -0500
|
||||
+++ apidoc.js 2018-11-10 15:49:38.873720011 -0500
|
||||
@@ -32448,6 +32448,31 @@
|
||||
"type" : "string",
|
||||
"typetext" : "<string>"
|
||||
},
|
||||
+ "freenas_user" : {
|
||||
+ "description" : "FreeNAS user for API access",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
+ },
|
||||
+ "freenas_password" : {
|
||||
+ "description" : "FreeNAS password for API access",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
+ },
|
||||
+ "freenas_use_ssl" : {
|
||||
+ "description" : "FreeNAS API access via SSL",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "boolean",
|
||||
+ "typetext" : "<boolean>"
|
||||
+ },
|
||||
+ "freenas_apiv4_host" : {
|
||||
+ "description" : "FreeNAS API Host via IPv4",
|
||||
+ "format" : "address",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
+ },
|
||||
"fuse" : {
|
||||
"description" : "Mount CephFS through FUSE.",
|
||||
"optional" : 1,
|
||||
@@ -32604,6 +32629,12 @@
|
||||
"type" : "boolean",
|
||||
"typetext" : "<boolean>"
|
||||
},
|
||||
+ "target" : {
|
||||
+ "description" : "iSCSI target.",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
+ },
|
||||
"transport" : {
|
||||
"description" : "Gluster transport: tcp or rdma",
|
||||
"enum" : [
|
||||
@@ -32810,6 +32841,31 @@
|
||||
"optional" : 1,
|
||||
"type" : "string",
|
||||
"typetext" : "<string>"
|
||||
+ },
|
||||
+ "freenas_user" : {
|
||||
+ "description" : "FreeNAS user for API access",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
+ },
|
||||
+ "freenas_password" : {
|
||||
+ "description" : "FreeNAS password for API access",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
+ },
|
||||
+ "freenas_use_ssl" : {
|
||||
+ "description" : "FreeNAS API access via SSL",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "boolean",
|
||||
+ "typetext" : "<boolean>"
|
||||
+ },
|
||||
+ "freenas_apiv4_host" : {
|
||||
+ "description" : "FreeNAS API Host via IPv4",
|
||||
+ "format" : "address",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
},
|
||||
"fuse" : {
|
||||
"description" : "Mount CephFS through FUSE.",
|
||||
|
|
@ -1,79 +0,0 @@
|
|||
--- apidoc-5.3-1.js 2018-12-21 12:46:55.459865478 -0500
|
||||
+++ apidoc.js 2018-12-06 15:50:21.487987807 -0500
|
||||
@@ -33477,6 +33477,31 @@
|
||||
"type" : "string",
|
||||
"typetext" : "<string>"
|
||||
},
|
||||
+ "freenas_user" : {
|
||||
+ "description" : "FreeNAS user for API access",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
+ },
|
||||
+ "freenas_password" : {
|
||||
+ "description" : "FreeNAS password for API access",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
+ },
|
||||
+ "freenas_use_ssl" : {
|
||||
+ "description" : "FreeNAS API access via SSL",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "boolean",
|
||||
+ "typetext" : "<boolean>"
|
||||
+ },
|
||||
+ "freenas_apiv4_host" : {
|
||||
+ "description" : "FreeNAS API Host via IPv4",
|
||||
+ "format" : "address",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
+ },
|
||||
"fuse" : {
|
||||
"description" : "Mount CephFS through FUSE.",
|
||||
"optional" : 1,
|
||||
@@ -33633,6 +33658,12 @@
|
||||
"type" : "boolean",
|
||||
"typetext" : "<boolean>"
|
||||
},
|
||||
+ "target" : {
|
||||
+ "description" : "iSCSI target.",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
+ },
|
||||
"transport" : {
|
||||
"description" : "Gluster transport: tcp or rdma",
|
||||
"enum" : [
|
||||
@@ -33840,6 +33871,31 @@
|
||||
"optional" : 1,
|
||||
"type" : "string",
|
||||
"typetext" : "<string>"
|
||||
+ },
|
||||
+ "freenas_user" : {
|
||||
+ "description" : "FreeNAS user for API access",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
+ },
|
||||
+ "freenas_password" : {
|
||||
+ "description" : "FreeNAS password for API access",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
+ },
|
||||
+ "freenas_use_ssl" : {
|
||||
+ "description" : "FreeNAS API access via SSL",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "boolean",
|
||||
+ "typetext" : "<boolean>"
|
||||
+ },
|
||||
+ "freenas_apiv4_host" : {
|
||||
+ "description" : "FreeNAS API Host via IPv4",
|
||||
+ "format" : "address",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
},
|
||||
"fuse" : {
|
||||
"description" : "Mount CephFS through FUSE.",
|
||||
|
|
@ -1,79 +0,0 @@
|
|||
--- apidoc.js.orig 2019-02-20 06:20:05.000000000 -0500
|
||||
+++ apidoc.js 2019-02-26 20:51:26.857204965 -0500
|
||||
@@ -33709,6 +33709,31 @@
|
||||
"type" : "string",
|
||||
"typetext" : "<string>"
|
||||
},
|
||||
+ "freenas_user" : {
|
||||
+ "description" : "FreeNAS user for API access",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
+ },
|
||||
+ "freenas_password" : {
|
||||
+ "description" : "FreeNAS password for API access",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
+ },
|
||||
+ "freenas_use_ssl" : {
|
||||
+ "description" : "FreeNAS API access via SSL",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "boolean",
|
||||
+ "typetext" : "<boolean>"
|
||||
+ },
|
||||
+ "freenas_apiv4_host" : {
|
||||
+ "description" : "FreeNAS API Host via IPv4",
|
||||
+ "format" : "address",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
+ },
|
||||
"fuse" : {
|
||||
"description" : "Mount CephFS through FUSE.",
|
||||
"optional" : 1,
|
||||
@@ -33865,6 +33890,12 @@
|
||||
"type" : "boolean",
|
||||
"typetext" : "<boolean>"
|
||||
},
|
||||
+ "target" : {
|
||||
+ "description" : "iSCSI target.",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
+ },
|
||||
"transport" : {
|
||||
"description" : "Gluster transport: tcp or rdma",
|
||||
"enum" : [
|
||||
@@ -34071,6 +34102,31 @@
|
||||
"optional" : 1,
|
||||
"type" : "string",
|
||||
"typetext" : "<string>"
|
||||
+ },
|
||||
+ "freenas_user" : {
|
||||
+ "description" : "FreeNAS user for API access",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
+ },
|
||||
+ "freenas_password" : {
|
||||
+ "description" : "FreeNAS password for API access",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
+ },
|
||||
+ "freenas_use_ssl" : {
|
||||
+ "description" : "FreeNAS API access via SSL",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "boolean",
|
||||
+ "typetext" : "<boolean>"
|
||||
+ },
|
||||
+ "freenas_apiv4_host" : {
|
||||
+ "description" : "FreeNAS API Host via IPv4",
|
||||
+ "format" : "address",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
},
|
||||
"fuse" : {
|
||||
"description" : "Mount CephFS through FUSE.",
|
||||
|
|
@ -1,79 +0,0 @@
|
|||
--- apidoc.js.orig 2019-04-08 02:02:09.000000000 -0400
|
||||
+++ apidoc.js 2019-05-11 10:34:28.305764709 -0400
|
||||
@@ -34385,6 +34385,31 @@
|
||||
"type" : "string",
|
||||
"typetext" : "<string>"
|
||||
},
|
||||
+ "freenas_user" : {
|
||||
+ "description" : "FreeNAS user for API access",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
+ },
|
||||
+ "freenas_password" : {
|
||||
+ "description" : "FreeNAS password for API access",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
+ },
|
||||
+ "freenas_use_ssl" : {
|
||||
+ "description" : "FreeNAS API access via SSL",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "boolean",
|
||||
+ "typetext" : "<boolean>"
|
||||
+ },
|
||||
+ "freenas_apiv4_host" : {
|
||||
+ "description" : "FreeNAS API Host via IPv4",
|
||||
+ "format" : "address",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
+ },
|
||||
"fuse" : {
|
||||
"description" : "Mount CephFS through FUSE.",
|
||||
"optional" : 1,
|
||||
@@ -34541,6 +34566,12 @@
|
||||
"type" : "boolean",
|
||||
"typetext" : "<boolean>"
|
||||
},
|
||||
+ "target" : {
|
||||
+ "description" : "iSCSI target.",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
+ },
|
||||
"transport" : {
|
||||
"description" : "Gluster transport: tcp or rdma",
|
||||
"enum" : [
|
||||
@@ -34748,6 +34779,31 @@
|
||||
"optional" : 1,
|
||||
"type" : "string",
|
||||
"typetext" : "<string>"
|
||||
+ },
|
||||
+ "freenas_user" : {
|
||||
+ "description" : "FreeNAS user for API access",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
+ },
|
||||
+ "freenas_password" : {
|
||||
+ "description" : "FreeNAS password for API access",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
+ },
|
||||
+ "freenas_use_ssl" : {
|
||||
+ "description" : "FreeNAS API access via SSL",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "boolean",
|
||||
+ "typetext" : "<boolean>"
|
||||
+ },
|
||||
+ "freenas_apiv4_host" : {
|
||||
+ "description" : "FreeNAS API Host via IPv4",
|
||||
+ "format" : "address",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
},
|
||||
"fuse" : {
|
||||
"description" : "Mount CephFS through FUSE.",
|
||||
|
|
@ -1,79 +0,0 @@
|
|||
--- apidoc.js.orig 2019-07-15 15:45:00.000000000 -0400
|
||||
+++ apidoc.js 2019-08-07 13:28:29.292382229 -0400
|
||||
@@ -35001,6 +35001,31 @@
|
||||
"type" : "string",
|
||||
"typetext" : "<string>"
|
||||
},
|
||||
+ "freenas_user" : {
|
||||
+ "description" : "FreeNAS user for API access",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
+ },
|
||||
+ "freenas_password" : {
|
||||
+ "description" : "FreeNAS password for API access",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
+ },
|
||||
+ "freenas_use_ssl" : {
|
||||
+ "description" : "FreeNAS API access via SSL",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "boolean",
|
||||
+ "typetext" : "<boolean>"
|
||||
+ },
|
||||
+ "freenas_apiv4_host" : {
|
||||
+ "description" : "FreeNAS API Host via IPv4",
|
||||
+ "format" : "address",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
+ },
|
||||
"fuse" : {
|
||||
"description" : "Mount CephFS through FUSE.",
|
||||
"optional" : 1,
|
||||
@@ -35157,6 +35182,12 @@
|
||||
"type" : "boolean",
|
||||
"typetext" : "<boolean>"
|
||||
},
|
||||
+ "target" : {
|
||||
+ "description" : "iSCSI target.",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
+ },
|
||||
"transport" : {
|
||||
"description" : "Gluster transport: tcp or rdma",
|
||||
"enum" : [
|
||||
@@ -35362,6 +35393,31 @@
|
||||
"optional" : 1,
|
||||
"type" : "string",
|
||||
"typetext" : "<string>"
|
||||
+ },
|
||||
+ "freenas_user" : {
|
||||
+ "description" : "FreeNAS user for API access",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
+ },
|
||||
+ "freenas_password" : {
|
||||
+ "description" : "FreeNAS password for API access",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
+ },
|
||||
+ "freenas_use_ssl" : {
|
||||
+ "description" : "FreeNAS API access via SSL",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "boolean",
|
||||
+ "typetext" : "<boolean>"
|
||||
+ },
|
||||
+ "freenas_apiv4_host" : {
|
||||
+ "description" : "FreeNAS API Host via IPv4",
|
||||
+ "format" : "address",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
},
|
||||
"fuse" : {
|
||||
"description" : "Mount CephFS through FUSE.",
|
||||
|
|
@ -1,79 +0,0 @@
|
|||
--- apidoc.js.orig 2021-11-15 10:07:34.000000000 -0500
|
||||
+++ apidoc.js 2021-12-06 08:04:01.648822707 -0500
|
||||
@@ -44064,6 +44064,31 @@
|
||||
"type" : "string",
|
||||
"typetext" : "<string>"
|
||||
},
|
||||
+ "freenas_user" : {
|
||||
+ "description" : "FreeNAS user for API access",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
+ },
|
||||
+ "freenas_password" : {
|
||||
+ "description" : "FreeNAS password for API access",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
+ },
|
||||
+ "freenas_use_ssl" : {
|
||||
+ "description" : "FreeNAS API access via SSL",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "boolean",
|
||||
+ "typetext" : "<boolean>"
|
||||
+ },
|
||||
+ "freenas_apiv4_host" : {
|
||||
+ "description" : "FreeNAS API Host via IPv4",
|
||||
+ "format" : "address",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
+ },
|
||||
"fuse" : {
|
||||
"description" : "Mount CephFS through FUSE.",
|
||||
"optional" : 1,
|
||||
@@ -44275,6 +44300,12 @@
|
||||
"type" : "boolean",
|
||||
"typetext" : "<boolean>"
|
||||
},
|
||||
+ "target" : {
|
||||
+ "description" : "iSCSI target.",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
+ },
|
||||
"transport" : {
|
||||
"description" : "Gluster transport: tcp or rdma",
|
||||
"enum" : [
|
||||
@@ -44547,6 +44578,31 @@
|
||||
"optional" : 1,
|
||||
"type" : "string",
|
||||
"typetext" : "<string>"
|
||||
+ },
|
||||
+ "freenas_user" : {
|
||||
+ "description" : "FreeNAS user for API access",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
+ },
|
||||
+ "freenas_password" : {
|
||||
+ "description" : "FreeNAS password for API access",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
+ },
|
||||
+ "freenas_use_ssl" : {
|
||||
+ "description" : "FreeNAS API access via SSL",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "boolean",
|
||||
+ "typetext" : "<boolean>"
|
||||
+ },
|
||||
+ "freenas_apiv4_host" : {
|
||||
+ "description" : "FreeNAS API Host via IPv4",
|
||||
+ "format" : "address",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
},
|
||||
"fuse" : {
|
||||
"description" : "Mount CephFS through FUSE.",
|
||||
|
|
@ -1,91 +0,0 @@
|
|||
--- apidoc.js.orig 2024-01-06 13:02:06.730512378 -0500
|
||||
+++ apidoc.js 2024-01-06 13:02:55.349787105 -0500
|
||||
@@ -50336,6 +50336,37 @@
|
||||
"type" : "string",
|
||||
"typetext" : "<string>"
|
||||
},
|
||||
+ "freenas_user" : {
|
||||
+ "description" : "FreeNAS user for API access",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
+ },
|
||||
+ "freenas_password" : {
|
||||
+ "description" : "FreeNAS password for API access (Deprecated)",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
+ },
|
||||
+ "truenas_secret" : {
|
||||
+ "description" : "TrueNAS Secret for API access",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
+ },
|
||||
+ "freenas_use_ssl" : {
|
||||
+ "description" : "FreeNAS API access via SSL",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "boolean",
|
||||
+ "typetext" : "<boolean>"
|
||||
+ },
|
||||
+ "freenas_apiv4_host" : {
|
||||
+ "description" : "FreeNAS API Host via IPv4",
|
||||
+ "format" : "address",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
+ },
|
||||
"fuse" : {
|
||||
"description" : "Mount CephFS through FUSE.",
|
||||
"optional" : 1,
|
||||
@@ -50555,6 +50586,12 @@
|
||||
"type" : "boolean",
|
||||
"typetext" : "<boolean>"
|
||||
},
|
||||
+ "target" : {
|
||||
+ "description" : "iSCSI target.",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
+ },
|
||||
"transport" : {
|
||||
"description" : "Gluster transport: tcp or rdma",
|
||||
"enum" : [
|
||||
@@ -50854,6 +50891,37 @@
|
||||
"optional" : 1,
|
||||
"type" : "string",
|
||||
"typetext" : "<string>"
|
||||
+ },
|
||||
+ "freenas_user" : {
|
||||
+ "description" : "FreeNAS user for API access",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
+ },
|
||||
+ "freenas_password" : {
|
||||
+ "description" : "FreeNAS password for API access (Deprecated)",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
+ },
|
||||
+ "truenas_secret" : {
|
||||
+ "description" : "TrueNAS secret for API access",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
+ },
|
||||
+ "freenas_use_ssl" : {
|
||||
+ "description" : "FreeNAS API access via SSL",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "boolean",
|
||||
+ "typetext" : "<boolean>"
|
||||
+ },
|
||||
+ "freenas_apiv4_host" : {
|
||||
+ "description" : "FreeNAS API Host via IPv4",
|
||||
+ "format" : "address",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
},
|
||||
"fuse" : {
|
||||
"description" : "Mount CephFS through FUSE.",
|
||||
|
|
@ -1,91 +0,0 @@
|
|||
--- apidoc.js.orig 2023-08-16 06:03:55.000000000 -0400
|
||||
+++ apidoc.js 2023-12-26 14:45:47.202566775 -0500
|
||||
@@ -47579,6 +47579,37 @@
|
||||
"type" : "string",
|
||||
"typetext" : "<string>"
|
||||
},
|
||||
+ "freenas_user" : {
|
||||
+ "description" : "FreeNAS user for API access",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
+ },
|
||||
+ "freenas_password" : {
|
||||
+ "description" : "FreeNAS password for API access (Deprecated)",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
+ },
|
||||
+ "truenas_secret" : {
|
||||
+ "description" : "TrueNAS Secret for API access",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
+ },
|
||||
+ "freenas_use_ssl" : {
|
||||
+ "description" : "FreeNAS API access via SSL",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "boolean",
|
||||
+ "typetext" : "<boolean>"
|
||||
+ },
|
||||
+ "freenas_apiv4_host" : {
|
||||
+ "description" : "FreeNAS API Host via IPv4",
|
||||
+ "format" : "address",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
+ },
|
||||
"fuse" : {
|
||||
"description" : "Mount CephFS through FUSE.",
|
||||
"optional" : 1,
|
||||
@@ -47798,6 +47829,12 @@
|
||||
"type" : "boolean",
|
||||
"typetext" : "<boolean>"
|
||||
},
|
||||
+ "target" : {
|
||||
+ "description" : "iSCSI target.",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
+ },
|
||||
"transport" : {
|
||||
"description" : "Gluster transport: tcp or rdma",
|
||||
"enum" : [
|
||||
@@ -48097,6 +48134,37 @@
|
||||
"optional" : 1,
|
||||
"type" : "string",
|
||||
"typetext" : "<string>"
|
||||
+ },
|
||||
+ "freenas_user" : {
|
||||
+ "description" : "FreeNAS user for API access",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
+ },
|
||||
+ "freenas_password" : {
|
||||
+ "description" : "FreeNAS password for API access (Deprecated)",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
+ },
|
||||
+ "truenas_secret" : {
|
||||
+ "description" : "TrueNAS secret for API access",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
+ },
|
||||
+ "freenas_use_ssl" : {
|
||||
+ "description" : "FreeNAS API access via SSL",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "boolean",
|
||||
+ "typetext" : "<boolean>"
|
||||
+ },
|
||||
+ "freenas_apiv4_host" : {
|
||||
+ "description" : "FreeNAS API Host via IPv4",
|
||||
+ "format" : "address",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
},
|
||||
"fuse" : {
|
||||
"description" : "Mount CephFS through FUSE.",
|
||||
|
|
@ -1 +0,0 @@
|
|||
/pvemanagerlib.js
|
||||
|
|
@ -1,167 +0,0 @@
|
|||
--- pvemanagerlib-5.2-10.js 2018-10-23 07:18:18.058603386 -0400
|
||||
+++ pvemanagerlib.js 2018-10-23 15:29:52.573563883 -0400
|
||||
@@ -5597,6 +5597,7 @@
|
||||
alias: ['widget.pveiScsiProviderSelector'],
|
||||
comboItems: [
|
||||
['comstar', 'Comstar'],
|
||||
+ ['freenas', 'FreeNAS-API'],
|
||||
[ 'istgt', 'istgt'],
|
||||
[ 'iet', 'IET'],
|
||||
[ 'LIO', 'LIO']
|
||||
@@ -28583,6 +28584,7 @@
|
||||
data: {
|
||||
isLIO: false,
|
||||
isComstar: true,
|
||||
+ isFreeNAS: false,
|
||||
hasWriteCacheOption: true
|
||||
}
|
||||
},
|
||||
@@ -28595,10 +28597,19 @@
|
||||
}
|
||||
},
|
||||
changeISCSIProvider: function(f, newVal, oldVal) {
|
||||
+ var me = this;
|
||||
var vm = this.getViewModel();
|
||||
vm.set('isLIO', newVal === 'LIO');
|
||||
vm.set('isComstar', newVal === 'comstar');
|
||||
- vm.set('hasWriteCacheOption', newVal === 'comstar' || newVal === 'istgt');
|
||||
+ vm.set('isFreeNAS', newVal === 'freenas');
|
||||
+ vm.set('hasWriteCacheOption', newVal === 'comstar' || newVal === 'freenas' || newVal === 'istgt');
|
||||
+ if (newVal !== 'freenas') {
|
||||
+ me.lookupReference('freenas_use_ssl_field').setValue(false);
|
||||
+ me.lookupReference('freenas_apiv4_host_field').setValue('');
|
||||
+ me.lookupReference('freenas_user_field').setValue('');
|
||||
+ me.lookupReference('freenas_password_field').setValue('');
|
||||
+ me.lookupReference('freenas_confirmpw_field').setValue('');
|
||||
+ }
|
||||
}
|
||||
},
|
||||
|
||||
@@ -28616,6 +28627,7 @@
|
||||
},
|
||||
|
||||
setValues: function diff(values) {
|
||||
+ values.freenas_confirmpw = values.freenas_password;
|
||||
values.writecache = values.nowritecache ? 0 : 1;
|
||||
this.callParent([values]);
|
||||
},
|
||||
@@ -28632,7 +28644,7 @@
|
||||
allowBlank: false
|
||||
},
|
||||
{
|
||||
- xtype: me.isCreate ? 'textfield' : 'displayfield',
|
||||
+ xtype: 'textfield',
|
||||
name: 'pool',
|
||||
value: '',
|
||||
fieldLabel: gettext('Pool'),
|
||||
@@ -28646,7 +28658,7 @@
|
||||
allowBlank: false
|
||||
},
|
||||
{
|
||||
- xtype: me.isCreate ? 'textfield' : 'displayfield',
|
||||
+ xtype: 'textfield',
|
||||
name: 'target',
|
||||
value: '',
|
||||
fieldLabel: gettext('Target'),
|
||||
@@ -28657,8 +28669,28 @@
|
||||
name: 'comstar_tg',
|
||||
value: '',
|
||||
fieldLabel: gettext('Target group'),
|
||||
- bind: me.isCreate ? { disabled: '{!isComstar}' } : { hidden: '{!isComstar}' },
|
||||
+ bind: { hidden: '{!isComstar}' },
|
||||
allowBlank: true
|
||||
+ },
|
||||
+ {
|
||||
+ xtype: 'proxmoxcheckbox',
|
||||
+ name: 'freenas_use_ssl',
|
||||
+ reference: 'freenas_use_ssl_field',
|
||||
+ inputId: 'freenas_use_ssl_field',
|
||||
+ checked: false,
|
||||
+ bind: { hidden: '{!isFreeNAS}' },
|
||||
+ uncheckedValue: 0,
|
||||
+ fieldLabel: gettext('API use SSL')
|
||||
+ },
|
||||
+ {
|
||||
+ xtype: 'textfield',
|
||||
+ name: 'freenas_user',
|
||||
+ reference: 'freenas_user_field',
|
||||
+ inputId: 'freenas_user_field',
|
||||
+ value: '',
|
||||
+ allowBlank: false,
|
||||
+ fieldLabel: gettext('API Username'),
|
||||
+ bind: { hidden: '{!isFreeNAS}' },
|
||||
}
|
||||
];
|
||||
|
||||
@@ -28689,7 +28721,7 @@
|
||||
xtype: me.isCreate ? 'textfield' : 'displayfield',
|
||||
name: 'comstar_hg',
|
||||
value: '',
|
||||
- bind: me.isCreate ? { disabled: '{!isComstar}' } : { hidden: '{!isComstar}' },
|
||||
+ bind: { hidden: '{!isComstar}' },
|
||||
fieldLabel: gettext('Host group'),
|
||||
allowBlank: true
|
||||
},
|
||||
@@ -28697,9 +28729,60 @@
|
||||
xtype: me.isCreate ? 'textfield' : 'displayfield',
|
||||
name: 'lio_tpg',
|
||||
value: '',
|
||||
- bind: me.isCreate ? { disabled: '{!isLIO}' } : { hidden: '{!isLIO}' },
|
||||
+ bind: { hidden: '{!isLIO}' },
|
||||
+ fieldLabel: gettext('Target portal group'),
|
||||
+ allowBlank: true
|
||||
+ },
|
||||
+ {
|
||||
+ xtype: 'proxmoxtextfield',
|
||||
+ name: 'freenas_apiv4_host',
|
||||
+ reference: 'freenas_apiv4_host_field',
|
||||
+ value: '',
|
||||
+ editable: true,
|
||||
+ allowBlank: true,
|
||||
+ emptyText: Proxmox.Utils.noneText,
|
||||
+ bind: { hidden: '{!isFreeNAS}' },
|
||||
+ fieldLabel: gettext('API IPv4 Host'),
|
||||
+ allowBlank: true
|
||||
+ },
|
||||
+ {
|
||||
+ xtype: 'proxmoxtextfield',
|
||||
+ name: 'freenas_password',
|
||||
+ reference: 'freenas_password_field',
|
||||
+ inputType: me.isCreate ? '' : 'password',
|
||||
+ value: '',
|
||||
+ editable: true,
|
||||
+ deleteEmpty: true,
|
||||
+ allowBlank: false,
|
||||
+ emptyText: Proxmox.Utils.noneText,
|
||||
+ bind: { hidden: '{!isFreeNAS}' },
|
||||
+ fieldLabel: gettext('API Password'),
|
||||
+ change: function(f, value) {
|
||||
+ if (f.rendered) {
|
||||
+ f.up().down('field[name=freenas_confirmpw]').validate();
|
||||
+ }
|
||||
+ }
|
||||
+ },
|
||||
+ {
|
||||
+ xtype: 'proxmoxtextfield',
|
||||
+ name: 'freenas_confirmpw',
|
||||
+ reference: 'freenas_confirmpw_field',
|
||||
+ inputType: me.isCreate ? '' : 'password',
|
||||
+ value: '',
|
||||
+ editable: true,
|
||||
+ deleteEmpty: true,
|
||||
allowBlank: false,
|
||||
- fieldLabel: gettext('Target portal group')
|
||||
+ submitValue: false,
|
||||
+ emptyText: Proxmox.Utils.noneText,
|
||||
+ bind: { hidden: '{!isFreeNAS}' },
|
||||
+ fieldLabel: gettext('Confirm Password'),
|
||||
+ validator: function(value) {
|
||||
+ var pw = this.up().down('field[name=freenas_password]').getValue();
|
||||
+ if (pw !== value) {
|
||||
+ return "Passwords do not match!";
|
||||
+ }
|
||||
+ return true;
|
||||
+ }
|
||||
}
|
||||
];
|
||||
|
||||
|
|
@ -1,86 +0,0 @@
|
|||
--- pvemanagerlib.js.orig 2018-07-30 23:13:28.045035059 -0400
|
||||
+++ pvemanagerlib.js.new 2018-08-19 10:42:58.494724196 -0400
|
||||
@@ -5379,6 +5379,7 @@
|
||||
alias: ['widget.pveiScsiProviderSelector'],
|
||||
comboItems: [
|
||||
['comstar', 'Comstar'],
|
||||
+ ['freenas', 'FreeNAS-API'],
|
||||
[ 'istgt', 'istgt'],
|
||||
[ 'iet', 'IET']
|
||||
]
|
||||
@@ -27361,7 +27362,23 @@
|
||||
value: '',
|
||||
fieldLabel: gettext('Target group'),
|
||||
allowBlank: true
|
||||
- }
|
||||
+ },
|
||||
+ {
|
||||
+ xtype: 'proxmoxcheckbox',
|
||||
+ name: 'freenas_use_ssl',
|
||||
+ checked: false,
|
||||
+ uncheckedValue: 0,
|
||||
+ hidden: me.iscsiprovider !== "freenas",
|
||||
+ fieldLabel: gettext('FreeNAS API use SSL')
|
||||
+ },
|
||||
+ {
|
||||
+ xtype: me.isCreate ? 'textfield' : 'displayfield',
|
||||
+ name: 'freenas_user',
|
||||
+ value: '',
|
||||
+ hidden: me.iscsiprovider !== "freenas",
|
||||
+ fieldLabel: gettext('FreeNAS User'),
|
||||
+ allowBlank: true
|
||||
+ },
|
||||
];
|
||||
|
||||
me.column2 = [
|
||||
@@ -27370,7 +27387,26 @@
|
||||
name: 'iscsiprovider',
|
||||
value: 'comstar',
|
||||
fieldLabel: gettext('iSCSI Provider'),
|
||||
- allowBlank: false
|
||||
+ allowBlank: false,
|
||||
+ listeners: {
|
||||
+ change: function(f, value) {
|
||||
+ if (value === "freenas") {
|
||||
+ me.down('field[name=freenas_use_ssl]').setHidden(false);
|
||||
+ me.down('field[name=freenas_apiv4_host]').setHidden(false);
|
||||
+ me.down('field[name=freenas_user]').setHidden(false);
|
||||
+ me.down('field[name=freenas_password]').setHidden(false);
|
||||
+ } else {
|
||||
+ me.down('field[name=freenas_use_ssl]').setHidden(true);
|
||||
+ me.down('field[name=freenas_use_ssl]').setValue(false);
|
||||
+ me.down('field[name=freenas_apiv4_host]').setHidden(true);
|
||||
+ me.down('field[name=freenas_apiv4_host]').setValue(false);
|
||||
+ me.down('field[name=freenas_user]').setHidden(true);
|
||||
+ me.down('field[name=freenas_user]').setValue('');
|
||||
+ me.down('field[name=freenas_password]').setHidden(true);
|
||||
+ me.down('field[name=freenas_password]').setValue('');
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
},
|
||||
{
|
||||
xtype: 'proxmoxcheckbox',
|
||||
@@ -27392,6 +27428,22 @@
|
||||
value: '',
|
||||
fieldLabel: gettext('Host group'),
|
||||
allowBlank: true
|
||||
+ },
|
||||
+ {
|
||||
+ xtype: me.isCreate ? 'textfield' : 'displayfield',
|
||||
+ name: 'freenas_apiv4_host',
|
||||
+ value: '',
|
||||
+ hidden: me.iscsiprovider !== "freenas",
|
||||
+ fieldLabel: gettext('API IPv4 Host'),
|
||||
+ allowBlank: true
|
||||
+ },
|
||||
+ {
|
||||
+ xtype: me.isCreate ? 'textfield' : 'displayfield',
|
||||
+ name: 'freenas_password',
|
||||
+ value: '',
|
||||
+ hidden: me.iscsiprovider !== "freenas",
|
||||
+ fieldLabel: gettext('FreeNAS Password'),
|
||||
+ allowBlank: true
|
||||
}
|
||||
];
|
||||
|
||||
|
|
@ -1,102 +0,0 @@
|
|||
--- pvemanagerlib.js.5.2-8.js 2018-08-30 20:10:52.957313992 -0400
|
||||
+++ pvemanagerlib.js.new 2018-09-01 16:20:05.131908768 -0400
|
||||
@@ -28578,6 +28578,7 @@
|
||||
data: {
|
||||
isLIO: false,
|
||||
isComstar: true,
|
||||
+ isFreeNAS: false,
|
||||
hasWriteCacheOption: true
|
||||
}
|
||||
},
|
||||
@@ -28590,11 +28591,18 @@
|
||||
}
|
||||
},
|
||||
changeISCSIProvider: function(f, newVal, oldVal) {
|
||||
+ var me = this;
|
||||
var vm = this.getViewModel();
|
||||
vm.set('isLIO', newVal === 'LIO');
|
||||
vm.set('isComstar', newVal === 'comstar');
|
||||
vm.set('isFreeNAS', newVal === 'freenas');
|
||||
- vm.set('hasWriteCacheOption', newVal === 'comstar' || newVal === 'istgt');
|
||||
+ vm.set('hasWriteCacheOption', newVal === 'comstar' || newVal === 'freenas' || newVal === 'istgt');
|
||||
+ if (newVal !== 'freenas') {
|
||||
+ me.lookupReference('freenas_use_ssl_field').setValue(false);
|
||||
+ me.lookupReference('freenas_apiv4_host_field').setValue('');
|
||||
+ me.lookupReference('freenas_user_field').setValue('');
|
||||
+ me.lookupReference('freenas_password_field').setValue('');
|
||||
+ }
|
||||
}
|
||||
},
|
||||
|
||||
@@ -28653,16 +28661,28 @@
|
||||
name: 'comstar_tg',
|
||||
value: '',
|
||||
fieldLabel: gettext('Target group'),
|
||||
- bind: me.isCreate ? { disabled: '{!isComstar}' } : { hidden: '{!isComstar}' },
|
||||
+ bind: me.isCreate ? { hidden: '{!isComstar}' } : { hidden: '{!isComstar}' },
|
||||
allowBlank: true
|
||||
},
|
||||
{
|
||||
+ xtype: 'proxmoxcheckbox',
|
||||
+ name: 'freenas_use_ssl',
|
||||
+ reference: 'freenas_use_ssl_field',
|
||||
+ inputId: 'freenas_use_ssl_field',
|
||||
+ checked: false,
|
||||
+ bind: me.isCreate ? { hidden: '{!isFreeNAS}' } : { hidden: '{!isFreeNAS}' },
|
||||
+ uncheckedValue: 0,
|
||||
+ fieldLabel: gettext('API use SSL')
|
||||
+ },
|
||||
+ {
|
||||
xtype: me.isCreate ? 'textfield' : 'displayfield',
|
||||
name: 'freenas_user',
|
||||
+ reference: 'freenas_user_field',
|
||||
+ inputId: 'freenas_user_field',
|
||||
value: '',
|
||||
- fieldLabel: gettext('FreeNAS User'),
|
||||
+ fieldLabel: gettext('API Username'),
|
||||
bind: me.isCreate ? { hidden: '{!isFreeNAS}' } : { hidden: '{!isFreeNAS}' },
|
||||
- allowBlank: true
|
||||
+ allowBlank: me.isFreeNAS ? false : true
|
||||
}
|
||||
];
|
||||
|
||||
@@ -28693,7 +28713,7 @@
|
||||
xtype: me.isCreate ? 'textfield' : 'displayfield',
|
||||
name: 'comstar_hg',
|
||||
value: '',
|
||||
- bind: me.isCreate ? { disabled: '{!isComstar}' } : { hidden: '{!isComstar}' },
|
||||
+ bind: me.isCreate ? { hidden: '{!isComstar}' } : { hidden: '{!isComstar}' },
|
||||
fieldLabel: gettext('Host group'),
|
||||
allowBlank: true
|
||||
},
|
||||
@@ -28701,9 +28721,27 @@
|
||||
xtype: me.isCreate ? 'textfield' : 'displayfield',
|
||||
name: 'lio_tpg',
|
||||
value: '',
|
||||
- bind: me.isCreate ? { disabled: '{!isLIO}' } : { hidden: '{!isLIO}' },
|
||||
- allowBlank: false,
|
||||
- fieldLabel: gettext('Target portal group')
|
||||
+ bind: me.isCreate ? { hidden: '{!isLIO}' } : { hidden: '{!isLIO}' },
|
||||
+ fieldLabel: gettext('Target portal group'),
|
||||
+ allowBlank: true
|
||||
+ },
|
||||
+ {
|
||||
+ xtype: me.isCreate ? 'textfield' : 'displayfield',
|
||||
+ name: 'freenas_apiv4_host',
|
||||
+ reference: 'freenas_apiv4_host_field',
|
||||
+ value: '',
|
||||
+ bind: me.isCreate ? { hidden: '{!isFreeNAS}' } : { hidden: '{!isFreeNAS}' },
|
||||
+ fieldLabel: gettext('API IPv4 Host'),
|
||||
+ allowBlank: true
|
||||
+ },
|
||||
+ {
|
||||
+ xtype: me.isCreate ? 'textfield' : 'displayfield',
|
||||
+ name: 'freenas_password',
|
||||
+ reference: 'freenas_password_field',
|
||||
+ value: '',
|
||||
+ bind: me.isCreate ? { hidden: '{!isFreeNAS}' } : { hidden: '{!isFreeNAS}' },
|
||||
+ fieldLabel: gettext('API Password'),
|
||||
+ allowBlank: true
|
||||
}
|
||||
];
|
||||
|
||||
|
|
@ -1,169 +0,0 @@
|
|||
--- pvemanagerlib-5.2-9.js 2018-10-16 07:17:21.915593478 -0400
|
||||
+++ pvemanagerlib.js 2018-10-21 21:52:20.781740237 -0400
|
||||
@@ -5597,6 +5597,7 @@
|
||||
alias: ['widget.pveiScsiProviderSelector'],
|
||||
comboItems: [
|
||||
['comstar', 'Comstar'],
|
||||
+ ['freenas', 'FreeNAS-API'],
|
||||
[ 'istgt', 'istgt'],
|
||||
[ 'iet', 'IET'],
|
||||
[ 'LIO', 'LIO']
|
||||
@@ -28574,6 +28575,7 @@
|
||||
data: {
|
||||
isLIO: false,
|
||||
isComstar: true,
|
||||
+ isFreeNAS: false,
|
||||
hasWriteCacheOption: true
|
||||
}
|
||||
},
|
||||
@@ -28586,10 +28588,19 @@
|
||||
}
|
||||
},
|
||||
changeISCSIProvider: function(f, newVal, oldVal) {
|
||||
+ var me = this;
|
||||
var vm = this.getViewModel();
|
||||
vm.set('isLIO', newVal === 'LIO');
|
||||
vm.set('isComstar', newVal === 'comstar');
|
||||
- vm.set('hasWriteCacheOption', newVal === 'comstar' || newVal === 'istgt');
|
||||
+ vm.set('isFreeNAS', newVal === 'freenas');
|
||||
+ vm.set('hasWriteCacheOption', newVal === 'comstar' || newVal === 'freenas' || newVal === 'istgt');
|
||||
+ if (newVal !== 'freenas') {
|
||||
+ me.lookupReference('freenas_use_ssl_field').setValue(false);
|
||||
+ me.lookupReference('freenas_apiv4_host_field').setValue('');
|
||||
+ me.lookupReference('freenas_user_field').setValue('');
|
||||
+ me.lookupReference('freenas_password_field').setValue('');
|
||||
+ me.lookupReference('freenas_confirmpw_field').setValue('');
|
||||
+ }
|
||||
}
|
||||
},
|
||||
|
||||
@@ -28607,6 +28618,7 @@
|
||||
},
|
||||
|
||||
setValues: function diff(values) {
|
||||
+ values.freenas_confirmpw = values.freenas_password;
|
||||
values.writecache = values.nowritecache ? 0 : 1;
|
||||
this.callParent([values]);
|
||||
},
|
||||
@@ -28623,7 +28635,7 @@
|
||||
allowBlank: false
|
||||
},
|
||||
{
|
||||
- xtype: me.isCreate ? 'textfield' : 'displayfield',
|
||||
+ xtype: 'textfield',
|
||||
name: 'pool',
|
||||
value: '',
|
||||
fieldLabel: gettext('Pool'),
|
||||
@@ -28637,7 +28649,7 @@
|
||||
allowBlank: false
|
||||
},
|
||||
{
|
||||
- xtype: me.isCreate ? 'textfield' : 'displayfield',
|
||||
+ xtype: 'textfield',
|
||||
name: 'target',
|
||||
value: '',
|
||||
fieldLabel: gettext('Target'),
|
||||
@@ -28648,8 +28660,28 @@
|
||||
name: 'comstar_tg',
|
||||
value: '',
|
||||
fieldLabel: gettext('Target group'),
|
||||
- bind: me.isCreate ? { disabled: '{!isComstar}' } : { hidden: '{!isComstar}' },
|
||||
+ bind: { hidden: '{!isComstar}' },
|
||||
allowBlank: true
|
||||
+ },
|
||||
+ {
|
||||
+ xtype: 'proxmoxcheckbox',
|
||||
+ name: 'freenas_use_ssl',
|
||||
+ reference: 'freenas_use_ssl_field',
|
||||
+ inputId: 'freenas_use_ssl_field',
|
||||
+ checked: false,
|
||||
+ bind: { hidden: '{!isFreeNAS}' },
|
||||
+ uncheckedValue: 0,
|
||||
+ fieldLabel: gettext('API use SSL')
|
||||
+ },
|
||||
+ {
|
||||
+ xtype: 'textfield',
|
||||
+ name: 'freenas_user',
|
||||
+ reference: 'freenas_user_field',
|
||||
+ inputId: 'freenas_user_field',
|
||||
+ value: '',
|
||||
+ allowBlank: false,
|
||||
+ fieldLabel: gettext('API Username'),
|
||||
+ bind: { hidden: '{!isFreeNAS}' },
|
||||
}
|
||||
];
|
||||
|
||||
@@ -28680,7 +28712,7 @@
|
||||
xtype: me.isCreate ? 'textfield' : 'displayfield',
|
||||
name: 'comstar_hg',
|
||||
value: '',
|
||||
- bind: me.isCreate ? { disabled: '{!isComstar}' } : { hidden: '{!isComstar}' },
|
||||
+ bind: { hidden: '{!isComstar}' },
|
||||
fieldLabel: gettext('Host group'),
|
||||
allowBlank: true
|
||||
},
|
||||
@@ -28688,9 +28720,61 @@
|
||||
xtype: me.isCreate ? 'textfield' : 'displayfield',
|
||||
name: 'lio_tpg',
|
||||
value: '',
|
||||
- bind: me.isCreate ? { disabled: '{!isLIO}' } : { hidden: '{!isLIO}' },
|
||||
- allowBlank: false,
|
||||
- fieldLabel: gettext('Target portal group')
|
||||
+ bind: { hidden: '{!isLIO}' },
|
||||
+ fieldLabel: gettext('Target portal group'),
|
||||
+ allowBlank: true
|
||||
+ },
|
||||
+ {
|
||||
+ xtype: 'proxmoxtextfield',
|
||||
+ name: 'freenas_apiv4_host',
|
||||
+ reference: 'freenas_apiv4_host_field',
|
||||
+ value: '',
|
||||
+ editable: true,
|
||||
+ deleteEmpty: true,
|
||||
+ allowBlank: true,
|
||||
+ emptyText: Proxmox.Utils.noneText,
|
||||
+ bind: { hidden: '{!isFreeNAS}' },
|
||||
+ fieldLabel: gettext('API IPv4 Host'),
|
||||
+ allowBlank: true
|
||||
+ },
|
||||
+ {
|
||||
+ xtype: 'proxmoxtextfield',
|
||||
+ name: 'freenas_password',
|
||||
+ reference: 'freenas_password_field',
|
||||
+ inputType: me.isCreate ? '' : 'password',
|
||||
+ value: '',
|
||||
+ editable: true,
|
||||
+ deleteEmpty: true,
|
||||
+ allowBlank: false,
|
||||
+ emptyText: Proxmox.Utils.noneText,
|
||||
+ bind: { hidden: '{!isFreeNAS}' },
|
||||
+ fieldLabel: gettext('API Password'),
|
||||
+ change: function(f, value) {
|
||||
+ if (f.rendered) {
|
||||
+ f.up().down('field[name=freenas_confirmpw]').validate();
|
||||
+ }
|
||||
+ }
|
||||
+ },
|
||||
+ {
|
||||
+ xtype: 'proxmoxtextfield',
|
||||
+ name: 'freenas_confirmpw',
|
||||
+ reference: 'freenas_confirmpw_field',
|
||||
+ inputType: me.isCreate ? '' : 'password',
|
||||
+ value: '',
|
||||
+ editable: true,
|
||||
+ deleteEmpty: true,
|
||||
+ allowBlank: false,
|
||||
+ submitValue: false,
|
||||
+ emptyText: Proxmox.Utils.noneText,
|
||||
+ bind: { hidden: '{!isFreeNAS}' },
|
||||
+ fieldLabel: gettext('Confirm Password'),
|
||||
+ validator: function(value) {
|
||||
+ var pw = this.up().down('field[name=freenas_password]').getValue();
|
||||
+ if (pw !== value) {
|
||||
+ return "Passwords do not match!";
|
||||
+ }
|
||||
+ return true;
|
||||
+ }
|
||||
}
|
||||
];
|
||||
|
||||
|
|
@ -1,189 +0,0 @@
|
|||
--- pvemanagerlib.js.orig 2019-02-20 13:40:43.000000000 -0500
|
||||
+++ pvemanagerlib.js 2019-02-26 20:49:41.032327478 -0500
|
||||
@@ -5920,6 +5920,7 @@
|
||||
alias: ['widget.pveiScsiProviderSelector'],
|
||||
comboItems: [
|
||||
['comstar', 'Comstar'],
|
||||
+ ['freenas', 'FreeNAS-API'],
|
||||
[ 'istgt', 'istgt'],
|
||||
[ 'iet', 'IET'],
|
||||
[ 'LIO', 'LIO']
|
||||
@@ -30182,6 +30183,7 @@
|
||||
data: {
|
||||
isLIO: false,
|
||||
isComstar: true,
|
||||
+ isFreeNAS: false,
|
||||
hasWriteCacheOption: true
|
||||
}
|
||||
},
|
||||
@@ -30194,10 +30196,26 @@
|
||||
}
|
||||
},
|
||||
changeISCSIProvider: function(f, newVal, oldVal) {
|
||||
+ var me = this;
|
||||
var vm = this.getViewModel();
|
||||
vm.set('isLIO', newVal === 'LIO');
|
||||
vm.set('isComstar', newVal === 'comstar');
|
||||
- vm.set('hasWriteCacheOption', newVal === 'comstar' || newVal === 'istgt');
|
||||
+ vm.set('isFreeNAS', newVal === 'freenas');
|
||||
+ vm.set('hasWriteCacheOption', newVal === 'comstar' || newVal === 'freenas' || newVal === 'istgt');
|
||||
+ if (newVal !== 'freenas') {
|
||||
+ me.lookupReference('freenas_use_ssl_field').setValue(false);
|
||||
+ me.lookupReference('freenas_apiv4_host_field').setValue('');
|
||||
+ me.lookupReference('freenas_user_field').setValue('');
|
||||
+ me.lookupReference('freenas_user_field').allowBlank = true;
|
||||
+ me.lookupReference('freenas_password_field').setValue('');
|
||||
+ me.lookupReference('freenas_password_field').allowBlank = true;
|
||||
+ me.lookupReference('freenas_confirmpw_field').setValue('');
|
||||
+ me.lookupReference('freenas_confirmpw_field').allowBlank = true;
|
||||
+ } else {
|
||||
+ me.lookupReference('freenas_user_field').allowBlank = false;
|
||||
+ me.lookupReference('freenas_password_field').allowBlank = false;
|
||||
+ me.lookupReference('freenas_confirmpw_field').allowBlank = false;
|
||||
+ }
|
||||
}
|
||||
},
|
||||
|
||||
@@ -30215,6 +30233,7 @@
|
||||
},
|
||||
|
||||
setValues: function diff(values) {
|
||||
+ values.freenas_confirmpw = values.freenas_password;
|
||||
values.writecache = values.nowritecache ? 0 : 1;
|
||||
this.callParent([values]);
|
||||
},
|
||||
@@ -30231,7 +30250,7 @@
|
||||
allowBlank: false
|
||||
},
|
||||
{
|
||||
- xtype: me.isCreate ? 'textfield' : 'displayfield',
|
||||
+ xtype: 'textfield',
|
||||
name: 'pool',
|
||||
value: '',
|
||||
fieldLabel: gettext('Pool'),
|
||||
@@ -30241,11 +30260,11 @@
|
||||
xtype: me.isCreate ? 'textfield' : 'displayfield',
|
||||
name: 'blocksize',
|
||||
value: '4k',
|
||||
- fieldLabel: gettext('Block Size'),
|
||||
+ fieldLabel: gettext('ZFS Block Size'),
|
||||
allowBlank: false
|
||||
},
|
||||
{
|
||||
- xtype: me.isCreate ? 'textfield' : 'displayfield',
|
||||
+ xtype: 'textfield',
|
||||
name: 'target',
|
||||
value: '',
|
||||
fieldLabel: gettext('Target'),
|
||||
@@ -30256,8 +30275,33 @@
|
||||
name: 'comstar_tg',
|
||||
value: '',
|
||||
fieldLabel: gettext('Target group'),
|
||||
- bind: me.isCreate ? { disabled: '{!isComstar}' } : { hidden: '{!isComstar}' },
|
||||
+ bind: {
|
||||
+ hidden: '{!isComstar}'
|
||||
+ },
|
||||
allowBlank: true
|
||||
+ },
|
||||
+ {
|
||||
+ xtype: 'proxmoxcheckbox',
|
||||
+ name: 'freenas_use_ssl',
|
||||
+ reference: 'freenas_use_ssl_field',
|
||||
+ inputId: 'freenas_use_ssl_field',
|
||||
+ checked: false,
|
||||
+ bind: {
|
||||
+ hidden: '{!isFreeNAS}'
|
||||
+ },
|
||||
+ uncheckedValue: 0,
|
||||
+ fieldLabel: gettext('API use SSL')
|
||||
+ },
|
||||
+ {
|
||||
+ xtype: 'textfield',
|
||||
+ name: 'freenas_user',
|
||||
+ reference: 'freenas_user_field',
|
||||
+ inputId: 'freenas_user_field',
|
||||
+ value: '',
|
||||
+ fieldLabel: gettext('API Username'),
|
||||
+ bind: {
|
||||
+ hidden: '{!isFreeNAS}'
|
||||
+ }
|
||||
}
|
||||
];
|
||||
|
||||
@@ -30288,7 +30332,9 @@
|
||||
xtype: me.isCreate ? 'textfield' : 'displayfield',
|
||||
name: 'comstar_hg',
|
||||
value: '',
|
||||
- bind: me.isCreate ? { disabled: '{!isComstar}' } : { hidden: '{!isComstar}' },
|
||||
+ bind: {
|
||||
+ hidden: '{!isComstar}'
|
||||
+ },
|
||||
fieldLabel: gettext('Host group'),
|
||||
allowBlank: true
|
||||
},
|
||||
@@ -30296,9 +30342,62 @@
|
||||
xtype: me.isCreate ? 'textfield' : 'displayfield',
|
||||
name: 'lio_tpg',
|
||||
value: '',
|
||||
- bind: me.isCreate ? { disabled: '{!isLIO}' } : { hidden: '{!isLIO}' },
|
||||
- allowBlank: false,
|
||||
- fieldLabel: gettext('Target portal group')
|
||||
+ bind: {
|
||||
+ hidden: '{!isLIO}'
|
||||
+ },
|
||||
+ fieldLabel: gettext('Target portal group'),
|
||||
+ allowBlank: true
|
||||
+ },
|
||||
+ {
|
||||
+ xtype: 'proxmoxtextfield',
|
||||
+ name: 'freenas_apiv4_host',
|
||||
+ reference: 'freenas_apiv4_host_field',
|
||||
+ value: '',
|
||||
+ editable: true,
|
||||
+ emptyText: Proxmox.Utils.noneText,
|
||||
+ bind: {
|
||||
+ hidden: '{!isFreeNAS}'
|
||||
+ },
|
||||
+ fieldLabel: gettext('API IPv4 Host'),
|
||||
+ },
|
||||
+ {
|
||||
+ xtype: 'proxmoxtextfield',
|
||||
+ name: 'freenas_password',
|
||||
+ reference: 'freenas_password_field',
|
||||
+ inputType: me.isCreate ? '' : 'password',
|
||||
+ value: '',
|
||||
+ editable: true,
|
||||
+ emptyText: Proxmox.Utils.noneText,
|
||||
+ bind: {
|
||||
+ hidden: '{!isFreeNAS}'
|
||||
+ },
|
||||
+ fieldLabel: gettext('API Password'),
|
||||
+ change: function(f, value) {
|
||||
+ if (f.rendered) {
|
||||
+ f.up().down('field[name=freenas_confirmpw]').validate();
|
||||
+ }
|
||||
+ }
|
||||
+ },
|
||||
+ {
|
||||
+ xtype: 'proxmoxtextfield',
|
||||
+ name: 'freenas_confirmpw',
|
||||
+ reference: 'freenas_confirmpw_field',
|
||||
+ inputType: me.isCreate ? '' : 'password',
|
||||
+ value: '',
|
||||
+ editable: true,
|
||||
+ submitValue: false,
|
||||
+ emptyText: Proxmox.Utils.noneText,
|
||||
+ bind: {
|
||||
+ hidden: '{!isFreeNAS}'
|
||||
+ },
|
||||
+ fieldLabel: gettext('Confirm Password'),
|
||||
+ validator: function(value) {
|
||||
+ var pw = this.up().down('field[name=freenas_password]').getValue();
|
||||
+ if (pw !== value) {
|
||||
+ return "Passwords do not match!";
|
||||
+ }
|
||||
+ return true;
|
||||
+ }
|
||||
}
|
||||
];
|
||||
|
||||
|
|
@ -1,172 +0,0 @@
|
|||
--- pvemanagerlib-5.3-5.js 2018-12-21 12:34:35.727962756 -0500
|
||||
+++ pvemanagerlib.js 2018-12-21 12:43:02.370462786 -0500
|
||||
@@ -5854,6 +5854,7 @@
|
||||
alias: ['widget.pveiScsiProviderSelector'],
|
||||
comboItems: [
|
||||
['comstar', 'Comstar'],
|
||||
+ ['freenas', 'FreeNAS-API'],
|
||||
[ 'istgt', 'istgt'],
|
||||
[ 'iet', 'IET'],
|
||||
[ 'LIO', 'LIO']
|
||||
@@ -29983,6 +29984,7 @@
|
||||
data: {
|
||||
isLIO: false,
|
||||
isComstar: true,
|
||||
+ isFreeNAS: false,
|
||||
hasWriteCacheOption: true
|
||||
}
|
||||
},
|
||||
@@ -29995,10 +29997,19 @@
|
||||
}
|
||||
},
|
||||
changeISCSIProvider: function(f, newVal, oldVal) {
|
||||
+ var me = this;
|
||||
var vm = this.getViewModel();
|
||||
vm.set('isLIO', newVal === 'LIO');
|
||||
vm.set('isComstar', newVal === 'comstar');
|
||||
- vm.set('hasWriteCacheOption', newVal === 'comstar' || newVal === 'istgt');
|
||||
+ vm.set('isFreeNAS', newVal === 'freenas');
|
||||
+ vm.set('hasWriteCacheOption', newVal === 'comstar' || newVal === 'freenas' || newVal === 'istgt');
|
||||
+ if (newVal !== 'freenas') {
|
||||
+ me.lookupReference('freenas_use_ssl_field').setValue(false);
|
||||
+ me.lookupReference('freenas_apiv4_host_field').setValue('');
|
||||
+ me.lookupReference('freenas_user_field').setValue('');
|
||||
+ me.lookupReference('freenas_password_field').setValue('');
|
||||
+ me.lookupReference('freenas_confirmpw_field').setValue('');
|
||||
+ }
|
||||
}
|
||||
},
|
||||
|
||||
@@ -30016,6 +30027,7 @@
|
||||
},
|
||||
|
||||
setValues: function diff(values) {
|
||||
+ values.freenas_confirmpw = values.freenas_password;
|
||||
values.writecache = values.nowritecache ? 0 : 1;
|
||||
this.callParent([values]);
|
||||
},
|
||||
@@ -30032,7 +30044,7 @@
|
||||
allowBlank: false
|
||||
},
|
||||
{
|
||||
- xtype: me.isCreate ? 'textfield' : 'displayfield',
|
||||
+ xtype: 'textfield',
|
||||
name: 'pool',
|
||||
value: '',
|
||||
fieldLabel: gettext('Pool'),
|
||||
@@ -30042,11 +30054,11 @@
|
||||
xtype: me.isCreate ? 'textfield' : 'displayfield',
|
||||
name: 'blocksize',
|
||||
value: '4k',
|
||||
- fieldLabel: gettext('Block Size'),
|
||||
+ fieldLabel: gettext('ZFS Block Size'),
|
||||
allowBlank: false
|
||||
},
|
||||
{
|
||||
- xtype: me.isCreate ? 'textfield' : 'displayfield',
|
||||
+ xtype: 'textfield',
|
||||
name: 'target',
|
||||
value: '',
|
||||
fieldLabel: gettext('Target'),
|
||||
@@ -30057,8 +30069,28 @@
|
||||
name: 'comstar_tg',
|
||||
value: '',
|
||||
fieldLabel: gettext('Target group'),
|
||||
- bind: me.isCreate ? { disabled: '{!isComstar}' } : { hidden: '{!isComstar}' },
|
||||
+ bind: { hidden: '{!isComstar}' },
|
||||
allowBlank: true
|
||||
+ },
|
||||
+ {
|
||||
+ xtype: 'proxmoxcheckbox',
|
||||
+ name: 'freenas_use_ssl',
|
||||
+ reference: 'freenas_use_ssl_field',
|
||||
+ inputId: 'freenas_use_ssl_field',
|
||||
+ checked: false,
|
||||
+ bind: { hidden: '{!isFreeNAS}' },
|
||||
+ uncheckedValue: 0,
|
||||
+ fieldLabel: gettext('API use SSL')
|
||||
+ },
|
||||
+ {
|
||||
+ xtype: 'textfield',
|
||||
+ name: 'freenas_user',
|
||||
+ reference: 'freenas_user_field',
|
||||
+ inputId: 'freenas_user_field',
|
||||
+ value: '',
|
||||
+ allowBlank: false,
|
||||
+ fieldLabel: gettext('API Username'),
|
||||
+ bind: { hidden: '{!isFreeNAS}' },
|
||||
}
|
||||
];
|
||||
|
||||
@@ -30089,7 +30121,7 @@
|
||||
xtype: me.isCreate ? 'textfield' : 'displayfield',
|
||||
name: 'comstar_hg',
|
||||
value: '',
|
||||
- bind: me.isCreate ? { disabled: '{!isComstar}' } : { hidden: '{!isComstar}' },
|
||||
+ bind: { hidden: '{!isComstar}' },
|
||||
fieldLabel: gettext('Host group'),
|
||||
allowBlank: true
|
||||
},
|
||||
@@ -30097,9 +30129,60 @@
|
||||
xtype: me.isCreate ? 'textfield' : 'displayfield',
|
||||
name: 'lio_tpg',
|
||||
value: '',
|
||||
- bind: me.isCreate ? { disabled: '{!isLIO}' } : { hidden: '{!isLIO}' },
|
||||
+ bind: { hidden: '{!isLIO}' },
|
||||
+ fieldLabel: gettext('Target portal group'),
|
||||
+ allowBlank: true
|
||||
+ },
|
||||
+ {
|
||||
+ xtype: 'proxmoxtextfield',
|
||||
+ name: 'freenas_apiv4_host',
|
||||
+ reference: 'freenas_apiv4_host_field',
|
||||
+ value: '',
|
||||
+ editable: true,
|
||||
+ allowBlank: true,
|
||||
+ emptyText: Proxmox.Utils.noneText,
|
||||
+ bind: { hidden: '{!isFreeNAS}' },
|
||||
+ fieldLabel: gettext('API IPv4 Host'),
|
||||
+ allowBlank: true
|
||||
+ },
|
||||
+ {
|
||||
+ xtype: 'proxmoxtextfield',
|
||||
+ name: 'freenas_password',
|
||||
+ reference: 'freenas_password_field',
|
||||
+ inputType: me.isCreate ? '' : 'password',
|
||||
+ value: '',
|
||||
+ editable: true,
|
||||
+ deleteEmpty: true,
|
||||
+ allowBlank: false,
|
||||
+ emptyText: Proxmox.Utils.noneText,
|
||||
+ bind: { hidden: '{!isFreeNAS}' },
|
||||
+ fieldLabel: gettext('API Password'),
|
||||
+ change: function(f, value) {
|
||||
+ if (f.rendered) {
|
||||
+ f.up().down('field[name=freenas_confirmpw]').validate();
|
||||
+ }
|
||||
+ }
|
||||
+ },
|
||||
+ {
|
||||
+ xtype: 'proxmoxtextfield',
|
||||
+ name: 'freenas_confirmpw',
|
||||
+ reference: 'freenas_confirmpw_field',
|
||||
+ inputType: me.isCreate ? '' : 'password',
|
||||
+ value: '',
|
||||
+ editable: true,
|
||||
+ deleteEmpty: true,
|
||||
allowBlank: false,
|
||||
- fieldLabel: gettext('Target portal group')
|
||||
+ submitValue: false,
|
||||
+ emptyText: Proxmox.Utils.noneText,
|
||||
+ bind: { hidden: '{!isFreeNAS}' },
|
||||
+ fieldLabel: gettext('Confirm Password'),
|
||||
+ validator: function(value) {
|
||||
+ var pw = this.up().down('field[name=freenas_password]').getValue();
|
||||
+ if (pw !== value) {
|
||||
+ return "Passwords do not match!";
|
||||
+ }
|
||||
+ return true;
|
||||
+ }
|
||||
}
|
||||
];
|
||||
|
||||
|
|
@ -1,191 +0,0 @@
|
|||
--- pvemanagerlib-5.3-6.js 2018-12-21 12:54:58.650421078 -0500
|
||||
+++ pvemanagerlib.js 2019-02-01 13:52:17.906074280 -0500
|
||||
@@ -5894,6 +5894,7 @@
|
||||
alias: ['widget.pveiScsiProviderSelector'],
|
||||
comboItems: [
|
||||
['comstar', 'Comstar'],
|
||||
+ ['freenas', 'FreeNAS-API'],
|
||||
[ 'istgt', 'istgt'],
|
||||
[ 'iet', 'IET'],
|
||||
[ 'LIO', 'LIO']
|
||||
@@ -30085,6 +30086,7 @@
|
||||
data: {
|
||||
isLIO: false,
|
||||
isComstar: true,
|
||||
+ isFreeNAS: false,
|
||||
hasWriteCacheOption: true
|
||||
}
|
||||
},
|
||||
@@ -30097,10 +30099,26 @@
|
||||
}
|
||||
},
|
||||
changeISCSIProvider: function(f, newVal, oldVal) {
|
||||
+ var me = this;
|
||||
var vm = this.getViewModel();
|
||||
vm.set('isLIO', newVal === 'LIO');
|
||||
vm.set('isComstar', newVal === 'comstar');
|
||||
- vm.set('hasWriteCacheOption', newVal === 'comstar' || newVal === 'istgt');
|
||||
+ vm.set('isFreeNAS', newVal === 'freenas');
|
||||
+ vm.set('hasWriteCacheOption', newVal === 'comstar' || newVal === 'freenas' || newVal === 'istgt');
|
||||
+ if (newVal !== 'freenas') {
|
||||
+ me.lookupReference('freenas_use_ssl_field').setValue(false);
|
||||
+ me.lookupReference('freenas_apiv4_host_field').setValue('');
|
||||
+ me.lookupReference('freenas_user_field').setValue('');
|
||||
+ me.lookupReference('freenas_user_field').allowBlank = true;
|
||||
+ me.lookupReference('freenas_password_field').setValue('');
|
||||
+ me.lookupReference('freenas_password_field').allowBlank = true;
|
||||
+ me.lookupReference('freenas_confirmpw_field').setValue('');
|
||||
+ me.lookupReference('freenas_confirmpw_field').allowBlank = true;
|
||||
+ } else {
|
||||
+ me.lookupReference('freenas_user_field').allowBlank = false;
|
||||
+ me.lookupReference('freenas_password_field').allowBlank = false;
|
||||
+ me.lookupReference('freenas_confirmpw_field').allowBlank = false;
|
||||
+ }
|
||||
}
|
||||
},
|
||||
|
||||
@@ -30118,6 +30136,7 @@
|
||||
},
|
||||
|
||||
setValues: function diff(values) {
|
||||
+ values.freenas_confirmpw = values.freenas_password;
|
||||
values.writecache = values.nowritecache ? 0 : 1;
|
||||
this.callParent([values]);
|
||||
},
|
||||
@@ -30134,7 +30153,7 @@
|
||||
allowBlank: false
|
||||
},
|
||||
{
|
||||
- xtype: me.isCreate ? 'textfield' : 'displayfield',
|
||||
+ xtype: 'textfield',
|
||||
name: 'pool',
|
||||
value: '',
|
||||
fieldLabel: gettext('Pool'),
|
||||
@@ -30144,11 +30163,11 @@
|
||||
xtype: me.isCreate ? 'textfield' : 'displayfield',
|
||||
name: 'blocksize',
|
||||
value: '4k',
|
||||
- fieldLabel: gettext('Block Size'),
|
||||
+ fieldLabel: gettext('ZFS Block Size'),
|
||||
allowBlank: false
|
||||
},
|
||||
{
|
||||
- xtype: me.isCreate ? 'textfield' : 'displayfield',
|
||||
+ xtype: 'textfield',
|
||||
name: 'target',
|
||||
value: '',
|
||||
fieldLabel: gettext('Target'),
|
||||
@@ -30159,8 +30178,33 @@
|
||||
name: 'comstar_tg',
|
||||
value: '',
|
||||
fieldLabel: gettext('Target group'),
|
||||
- bind: me.isCreate ? { disabled: '{!isComstar}' } : { hidden: '{!isComstar}' },
|
||||
+ bind: {
|
||||
+ hidden: '{!isComstar}'
|
||||
+ },
|
||||
allowBlank: true
|
||||
+ },
|
||||
+ {
|
||||
+ xtype: 'proxmoxcheckbox',
|
||||
+ name: 'freenas_use_ssl',
|
||||
+ reference: 'freenas_use_ssl_field',
|
||||
+ inputId: 'freenas_use_ssl_field',
|
||||
+ checked: false,
|
||||
+ bind: {
|
||||
+ hidden: '{!isFreeNAS}'
|
||||
+ },
|
||||
+ uncheckedValue: 0,
|
||||
+ fieldLabel: gettext('API use SSL')
|
||||
+ },
|
||||
+ {
|
||||
+ xtype: 'textfield',
|
||||
+ name: 'freenas_user',
|
||||
+ reference: 'freenas_user_field',
|
||||
+ inputId: 'freenas_user_field',
|
||||
+ value: '',
|
||||
+ fieldLabel: gettext('API Username'),
|
||||
+ bind: {
|
||||
+ hidden: '{!isFreeNAS}'
|
||||
+ }
|
||||
}
|
||||
];
|
||||
|
||||
@@ -30191,7 +30235,9 @@
|
||||
xtype: me.isCreate ? 'textfield' : 'displayfield',
|
||||
name: 'comstar_hg',
|
||||
value: '',
|
||||
- bind: me.isCreate ? { disabled: '{!isComstar}' } : { hidden: '{!isComstar}' },
|
||||
+ bind: {
|
||||
+ hidden: '{!isComstar}'
|
||||
+ },
|
||||
fieldLabel: gettext('Host group'),
|
||||
allowBlank: true
|
||||
},
|
||||
@@ -30199,9 +30245,64 @@
|
||||
xtype: me.isCreate ? 'textfield' : 'displayfield',
|
||||
name: 'lio_tpg',
|
||||
value: '',
|
||||
- bind: me.isCreate ? { disabled: '{!isLIO}' } : { hidden: '{!isLIO}' },
|
||||
- allowBlank: false,
|
||||
- fieldLabel: gettext('Target portal group')
|
||||
+ bind: {
|
||||
+ hidden: '{!isLIO}'
|
||||
+ },
|
||||
+ fieldLabel: gettext('Target portal group'),
|
||||
+ allowBlank: true
|
||||
+ },
|
||||
+ {
|
||||
+ xtype: 'proxmoxtextfield',
|
||||
+ name: 'freenas_apiv4_host',
|
||||
+ reference: 'freenas_apiv4_host_field',
|
||||
+ value: '',
|
||||
+ editable: true,
|
||||
+ emptyText: Proxmox.Utils.noneText,
|
||||
+ bind: {
|
||||
+ hidden: '{!isFreeNAS}'
|
||||
+ },
|
||||
+ fieldLabel: gettext('API IPv4 Host'),
|
||||
+ },
|
||||
+ {
|
||||
+ xtype: 'proxmoxtextfield',
|
||||
+ name: 'freenas_password',
|
||||
+ reference: 'freenas_password_field',
|
||||
+ inputType: me.isCreate ? '' : 'password',
|
||||
+ value: '',
|
||||
+ editable: true,
|
||||
+ deleteEmpty: true,
|
||||
+ emptyText: Proxmox.Utils.noneText,
|
||||
+ bind: {
|
||||
+ hidden: '{!isFreeNAS}'
|
||||
+ },
|
||||
+ fieldLabel: gettext('API Password'),
|
||||
+ change: function(f, value) {
|
||||
+ if (f.rendered) {
|
||||
+ f.up().down('field[name=freenas_confirmpw]').validate();
|
||||
+ }
|
||||
+ }
|
||||
+ },
|
||||
+ {
|
||||
+ xtype: 'proxmoxtextfield',
|
||||
+ name: 'freenas_confirmpw',
|
||||
+ reference: 'freenas_confirmpw_field',
|
||||
+ inputType: me.isCreate ? '' : 'password',
|
||||
+ value: '',
|
||||
+ editable: true,
|
||||
+ submitValue: false,
|
||||
+ deleteEmpty: true,
|
||||
+ emptyText: Proxmox.Utils.noneText,
|
||||
+ bind: {
|
||||
+ hidden: '{!isFreeNAS}'
|
||||
+ },
|
||||
+ fieldLabel: gettext('Confirm Password'),
|
||||
+ validator: function(value) {
|
||||
+ var pw = this.up().down('field[name=freenas_password]').getValue();
|
||||
+ if (pw !== value) {
|
||||
+ return "Passwords do not match!";
|
||||
+ }
|
||||
+ return true;
|
||||
+ }
|
||||
}
|
||||
];
|
||||
|
||||
|
|
@ -1,189 +0,0 @@
|
|||
--- pvemanagerlib.js.orig 2019-01-11 04:23:42.000000000 -0500
|
||||
+++ pvemanagerlib.js 2019-02-05 07:30:18.168655803 -0500
|
||||
@@ -5894,6 +5894,7 @@
|
||||
alias: ['widget.pveiScsiProviderSelector'],
|
||||
comboItems: [
|
||||
['comstar', 'Comstar'],
|
||||
+ ['freenas', 'FreeNAS-API'],
|
||||
[ 'istgt', 'istgt'],
|
||||
[ 'iet', 'IET'],
|
||||
[ 'LIO', 'LIO']
|
||||
@@ -30085,6 +30086,7 @@
|
||||
data: {
|
||||
isLIO: false,
|
||||
isComstar: true,
|
||||
+ isFreeNAS: false,
|
||||
hasWriteCacheOption: true
|
||||
}
|
||||
},
|
||||
@@ -30097,10 +30099,26 @@
|
||||
}
|
||||
},
|
||||
changeISCSIProvider: function(f, newVal, oldVal) {
|
||||
+ var me = this;
|
||||
var vm = this.getViewModel();
|
||||
vm.set('isLIO', newVal === 'LIO');
|
||||
vm.set('isComstar', newVal === 'comstar');
|
||||
- vm.set('hasWriteCacheOption', newVal === 'comstar' || newVal === 'istgt');
|
||||
+ vm.set('isFreeNAS', newVal === 'freenas');
|
||||
+ vm.set('hasWriteCacheOption', newVal === 'comstar' || newVal === 'freenas' || newVal === 'istgt');
|
||||
+ if (newVal !== 'freenas') {
|
||||
+ me.lookupReference('freenas_use_ssl_field').setValue(false);
|
||||
+ me.lookupReference('freenas_apiv4_host_field').setValue('');
|
||||
+ me.lookupReference('freenas_user_field').setValue('');
|
||||
+ me.lookupReference('freenas_user_field').allowBlank = true;
|
||||
+ me.lookupReference('freenas_password_field').setValue('');
|
||||
+ me.lookupReference('freenas_password_field').allowBlank = true;
|
||||
+ me.lookupReference('freenas_confirmpw_field').setValue('');
|
||||
+ me.lookupReference('freenas_confirmpw_field').allowBlank = true;
|
||||
+ } else {
|
||||
+ me.lookupReference('freenas_user_field').allowBlank = false;
|
||||
+ me.lookupReference('freenas_password_field').allowBlank = false;
|
||||
+ me.lookupReference('freenas_confirmpw_field').allowBlank = false;
|
||||
+ }
|
||||
}
|
||||
},
|
||||
|
||||
@@ -30118,6 +30136,7 @@
|
||||
},
|
||||
|
||||
setValues: function diff(values) {
|
||||
+ values.freenas_confirmpw = values.freenas_password;
|
||||
values.writecache = values.nowritecache ? 0 : 1;
|
||||
this.callParent([values]);
|
||||
},
|
||||
@@ -30134,7 +30153,7 @@
|
||||
allowBlank: false
|
||||
},
|
||||
{
|
||||
- xtype: me.isCreate ? 'textfield' : 'displayfield',
|
||||
+ xtype: 'textfield',
|
||||
name: 'pool',
|
||||
value: '',
|
||||
fieldLabel: gettext('Pool'),
|
||||
@@ -30144,11 +30163,11 @@
|
||||
xtype: me.isCreate ? 'textfield' : 'displayfield',
|
||||
name: 'blocksize',
|
||||
value: '4k',
|
||||
- fieldLabel: gettext('Block Size'),
|
||||
+ fieldLabel: gettext('ZFS Block Size'),
|
||||
allowBlank: false
|
||||
},
|
||||
{
|
||||
- xtype: me.isCreate ? 'textfield' : 'displayfield',
|
||||
+ xtype: 'textfield',
|
||||
name: 'target',
|
||||
value: '',
|
||||
fieldLabel: gettext('Target'),
|
||||
@@ -30159,8 +30178,33 @@
|
||||
name: 'comstar_tg',
|
||||
value: '',
|
||||
fieldLabel: gettext('Target group'),
|
||||
- bind: me.isCreate ? { disabled: '{!isComstar}' } : { hidden: '{!isComstar}' },
|
||||
+ bind: {
|
||||
+ hidden: '{!isComstar}'
|
||||
+ },
|
||||
allowBlank: true
|
||||
+ },
|
||||
+ {
|
||||
+ xtype: 'proxmoxcheckbox',
|
||||
+ name: 'freenas_use_ssl',
|
||||
+ reference: 'freenas_use_ssl_field',
|
||||
+ inputId: 'freenas_use_ssl_field',
|
||||
+ checked: false,
|
||||
+ bind: {
|
||||
+ hidden: '{!isFreeNAS}'
|
||||
+ },
|
||||
+ uncheckedValue: 0,
|
||||
+ fieldLabel: gettext('API use SSL')
|
||||
+ },
|
||||
+ {
|
||||
+ xtype: 'textfield',
|
||||
+ name: 'freenas_user',
|
||||
+ reference: 'freenas_user_field',
|
||||
+ inputId: 'freenas_user_field',
|
||||
+ value: '',
|
||||
+ fieldLabel: gettext('API Username'),
|
||||
+ bind: {
|
||||
+ hidden: '{!isFreeNAS}'
|
||||
+ }
|
||||
}
|
||||
];
|
||||
|
||||
@@ -30191,7 +30235,9 @@
|
||||
xtype: me.isCreate ? 'textfield' : 'displayfield',
|
||||
name: 'comstar_hg',
|
||||
value: '',
|
||||
- bind: me.isCreate ? { disabled: '{!isComstar}' } : { hidden: '{!isComstar}' },
|
||||
+ bind: {
|
||||
+ hidden: '{!isComstar}'
|
||||
+ },
|
||||
fieldLabel: gettext('Host group'),
|
||||
allowBlank: true
|
||||
},
|
||||
@@ -30199,9 +30245,62 @@
|
||||
xtype: me.isCreate ? 'textfield' : 'displayfield',
|
||||
name: 'lio_tpg',
|
||||
value: '',
|
||||
- bind: me.isCreate ? { disabled: '{!isLIO}' } : { hidden: '{!isLIO}' },
|
||||
- allowBlank: false,
|
||||
- fieldLabel: gettext('Target portal group')
|
||||
+ bind: {
|
||||
+ hidden: '{!isLIO}'
|
||||
+ },
|
||||
+ fieldLabel: gettext('Target portal group'),
|
||||
+ allowBlank: true
|
||||
+ },
|
||||
+ {
|
||||
+ xtype: 'proxmoxtextfield',
|
||||
+ name: 'freenas_apiv4_host',
|
||||
+ reference: 'freenas_apiv4_host_field',
|
||||
+ value: '',
|
||||
+ editable: true,
|
||||
+ emptyText: Proxmox.Utils.noneText,
|
||||
+ bind: {
|
||||
+ hidden: '{!isFreeNAS}'
|
||||
+ },
|
||||
+ fieldLabel: gettext('API IPv4 Host'),
|
||||
+ },
|
||||
+ {
|
||||
+ xtype: 'proxmoxtextfield',
|
||||
+ name: 'freenas_password',
|
||||
+ reference: 'freenas_password_field',
|
||||
+ inputType: me.isCreate ? '' : 'password',
|
||||
+ value: '',
|
||||
+ editable: true,
|
||||
+ emptyText: Proxmox.Utils.noneText,
|
||||
+ bind: {
|
||||
+ hidden: '{!isFreeNAS}'
|
||||
+ },
|
||||
+ fieldLabel: gettext('API Password'),
|
||||
+ change: function(f, value) {
|
||||
+ if (f.rendered) {
|
||||
+ f.up().down('field[name=freenas_confirmpw]').validate();
|
||||
+ }
|
||||
+ }
|
||||
+ },
|
||||
+ {
|
||||
+ xtype: 'proxmoxtextfield',
|
||||
+ name: 'freenas_confirmpw',
|
||||
+ reference: 'freenas_confirmpw_field',
|
||||
+ inputType: me.isCreate ? '' : 'password',
|
||||
+ value: '',
|
||||
+ editable: true,
|
||||
+ submitValue: false,
|
||||
+ emptyText: Proxmox.Utils.noneText,
|
||||
+ bind: {
|
||||
+ hidden: '{!isFreeNAS}'
|
||||
+ },
|
||||
+ fieldLabel: gettext('Confirm Password'),
|
||||
+ validator: function(value) {
|
||||
+ var pw = this.up().down('field[name=freenas_password]').getValue();
|
||||
+ if (pw !== value) {
|
||||
+ return "Passwords do not match!";
|
||||
+ }
|
||||
+ return true;
|
||||
+ }
|
||||
}
|
||||
];
|
||||
|
||||
|
|
@ -1,189 +0,0 @@
|
|||
--- pvemanagerlib.js.orig 2019-07-09 03:52:19.000000000 -0400
|
||||
+++ pvemanagerlib.js 2019-07-16 13:59:37.658409977 -0400
|
||||
@@ -6094,6 +6094,7 @@
|
||||
alias: ['widget.pveiScsiProviderSelector'],
|
||||
comboItems: [
|
||||
['comstar', 'Comstar'],
|
||||
+ ['freenas', 'FreeNAS-API'],
|
||||
[ 'istgt', 'istgt'],
|
||||
[ 'iet', 'IET'],
|
||||
[ 'LIO', 'LIO']
|
||||
@@ -31700,6 +31701,7 @@
|
||||
data: {
|
||||
isLIO: false,
|
||||
isComstar: true,
|
||||
+ isFreeNAS: false,
|
||||
hasWriteCacheOption: true
|
||||
}
|
||||
},
|
||||
@@ -31712,10 +31714,26 @@
|
||||
}
|
||||
},
|
||||
changeISCSIProvider: function(f, newVal, oldVal) {
|
||||
+ var me = this;
|
||||
var vm = this.getViewModel();
|
||||
vm.set('isLIO', newVal === 'LIO');
|
||||
vm.set('isComstar', newVal === 'comstar');
|
||||
- vm.set('hasWriteCacheOption', newVal === 'comstar' || newVal === 'istgt');
|
||||
+ vm.set('isFreeNAS', newVal === 'freenas');
|
||||
+ vm.set('hasWriteCacheOption', newVal === 'comstar' || newVal === 'freenas' || newVal === 'istgt');
|
||||
+ if (newVal !== 'freenas') {
|
||||
+ me.lookupReference('freenas_use_ssl_field').setValue(false);
|
||||
+ me.lookupReference('freenas_apiv4_host_field').setValue('');
|
||||
+ me.lookupReference('freenas_user_field').setValue('');
|
||||
+ me.lookupReference('freenas_user_field').allowBlank = true;
|
||||
+ me.lookupReference('freenas_password_field').setValue('');
|
||||
+ me.lookupReference('freenas_password_field').allowBlank = true;
|
||||
+ me.lookupReference('freenas_confirmpw_field').setValue('');
|
||||
+ me.lookupReference('freenas_confirmpw_field').allowBlank = true;
|
||||
+ } else {
|
||||
+ me.lookupReference('freenas_user_field').allowBlank = false;
|
||||
+ me.lookupReference('freenas_password_field').allowBlank = false;
|
||||
+ me.lookupReference('freenas_confirmpw_field').allowBlank = false;
|
||||
+ }
|
||||
}
|
||||
},
|
||||
|
||||
@@ -31733,6 +31751,7 @@
|
||||
},
|
||||
|
||||
setValues: function diff(values) {
|
||||
+ values.freenas_confirmpw = values.freenas_password;
|
||||
values.writecache = values.nowritecache ? 0 : 1;
|
||||
this.callParent([values]);
|
||||
},
|
||||
@@ -31749,7 +31768,7 @@
|
||||
allowBlank: false
|
||||
},
|
||||
{
|
||||
- xtype: me.isCreate ? 'textfield' : 'displayfield',
|
||||
+ xtype: 'textfield',
|
||||
name: 'pool',
|
||||
value: '',
|
||||
fieldLabel: gettext('Pool'),
|
||||
@@ -31759,11 +31778,11 @@
|
||||
xtype: me.isCreate ? 'textfield' : 'displayfield',
|
||||
name: 'blocksize',
|
||||
value: '4k',
|
||||
- fieldLabel: gettext('Block Size'),
|
||||
+ fieldLabel: gettext('ZFS Block Size'),
|
||||
allowBlank: false
|
||||
},
|
||||
{
|
||||
- xtype: me.isCreate ? 'textfield' : 'displayfield',
|
||||
+ xtype: 'textfield',
|
||||
name: 'target',
|
||||
value: '',
|
||||
fieldLabel: gettext('Target'),
|
||||
@@ -31774,8 +31793,33 @@
|
||||
name: 'comstar_tg',
|
||||
value: '',
|
||||
fieldLabel: gettext('Target group'),
|
||||
- bind: me.isCreate ? { disabled: '{!isComstar}' } : { hidden: '{!isComstar}' },
|
||||
+ bind: {
|
||||
+ hidden: '{!isComstar}'
|
||||
+ },
|
||||
allowBlank: true
|
||||
+ },
|
||||
+ {
|
||||
+ xtype: 'proxmoxcheckbox',
|
||||
+ name: 'freenas_use_ssl',
|
||||
+ reference: 'freenas_use_ssl_field',
|
||||
+ inputId: 'freenas_use_ssl_field',
|
||||
+ checked: false,
|
||||
+ bind: {
|
||||
+ hidden: '{!isFreeNAS}'
|
||||
+ },
|
||||
+ uncheckedValue: 0,
|
||||
+ fieldLabel: gettext('API use SSL')
|
||||
+ },
|
||||
+ {
|
||||
+ xtype: 'textfield',
|
||||
+ name: 'freenas_user',
|
||||
+ reference: 'freenas_user_field',
|
||||
+ inputId: 'freenas_user_field',
|
||||
+ value: '',
|
||||
+ fieldLabel: gettext('API Username'),
|
||||
+ bind: {
|
||||
+ hidden: '{!isFreeNAS}'
|
||||
+ }
|
||||
}
|
||||
];
|
||||
|
||||
@@ -31806,7 +31850,9 @@
|
||||
xtype: me.isCreate ? 'textfield' : 'displayfield',
|
||||
name: 'comstar_hg',
|
||||
value: '',
|
||||
- bind: me.isCreate ? { disabled: '{!isComstar}' } : { hidden: '{!isComstar}' },
|
||||
+ bind: {
|
||||
+ hidden: '{!isComstar}'
|
||||
+ },
|
||||
fieldLabel: gettext('Host group'),
|
||||
allowBlank: true
|
||||
},
|
||||
@@ -31814,9 +31860,62 @@
|
||||
xtype: me.isCreate ? 'textfield' : 'displayfield',
|
||||
name: 'lio_tpg',
|
||||
value: '',
|
||||
- bind: me.isCreate ? { disabled: '{!isLIO}' } : { hidden: '{!isLIO}' },
|
||||
- allowBlank: false,
|
||||
- fieldLabel: gettext('Target portal group')
|
||||
+ bind: {
|
||||
+ hidden: '{!isLIO}'
|
||||
+ },
|
||||
+ fieldLabel: gettext('Target portal group'),
|
||||
+ allowBlank: true
|
||||
+ },
|
||||
+ {
|
||||
+ xtype: 'proxmoxtextfield',
|
||||
+ name: 'freenas_apiv4_host',
|
||||
+ reference: 'freenas_apiv4_host_field',
|
||||
+ value: '',
|
||||
+ editable: true,
|
||||
+ emptyText: Proxmox.Utils.noneText,
|
||||
+ bind: {
|
||||
+ hidden: '{!isFreeNAS}'
|
||||
+ },
|
||||
+ fieldLabel: gettext('API IPv4 Host'),
|
||||
+ },
|
||||
+ {
|
||||
+ xtype: 'proxmoxtextfield',
|
||||
+ name: 'freenas_password',
|
||||
+ reference: 'freenas_password_field',
|
||||
+ inputType: me.isCreate ? '' : 'password',
|
||||
+ value: '',
|
||||
+ editable: true,
|
||||
+ emptyText: Proxmox.Utils.noneText,
|
||||
+ bind: {
|
||||
+ hidden: '{!isFreeNAS}'
|
||||
+ },
|
||||
+ fieldLabel: gettext('API Password'),
|
||||
+ change: function(f, value) {
|
||||
+ if (f.rendered) {
|
||||
+ f.up().down('field[name=freenas_confirmpw]').validate();
|
||||
+ }
|
||||
+ }
|
||||
+ },
|
||||
+ {
|
||||
+ xtype: 'proxmoxtextfield',
|
||||
+ name: 'freenas_confirmpw',
|
||||
+ reference: 'freenas_confirmpw_field',
|
||||
+ inputType: me.isCreate ? '' : 'password',
|
||||
+ value: '',
|
||||
+ editable: true,
|
||||
+ submitValue: false,
|
||||
+ emptyText: Proxmox.Utils.noneText,
|
||||
+ bind: {
|
||||
+ hidden: '{!isFreeNAS}'
|
||||
+ },
|
||||
+ fieldLabel: gettext('Confirm Password'),
|
||||
+ validator: function(value) {
|
||||
+ var pw = this.up().down('field[name=freenas_password]').getValue();
|
||||
+ if (pw !== value) {
|
||||
+ return "Passwords do not match!";
|
||||
+ }
|
||||
+ return true;
|
||||
+ }
|
||||
}
|
||||
];
|
||||
|
||||
|
|
@ -1,189 +0,0 @@
|
|||
--- pvemanagerlib.js.orig 2019-07-26 04:14:56.000000000 -0400
|
||||
+++ pvemanagerlib.js 2019-08-05 16:02:38.484913553 -0400
|
||||
@@ -6094,6 +6094,7 @@
|
||||
alias: ['widget.pveiScsiProviderSelector'],
|
||||
comboItems: [
|
||||
['comstar', 'Comstar'],
|
||||
+ ['freenas', 'FreeNAS-API'],
|
||||
[ 'istgt', 'istgt'],
|
||||
[ 'iet', 'IET'],
|
||||
[ 'LIO', 'LIO']
|
||||
@@ -31722,6 +31723,7 @@
|
||||
data: {
|
||||
isLIO: false,
|
||||
isComstar: true,
|
||||
+ isFreeNAS: false,
|
||||
hasWriteCacheOption: true
|
||||
}
|
||||
},
|
||||
@@ -31734,10 +31736,26 @@
|
||||
}
|
||||
},
|
||||
changeISCSIProvider: function(f, newVal, oldVal) {
|
||||
+ var me = this;
|
||||
var vm = this.getViewModel();
|
||||
vm.set('isLIO', newVal === 'LIO');
|
||||
vm.set('isComstar', newVal === 'comstar');
|
||||
- vm.set('hasWriteCacheOption', newVal === 'comstar' || newVal === 'istgt');
|
||||
+ vm.set('isFreeNAS', newVal === 'freenas');
|
||||
+ vm.set('hasWriteCacheOption', newVal === 'comstar' || newVal === 'freenas' || newVal === 'istgt');
|
||||
+ if (newVal !== 'freenas') {
|
||||
+ me.lookupReference('freenas_use_ssl_field').setValue(false);
|
||||
+ me.lookupReference('freenas_apiv4_host_field').setValue('');
|
||||
+ me.lookupReference('freenas_user_field').setValue('');
|
||||
+ me.lookupReference('freenas_user_field').allowBlank = true;
|
||||
+ me.lookupReference('freenas_password_field').setValue('');
|
||||
+ me.lookupReference('freenas_password_field').allowBlank = true;
|
||||
+ me.lookupReference('freenas_confirmpw_field').setValue('');
|
||||
+ me.lookupReference('freenas_confirmpw_field').allowBlank = true;
|
||||
+ } else {
|
||||
+ me.lookupReference('freenas_user_field').allowBlank = false;
|
||||
+ me.lookupReference('freenas_password_field').allowBlank = false;
|
||||
+ me.lookupReference('freenas_confirmpw_field').allowBlank = false;
|
||||
+ }
|
||||
}
|
||||
},
|
||||
|
||||
@@ -31755,6 +31773,7 @@
|
||||
},
|
||||
|
||||
setValues: function diff(values) {
|
||||
+ values.freenas_confirmpw = values.freenas_password;
|
||||
values.writecache = values.nowritecache ? 0 : 1;
|
||||
this.callParent([values]);
|
||||
},
|
||||
@@ -31771,7 +31790,7 @@
|
||||
allowBlank: false
|
||||
},
|
||||
{
|
||||
- xtype: me.isCreate ? 'textfield' : 'displayfield',
|
||||
+ xtype: 'textfield',
|
||||
name: 'pool',
|
||||
value: '',
|
||||
fieldLabel: gettext('Pool'),
|
||||
@@ -31781,11 +31800,11 @@
|
||||
xtype: me.isCreate ? 'textfield' : 'displayfield',
|
||||
name: 'blocksize',
|
||||
value: '4k',
|
||||
- fieldLabel: gettext('Block Size'),
|
||||
+ fieldLabel: gettext('ZFS Block Size'),
|
||||
allowBlank: false
|
||||
},
|
||||
{
|
||||
- xtype: me.isCreate ? 'textfield' : 'displayfield',
|
||||
+ xtype: 'textfield',
|
||||
name: 'target',
|
||||
value: '',
|
||||
fieldLabel: gettext('Target'),
|
||||
@@ -31796,8 +31815,33 @@
|
||||
name: 'comstar_tg',
|
||||
value: '',
|
||||
fieldLabel: gettext('Target group'),
|
||||
- bind: me.isCreate ? { disabled: '{!isComstar}' } : { hidden: '{!isComstar}' },
|
||||
+ bind: {
|
||||
+ hidden: '{!isComstar}'
|
||||
+ },
|
||||
allowBlank: true
|
||||
+ },
|
||||
+ {
|
||||
+ xtype: 'proxmoxcheckbox',
|
||||
+ name: 'freenas_use_ssl',
|
||||
+ reference: 'freenas_use_ssl_field',
|
||||
+ inputId: 'freenas_use_ssl_field',
|
||||
+ checked: false,
|
||||
+ bind: {
|
||||
+ hidden: '{!isFreeNAS}'
|
||||
+ },
|
||||
+ uncheckedValue: 0,
|
||||
+ fieldLabel: gettext('API use SSL')
|
||||
+ },
|
||||
+ {
|
||||
+ xtype: 'textfield',
|
||||
+ name: 'freenas_user',
|
||||
+ reference: 'freenas_user_field',
|
||||
+ inputId: 'freenas_user_field',
|
||||
+ value: '',
|
||||
+ fieldLabel: gettext('API Username'),
|
||||
+ bind: {
|
||||
+ hidden: '{!isFreeNAS}'
|
||||
+ }
|
||||
}
|
||||
];
|
||||
|
||||
@@ -31828,7 +31872,9 @@
|
||||
xtype: me.isCreate ? 'textfield' : 'displayfield',
|
||||
name: 'comstar_hg',
|
||||
value: '',
|
||||
- bind: me.isCreate ? { disabled: '{!isComstar}' } : { hidden: '{!isComstar}' },
|
||||
+ bind: {
|
||||
+ hidden: '{!isComstar}'
|
||||
+ },
|
||||
fieldLabel: gettext('Host group'),
|
||||
allowBlank: true
|
||||
},
|
||||
@@ -31836,9 +31882,62 @@
|
||||
xtype: me.isCreate ? 'textfield' : 'displayfield',
|
||||
name: 'lio_tpg',
|
||||
value: '',
|
||||
- bind: me.isCreate ? { disabled: '{!isLIO}' } : { hidden: '{!isLIO}' },
|
||||
- allowBlank: false,
|
||||
- fieldLabel: gettext('Target portal group')
|
||||
+ bind: {
|
||||
+ hidden: '{!isLIO}'
|
||||
+ },
|
||||
+ fieldLabel: gettext('Target portal group'),
|
||||
+ allowBlank: true
|
||||
+ },
|
||||
+ {
|
||||
+ xtype: 'proxmoxtextfield',
|
||||
+ name: 'freenas_apiv4_host',
|
||||
+ reference: 'freenas_apiv4_host_field',
|
||||
+ value: '',
|
||||
+ editable: true,
|
||||
+ emptyText: Proxmox.Utils.noneText,
|
||||
+ bind: {
|
||||
+ hidden: '{!isFreeNAS}'
|
||||
+ },
|
||||
+ fieldLabel: gettext('API IPv4 Host'),
|
||||
+ },
|
||||
+ {
|
||||
+ xtype: 'proxmoxtextfield',
|
||||
+ name: 'freenas_password',
|
||||
+ reference: 'freenas_password_field',
|
||||
+ inputType: me.isCreate ? '' : 'password',
|
||||
+ value: '',
|
||||
+ editable: true,
|
||||
+ emptyText: Proxmox.Utils.noneText,
|
||||
+ bind: {
|
||||
+ hidden: '{!isFreeNAS}'
|
||||
+ },
|
||||
+ fieldLabel: gettext('API Password'),
|
||||
+ change: function(f, value) {
|
||||
+ if (f.rendered) {
|
||||
+ f.up().down('field[name=freenas_confirmpw]').validate();
|
||||
+ }
|
||||
+ }
|
||||
+ },
|
||||
+ {
|
||||
+ xtype: 'proxmoxtextfield',
|
||||
+ name: 'freenas_confirmpw',
|
||||
+ reference: 'freenas_confirmpw_field',
|
||||
+ inputType: me.isCreate ? '' : 'password',
|
||||
+ value: '',
|
||||
+ editable: true,
|
||||
+ submitValue: false,
|
||||
+ emptyText: Proxmox.Utils.noneText,
|
||||
+ bind: {
|
||||
+ hidden: '{!isFreeNAS}'
|
||||
+ },
|
||||
+ fieldLabel: gettext('Confirm Password'),
|
||||
+ validator: function(value) {
|
||||
+ var pw = this.up().down('field[name=freenas_password]').getValue();
|
||||
+ if (pw !== value) {
|
||||
+ return "Passwords do not match!";
|
||||
+ }
|
||||
+ return true;
|
||||
+ }
|
||||
}
|
||||
];
|
||||
|
||||
|
|
@ -1,189 +0,0 @@
|
|||
--- pvemanagerlib.js.orig 2019-04-24 07:28:11.000000000 -0400
|
||||
+++ pvemanagerlib.js 2019-05-11 10:33:38.130198999 -0400
|
||||
@@ -6094,6 +6094,7 @@
|
||||
alias: ['widget.pveiScsiProviderSelector'],
|
||||
comboItems: [
|
||||
['comstar', 'Comstar'],
|
||||
+ ['freenas', 'FreeNAS-API'],
|
||||
[ 'istgt', 'istgt'],
|
||||
[ 'iet', 'IET'],
|
||||
[ 'LIO', 'LIO']
|
||||
@@ -31483,6 +31484,7 @@
|
||||
data: {
|
||||
isLIO: false,
|
||||
isComstar: true,
|
||||
+ isFreeNAS: false,
|
||||
hasWriteCacheOption: true
|
||||
}
|
||||
},
|
||||
@@ -31495,10 +31497,26 @@
|
||||
}
|
||||
},
|
||||
changeISCSIProvider: function(f, newVal, oldVal) {
|
||||
+ var me = this;
|
||||
var vm = this.getViewModel();
|
||||
vm.set('isLIO', newVal === 'LIO');
|
||||
vm.set('isComstar', newVal === 'comstar');
|
||||
- vm.set('hasWriteCacheOption', newVal === 'comstar' || newVal === 'istgt');
|
||||
+ vm.set('isFreeNAS', newVal === 'freenas');
|
||||
+ vm.set('hasWriteCacheOption', newVal === 'comstar' || newVal === 'freenas' || newVal === 'istgt');
|
||||
+ if (newVal !== 'freenas') {
|
||||
+ me.lookupReference('freenas_use_ssl_field').setValue(false);
|
||||
+ me.lookupReference('freenas_apiv4_host_field').setValue('');
|
||||
+ me.lookupReference('freenas_user_field').setValue('');
|
||||
+ me.lookupReference('freenas_user_field').allowBlank = true;
|
||||
+ me.lookupReference('freenas_password_field').setValue('');
|
||||
+ me.lookupReference('freenas_password_field').allowBlank = true;
|
||||
+ me.lookupReference('freenas_confirmpw_field').setValue('');
|
||||
+ me.lookupReference('freenas_confirmpw_field').allowBlank = true;
|
||||
+ } else {
|
||||
+ me.lookupReference('freenas_user_field').allowBlank = false;
|
||||
+ me.lookupReference('freenas_password_field').allowBlank = false;
|
||||
+ me.lookupReference('freenas_confirmpw_field').allowBlank = false;
|
||||
+ }
|
||||
}
|
||||
},
|
||||
|
||||
@@ -31516,6 +31534,7 @@
|
||||
},
|
||||
|
||||
setValues: function diff(values) {
|
||||
+ values.freenas_confirmpw = values.freenas_password;
|
||||
values.writecache = values.nowritecache ? 0 : 1;
|
||||
this.callParent([values]);
|
||||
},
|
||||
@@ -31532,7 +31551,7 @@
|
||||
allowBlank: false
|
||||
},
|
||||
{
|
||||
- xtype: me.isCreate ? 'textfield' : 'displayfield',
|
||||
+ xtype: 'textfield',
|
||||
name: 'pool',
|
||||
value: '',
|
||||
fieldLabel: gettext('Pool'),
|
||||
@@ -31542,11 +31561,11 @@
|
||||
xtype: me.isCreate ? 'textfield' : 'displayfield',
|
||||
name: 'blocksize',
|
||||
value: '4k',
|
||||
- fieldLabel: gettext('Block Size'),
|
||||
+ fieldLabel: gettext('ZFS Block Size'),
|
||||
allowBlank: false
|
||||
},
|
||||
{
|
||||
- xtype: me.isCreate ? 'textfield' : 'displayfield',
|
||||
+ xtype: 'textfield',
|
||||
name: 'target',
|
||||
value: '',
|
||||
fieldLabel: gettext('Target'),
|
||||
@@ -31557,8 +31576,33 @@
|
||||
name: 'comstar_tg',
|
||||
value: '',
|
||||
fieldLabel: gettext('Target group'),
|
||||
- bind: me.isCreate ? { disabled: '{!isComstar}' } : { hidden: '{!isComstar}' },
|
||||
+ bind: {
|
||||
+ hidden: '{!isComstar}'
|
||||
+ },
|
||||
allowBlank: true
|
||||
+ },
|
||||
+ {
|
||||
+ xtype: 'proxmoxcheckbox',
|
||||
+ name: 'freenas_use_ssl',
|
||||
+ reference: 'freenas_use_ssl_field',
|
||||
+ inputId: 'freenas_use_ssl_field',
|
||||
+ checked: false,
|
||||
+ bind: {
|
||||
+ hidden: '{!isFreeNAS}'
|
||||
+ },
|
||||
+ uncheckedValue: 0,
|
||||
+ fieldLabel: gettext('API use SSL')
|
||||
+ },
|
||||
+ {
|
||||
+ xtype: 'textfield',
|
||||
+ name: 'freenas_user',
|
||||
+ reference: 'freenas_user_field',
|
||||
+ inputId: 'freenas_user_field',
|
||||
+ value: '',
|
||||
+ fieldLabel: gettext('API Username'),
|
||||
+ bind: {
|
||||
+ hidden: '{!isFreeNAS}'
|
||||
+ }
|
||||
}
|
||||
];
|
||||
|
||||
@@ -31589,7 +31633,9 @@
|
||||
xtype: me.isCreate ? 'textfield' : 'displayfield',
|
||||
name: 'comstar_hg',
|
||||
value: '',
|
||||
- bind: me.isCreate ? { disabled: '{!isComstar}' } : { hidden: '{!isComstar}' },
|
||||
+ bind: {
|
||||
+ hidden: '{!isComstar}'
|
||||
+ },
|
||||
fieldLabel: gettext('Host group'),
|
||||
allowBlank: true
|
||||
},
|
||||
@@ -31597,9 +31643,62 @@
|
||||
xtype: me.isCreate ? 'textfield' : 'displayfield',
|
||||
name: 'lio_tpg',
|
||||
value: '',
|
||||
- bind: me.isCreate ? { disabled: '{!isLIO}' } : { hidden: '{!isLIO}' },
|
||||
- allowBlank: false,
|
||||
- fieldLabel: gettext('Target portal group')
|
||||
+ bind: {
|
||||
+ hidden: '{!isLIO}'
|
||||
+ },
|
||||
+ fieldLabel: gettext('Target portal group'),
|
||||
+ allowBlank: true
|
||||
+ },
|
||||
+ {
|
||||
+ xtype: 'proxmoxtextfield',
|
||||
+ name: 'freenas_apiv4_host',
|
||||
+ reference: 'freenas_apiv4_host_field',
|
||||
+ value: '',
|
||||
+ editable: true,
|
||||
+ emptyText: Proxmox.Utils.noneText,
|
||||
+ bind: {
|
||||
+ hidden: '{!isFreeNAS}'
|
||||
+ },
|
||||
+ fieldLabel: gettext('API IPv4 Host'),
|
||||
+ },
|
||||
+ {
|
||||
+ xtype: 'proxmoxtextfield',
|
||||
+ name: 'freenas_password',
|
||||
+ reference: 'freenas_password_field',
|
||||
+ inputType: me.isCreate ? '' : 'password',
|
||||
+ value: '',
|
||||
+ editable: true,
|
||||
+ emptyText: Proxmox.Utils.noneText,
|
||||
+ bind: {
|
||||
+ hidden: '{!isFreeNAS}'
|
||||
+ },
|
||||
+ fieldLabel: gettext('API Password'),
|
||||
+ change: function(f, value) {
|
||||
+ if (f.rendered) {
|
||||
+ f.up().down('field[name=freenas_confirmpw]').validate();
|
||||
+ }
|
||||
+ }
|
||||
+ },
|
||||
+ {
|
||||
+ xtype: 'proxmoxtextfield',
|
||||
+ name: 'freenas_confirmpw',
|
||||
+ reference: 'freenas_confirmpw_field',
|
||||
+ inputType: me.isCreate ? '' : 'password',
|
||||
+ value: '',
|
||||
+ editable: true,
|
||||
+ submitValue: false,
|
||||
+ emptyText: Proxmox.Utils.noneText,
|
||||
+ bind: {
|
||||
+ hidden: '{!isFreeNAS}'
|
||||
+ },
|
||||
+ fieldLabel: gettext('Confirm Password'),
|
||||
+ validator: function(value) {
|
||||
+ var pw = this.up().down('field[name=freenas_password]').getValue();
|
||||
+ if (pw !== value) {
|
||||
+ return "Passwords do not match!";
|
||||
+ }
|
||||
+ return true;
|
||||
+ }
|
||||
}
|
||||
];
|
||||
|
||||
|
|
@ -1,189 +0,0 @@
|
|||
--- pvemanagerlib.js.orig 2019-07-23 13:21:08.000000000 -0400
|
||||
+++ pvemanagerlib.js 2019-08-07 13:28:29.304381840 -0400
|
||||
@@ -6182,6 +6182,7 @@
|
||||
alias: ['widget.pveiScsiProviderSelector'],
|
||||
comboItems: [
|
||||
['comstar', 'Comstar'],
|
||||
+ ['freenas', 'FreeNAS-API'],
|
||||
[ 'istgt', 'istgt'],
|
||||
[ 'iet', 'IET'],
|
||||
[ 'LIO', 'LIO']
|
||||
@@ -32977,6 +32978,7 @@
|
||||
data: {
|
||||
isLIO: false,
|
||||
isComstar: true,
|
||||
+ isFreeNAS: false,
|
||||
hasWriteCacheOption: true
|
||||
}
|
||||
},
|
||||
@@ -32989,10 +32991,26 @@
|
||||
}
|
||||
},
|
||||
changeISCSIProvider: function(f, newVal, oldVal) {
|
||||
+ var me = this;
|
||||
var vm = this.getViewModel();
|
||||
vm.set('isLIO', newVal === 'LIO');
|
||||
vm.set('isComstar', newVal === 'comstar');
|
||||
- vm.set('hasWriteCacheOption', newVal === 'comstar' || newVal === 'istgt');
|
||||
+ vm.set('isFreeNAS', newVal === 'freenas');
|
||||
+ vm.set('hasWriteCacheOption', newVal === 'comstar' || newVal === 'freenas' || newVal === 'istgt');
|
||||
+ if (newVal !== 'freenas') {
|
||||
+ me.lookupReference('freenas_use_ssl_field').setValue(false);
|
||||
+ me.lookupReference('freenas_apiv4_host_field').setValue('');
|
||||
+ me.lookupReference('freenas_user_field').setValue('');
|
||||
+ me.lookupReference('freenas_user_field').allowBlank = true;
|
||||
+ me.lookupReference('freenas_password_field').setValue('');
|
||||
+ me.lookupReference('freenas_password_field').allowBlank = true;
|
||||
+ me.lookupReference('freenas_confirmpw_field').setValue('');
|
||||
+ me.lookupReference('freenas_confirmpw_field').allowBlank = true;
|
||||
+ } else {
|
||||
+ me.lookupReference('freenas_user_field').allowBlank = false;
|
||||
+ me.lookupReference('freenas_password_field').allowBlank = false;
|
||||
+ me.lookupReference('freenas_confirmpw_field').allowBlank = false;
|
||||
+ }
|
||||
}
|
||||
},
|
||||
|
||||
@@ -33010,6 +33028,7 @@
|
||||
},
|
||||
|
||||
setValues: function diff(values) {
|
||||
+ values.freenas_confirmpw = values.freenas_password;
|
||||
values.writecache = values.nowritecache ? 0 : 1;
|
||||
this.callParent([values]);
|
||||
},
|
||||
@@ -33026,7 +33045,7 @@
|
||||
allowBlank: false
|
||||
},
|
||||
{
|
||||
- xtype: me.isCreate ? 'textfield' : 'displayfield',
|
||||
+ xtype: 'textfield',
|
||||
name: 'pool',
|
||||
value: '',
|
||||
fieldLabel: gettext('Pool'),
|
||||
@@ -33036,11 +33055,11 @@
|
||||
xtype: me.isCreate ? 'textfield' : 'displayfield',
|
||||
name: 'blocksize',
|
||||
value: '4k',
|
||||
- fieldLabel: gettext('Block Size'),
|
||||
+ fieldLabel: gettext('ZFS Block Size'),
|
||||
allowBlank: false
|
||||
},
|
||||
{
|
||||
- xtype: me.isCreate ? 'textfield' : 'displayfield',
|
||||
+ xtype: 'textfield',
|
||||
name: 'target',
|
||||
value: '',
|
||||
fieldLabel: gettext('Target'),
|
||||
@@ -33051,8 +33070,33 @@
|
||||
name: 'comstar_tg',
|
||||
value: '',
|
||||
fieldLabel: gettext('Target group'),
|
||||
- bind: me.isCreate ? { disabled: '{!isComstar}' } : { hidden: '{!isComstar}' },
|
||||
+ bind: {
|
||||
+ hidden: '{!isComstar}'
|
||||
+ },
|
||||
allowBlank: true
|
||||
+ },
|
||||
+ {
|
||||
+ xtype: 'proxmoxcheckbox',
|
||||
+ name: 'freenas_use_ssl',
|
||||
+ reference: 'freenas_use_ssl_field',
|
||||
+ inputId: 'freenas_use_ssl_field',
|
||||
+ checked: false,
|
||||
+ bind: {
|
||||
+ hidden: '{!isFreeNAS}'
|
||||
+ },
|
||||
+ uncheckedValue: 0,
|
||||
+ fieldLabel: gettext('API use SSL')
|
||||
+ },
|
||||
+ {
|
||||
+ xtype: 'textfield',
|
||||
+ name: 'freenas_user',
|
||||
+ reference: 'freenas_user_field',
|
||||
+ inputId: 'freenas_user_field',
|
||||
+ value: '',
|
||||
+ fieldLabel: gettext('API Username'),
|
||||
+ bind: {
|
||||
+ hidden: '{!isFreeNAS}'
|
||||
+ }
|
||||
}
|
||||
];
|
||||
|
||||
@@ -33083,7 +33127,9 @@
|
||||
xtype: me.isCreate ? 'textfield' : 'displayfield',
|
||||
name: 'comstar_hg',
|
||||
value: '',
|
||||
- bind: me.isCreate ? { disabled: '{!isComstar}' } : { hidden: '{!isComstar}' },
|
||||
+ bind: {
|
||||
+ hidden: '{!isComstar}'
|
||||
+ },
|
||||
fieldLabel: gettext('Host group'),
|
||||
allowBlank: true
|
||||
},
|
||||
@@ -33091,9 +33137,62 @@
|
||||
xtype: me.isCreate ? 'textfield' : 'displayfield',
|
||||
name: 'lio_tpg',
|
||||
value: '',
|
||||
- bind: me.isCreate ? { disabled: '{!isLIO}' } : { hidden: '{!isLIO}' },
|
||||
- allowBlank: false,
|
||||
- fieldLabel: gettext('Target portal group')
|
||||
+ bind: {
|
||||
+ hidden: '{!isLIO}'
|
||||
+ },
|
||||
+ fieldLabel: gettext('Target portal group'),
|
||||
+ allowBlank: true
|
||||
+ },
|
||||
+ {
|
||||
+ xtype: 'proxmoxtextfield',
|
||||
+ name: 'freenas_apiv4_host',
|
||||
+ reference: 'freenas_apiv4_host_field',
|
||||
+ value: '',
|
||||
+ editable: true,
|
||||
+ emptyText: Proxmox.Utils.noneText,
|
||||
+ bind: {
|
||||
+ hidden: '{!isFreeNAS}'
|
||||
+ },
|
||||
+ fieldLabel: gettext('API IPv4 Host'),
|
||||
+ },
|
||||
+ {
|
||||
+ xtype: 'proxmoxtextfield',
|
||||
+ name: 'freenas_password',
|
||||
+ reference: 'freenas_password_field',
|
||||
+ inputType: me.isCreate ? '' : 'password',
|
||||
+ value: '',
|
||||
+ editable: true,
|
||||
+ emptyText: Proxmox.Utils.noneText,
|
||||
+ bind: {
|
||||
+ hidden: '{!isFreeNAS}'
|
||||
+ },
|
||||
+ fieldLabel: gettext('API Password'),
|
||||
+ change: function(f, value) {
|
||||
+ if (f.rendered) {
|
||||
+ f.up().down('field[name=freenas_confirmpw]').validate();
|
||||
+ }
|
||||
+ }
|
||||
+ },
|
||||
+ {
|
||||
+ xtype: 'proxmoxtextfield',
|
||||
+ name: 'freenas_confirmpw',
|
||||
+ reference: 'freenas_confirmpw_field',
|
||||
+ inputType: me.isCreate ? '' : 'password',
|
||||
+ value: '',
|
||||
+ editable: true,
|
||||
+ submitValue: false,
|
||||
+ emptyText: Proxmox.Utils.noneText,
|
||||
+ bind: {
|
||||
+ hidden: '{!isFreeNAS}'
|
||||
+ },
|
||||
+ fieldLabel: gettext('Confirm Password'),
|
||||
+ validator: function(value) {
|
||||
+ var pw = this.up().down('field[name=freenas_password]').getValue();
|
||||
+ if (pw !== value) {
|
||||
+ return "Passwords do not match!";
|
||||
+ }
|
||||
+ return true;
|
||||
+ }
|
||||
}
|
||||
];
|
||||
|
||||
|
|
@ -1,189 +0,0 @@
|
|||
--- pvemanagerlib.js.orig 2019-09-03 04:31:21.000000000 -0400
|
||||
+++ pvemanagerlib.js 2019-09-22 13:54:51.602048049 -0400
|
||||
@@ -6183,6 +6183,7 @@
|
||||
alias: ['widget.pveiScsiProviderSelector'],
|
||||
comboItems: [
|
||||
['comstar', 'Comstar'],
|
||||
+ ['freenas', 'FreeNAS-API'],
|
||||
[ 'istgt', 'istgt'],
|
||||
[ 'iet', 'IET'],
|
||||
[ 'LIO', 'LIO']
|
||||
@@ -32992,6 +32993,7 @@
|
||||
data: {
|
||||
isLIO: false,
|
||||
isComstar: true,
|
||||
+ isFreeNAS: false,
|
||||
hasWriteCacheOption: true
|
||||
}
|
||||
},
|
||||
@@ -33004,10 +33006,26 @@
|
||||
}
|
||||
},
|
||||
changeISCSIProvider: function(f, newVal, oldVal) {
|
||||
+ var me = this;
|
||||
var vm = this.getViewModel();
|
||||
vm.set('isLIO', newVal === 'LIO');
|
||||
vm.set('isComstar', newVal === 'comstar');
|
||||
- vm.set('hasWriteCacheOption', newVal === 'comstar' || newVal === 'istgt');
|
||||
+ vm.set('isFreeNAS', newVal === 'freenas');
|
||||
+ vm.set('hasWriteCacheOption', newVal === 'comstar' || newVal === 'freenas' || newVal === 'istgt');
|
||||
+ if (newVal !== 'freenas') {
|
||||
+ me.lookupReference('freenas_use_ssl_field').setValue(false);
|
||||
+ me.lookupReference('freenas_apiv4_host_field').setValue('');
|
||||
+ me.lookupReference('freenas_user_field').setValue('');
|
||||
+ me.lookupReference('freenas_user_field').allowBlank = true;
|
||||
+ me.lookupReference('freenas_password_field').setValue('');
|
||||
+ me.lookupReference('freenas_password_field').allowBlank = true;
|
||||
+ me.lookupReference('freenas_confirmpw_field').setValue('');
|
||||
+ me.lookupReference('freenas_confirmpw_field').allowBlank = true;
|
||||
+ } else {
|
||||
+ me.lookupReference('freenas_user_field').allowBlank = false;
|
||||
+ me.lookupReference('freenas_password_field').allowBlank = false;
|
||||
+ me.lookupReference('freenas_confirmpw_field').allowBlank = false;
|
||||
+ }
|
||||
}
|
||||
},
|
||||
|
||||
@@ -33025,6 +33043,7 @@
|
||||
},
|
||||
|
||||
setValues: function diff(values) {
|
||||
+ values.freenas_confirmpw = values.freenas_password;
|
||||
values.writecache = values.nowritecache ? 0 : 1;
|
||||
this.callParent([values]);
|
||||
},
|
||||
@@ -33041,7 +33060,7 @@
|
||||
allowBlank: false
|
||||
},
|
||||
{
|
||||
- xtype: me.isCreate ? 'textfield' : 'displayfield',
|
||||
+ xtype: 'textfield',
|
||||
name: 'pool',
|
||||
value: '',
|
||||
fieldLabel: gettext('Pool'),
|
||||
@@ -33051,11 +33070,11 @@
|
||||
xtype: me.isCreate ? 'textfield' : 'displayfield',
|
||||
name: 'blocksize',
|
||||
value: '4k',
|
||||
- fieldLabel: gettext('Block Size'),
|
||||
+ fieldLabel: gettext('ZFS Block Size'),
|
||||
allowBlank: false
|
||||
},
|
||||
{
|
||||
- xtype: me.isCreate ? 'textfield' : 'displayfield',
|
||||
+ xtype: 'textfield',
|
||||
name: 'target',
|
||||
value: '',
|
||||
fieldLabel: gettext('Target'),
|
||||
@@ -33066,8 +33085,33 @@
|
||||
name: 'comstar_tg',
|
||||
value: '',
|
||||
fieldLabel: gettext('Target group'),
|
||||
- bind: me.isCreate ? { disabled: '{!isComstar}' } : { hidden: '{!isComstar}' },
|
||||
+ bind: {
|
||||
+ hidden: '{!isComstar}'
|
||||
+ },
|
||||
allowBlank: true
|
||||
+ },
|
||||
+ {
|
||||
+ xtype: 'proxmoxcheckbox',
|
||||
+ name: 'freenas_use_ssl',
|
||||
+ reference: 'freenas_use_ssl_field',
|
||||
+ inputId: 'freenas_use_ssl_field',
|
||||
+ checked: false,
|
||||
+ bind: {
|
||||
+ hidden: '{!isFreeNAS}'
|
||||
+ },
|
||||
+ uncheckedValue: 0,
|
||||
+ fieldLabel: gettext('API use SSL')
|
||||
+ },
|
||||
+ {
|
||||
+ xtype: 'textfield',
|
||||
+ name: 'freenas_user',
|
||||
+ reference: 'freenas_user_field',
|
||||
+ inputId: 'freenas_user_field',
|
||||
+ value: '',
|
||||
+ fieldLabel: gettext('API Username'),
|
||||
+ bind: {
|
||||
+ hidden: '{!isFreeNAS}'
|
||||
+ }
|
||||
}
|
||||
];
|
||||
|
||||
@@ -33098,7 +33142,9 @@
|
||||
xtype: me.isCreate ? 'textfield' : 'displayfield',
|
||||
name: 'comstar_hg',
|
||||
value: '',
|
||||
- bind: me.isCreate ? { disabled: '{!isComstar}' } : { hidden: '{!isComstar}' },
|
||||
+ bind: {
|
||||
+ hidden: '{!isComstar}'
|
||||
+ },
|
||||
fieldLabel: gettext('Host group'),
|
||||
allowBlank: true
|
||||
},
|
||||
@@ -33106,9 +33152,62 @@
|
||||
xtype: me.isCreate ? 'textfield' : 'displayfield',
|
||||
name: 'lio_tpg',
|
||||
value: '',
|
||||
- bind: me.isCreate ? { disabled: '{!isLIO}' } : { hidden: '{!isLIO}' },
|
||||
- allowBlank: false,
|
||||
- fieldLabel: gettext('Target portal group')
|
||||
+ bind: {
|
||||
+ hidden: '{!isLIO}'
|
||||
+ },
|
||||
+ fieldLabel: gettext('Target portal group'),
|
||||
+ allowBlank: true
|
||||
+ },
|
||||
+ {
|
||||
+ xtype: 'proxmoxtextfield',
|
||||
+ name: 'freenas_apiv4_host',
|
||||
+ reference: 'freenas_apiv4_host_field',
|
||||
+ value: '',
|
||||
+ editable: true,
|
||||
+ emptyText: Proxmox.Utils.noneText,
|
||||
+ bind: {
|
||||
+ hidden: '{!isFreeNAS}'
|
||||
+ },
|
||||
+ fieldLabel: gettext('API IPv4 Host'),
|
||||
+ },
|
||||
+ {
|
||||
+ xtype: 'proxmoxtextfield',
|
||||
+ name: 'freenas_password',
|
||||
+ reference: 'freenas_password_field',
|
||||
+ inputType: me.isCreate ? '' : 'password',
|
||||
+ value: '',
|
||||
+ editable: true,
|
||||
+ emptyText: Proxmox.Utils.noneText,
|
||||
+ bind: {
|
||||
+ hidden: '{!isFreeNAS}'
|
||||
+ },
|
||||
+ fieldLabel: gettext('API Password'),
|
||||
+ change: function(f, value) {
|
||||
+ if (f.rendered) {
|
||||
+ f.up().down('field[name=freenas_confirmpw]').validate();
|
||||
+ }
|
||||
+ }
|
||||
+ },
|
||||
+ {
|
||||
+ xtype: 'proxmoxtextfield',
|
||||
+ name: 'freenas_confirmpw',
|
||||
+ reference: 'freenas_confirmpw_field',
|
||||
+ inputType: me.isCreate ? '' : 'password',
|
||||
+ value: '',
|
||||
+ editable: true,
|
||||
+ submitValue: false,
|
||||
+ emptyText: Proxmox.Utils.noneText,
|
||||
+ bind: {
|
||||
+ hidden: '{!isFreeNAS}'
|
||||
+ },
|
||||
+ fieldLabel: gettext('Confirm Password'),
|
||||
+ validator: function(value) {
|
||||
+ var pw = this.up().down('field[name=freenas_password]').getValue();
|
||||
+ if (pw !== value) {
|
||||
+ return "Passwords do not match!";
|
||||
+ }
|
||||
+ return true;
|
||||
+ }
|
||||
}
|
||||
];
|
||||
|
||||
|
|
@ -1,189 +0,0 @@
|
|||
--- pvemanagerlib.js.orig 2021-03-09 02:22:47.000000000 -0500
|
||||
+++ pvemanagerlib.js 2021-03-20 11:52:39.694828636 -0400
|
||||
@@ -7849,6 +7849,7 @@
|
||||
alias: ['widget.pveiScsiProviderSelector'],
|
||||
comboItems: [
|
||||
['comstar', 'Comstar'],
|
||||
+ ['freenas', 'FreeNAS-API'],
|
||||
['istgt', 'istgt'],
|
||||
['iet', 'IET'],
|
||||
['LIO', 'LIO'],
|
||||
@@ -46874,6 +46875,7 @@
|
||||
data: {
|
||||
isLIO: false,
|
||||
isComstar: true,
|
||||
+ isFreeNAS: false,
|
||||
hasWriteCacheOption: true,
|
||||
},
|
||||
},
|
||||
@@ -46886,10 +46888,26 @@
|
||||
},
|
||||
},
|
||||
changeISCSIProvider: function(f, newVal, oldVal) {
|
||||
+ var me = this;
|
||||
var vm = this.getViewModel();
|
||||
vm.set('isLIO', newVal === 'LIO');
|
||||
vm.set('isComstar', newVal === 'comstar');
|
||||
- vm.set('hasWriteCacheOption', newVal === 'comstar' || newVal === 'istgt');
|
||||
+ vm.set('isFreeNAS', newVal === 'freenas');
|
||||
+ vm.set('hasWriteCacheOption', newVal === 'comstar' || newVal === 'freenas' || newVal === 'istgt');
|
||||
+ if (newVal !== 'freenas') {
|
||||
+ me.lookupReference('freenas_use_ssl_field').setValue(false);
|
||||
+ me.lookupReference('freenas_apiv4_host_field').setValue('');
|
||||
+ me.lookupReference('freenas_user_field').setValue('');
|
||||
+ me.lookupReference('freenas_user_field').allowBlank = true;
|
||||
+ me.lookupReference('freenas_password_field').setValue('');
|
||||
+ me.lookupReference('freenas_password_field').allowBlank = true;
|
||||
+ me.lookupReference('freenas_confirmpw_field').setValue('');
|
||||
+ me.lookupReference('freenas_confirmpw_field').allowBlank = true;
|
||||
+ } else {
|
||||
+ me.lookupReference('freenas_user_field').allowBlank = false;
|
||||
+ me.lookupReference('freenas_password_field').allowBlank = false;
|
||||
+ me.lookupReference('freenas_confirmpw_field').allowBlank = false;
|
||||
+ }
|
||||
},
|
||||
},
|
||||
|
||||
@@ -46907,6 +46925,7 @@
|
||||
},
|
||||
|
||||
setValues: function diff(values) {
|
||||
+ values.freenas_confirmpw = values.freenas_password;
|
||||
values.writecache = values.nowritecache ? 0 : 1;
|
||||
this.callParent([values]);
|
||||
},
|
||||
@@ -46923,7 +46942,7 @@
|
||||
allowBlank: false,
|
||||
},
|
||||
{
|
||||
- xtype: me.isCreate ? 'textfield' : 'displayfield',
|
||||
+ xtype: 'textfield',
|
||||
name: 'pool',
|
||||
value: '',
|
||||
fieldLabel: gettext('Pool'),
|
||||
@@ -46933,11 +46952,11 @@
|
||||
xtype: me.isCreate ? 'textfield' : 'displayfield',
|
||||
name: 'blocksize',
|
||||
value: '4k',
|
||||
- fieldLabel: gettext('Block Size'),
|
||||
+ fieldLabel: gettext('ZFS Block Size'),
|
||||
allowBlank: false,
|
||||
},
|
||||
{
|
||||
- xtype: me.isCreate ? 'textfield' : 'displayfield',
|
||||
+ xtype: 'textfield',
|
||||
name: 'target',
|
||||
value: '',
|
||||
fieldLabel: gettext('Target'),
|
||||
@@ -46948,9 +46967,34 @@
|
||||
name: 'comstar_tg',
|
||||
value: '',
|
||||
fieldLabel: gettext('Target group'),
|
||||
- bind: me.isCreate ? { disabled: '{!isComstar}' } : { hidden: '{!isComstar}' },
|
||||
+ bind: {
|
||||
+ hidden: '{!isComstar}'
|
||||
+ },
|
||||
allowBlank: true,
|
||||
},
|
||||
+ {
|
||||
+ xtype: 'proxmoxcheckbox',
|
||||
+ name: 'freenas_use_ssl',
|
||||
+ reference: 'freenas_use_ssl_field',
|
||||
+ inputId: 'freenas_use_ssl_field',
|
||||
+ checked: false,
|
||||
+ bind: {
|
||||
+ hidden: '{!isFreeNAS}'
|
||||
+ },
|
||||
+ uncheckedValue: 0,
|
||||
+ fieldLabel: gettext('API use SSL'),
|
||||
+ },
|
||||
+ {
|
||||
+ xtype: 'textfield',
|
||||
+ name: 'freenas_user',
|
||||
+ reference: 'freenas_user_field',
|
||||
+ inputId: 'freenas_user_field',
|
||||
+ value: '',
|
||||
+ fieldLabel: gettext('API Username'),
|
||||
+ bind: {
|
||||
+ hidden: '{!isFreeNAS}'
|
||||
+ },
|
||||
+ },
|
||||
];
|
||||
|
||||
me.column2 = [
|
||||
@@ -46980,7 +47024,9 @@
|
||||
xtype: me.isCreate ? 'textfield' : 'displayfield',
|
||||
name: 'comstar_hg',
|
||||
value: '',
|
||||
- bind: me.isCreate ? { disabled: '{!isComstar}' } : { hidden: '{!isComstar}' },
|
||||
+ bind: {
|
||||
+ hidden: '{!isComstar}'
|
||||
+ },
|
||||
fieldLabel: gettext('Host group'),
|
||||
allowBlank: true,
|
||||
},
|
||||
@@ -46988,9 +47034,62 @@
|
||||
xtype: me.isCreate ? 'textfield' : 'displayfield',
|
||||
name: 'lio_tpg',
|
||||
value: '',
|
||||
- bind: me.isCreate ? { disabled: '{!isLIO}' } : { hidden: '{!isLIO}' },
|
||||
- allowBlank: false,
|
||||
+ bind: {
|
||||
+ hidden: '{!isLIO}'
|
||||
+ },
|
||||
fieldLabel: gettext('Target portal group'),
|
||||
+ allowBlank: true
|
||||
+ },
|
||||
+ {
|
||||
+ xtype: 'proxmoxtextfield',
|
||||
+ name: 'freenas_apiv4_host',
|
||||
+ reference: 'freenas_apiv4_host_field',
|
||||
+ value: '',
|
||||
+ editable: true,
|
||||
+ emptyText: Proxmox.Utils.noneText,
|
||||
+ bind: {
|
||||
+ hidden: '{!isFreeNAS}'
|
||||
+ },
|
||||
+ fieldLabel: gettext('API IPv4 Host'),
|
||||
+ },
|
||||
+ {
|
||||
+ xtype: 'proxmoxtextfield',
|
||||
+ name: 'freenas_password',
|
||||
+ reference: 'freenas_password_field',
|
||||
+ inputType: me.isCreate ? '' : 'password',
|
||||
+ value: '',
|
||||
+ editable: true,
|
||||
+ emptyText: Proxmox.Utils.noneText,
|
||||
+ bind: {
|
||||
+ hidden: '{!isFreeNAS}'
|
||||
+ },
|
||||
+ fieldLabel: gettext('API Password'),
|
||||
+ change: function(f, value) {
|
||||
+ if (f.rendered) {
|
||||
+ f.up().down('field[name=freenas_confirmpw]').validate();
|
||||
+ }
|
||||
+ },
|
||||
+ },
|
||||
+ {
|
||||
+ xtype: 'proxmoxtextfield',
|
||||
+ name: 'freenas_confirmpw',
|
||||
+ reference: 'freenas_confirmpw_field',
|
||||
+ inputType: me.isCreate ? '' : 'password',
|
||||
+ value: '',
|
||||
+ editable: true,
|
||||
+ submitValue: false,
|
||||
+ emptyText: Proxmox.Utils.noneText,
|
||||
+ bind: {
|
||||
+ hidden: '{!isFreeNAS}'
|
||||
+ },
|
||||
+ fieldLabel: gettext('Confirm Password'),
|
||||
+ validator: function(value) {
|
||||
+ var pw = this.up().down('field[name=freenas_password]').getValue();
|
||||
+ if (pw !== value) {
|
||||
+ return "Passwords do not match!";
|
||||
+ }
|
||||
+ return true;
|
||||
+ },
|
||||
},
|
||||
];
|
||||
|
||||
|
|
@ -1,189 +0,0 @@
|
|||
--- pvemanagerlib.js.orig 2021-05-27 08:28:35.000000000 -0400
|
||||
+++ pvemanagerlib.js 2021-06-10 11:03:15.380175988 -0400
|
||||
@@ -7900,6 +7900,7 @@
|
||||
alias: ['widget.pveiScsiProviderSelector'],
|
||||
comboItems: [
|
||||
['comstar', 'Comstar'],
|
||||
+ ['freenas', 'FreeNAS-API'],
|
||||
['istgt', 'istgt'],
|
||||
['iet', 'IET'],
|
||||
['LIO', 'LIO'],
|
||||
@@ -47367,6 +47368,7 @@
|
||||
data: {
|
||||
isLIO: false,
|
||||
isComstar: true,
|
||||
+ isFreeNAS: false,
|
||||
hasWriteCacheOption: true,
|
||||
},
|
||||
},
|
||||
@@ -47379,10 +47381,26 @@
|
||||
},
|
||||
},
|
||||
changeISCSIProvider: function(f, newVal, oldVal) {
|
||||
+ var me = this;
|
||||
var vm = this.getViewModel();
|
||||
vm.set('isLIO', newVal === 'LIO');
|
||||
vm.set('isComstar', newVal === 'comstar');
|
||||
- vm.set('hasWriteCacheOption', newVal === 'comstar' || newVal === 'istgt');
|
||||
+ vm.set('isFreeNAS', newVal === 'freenas');
|
||||
+ vm.set('hasWriteCacheOption', newVal === 'comstar' || newVal === 'freenas' || newVal === 'istgt');
|
||||
+ if (newVal !== 'freenas') {
|
||||
+ me.lookupReference('freenas_use_ssl_field').setValue(false);
|
||||
+ me.lookupReference('freenas_apiv4_host_field').setValue('');
|
||||
+ me.lookupReference('freenas_user_field').setValue('');
|
||||
+ me.lookupReference('freenas_user_field').allowBlank = true;
|
||||
+ me.lookupReference('freenas_password_field').setValue('');
|
||||
+ me.lookupReference('freenas_password_field').allowBlank = true;
|
||||
+ me.lookupReference('freenas_confirmpw_field').setValue('');
|
||||
+ me.lookupReference('freenas_confirmpw_field').allowBlank = true;
|
||||
+ } else {
|
||||
+ me.lookupReference('freenas_user_field').allowBlank = false;
|
||||
+ me.lookupReference('freenas_password_field').allowBlank = false;
|
||||
+ me.lookupReference('freenas_confirmpw_field').allowBlank = false;
|
||||
+ }
|
||||
},
|
||||
},
|
||||
|
||||
@@ -47400,6 +47418,7 @@
|
||||
},
|
||||
|
||||
setValues: function(values) {
|
||||
+ values.freenas_confirmpw = values.freenas_password;
|
||||
values.writecache = values.nowritecache ? 0 : 1;
|
||||
this.callParent([values]);
|
||||
},
|
||||
@@ -47416,7 +47435,7 @@
|
||||
allowBlank: false,
|
||||
},
|
||||
{
|
||||
- xtype: me.isCreate ? 'textfield' : 'displayfield',
|
||||
+ xtype: 'textfield',
|
||||
name: 'pool',
|
||||
value: '',
|
||||
fieldLabel: gettext('Pool'),
|
||||
@@ -47426,11 +47445,11 @@
|
||||
xtype: me.isCreate ? 'textfield' : 'displayfield',
|
||||
name: 'blocksize',
|
||||
value: '4k',
|
||||
- fieldLabel: gettext('Block Size'),
|
||||
+ fieldLabel: gettext('ZFS Block Size'),
|
||||
allowBlank: false,
|
||||
},
|
||||
{
|
||||
- xtype: me.isCreate ? 'textfield' : 'displayfield',
|
||||
+ xtype: 'textfield',
|
||||
name: 'target',
|
||||
value: '',
|
||||
fieldLabel: gettext('Target'),
|
||||
@@ -47441,9 +47460,34 @@
|
||||
name: 'comstar_tg',
|
||||
value: '',
|
||||
fieldLabel: gettext('Target group'),
|
||||
- bind: me.isCreate ? { disabled: '{!isComstar}' } : { hidden: '{!isComstar}' },
|
||||
+ bind: {
|
||||
+ hidden: '{!isComstar}'
|
||||
+ },
|
||||
allowBlank: true,
|
||||
},
|
||||
+ {
|
||||
+ xtype: 'proxmoxcheckbox',
|
||||
+ name: 'freenas_use_ssl',
|
||||
+ reference: 'freenas_use_ssl_field',
|
||||
+ inputId: 'freenas_use_ssl_field',
|
||||
+ checked: false,
|
||||
+ bind: {
|
||||
+ hidden: '{!isFreeNAS}'
|
||||
+ },
|
||||
+ uncheckedValue: 0,
|
||||
+ fieldLabel: gettext('API use SSL'),
|
||||
+ },
|
||||
+ {
|
||||
+ xtype: 'textfield',
|
||||
+ name: 'freenas_user',
|
||||
+ reference: 'freenas_user_field',
|
||||
+ inputId: 'freenas_user_field',
|
||||
+ value: '',
|
||||
+ fieldLabel: gettext('API Username'),
|
||||
+ bind: {
|
||||
+ hidden: '{!isFreeNAS}'
|
||||
+ },
|
||||
+ },
|
||||
];
|
||||
|
||||
me.column2 = [
|
||||
@@ -47473,7 +47517,9 @@
|
||||
xtype: me.isCreate ? 'textfield' : 'displayfield',
|
||||
name: 'comstar_hg',
|
||||
value: '',
|
||||
- bind: me.isCreate ? { disabled: '{!isComstar}' } : { hidden: '{!isComstar}' },
|
||||
+ bind: {
|
||||
+ hidden: '{!isComstar}'
|
||||
+ },
|
||||
fieldLabel: gettext('Host group'),
|
||||
allowBlank: true,
|
||||
},
|
||||
@@ -47481,9 +47527,62 @@
|
||||
xtype: me.isCreate ? 'textfield' : 'displayfield',
|
||||
name: 'lio_tpg',
|
||||
value: '',
|
||||
- bind: me.isCreate ? { disabled: '{!isLIO}' } : { hidden: '{!isLIO}' },
|
||||
- allowBlank: false,
|
||||
+ bind: {
|
||||
+ hidden: '{!isLIO}'
|
||||
+ },
|
||||
fieldLabel: gettext('Target portal group'),
|
||||
+ allowBlank: true
|
||||
+ },
|
||||
+ {
|
||||
+ xtype: 'proxmoxtextfield',
|
||||
+ name: 'freenas_apiv4_host',
|
||||
+ reference: 'freenas_apiv4_host_field',
|
||||
+ value: '',
|
||||
+ editable: true,
|
||||
+ emptyText: Proxmox.Utils.noneText,
|
||||
+ bind: {
|
||||
+ hidden: '{!isFreeNAS}'
|
||||
+ },
|
||||
+ fieldLabel: gettext('API IPv4 Host'),
|
||||
+ },
|
||||
+ {
|
||||
+ xtype: 'proxmoxtextfield',
|
||||
+ name: 'freenas_password',
|
||||
+ reference: 'freenas_password_field',
|
||||
+ inputType: me.isCreate ? '' : 'password',
|
||||
+ value: '',
|
||||
+ editable: true,
|
||||
+ emptyText: Proxmox.Utils.noneText,
|
||||
+ bind: {
|
||||
+ hidden: '{!isFreeNAS}'
|
||||
+ },
|
||||
+ fieldLabel: gettext('API Password'),
|
||||
+ change: function(f, value) {
|
||||
+ if (f.rendered) {
|
||||
+ f.up().down('field[name=freenas_confirmpw]').validate();
|
||||
+ }
|
||||
+ },
|
||||
+ },
|
||||
+ {
|
||||
+ xtype: 'proxmoxtextfield',
|
||||
+ name: 'freenas_confirmpw',
|
||||
+ reference: 'freenas_confirmpw_field',
|
||||
+ inputType: me.isCreate ? '' : 'password',
|
||||
+ value: '',
|
||||
+ editable: true,
|
||||
+ submitValue: false,
|
||||
+ emptyText: Proxmox.Utils.noneText,
|
||||
+ bind: {
|
||||
+ hidden: '{!isFreeNAS}'
|
||||
+ },
|
||||
+ fieldLabel: gettext('Confirm Password'),
|
||||
+ validator: function(value) {
|
||||
+ var pw = this.up().down('field[name=freenas_password]').getValue();
|
||||
+ if (pw !== value) {
|
||||
+ return "Passwords do not match!";
|
||||
+ }
|
||||
+ return true;
|
||||
+ },
|
||||
},
|
||||
];
|
||||
|
||||
|
|
@ -1,189 +0,0 @@
|
|||
--- pvemanagerlib.js.orig 2022-03-17 09:08:40.000000000 -0400
|
||||
+++ pvemanagerlib.js 2022-04-03 08:54:10.229689187 -0400
|
||||
@@ -8068,6 +8068,7 @@
|
||||
alias: ['widget.pveiScsiProviderSelector'],
|
||||
comboItems: [
|
||||
['comstar', 'Comstar'],
|
||||
+ ['freenas', 'FreeNAS-API'],
|
||||
['istgt', 'istgt'],
|
||||
['iet', 'IET'],
|
||||
['LIO', 'LIO'],
|
||||
@@ -49636,6 +49637,7 @@
|
||||
data: {
|
||||
isLIO: false,
|
||||
isComstar: true,
|
||||
+ isFreeNAS: false,
|
||||
hasWriteCacheOption: true,
|
||||
},
|
||||
},
|
||||
@@ -49648,10 +49650,26 @@
|
||||
},
|
||||
},
|
||||
changeISCSIProvider: function(f, newVal, oldVal) {
|
||||
+ var me = this;
|
||||
var vm = this.getViewModel();
|
||||
vm.set('isLIO', newVal === 'LIO');
|
||||
vm.set('isComstar', newVal === 'comstar');
|
||||
- vm.set('hasWriteCacheOption', newVal === 'comstar' || newVal === 'istgt');
|
||||
+ vm.set('isFreeNAS', newVal === 'freenas');
|
||||
+ vm.set('hasWriteCacheOption', newVal === 'comstar' || newVal === 'freenas' || newVal === 'istgt');
|
||||
+ if (newVal !== 'freenas') {
|
||||
+ me.lookupReference('freenas_use_ssl_field').setValue(false);
|
||||
+ me.lookupReference('freenas_apiv4_host_field').setValue('');
|
||||
+ me.lookupReference('freenas_user_field').setValue('');
|
||||
+ me.lookupReference('freenas_user_field').allowBlank = true;
|
||||
+ me.lookupReference('freenas_password_field').setValue('');
|
||||
+ me.lookupReference('freenas_password_field').allowBlank = true;
|
||||
+ me.lookupReference('freenas_confirmpw_field').setValue('');
|
||||
+ me.lookupReference('freenas_confirmpw_field').allowBlank = true;
|
||||
+ } else {
|
||||
+ me.lookupReference('freenas_user_field').allowBlank = false;
|
||||
+ me.lookupReference('freenas_password_field').allowBlank = false;
|
||||
+ me.lookupReference('freenas_confirmpw_field').allowBlank = false;
|
||||
+ }
|
||||
},
|
||||
},
|
||||
|
||||
@@ -49669,6 +49687,7 @@
|
||||
},
|
||||
|
||||
setValues: function(values) {
|
||||
+ values.freenas_confirmpw = values.freenas_password;
|
||||
values.writecache = values.nowritecache ? 0 : 1;
|
||||
this.callParent([values]);
|
||||
},
|
||||
@@ -49685,7 +49704,7 @@
|
||||
allowBlank: false,
|
||||
},
|
||||
{
|
||||
- xtype: me.isCreate ? 'textfield' : 'displayfield',
|
||||
+ xtype: 'textfield',
|
||||
name: 'pool',
|
||||
value: '',
|
||||
fieldLabel: gettext('Pool'),
|
||||
@@ -49695,11 +49714,11 @@
|
||||
xtype: me.isCreate ? 'textfield' : 'displayfield',
|
||||
name: 'blocksize',
|
||||
value: '4k',
|
||||
- fieldLabel: gettext('Block Size'),
|
||||
+ fieldLabel: gettext('ZFS Block Size'),
|
||||
allowBlank: false,
|
||||
},
|
||||
{
|
||||
- xtype: me.isCreate ? 'textfield' : 'displayfield',
|
||||
+ xtype: 'textfield',
|
||||
name: 'target',
|
||||
value: '',
|
||||
fieldLabel: gettext('Target'),
|
||||
@@ -49710,9 +49729,34 @@
|
||||
name: 'comstar_tg',
|
||||
value: '',
|
||||
fieldLabel: gettext('Target group'),
|
||||
- bind: me.isCreate ? { disabled: '{!isComstar}' } : { hidden: '{!isComstar}' },
|
||||
+ bind: {
|
||||
+ hidden: '{!isComstar}'
|
||||
+ },
|
||||
allowBlank: true,
|
||||
},
|
||||
+ {
|
||||
+ xtype: 'proxmoxcheckbox',
|
||||
+ name: 'freenas_use_ssl',
|
||||
+ reference: 'freenas_use_ssl_field',
|
||||
+ inputId: 'freenas_use_ssl_field',
|
||||
+ checked: false,
|
||||
+ bind: {
|
||||
+ hidden: '{!isFreeNAS}'
|
||||
+ },
|
||||
+ uncheckedValue: 0,
|
||||
+ fieldLabel: gettext('API use SSL'),
|
||||
+ },
|
||||
+ {
|
||||
+ xtype: 'textfield',
|
||||
+ name: 'freenas_user',
|
||||
+ reference: 'freenas_user_field',
|
||||
+ inputId: 'freenas_user_field',
|
||||
+ value: '',
|
||||
+ fieldLabel: gettext('API Username'),
|
||||
+ bind: {
|
||||
+ hidden: '{!isFreeNAS}'
|
||||
+ },
|
||||
+ },
|
||||
];
|
||||
|
||||
me.column2 = [
|
||||
@@ -49742,7 +49786,9 @@
|
||||
xtype: me.isCreate ? 'textfield' : 'displayfield',
|
||||
name: 'comstar_hg',
|
||||
value: '',
|
||||
- bind: me.isCreate ? { disabled: '{!isComstar}' } : { hidden: '{!isComstar}' },
|
||||
+ bind: {
|
||||
+ hidden: '{!isComstar}'
|
||||
+ },
|
||||
fieldLabel: gettext('Host group'),
|
||||
allowBlank: true,
|
||||
},
|
||||
@@ -49750,9 +49796,62 @@
|
||||
xtype: me.isCreate ? 'textfield' : 'displayfield',
|
||||
name: 'lio_tpg',
|
||||
value: '',
|
||||
- bind: me.isCreate ? { disabled: '{!isLIO}' } : { hidden: '{!isLIO}' },
|
||||
- allowBlank: false,
|
||||
+ bind: {
|
||||
+ hidden: '{!isLIO}'
|
||||
+ },
|
||||
fieldLabel: gettext('Target portal group'),
|
||||
+ allowBlank: true
|
||||
+ },
|
||||
+ {
|
||||
+ xtype: 'proxmoxtextfield',
|
||||
+ name: 'freenas_apiv4_host',
|
||||
+ reference: 'freenas_apiv4_host_field',
|
||||
+ value: '',
|
||||
+ editable: true,
|
||||
+ emptyText: Proxmox.Utils.noneText,
|
||||
+ bind: {
|
||||
+ hidden: '{!isFreeNAS}'
|
||||
+ },
|
||||
+ fieldLabel: gettext('API IPv4 Host'),
|
||||
+ },
|
||||
+ {
|
||||
+ xtype: 'proxmoxtextfield',
|
||||
+ name: 'freenas_password',
|
||||
+ reference: 'freenas_password_field',
|
||||
+ inputType: me.isCreate ? '' : 'password',
|
||||
+ value: '',
|
||||
+ editable: true,
|
||||
+ emptyText: Proxmox.Utils.noneText,
|
||||
+ bind: {
|
||||
+ hidden: '{!isFreeNAS}'
|
||||
+ },
|
||||
+ fieldLabel: gettext('API Password'),
|
||||
+ change: function(f, value) {
|
||||
+ if (f.rendered) {
|
||||
+ f.up().down('field[name=freenas_confirmpw]').validate();
|
||||
+ }
|
||||
+ },
|
||||
+ },
|
||||
+ {
|
||||
+ xtype: 'proxmoxtextfield',
|
||||
+ name: 'freenas_confirmpw',
|
||||
+ reference: 'freenas_confirmpw_field',
|
||||
+ inputType: me.isCreate ? '' : 'password',
|
||||
+ value: '',
|
||||
+ editable: true,
|
||||
+ submitValue: false,
|
||||
+ emptyText: Proxmox.Utils.noneText,
|
||||
+ bind: {
|
||||
+ hidden: '{!isFreeNAS}'
|
||||
+ },
|
||||
+ fieldLabel: gettext('Confirm Password'),
|
||||
+ validator: function(value) {
|
||||
+ var pw = this.up().down('field[name=freenas_password]').getValue();
|
||||
+ if (pw !== value) {
|
||||
+ return "Passwords do not match!";
|
||||
+ }
|
||||
+ return true;
|
||||
+ },
|
||||
},
|
||||
];
|
||||
|
||||
|
|
@ -1,286 +0,0 @@
|
|||
--- pvemanagerlib.js.orig 2024-01-06 14:50:39.890252726 -0500
|
||||
+++ pvemanagerlib.js 2024-01-06 15:51:53.708260622 -0500
|
||||
@@ -9228,6 +9228,7 @@
|
||||
alias: ['widget.pveiScsiProviderSelector'],
|
||||
comboItems: [
|
||||
['comstar', 'Comstar'],
|
||||
+ ['freenas', 'FreeNAS/TrueNAS API'],
|
||||
['istgt', 'istgt'],
|
||||
['iet', 'IET'],
|
||||
['LIO', 'LIO'],
|
||||
@@ -58017,16 +58018,23 @@
|
||||
me.callParent();
|
||||
},
|
||||
});
|
||||
+
|
||||
Ext.define('PVE.storage.ZFSInputPanel', {
|
||||
extend: 'PVE.panel.StorageBase',
|
||||
-
|
||||
viewModel: {
|
||||
- parent: null,
|
||||
- data: {
|
||||
- isLIO: false,
|
||||
- isComstar: true,
|
||||
- hasWriteCacheOption: true,
|
||||
- },
|
||||
+ parent: null,
|
||||
+ data: {
|
||||
+ isComstar: true,
|
||||
+ isFreeNAS: false,
|
||||
+ isLIO: false,
|
||||
+ isToken: false,
|
||||
+ hasWriteCacheOption: true,
|
||||
+ },
|
||||
+ formulas: {
|
||||
+ hideUsername: function(get) {
|
||||
+ return (!get('isFreeNAS') || !(get('isFreeNAS') && !get('isToken')));
|
||||
+ },
|
||||
+ },
|
||||
},
|
||||
|
||||
controller: {
|
||||
@@ -58035,12 +58043,41 @@
|
||||
'field[name=iscsiprovider]': {
|
||||
change: 'changeISCSIProvider',
|
||||
},
|
||||
+ 'field[name=truenas_token_auth]': {
|
||||
+ change: 'changeUsername',
|
||||
+ },
|
||||
},
|
||||
changeISCSIProvider: function(f, newVal, oldVal) {
|
||||
+ var me = this;
|
||||
var vm = this.getViewModel();
|
||||
vm.set('isLIO', newVal === 'LIO');
|
||||
vm.set('isComstar', newVal === 'comstar');
|
||||
- vm.set('hasWriteCacheOption', newVal === 'comstar' || newVal === 'istgt');
|
||||
+ vm.set('isFreeNAS', newVal === 'freenas');
|
||||
+ vm.set('hasWriteCacheOption', newVal === 'comstar' || newVal === 'freenas' || newVal === 'istgt');
|
||||
+ if (newVal !== 'freenas') {
|
||||
+ me.lookupReference('freenas_use_ssl_field').setValue(false);
|
||||
+ me.lookupReference('truenas_token_auth_field').setValue(false);
|
||||
+ me.lookupReference('freenas_apiv4_host_field').setValue('');
|
||||
+ me.lookupReference('freenas_user_field').setValue('');
|
||||
+ me.lookupReference('freenas_user_field').allowBlank = true;
|
||||
+ me.lookupReference('truenas_secret_field').setValue('');
|
||||
+ me.lookupReference('truenas_secret_field').allowBlank = true;
|
||||
+ me.lookupReference('truenas_confirm_secret_field').setValue('');
|
||||
+ me.lookupReference('truenas_confirm_secret_field').allowBlank = true;
|
||||
+ } else {
|
||||
+ me.lookupReference('freenas_user_field').allowBlank = false;
|
||||
+ me.lookupReference('truenas_secret_field').allowBlank = false;
|
||||
+ me.lookupReference('truenas_confirm_secret_field').allowBlank = false;
|
||||
+ }
|
||||
+ },
|
||||
+ changeUsername: function(f, newVal, oldVal) {
|
||||
+ var me = this;
|
||||
+ var vm = me.getViewModel();
|
||||
+ vm.set('isToken', newVal);
|
||||
+ me.lookupReference('freenas_user_field').allowBlank = newVal;
|
||||
+ if (newVal) {
|
||||
+ me.lookupReference('freenas_user_field').setValue('');
|
||||
+ }
|
||||
},
|
||||
},
|
||||
|
||||
@@ -58053,18 +58090,68 @@
|
||||
|
||||
values.nowritecache = values.writecache ? 0 : 1;
|
||||
delete values.writecache;
|
||||
+ console.warn(values.freenas_password);
|
||||
+ if (values.freenas_password) {
|
||||
+ values.truenas_secret = values.freenas_password;
|
||||
+ }
|
||||
+ console.warn(values.truenas_secret);
|
||||
|
||||
return me.callParent([values]);
|
||||
},
|
||||
|
||||
setValues: function(values) {
|
||||
- values.writecache = values.nowritecache ? 0 : 1;
|
||||
- this.callParent([values]);
|
||||
+ if (values.freenas_password) {
|
||||
+ values.truenas_secret = values.freenas_password;
|
||||
+ }
|
||||
+ values.truenas_confirm_secret = values.truenas_secret;
|
||||
+ values.writecache = values.nowritecache ? 0 : 1;
|
||||
+ this.callParent([values]);
|
||||
},
|
||||
|
||||
initComponent: function() {
|
||||
var me = this;
|
||||
|
||||
+ var tnsecret = Ext.create('Ext.form.TextField', {
|
||||
+ xtype: 'proxmoxtextfield',
|
||||
+ name: 'truenas_secret',
|
||||
+ reference: 'truenas_secret_field',
|
||||
+ inputType: me.isCreate ? '' : 'password',
|
||||
+ value: '',
|
||||
+ editable: true,
|
||||
+ emptyText: Proxmox.Utils.noneText,
|
||||
+ bind: {
|
||||
+ hidden: '{!isFreeNAS}'
|
||||
+ },
|
||||
+ fieldLabel: gettext('API Password'),
|
||||
+ change: function(f, value) {
|
||||
+ if (f.rendered) {
|
||||
+ f.up().down('field[name=truenas_confirm_secret]').validate();
|
||||
+ }
|
||||
+ },
|
||||
+ });
|
||||
+
|
||||
+ var tnconfirmsecret = Ext.create('Ext.form.TextField', {
|
||||
+ xtype: 'proxmoxtextfield',
|
||||
+ name: 'truenas_confirm_secret',
|
||||
+ reference: 'truenas_confirm_secret_field',
|
||||
+ inputType: me.isCreate ? '' : 'password',
|
||||
+ value: '',
|
||||
+ editable: true,
|
||||
+ submitValue: false,
|
||||
+ emptyText: Proxmox.Utils.noneText,
|
||||
+ bind: {
|
||||
+ hidden: '{!isFreeNAS}'
|
||||
+ },
|
||||
+ fieldLabel: gettext('Confirm API Password'),
|
||||
+ validator: function(value) {
|
||||
+ var pw = me.up().down('field[name=truenas_secret]').getValue();
|
||||
+ if (pw !== value) {
|
||||
+ return "Secrets do not match!";
|
||||
+ }
|
||||
+ return true;
|
||||
+ },
|
||||
+ });
|
||||
+
|
||||
me.column1 = [
|
||||
{
|
||||
xtype: me.isCreate ? 'textfield' : 'displayfield',
|
||||
@@ -58074,7 +58161,7 @@
|
||||
allowBlank: false,
|
||||
},
|
||||
{
|
||||
- xtype: me.isCreate ? 'textfield' : 'displayfield',
|
||||
+ xtype: 'textfield',
|
||||
name: 'pool',
|
||||
value: '',
|
||||
fieldLabel: gettext('Pool'),
|
||||
@@ -58084,11 +58171,11 @@
|
||||
xtype: me.isCreate ? 'textfield' : 'displayfield',
|
||||
name: 'blocksize',
|
||||
value: '4k',
|
||||
- fieldLabel: gettext('Block Size'),
|
||||
+ fieldLabel: gettext('ZFS Block Size'),
|
||||
allowBlank: false,
|
||||
},
|
||||
{
|
||||
- xtype: me.isCreate ? 'textfield' : 'displayfield',
|
||||
+ xtype: 'textfield',
|
||||
name: 'target',
|
||||
value: '',
|
||||
fieldLabel: gettext('Target'),
|
||||
@@ -58099,9 +58186,60 @@
|
||||
name: 'comstar_tg',
|
||||
value: '',
|
||||
fieldLabel: gettext('Target group'),
|
||||
- bind: me.isCreate ? { disabled: '{!isComstar}' } : { hidden: '{!isComstar}' },
|
||||
+ bind: {
|
||||
+ hidden: '{!isComstar}'
|
||||
+ },
|
||||
allowBlank: true,
|
||||
},
|
||||
+ {
|
||||
+ xtype: 'proxmoxcheckbox',
|
||||
+ name: 'freenas_use_ssl',
|
||||
+ reference: 'freenas_use_ssl_field',
|
||||
+ inputId: 'freenas_use_ssl_field',
|
||||
+ checked: false,
|
||||
+ bind: {
|
||||
+ hidden: '{!isFreeNAS}'
|
||||
+ },
|
||||
+ uncheckedValue: 0,
|
||||
+ fieldLabel: gettext('API use SSL'),
|
||||
+ },
|
||||
+ {
|
||||
+ xtype: 'proxmoxcheckbox',
|
||||
+ name: 'truenas_token_auth',
|
||||
+ reference: 'truenas_token_auth_field',
|
||||
+ inputId: 'truenas_use_token_auth_field',
|
||||
+ checked: false,
|
||||
+ listeners: {
|
||||
+ change: function(field, newValue) {
|
||||
+ if (newValue === true) {
|
||||
+ tnsecret.labelEl.update('API Token');
|
||||
+ tnconfirmsecret.labelEl.update('Confirm API Token');
|
||||
+ me.lookupReference('freenas_user_field').setValue('');
|
||||
+ me.lookupReference('freenas_user_field').allowBlank = true;
|
||||
+ } else {
|
||||
+ tnsecret.labelEl.update('API Password');
|
||||
+ tnconfirmsecret.labelEl.update('Confirm API Password');
|
||||
+ me.lookupReference('freenas_user_field').allowBlank = false;
|
||||
+ }
|
||||
+ },
|
||||
+ },
|
||||
+ bind: {
|
||||
+ hidden: '{!isFreeNAS}'
|
||||
+ },
|
||||
+ uncheckedValue: 0,
|
||||
+ fieldLabel: gettext('API Token Auth'),
|
||||
+ },
|
||||
+ {
|
||||
+ xtype: 'textfield',
|
||||
+ name: 'freenas_user',
|
||||
+ reference: 'freenas_user_field',
|
||||
+ inputId: 'freenas_user_field',
|
||||
+ value: '',
|
||||
+ fieldLabel: gettext('API Username'),
|
||||
+ bind: {
|
||||
+ hidden: '{hideUsername}'
|
||||
+ },
|
||||
+ },
|
||||
];
|
||||
|
||||
me.column2 = [
|
||||
@@ -58131,7 +58269,9 @@
|
||||
xtype: me.isCreate ? 'textfield' : 'displayfield',
|
||||
name: 'comstar_hg',
|
||||
value: '',
|
||||
- bind: me.isCreate ? { disabled: '{!isComstar}' } : { hidden: '{!isComstar}' },
|
||||
+ bind: {
|
||||
+ hidden: '{!isComstar}'
|
||||
+ },
|
||||
fieldLabel: gettext('Host group'),
|
||||
allowBlank: true,
|
||||
},
|
||||
@@ -58139,15 +58279,32 @@
|
||||
xtype: me.isCreate ? 'textfield' : 'displayfield',
|
||||
name: 'lio_tpg',
|
||||
value: '',
|
||||
- bind: me.isCreate ? { disabled: '{!isLIO}' } : { hidden: '{!isLIO}' },
|
||||
- allowBlank: false,
|
||||
+ bind: {
|
||||
+ hidden: '{!isLIO}'
|
||||
+ },
|
||||
fieldLabel: gettext('Target portal group'),
|
||||
+ allowBlank: true
|
||||
+ },
|
||||
+ {
|
||||
+ xtype: 'proxmoxtextfield',
|
||||
+ name: 'freenas_apiv4_host',
|
||||
+ reference: 'freenas_apiv4_host_field',
|
||||
+ value: '',
|
||||
+ editable: true,
|
||||
+ emptyText: Proxmox.Utils.noneText,
|
||||
+ bind: {
|
||||
+ hidden: '{!isFreeNAS}'
|
||||
+ },
|
||||
+ fieldLabel: gettext('API IPv4 Host'),
|
||||
},
|
||||
+ tnsecret,
|
||||
+ tnconfirmsecret,
|
||||
];
|
||||
|
||||
me.callParent();
|
||||
},
|
||||
});
|
||||
+
|
||||
Ext.define('PVE.storage.ZFSPoolSelector', {
|
||||
extend: 'PVE.form.ComboBoxSetStoreNode',
|
||||
alias: 'widget.pveZFSPoolSelector',
|
||||
|
|
@ -1,241 +0,0 @@
|
|||
--- /tmp/pvemanagerlib-8.4.14-stock.js 2026-05-19 17:46:16.553917259 -0400
|
||||
+++ /tmp/pvemanagerlib-8.4.14-patched-v2.js 2026-05-19 21:35:03.162563535 -0400
|
||||
@@ -9745,6 +9745,7 @@
|
||||
alias: ['widget.pveiScsiProviderSelector'],
|
||||
comboItems: [
|
||||
['comstar', 'Comstar'],
|
||||
+ ['freenas', 'FreeNAS/TrueNAS API'],
|
||||
['istgt', 'istgt'],
|
||||
['iet', 'IET'],
|
||||
['LIO', 'LIO'],
|
||||
@@ -62673,10 +62674,17 @@
|
||||
viewModel: {
|
||||
parent: null,
|
||||
data: {
|
||||
- isLIO: false,
|
||||
isComstar: true,
|
||||
+ isFreeNAS: false,
|
||||
+ isLIO: false,
|
||||
+ isToken: false,
|
||||
hasWriteCacheOption: true,
|
||||
},
|
||||
+ formulas: {
|
||||
+ hideUsername: function(get) {
|
||||
+ return (!get('isFreeNAS') || !(get('isFreeNAS') && !get('isToken')));
|
||||
+ },
|
||||
+ },
|
||||
},
|
||||
|
||||
controller: {
|
||||
@@ -62685,12 +62693,41 @@
|
||||
'field[name=iscsiprovider]': {
|
||||
change: 'changeISCSIProvider',
|
||||
},
|
||||
+ 'field[name=truenas_token_auth]': {
|
||||
+ change: 'changeUsername',
|
||||
+ },
|
||||
},
|
||||
changeISCSIProvider: function (f, newVal, oldVal) {
|
||||
+ var me = this;
|
||||
var vm = this.getViewModel();
|
||||
vm.set('isLIO', newVal === 'LIO');
|
||||
vm.set('isComstar', newVal === 'comstar');
|
||||
- vm.set('hasWriteCacheOption', newVal === 'comstar' || newVal === 'istgt');
|
||||
+ vm.set('isFreeNAS', newVal === 'freenas');
|
||||
+ vm.set('hasWriteCacheOption', newVal === 'comstar' || newVal === 'freenas' || newVal === 'istgt');
|
||||
+ if (newVal !== 'freenas') {
|
||||
+ me.lookupReference('freenas_use_ssl_field').setValue(false);
|
||||
+ me.lookupReference('truenas_token_auth_field').setValue(false);
|
||||
+ me.lookupReference('freenas_apiv4_host_field').setValue('');
|
||||
+ me.lookupReference('freenas_user_field').setValue('');
|
||||
+ me.lookupReference('freenas_user_field').allowBlank = true;
|
||||
+ me.lookupReference('truenas_secret_field').setValue('');
|
||||
+ me.lookupReference('truenas_secret_field').allowBlank = true;
|
||||
+ me.lookupReference('truenas_confirm_secret_field').setValue('');
|
||||
+ me.lookupReference('truenas_confirm_secret_field').allowBlank = true;
|
||||
+ } else {
|
||||
+ me.lookupReference('freenas_user_field').allowBlank = false;
|
||||
+ me.lookupReference('truenas_secret_field').allowBlank = false;
|
||||
+ me.lookupReference('truenas_confirm_secret_field').allowBlank = false;
|
||||
+ }
|
||||
+ },
|
||||
+ changeUsername: function (f, newVal, oldVal) {
|
||||
+ var me = this;
|
||||
+ var vm = me.getViewModel();
|
||||
+ vm.set('isToken', newVal);
|
||||
+ me.lookupReference('freenas_user_field').allowBlank = newVal;
|
||||
+ if (newVal) {
|
||||
+ me.lookupReference('freenas_user_field').setValue('');
|
||||
+ }
|
||||
},
|
||||
},
|
||||
|
||||
@@ -62703,11 +62740,18 @@
|
||||
|
||||
values.nowritecache = values.writecache ? 0 : 1;
|
||||
delete values.writecache;
|
||||
+ if (values.freenas_password) {
|
||||
+ values.truenas_secret = values.freenas_password;
|
||||
+ }
|
||||
|
||||
return me.callParent([values]);
|
||||
},
|
||||
|
||||
setValues: function (values) {
|
||||
+ if (values.freenas_password) {
|
||||
+ values.truenas_secret = values.freenas_password;
|
||||
+ }
|
||||
+ values.truenas_confirm_secret = values.truenas_secret;
|
||||
values.writecache = values.nowritecache ? 0 : 1;
|
||||
this.callParent([values]);
|
||||
},
|
||||
@@ -62715,6 +62759,47 @@
|
||||
initComponent: function () {
|
||||
var me = this;
|
||||
|
||||
+ var tnsecret = Ext.create('Ext.form.TextField', {
|
||||
+ xtype: 'proxmoxtextfield',
|
||||
+ name: 'truenas_secret',
|
||||
+ reference: 'truenas_secret_field',
|
||||
+ inputType: me.isCreate ? '' : 'password',
|
||||
+ value: '',
|
||||
+ editable: true,
|
||||
+ emptyText: Proxmox.Utils.noneText,
|
||||
+ bind: {
|
||||
+ hidden: '{!isFreeNAS}',
|
||||
+ },
|
||||
+ fieldLabel: gettext('API Password'),
|
||||
+ change: function (f, value) {
|
||||
+ if (f.rendered) {
|
||||
+ f.up().down('field[name=truenas_confirm_secret]').validate();
|
||||
+ }
|
||||
+ },
|
||||
+ });
|
||||
+
|
||||
+ var tnconfirmsecret = Ext.create('Ext.form.TextField', {
|
||||
+ xtype: 'proxmoxtextfield',
|
||||
+ name: 'truenas_confirm_secret',
|
||||
+ reference: 'truenas_confirm_secret_field',
|
||||
+ inputType: me.isCreate ? '' : 'password',
|
||||
+ value: '',
|
||||
+ editable: true,
|
||||
+ submitValue: false,
|
||||
+ emptyText: Proxmox.Utils.noneText,
|
||||
+ bind: {
|
||||
+ hidden: '{!isFreeNAS}',
|
||||
+ },
|
||||
+ fieldLabel: gettext('Confirm API Password'),
|
||||
+ validator: function (value) {
|
||||
+ var pw = me.up().down('field[name=truenas_secret]').getValue();
|
||||
+ if (pw !== value) {
|
||||
+ return 'Secrets do not match!';
|
||||
+ }
|
||||
+ return true;
|
||||
+ },
|
||||
+ });
|
||||
+
|
||||
me.column1 = [
|
||||
{
|
||||
xtype: me.isCreate ? 'textfield' : 'displayfield',
|
||||
@@ -62749,9 +62834,60 @@
|
||||
name: 'comstar_tg',
|
||||
value: '',
|
||||
fieldLabel: gettext('Target group'),
|
||||
- bind: me.isCreate ? { disabled: '{!isComstar}' } : { hidden: '{!isComstar}' },
|
||||
+ bind: {
|
||||
+ hidden: '{!isComstar}',
|
||||
+ },
|
||||
allowBlank: true,
|
||||
},
|
||||
+ {
|
||||
+ xtype: 'proxmoxcheckbox',
|
||||
+ name: 'freenas_use_ssl',
|
||||
+ reference: 'freenas_use_ssl_field',
|
||||
+ inputId: 'freenas_use_ssl_field',
|
||||
+ checked: false,
|
||||
+ bind: {
|
||||
+ hidden: '{!isFreeNAS}',
|
||||
+ },
|
||||
+ uncheckedValue: 0,
|
||||
+ fieldLabel: gettext('API use SSL'),
|
||||
+ },
|
||||
+ {
|
||||
+ xtype: 'proxmoxcheckbox',
|
||||
+ name: 'truenas_token_auth',
|
||||
+ reference: 'truenas_token_auth_field',
|
||||
+ inputId: 'truenas_use_token_auth_field',
|
||||
+ checked: false,
|
||||
+ listeners: {
|
||||
+ change: function (field, newValue) {
|
||||
+ if (newValue === true) {
|
||||
+ tnsecret.labelEl.update('API Token');
|
||||
+ tnconfirmsecret.labelEl.update('Confirm API Token');
|
||||
+ me.lookupReference('freenas_user_field').setValue('');
|
||||
+ me.lookupReference('freenas_user_field').allowBlank = true;
|
||||
+ } else {
|
||||
+ tnsecret.labelEl.update('API Password');
|
||||
+ tnconfirmsecret.labelEl.update('Confirm API Password');
|
||||
+ me.lookupReference('freenas_user_field').allowBlank = false;
|
||||
+ }
|
||||
+ },
|
||||
+ },
|
||||
+ bind: {
|
||||
+ hidden: '{!isFreeNAS}',
|
||||
+ },
|
||||
+ uncheckedValue: 0,
|
||||
+ fieldLabel: gettext('API Token Auth'),
|
||||
+ },
|
||||
+ {
|
||||
+ xtype: 'textfield',
|
||||
+ name: 'freenas_user',
|
||||
+ reference: 'freenas_user_field',
|
||||
+ inputId: 'freenas_user_field',
|
||||
+ value: '',
|
||||
+ fieldLabel: gettext('API Username'),
|
||||
+ bind: {
|
||||
+ hidden: '{hideUsername}',
|
||||
+ },
|
||||
+ },
|
||||
];
|
||||
|
||||
me.column2 = [
|
||||
@@ -62783,7 +62919,9 @@
|
||||
xtype: me.isCreate ? 'textfield' : 'displayfield',
|
||||
name: 'comstar_hg',
|
||||
value: '',
|
||||
- bind: me.isCreate ? { disabled: '{!isComstar}' } : { hidden: '{!isComstar}' },
|
||||
+ bind: {
|
||||
+ hidden: '{!isComstar}',
|
||||
+ },
|
||||
fieldLabel: gettext('Host group'),
|
||||
allowBlank: true,
|
||||
},
|
||||
@@ -62791,10 +62929,26 @@
|
||||
xtype: me.isCreate ? 'textfield' : 'displayfield',
|
||||
name: 'lio_tpg',
|
||||
value: '',
|
||||
- bind: me.isCreate ? { disabled: '{!isLIO}' } : { hidden: '{!isLIO}' },
|
||||
- allowBlank: false,
|
||||
+ bind: {
|
||||
+ hidden: '{!isLIO}',
|
||||
+ },
|
||||
+ allowBlank: true,
|
||||
fieldLabel: gettext('Target portal group'),
|
||||
},
|
||||
+ {
|
||||
+ xtype: 'proxmoxtextfield',
|
||||
+ name: 'freenas_apiv4_host',
|
||||
+ reference: 'freenas_apiv4_host_field',
|
||||
+ value: '',
|
||||
+ editable: true,
|
||||
+ emptyText: Proxmox.Utils.noneText,
|
||||
+ bind: {
|
||||
+ hidden: '{!isFreeNAS}',
|
||||
+ },
|
||||
+ fieldLabel: gettext('API IPv4 Host'),
|
||||
+ },
|
||||
+ tnsecret,
|
||||
+ tnconfirmsecret,
|
||||
];
|
||||
|
||||
me.callParent();
|
||||
|
|
@ -1,286 +0,0 @@
|
|||
--- pvemanagerlib.js.orig 2024-01-06 14:50:39.890252726 -0500
|
||||
+++ pvemanagerlib.js 2024-01-06 15:51:53.708260622 -0500
|
||||
@@ -9228,6 +9228,7 @@
|
||||
alias: ['widget.pveiScsiProviderSelector'],
|
||||
comboItems: [
|
||||
['comstar', 'Comstar'],
|
||||
+ ['freenas', 'FreeNAS/TrueNAS API'],
|
||||
['istgt', 'istgt'],
|
||||
['iet', 'IET'],
|
||||
['LIO', 'LIO'],
|
||||
@@ -58017,16 +58018,23 @@
|
||||
me.callParent();
|
||||
},
|
||||
});
|
||||
+
|
||||
Ext.define('PVE.storage.ZFSInputPanel', {
|
||||
extend: 'PVE.panel.StorageBase',
|
||||
-
|
||||
viewModel: {
|
||||
- parent: null,
|
||||
- data: {
|
||||
- isLIO: false,
|
||||
- isComstar: true,
|
||||
- hasWriteCacheOption: true,
|
||||
- },
|
||||
+ parent: null,
|
||||
+ data: {
|
||||
+ isComstar: true,
|
||||
+ isFreeNAS: false,
|
||||
+ isLIO: false,
|
||||
+ isToken: false,
|
||||
+ hasWriteCacheOption: true,
|
||||
+ },
|
||||
+ formulas: {
|
||||
+ hideUsername: function(get) {
|
||||
+ return (!get('isFreeNAS') || !(get('isFreeNAS') && !get('isToken')));
|
||||
+ },
|
||||
+ },
|
||||
},
|
||||
|
||||
controller: {
|
||||
@@ -58035,12 +58043,41 @@
|
||||
'field[name=iscsiprovider]': {
|
||||
change: 'changeISCSIProvider',
|
||||
},
|
||||
+ 'field[name=truenas_token_auth]': {
|
||||
+ change: 'changeUsername',
|
||||
+ },
|
||||
},
|
||||
changeISCSIProvider: function(f, newVal, oldVal) {
|
||||
+ var me = this;
|
||||
var vm = this.getViewModel();
|
||||
vm.set('isLIO', newVal === 'LIO');
|
||||
vm.set('isComstar', newVal === 'comstar');
|
||||
- vm.set('hasWriteCacheOption', newVal === 'comstar' || newVal === 'istgt');
|
||||
+ vm.set('isFreeNAS', newVal === 'freenas');
|
||||
+ vm.set('hasWriteCacheOption', newVal === 'comstar' || newVal === 'freenas' || newVal === 'istgt');
|
||||
+ if (newVal !== 'freenas') {
|
||||
+ me.lookupReference('freenas_use_ssl_field').setValue(false);
|
||||
+ me.lookupReference('truenas_token_auth_field').setValue(false);
|
||||
+ me.lookupReference('freenas_apiv4_host_field').setValue('');
|
||||
+ me.lookupReference('freenas_user_field').setValue('');
|
||||
+ me.lookupReference('freenas_user_field').allowBlank = true;
|
||||
+ me.lookupReference('truenas_secret_field').setValue('');
|
||||
+ me.lookupReference('truenas_secret_field').allowBlank = true;
|
||||
+ me.lookupReference('truenas_confirm_secret_field').setValue('');
|
||||
+ me.lookupReference('truenas_confirm_secret_field').allowBlank = true;
|
||||
+ } else {
|
||||
+ me.lookupReference('freenas_user_field').allowBlank = false;
|
||||
+ me.lookupReference('truenas_secret_field').allowBlank = false;
|
||||
+ me.lookupReference('truenas_confirm_secret_field').allowBlank = false;
|
||||
+ }
|
||||
+ },
|
||||
+ changeUsername: function(f, newVal, oldVal) {
|
||||
+ var me = this;
|
||||
+ var vm = me.getViewModel();
|
||||
+ vm.set('isToken', newVal);
|
||||
+ me.lookupReference('freenas_user_field').allowBlank = newVal;
|
||||
+ if (newVal) {
|
||||
+ me.lookupReference('freenas_user_field').setValue('');
|
||||
+ }
|
||||
},
|
||||
},
|
||||
|
||||
@@ -58053,18 +58090,68 @@
|
||||
|
||||
values.nowritecache = values.writecache ? 0 : 1;
|
||||
delete values.writecache;
|
||||
+ console.warn(values.freenas_password);
|
||||
+ if (values.freenas_password) {
|
||||
+ values.truenas_secret = values.freenas_password;
|
||||
+ }
|
||||
+ console.warn(values.truenas_secret);
|
||||
|
||||
return me.callParent([values]);
|
||||
},
|
||||
|
||||
setValues: function(values) {
|
||||
- values.writecache = values.nowritecache ? 0 : 1;
|
||||
- this.callParent([values]);
|
||||
+ if (values.freenas_password) {
|
||||
+ values.truenas_secret = values.freenas_password;
|
||||
+ }
|
||||
+ values.truenas_confirm_secret = values.truenas_secret;
|
||||
+ values.writecache = values.nowritecache ? 0 : 1;
|
||||
+ this.callParent([values]);
|
||||
},
|
||||
|
||||
initComponent: function() {
|
||||
var me = this;
|
||||
|
||||
+ var tnsecret = Ext.create('Ext.form.TextField', {
|
||||
+ xtype: 'proxmoxtextfield',
|
||||
+ name: 'truenas_secret',
|
||||
+ reference: 'truenas_secret_field',
|
||||
+ inputType: me.isCreate ? '' : 'password',
|
||||
+ value: '',
|
||||
+ editable: true,
|
||||
+ emptyText: Proxmox.Utils.noneText,
|
||||
+ bind: {
|
||||
+ hidden: '{!isFreeNAS}'
|
||||
+ },
|
||||
+ fieldLabel: gettext('API Password'),
|
||||
+ change: function(f, value) {
|
||||
+ if (f.rendered) {
|
||||
+ f.up().down('field[name=truenas_confirm_secret]').validate();
|
||||
+ }
|
||||
+ },
|
||||
+ });
|
||||
+
|
||||
+ var tnconfirmsecret = Ext.create('Ext.form.TextField', {
|
||||
+ xtype: 'proxmoxtextfield',
|
||||
+ name: 'truenas_confirm_secret',
|
||||
+ reference: 'truenas_confirm_secret_field',
|
||||
+ inputType: me.isCreate ? '' : 'password',
|
||||
+ value: '',
|
||||
+ editable: true,
|
||||
+ submitValue: false,
|
||||
+ emptyText: Proxmox.Utils.noneText,
|
||||
+ bind: {
|
||||
+ hidden: '{!isFreeNAS}'
|
||||
+ },
|
||||
+ fieldLabel: gettext('Confirm API Password'),
|
||||
+ validator: function(value) {
|
||||
+ var pw = me.up().down('field[name=truenas_secret]').getValue();
|
||||
+ if (pw !== value) {
|
||||
+ return "Secrets do not match!";
|
||||
+ }
|
||||
+ return true;
|
||||
+ },
|
||||
+ });
|
||||
+
|
||||
me.column1 = [
|
||||
{
|
||||
xtype: me.isCreate ? 'textfield' : 'displayfield',
|
||||
@@ -58074,7 +58161,7 @@
|
||||
allowBlank: false,
|
||||
},
|
||||
{
|
||||
- xtype: me.isCreate ? 'textfield' : 'displayfield',
|
||||
+ xtype: 'textfield',
|
||||
name: 'pool',
|
||||
value: '',
|
||||
fieldLabel: gettext('Pool'),
|
||||
@@ -58084,11 +58171,11 @@
|
||||
xtype: me.isCreate ? 'textfield' : 'displayfield',
|
||||
name: 'blocksize',
|
||||
value: '4k',
|
||||
- fieldLabel: gettext('Block Size'),
|
||||
+ fieldLabel: gettext('ZFS Block Size'),
|
||||
allowBlank: false,
|
||||
},
|
||||
{
|
||||
- xtype: me.isCreate ? 'textfield' : 'displayfield',
|
||||
+ xtype: 'textfield',
|
||||
name: 'target',
|
||||
value: '',
|
||||
fieldLabel: gettext('Target'),
|
||||
@@ -58099,9 +58186,60 @@
|
||||
name: 'comstar_tg',
|
||||
value: '',
|
||||
fieldLabel: gettext('Target group'),
|
||||
- bind: me.isCreate ? { disabled: '{!isComstar}' } : { hidden: '{!isComstar}' },
|
||||
+ bind: {
|
||||
+ hidden: '{!isComstar}'
|
||||
+ },
|
||||
allowBlank: true,
|
||||
},
|
||||
+ {
|
||||
+ xtype: 'proxmoxcheckbox',
|
||||
+ name: 'freenas_use_ssl',
|
||||
+ reference: 'freenas_use_ssl_field',
|
||||
+ inputId: 'freenas_use_ssl_field',
|
||||
+ checked: false,
|
||||
+ bind: {
|
||||
+ hidden: '{!isFreeNAS}'
|
||||
+ },
|
||||
+ uncheckedValue: 0,
|
||||
+ fieldLabel: gettext('API use SSL'),
|
||||
+ },
|
||||
+ {
|
||||
+ xtype: 'proxmoxcheckbox',
|
||||
+ name: 'truenas_token_auth',
|
||||
+ reference: 'truenas_token_auth_field',
|
||||
+ inputId: 'truenas_use_token_auth_field',
|
||||
+ checked: false,
|
||||
+ listeners: {
|
||||
+ change: function(field, newValue) {
|
||||
+ if (newValue === true) {
|
||||
+ tnsecret.labelEl.update('API Token');
|
||||
+ tnconfirmsecret.labelEl.update('Confirm API Token');
|
||||
+ me.lookupReference('freenas_user_field').setValue('');
|
||||
+ me.lookupReference('freenas_user_field').allowBlank = true;
|
||||
+ } else {
|
||||
+ tnsecret.labelEl.update('API Password');
|
||||
+ tnconfirmsecret.labelEl.update('Confirm API Password');
|
||||
+ me.lookupReference('freenas_user_field').allowBlank = false;
|
||||
+ }
|
||||
+ },
|
||||
+ },
|
||||
+ bind: {
|
||||
+ hidden: '{!isFreeNAS}'
|
||||
+ },
|
||||
+ uncheckedValue: 0,
|
||||
+ fieldLabel: gettext('API Token Auth'),
|
||||
+ },
|
||||
+ {
|
||||
+ xtype: 'textfield',
|
||||
+ name: 'freenas_user',
|
||||
+ reference: 'freenas_user_field',
|
||||
+ inputId: 'freenas_user_field',
|
||||
+ value: '',
|
||||
+ fieldLabel: gettext('API Username'),
|
||||
+ bind: {
|
||||
+ hidden: '{hideUsername}'
|
||||
+ },
|
||||
+ },
|
||||
];
|
||||
|
||||
me.column2 = [
|
||||
@@ -58131,7 +58269,9 @@
|
||||
xtype: me.isCreate ? 'textfield' : 'displayfield',
|
||||
name: 'comstar_hg',
|
||||
value: '',
|
||||
- bind: me.isCreate ? { disabled: '{!isComstar}' } : { hidden: '{!isComstar}' },
|
||||
+ bind: {
|
||||
+ hidden: '{!isComstar}'
|
||||
+ },
|
||||
fieldLabel: gettext('Host group'),
|
||||
allowBlank: true,
|
||||
},
|
||||
@@ -58139,15 +58279,32 @@
|
||||
xtype: me.isCreate ? 'textfield' : 'displayfield',
|
||||
name: 'lio_tpg',
|
||||
value: '',
|
||||
- bind: me.isCreate ? { disabled: '{!isLIO}' } : { hidden: '{!isLIO}' },
|
||||
- allowBlank: false,
|
||||
+ bind: {
|
||||
+ hidden: '{!isLIO}'
|
||||
+ },
|
||||
fieldLabel: gettext('Target portal group'),
|
||||
+ allowBlank: true
|
||||
+ },
|
||||
+ {
|
||||
+ xtype: 'proxmoxtextfield',
|
||||
+ name: 'freenas_apiv4_host',
|
||||
+ reference: 'freenas_apiv4_host_field',
|
||||
+ value: '',
|
||||
+ editable: true,
|
||||
+ emptyText: Proxmox.Utils.noneText,
|
||||
+ bind: {
|
||||
+ hidden: '{!isFreeNAS}'
|
||||
+ },
|
||||
+ fieldLabel: gettext('API IPv4 Host'),
|
||||
},
|
||||
+ tnsecret,
|
||||
+ tnconfirmsecret,
|
||||
];
|
||||
|
||||
me.callParent();
|
||||
},
|
||||
});
|
||||
+
|
||||
Ext.define('PVE.storage.ZFSPoolSelector', {
|
||||
extend: 'PVE.form.ComboBoxSetStoreNode',
|
||||
alias: 'widget.pveZFSPoolSelector',
|
||||
|
|
@ -0,0 +1,158 @@
|
|||
#!/usr/bin/env bash
|
||||
# Migrate the truenas-proxmox / freenas-proxmox apt source from Cloudsmith
|
||||
# to the GitHub Pages apt repo.
|
||||
#
|
||||
# Run on each Proxmox node as root (or with sudo).
|
||||
#
|
||||
# What this script does:
|
||||
# 1. Detects the currently installed version and which repo is configured
|
||||
# 2. Removes any Cloudsmith source files and keyrings for this package
|
||||
# 3. Imports the GitHub Pages GPG key
|
||||
# 4. Adds the correct dist track (v3 for v3.x installs, main for v2.x)
|
||||
# 5. Runs apt update and confirms the new repo is reachable
|
||||
#
|
||||
# Safe to re-run: it detects if you're already on GitHub Pages and exits early.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
GITHUB_PAGES_BASE="https://thegrandwazoo.github.io/freenas-proxmox"
|
||||
KEYRING_PATH="/etc/apt/keyrings/truenas-proxmox.gpg"
|
||||
SOURCES_FILE="/etc/apt/sources.list.d/truenas-proxmox.list"
|
||||
CLOUDSMITH_URL_PATTERN="dl.cloudsmith.io.*ksatechnologies.*truenas-proxmox"
|
||||
|
||||
# ── Must run as root ──────────────────────────────────────────────────────────
|
||||
if [[ "$EUID" -ne 0 ]]; then
|
||||
echo "Error: run this script as root or with sudo." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ── Detect installed version ──────────────────────────────────────────────────
|
||||
INSTALLED_PKG=""
|
||||
INSTALLED_VER=""
|
||||
for pkg in truenas-proxmox freenas-proxmox; do
|
||||
ver="$(dpkg-query -W -f='${Version}' "$pkg" 2>/dev/null || true)"
|
||||
if [[ -n "$ver" ]]; then
|
||||
INSTALLED_PKG="$pkg"
|
||||
INSTALLED_VER="$ver"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ -z "$INSTALLED_VER" ]]; then
|
||||
echo "Neither truenas-proxmox nor freenas-proxmox is installed on this node."
|
||||
echo "Use the install instructions in the README for a fresh install."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
MAJOR="${INSTALLED_VER%%.*}"
|
||||
echo "Detected: $INSTALLED_PKG $INSTALLED_VER (major version: $MAJOR)"
|
||||
|
||||
# ── Determine target dist track ───────────────────────────────────────────────
|
||||
case "$MAJOR" in
|
||||
2) DIST="main" ;;
|
||||
3) DIST="v3" ;;
|
||||
4) DIST="v4" ;;
|
||||
*)
|
||||
echo "Error: unrecognised major version '$MAJOR' — update this script." >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
# ── Check if already on GitHub Pages with the correct dist track ─────────────
|
||||
if grep -qrF "$GITHUB_PAGES_BASE $DIST main" /etc/apt/sources.list.d/ 2>/dev/null; then
|
||||
echo "Already configured correctly for GitHub Pages (dist: $DIST) — nothing to do."
|
||||
echo "Current source:"
|
||||
grep -rF "$GITHUB_PAGES_BASE" /etc/apt/sources.list.d/
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# GitHub Pages is configured but with the wrong dist track — fix it
|
||||
if grep -qr "$GITHUB_PAGES_BASE" /etc/apt/sources.list.d/ 2>/dev/null; then
|
||||
echo "GitHub Pages is configured but with the wrong dist track — correcting..."
|
||||
WRONG=$(grep -rl "$GITHUB_PAGES_BASE" /etc/apt/sources.list.d/)
|
||||
echo " Removing: $WRONG"
|
||||
echo "$WRONG" | xargs rm -f
|
||||
fi
|
||||
|
||||
# ── Find and remove Cloudsmith sources ───────────────────────────────────────
|
||||
echo ""
|
||||
echo "==> Searching for Cloudsmith apt sources..."
|
||||
CLOUDSMITH_FILES="$(grep -rl "$CLOUDSMITH_URL_PATTERN" /etc/apt/sources.list.d/ 2>/dev/null || true)"
|
||||
|
||||
if [[ -z "$CLOUDSMITH_FILES" ]]; then
|
||||
echo " No Cloudsmith sources found for this package."
|
||||
else
|
||||
echo " Found:"
|
||||
while IFS= read -r f; do echo " $f"; done <<< "$CLOUDSMITH_FILES"
|
||||
echo " Removing..."
|
||||
echo "$CLOUDSMITH_FILES" | xargs rm -f
|
||||
echo " Done."
|
||||
fi
|
||||
|
||||
# ── Remove Cloudsmith keyrings ────────────────────────────────────────────────
|
||||
echo ""
|
||||
echo "==> Searching for Cloudsmith keyrings..."
|
||||
CLOUDSMITH_KEYRINGS="$(find /usr/share/keyrings /etc/apt/keyrings \
|
||||
-name 'ksatechnologies-truenas-proxmox*.gpg' \
|
||||
-o -name 'ksatechnologies-freenas-proxmox*.gpg' 2>/dev/null || true)"
|
||||
|
||||
if [[ -z "$CLOUDSMITH_KEYRINGS" ]]; then
|
||||
echo " No Cloudsmith keyrings found."
|
||||
else
|
||||
echo " Found:"
|
||||
while IFS= read -r f; do echo " $f"; done <<< "$CLOUDSMITH_KEYRINGS"
|
||||
echo " Removing..."
|
||||
echo "$CLOUDSMITH_KEYRINGS" | xargs rm -f
|
||||
echo " Done."
|
||||
fi
|
||||
|
||||
# ── Import GitHub Pages GPG key ───────────────────────────────────────────────
|
||||
echo ""
|
||||
echo "==> Importing GitHub Pages GPG key → $KEYRING_PATH"
|
||||
mkdir -p /etc/apt/keyrings
|
||||
curl -fsSL "${GITHUB_PAGES_BASE}/public.gpg.key" \
|
||||
| gpg --dearmor \
|
||||
| tee "$KEYRING_PATH" > /dev/null
|
||||
echo " Done."
|
||||
|
||||
# ── Add new source ────────────────────────────────────────────────────────────
|
||||
echo ""
|
||||
echo "==> Adding GitHub Pages source (dist: $DIST) → $SOURCES_FILE"
|
||||
echo "deb [signed-by=${KEYRING_PATH}] ${GITHUB_PAGES_BASE} ${DIST} main" \
|
||||
> "$SOURCES_FILE"
|
||||
cat "$SOURCES_FILE"
|
||||
|
||||
# ── apt update ───────────────────────────────────────────────────────────────
|
||||
echo ""
|
||||
echo "==> Running apt update..."
|
||||
apt-get update 2>&1 \
|
||||
| grep -E "^(Get|Hit|Err|Fetched|W:|E:)" || true
|
||||
|
||||
# Confirm the package is visible from the new repo
|
||||
CANDIDATE="$(apt-cache policy "$INSTALLED_PKG" 2>/dev/null \
|
||||
| awk '/Candidate:/{print $2}')"
|
||||
|
||||
if apt-cache policy "$INSTALLED_PKG" 2>/dev/null \
|
||||
| grep -q "$GITHUB_PAGES_BASE"; then
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "Migration complete."
|
||||
echo " Package : $INSTALLED_PKG $INSTALLED_VER"
|
||||
echo " Repo : $GITHUB_PAGES_BASE"
|
||||
echo " Dist : $DIST"
|
||||
echo " Candidate from new repo: $CANDIDATE"
|
||||
echo ""
|
||||
if [[ "$CANDIDATE" != "$INSTALLED_VER" ]]; then
|
||||
echo " Note: a newer version ($CANDIDATE) is available."
|
||||
echo " Run: apt-get install $INSTALLED_PKG"
|
||||
else
|
||||
echo " You are on the latest release in this track."
|
||||
fi
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
else
|
||||
echo ""
|
||||
echo "Warning: migration completed but $INSTALLED_PKG was not found in" >&2
|
||||
echo " $GITHUB_PAGES_BASE" >&2
|
||||
echo "Run 'apt-get update && apt-cache policy $INSTALLED_PKG' to investigate." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
#!/usr/bin/env bash
|
||||
# One-time setup: generate the GPG key used to sign the GitHub Pages apt repo.
|
||||
#
|
||||
# Run this locally once. After running:
|
||||
# 1. Commit public.gpg.key to the gh-pages branch (instructions printed below)
|
||||
# 2. Add APT_SIGNING_KEY to GitHub Actions secrets
|
||||
# 3. Add APT_SIGNING_KEY_PASSPHRASE to GitHub Actions secrets (empty string if no passphrase)
|
||||
#
|
||||
# Re-running is safe — it checks for an existing key first.
|
||||
set -euo pipefail
|
||||
|
||||
REPO="TheGrandWazoo/freenas-proxmox"
|
||||
KEY_NAME="truenas-proxmox"
|
||||
KEY_EMAIL="packages@ksatechnologies.com"
|
||||
KEY_COMMENT="truenas-proxmox apt repo signing key"
|
||||
|
||||
# ── Check for existing key ────────────────────────────────────────────────────
|
||||
EXISTING="$(gpg --list-secret-keys --with-colons 2>/dev/null \
|
||||
| awk -F: -v name="$KEY_NAME" '$1=="uid" && $10 ~ name {found=1} END {print found+0}')"
|
||||
|
||||
if [[ "$EXISTING" == "1" ]]; then
|
||||
echo "Key '$KEY_NAME' already exists in your keyring — skipping generation."
|
||||
KEY_ID="$(gpg --list-secret-keys --with-colons 2>/dev/null \
|
||||
| awk -F: '/^sec/{print $5; exit}')"
|
||||
else
|
||||
# ── Generate key ─────────────────────────────────────────────────────────────
|
||||
echo "Generating GPG key for apt repo signing..."
|
||||
read -r -s -p "Enter a passphrase (leave empty for no passphrase): " PASSPHRASE
|
||||
echo
|
||||
|
||||
gpg --batch --gen-key <<EOF
|
||||
%echo Generating truenas-proxmox apt signing key
|
||||
Key-Type: RSA
|
||||
Key-Length: 4096
|
||||
Subkey-Type: RSA
|
||||
Subkey-Length: 4096
|
||||
Name-Real: ${KEY_NAME}
|
||||
Name-Comment: ${KEY_COMMENT}
|
||||
Name-Email: ${KEY_EMAIL}
|
||||
Expire-Date: 0
|
||||
$([ -n "$PASSPHRASE" ] && echo "Passphrase: ${PASSPHRASE}" || echo "%no-protection")
|
||||
%commit
|
||||
%echo Done
|
||||
EOF
|
||||
|
||||
KEY_ID="$(gpg --list-secret-keys --with-colons 2>/dev/null \
|
||||
| awk -F: '/^sec/{print $5; exit}')"
|
||||
echo "Generated key: $KEY_ID"
|
||||
fi
|
||||
|
||||
# ── Export public key ─────────────────────────────────────────────────────────
|
||||
echo
|
||||
echo "==> Exporting public key..."
|
||||
gpg --export --armor "$KEY_ID" > /tmp/truenas-proxmox-public.gpg.key
|
||||
echo "Public key written to /tmp/truenas-proxmox-public.gpg.key"
|
||||
|
||||
# ── Export private key (base64) ───────────────────────────────────────────────
|
||||
echo
|
||||
echo "==> Exporting private key (base64) for GitHub Actions secret..."
|
||||
PRIVATE_KEY_B64="$(gpg --export-secret-keys --armor "$KEY_ID" | base64 -w 0)"
|
||||
|
||||
# ── Instructions ─────────────────────────────────────────────────────────────
|
||||
cat <<INSTRUCTIONS
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
NEXT STEPS
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
1. Add APT_SIGNING_KEY to GitHub Actions secrets:
|
||||
gh secret set APT_SIGNING_KEY --repo ${REPO} --body "${PRIVATE_KEY_B64}"
|
||||
|
||||
2. Add APT_SIGNING_KEY_PASSPHRASE to GitHub Actions secrets:
|
||||
gh secret set APT_SIGNING_KEY_PASSPHRASE --repo ${REPO} --body "YOUR_PASSPHRASE"
|
||||
(use empty string if you chose no passphrase:)
|
||||
gh secret set APT_SIGNING_KEY_PASSPHRASE --repo ${REPO} --body ""
|
||||
|
||||
3. Commit public.gpg.key to the gh-pages branch:
|
||||
git checkout gh-pages
|
||||
cp /tmp/truenas-proxmox-public.gpg.key public.gpg.key
|
||||
git add public.gpg.key
|
||||
git commit -m "chore: add apt repo GPG public key"
|
||||
git push origin gh-pages
|
||||
git checkout release/3.x
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
INSTRUCTIONS
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
/ZFSPlugin.pm
|
||||
/ZFSPoolPlugin.pm
|
||||
/ZFSPlugin-new.pm
|
||||
/ZFSPlugin-newver.patch
|
||||
|
|
@ -1,546 +0,0 @@
|
|||
package PVE::Storage::LunCmd::FreeNAS;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use Data::Dumper;
|
||||
use PVE::SafeSyslog;
|
||||
use IO::Socket::SSL;
|
||||
|
||||
use REST::Client;
|
||||
use MIME::Base64;
|
||||
use JSON;
|
||||
|
||||
# Max LUNS per target on the iSCSI server
|
||||
my $MAX_LUNS = 255;
|
||||
|
||||
#
|
||||
#
|
||||
#
|
||||
sub get_base {
|
||||
return '/dev/zvol';
|
||||
}
|
||||
|
||||
#
|
||||
#
|
||||
#
|
||||
sub run_lun_command {
|
||||
my ($scfg, $timeout, $method, @params) = @_;
|
||||
|
||||
if(!defined($scfg->{'freenas_user'}) || !defined($scfg->{'freenas_password'})) {
|
||||
die "Undefined freenas_user and/or freenas_password.";
|
||||
}
|
||||
|
||||
syslog("info","FreeNAS::lun_command : $method(@params)");
|
||||
|
||||
if($method eq "create_lu") {
|
||||
return run_create_lu($scfg, $timeout, $method, @params);
|
||||
}
|
||||
if($method eq "delete_lu") {
|
||||
return run_delete_lu($scfg, $timeout, $method, @params);
|
||||
}
|
||||
if($method eq "import_lu") {
|
||||
return run_create_lu($scfg, $timeout, $method, @params);
|
||||
}
|
||||
if($method eq "modify_lu") {
|
||||
return run_modify_lu($scfg, $timeout, $method, @params);
|
||||
}
|
||||
if($method eq "add_view") {
|
||||
return run_add_view($scfg, $timeout, $method, @params);
|
||||
}
|
||||
if($method eq "list_view") {
|
||||
return run_list_view($scfg, $timeout, $method, @params);
|
||||
}
|
||||
if($method eq "list_extent") {
|
||||
return run_list_extent($scfg, $timeout, $method, @params);
|
||||
}
|
||||
if($method eq "list_lu") {
|
||||
return run_list_lu($scfg, $timeout, $method, "name", @params);
|
||||
}
|
||||
|
||||
syslog("error","FreeNAS::lun_command : unknown method $method");
|
||||
return undef;
|
||||
}
|
||||
|
||||
#
|
||||
#
|
||||
#
|
||||
sub run_add_view {
|
||||
return '';
|
||||
}
|
||||
|
||||
#
|
||||
# a modify_lu occur by example on a zvol resize. we just need to destroy and recreate the lun with the same zvol.
|
||||
# Be careful, the first param is the new size of the zvol, we must shift params
|
||||
#
|
||||
sub run_modify_lu {
|
||||
my ($scfg, $timeout, $method, @params) = @_;
|
||||
shift(@params);
|
||||
run_delete_lu($scfg, $timeout, $method, @params);
|
||||
return run_create_lu($scfg, $timeout, $method, @params);
|
||||
}
|
||||
|
||||
#
|
||||
#
|
||||
#
|
||||
sub run_list_view {
|
||||
my ($scfg, $timeout, $method, @params) = @_;
|
||||
return run_list_lu($scfg, $timeout, $method, "lun-id", @params);
|
||||
}
|
||||
|
||||
#
|
||||
#
|
||||
#
|
||||
sub run_list_lu {
|
||||
my ($scfg, $timeout, $method, $result_value_type, @params) = @_;
|
||||
my $object = $params[0];
|
||||
my $result = undef;
|
||||
|
||||
my $luns = freenas_list_lu($scfg);
|
||||
foreach my $lun (@$luns) {
|
||||
if ($lun->{'iscsi_target_extent_path'} =~ /^$object$/) {
|
||||
$result = $result_value_type eq "lun-id" ? $lun->{'iscsi_lunid'} : $lun->{'iscsi_target_extent_path'};
|
||||
syslog("info","FreeNAS::list_lu($object):$result_value_type : lun found $result");
|
||||
last;
|
||||
}
|
||||
}
|
||||
if(!defined($result)) {
|
||||
syslog("info","FreeNAS::list_lu($object):$result_value_type : lun not found");
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
#
|
||||
#
|
||||
#
|
||||
sub run_list_extent {
|
||||
my ($scfg, $timeout, $method, @params) = @_;
|
||||
my $object = $params[0];
|
||||
my $result = undef;
|
||||
|
||||
my $luns = freenas_list_lu($scfg);
|
||||
foreach my $lun (@$luns) {
|
||||
if ($lun->{'iscsi_target_extent_path'} =~ /^$object$/) {
|
||||
$result = $lun->{'iscsi_target_extent_naa'};
|
||||
syslog("info","FreeNAS::list_extent($object): naa found $result");
|
||||
last;
|
||||
}
|
||||
}
|
||||
if (!defined($result)) {
|
||||
syslog("info","FreeNAS::list_extent($object): naa not found");
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
#
|
||||
#
|
||||
#
|
||||
sub run_create_lu {
|
||||
my ($scfg, $timeout, $method, @params) = @_;
|
||||
|
||||
my $lun_path = $params[0];
|
||||
my $lun_id = freenas_get_first_available_lunid($scfg);
|
||||
|
||||
die "Maximum number of LUNs per target is $MAX_LUNS" if scalar $lun_id >= $MAX_LUNS;
|
||||
die "$params[0]: LUN $lun_path exists" if defined(run_list_lu($scfg, $timeout, $method, "name", @params));
|
||||
|
||||
my $target_id = freenas_get_targetid($scfg);
|
||||
die "Unable to find the target id for $scfg->{target}" if !defined($target_id);
|
||||
|
||||
# Create the extent
|
||||
my $extent = freenas_iscsi_create_extent($scfg, $lun_path);
|
||||
|
||||
# Associate the new extent to the target
|
||||
my $link = freenas_iscsi_create_target_to_extent($scfg, $target_id, $extent->{'id'}, $lun_id);
|
||||
|
||||
if (defined($link)) {
|
||||
syslog("info","FreeNAS::create_lu(lun_path=$lun_path, lun_id=$lun_id) : sucessfull");
|
||||
} else {
|
||||
die "Unable to create lun $lun_path";
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
#
|
||||
#
|
||||
#
|
||||
sub run_delete_lu {
|
||||
my ($scfg, $timeout, $method, @params) = @_;
|
||||
|
||||
my $lun_path = $params[0];
|
||||
my $luns = freenas_list_lu($scfg);
|
||||
my $lun = undef;
|
||||
my $link = undef;
|
||||
|
||||
foreach my $item (@$luns) {
|
||||
if($item->{'iscsi_target_extent_path'} =~ /^$lun_path$/) {
|
||||
$lun = $item;
|
||||
last;
|
||||
}
|
||||
}
|
||||
|
||||
die "Unable to find the lun $lun_path for $scfg->{target}" if !defined($lun);
|
||||
|
||||
my $target_id = freenas_get_targetid($scfg);
|
||||
die "Unable to find the target id for $scfg->{target}" if !defined($target_id);
|
||||
|
||||
# find the target to extent
|
||||
my $target2extents = freenas_iscsi_get_target_to_extent($scfg);
|
||||
|
||||
foreach my $item (@$target2extents) {
|
||||
if($item->{'iscsi_target'} == $target_id &&
|
||||
$item->{'iscsi_lunid'} == $lun->{'iscsi_lunid'} &&
|
||||
$item->{'iscsi_extent'} == $lun->{'id'}) {
|
||||
|
||||
$link = $item;
|
||||
last;
|
||||
}
|
||||
}
|
||||
die "Unable to find the link for the lun $lun_path for $scfg->{target}" if !defined($link);
|
||||
|
||||
# Remove the link
|
||||
my $remove_link = freenas_iscsi_remove_target_to_extent($scfg, $link->{'id'});
|
||||
|
||||
# Remove the extent
|
||||
my $remove_extent = freenas_iscsi_remove_extent($scfg, $lun->{'id'});
|
||||
|
||||
if($remove_link == 1 && $remove_extent == 1) {
|
||||
syslog("info","FreeNAS::delete_lu(lun_path=$lun_path) : sucessfull");
|
||||
} else {
|
||||
die "Unable to delete lun $lun_path";
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
#
|
||||
### FREENAS API CALLING ###
|
||||
#
|
||||
sub freenas_api_call {
|
||||
my ($scfg, $method, $path, $data) = @_;
|
||||
my $client = undef;
|
||||
my $scheme = $scfg->{freenas_use_ssl} ? "https" : "http";
|
||||
my $apihost = defined($scfg->{freenas_apiv4_host}) ? $scfg->{freenas_apiv4_host} : $scfg->{portal};
|
||||
my $apiping = '/api/v1.0/system/version/';
|
||||
|
||||
$client = REST::Client->new();
|
||||
$client->setHost($scheme . '://' . $apihost);
|
||||
$client->addHeader('Content-Type' , 'application/json');
|
||||
$client->addHeader('Authorization' , 'Basic ' . encode_base64($scfg->{freenas_user} . ':' . $scfg->{freenas_password}));
|
||||
# If using SSL, don't verify SSL certs
|
||||
if ($scfg->{freenas_use_ssl}) {
|
||||
$client->getUseragent()->ssl_opts(verify_hostname => 0);
|
||||
$client->getUseragent()->ssl_opts(SSL_verify_mode => SSL_VERIFY_NONE);
|
||||
}
|
||||
# Check if the APIs are accessable via the selected host and scheme
|
||||
my $code = $client->request('GET', $apiping)->responseCode();
|
||||
if ($code != 200) {
|
||||
freenas_api_log_error($client, "freenas_api_call");
|
||||
die "Unable to connect to the FreeNAS API service at '" . $apihost . "' using the '" . $scheme . "' protocol";
|
||||
}
|
||||
syslog("info","FreeNAS::api_call : setup : sucessfull");
|
||||
if ($method eq 'GET') {
|
||||
$client->GET($path);
|
||||
}
|
||||
if ($method eq 'DELETE') {
|
||||
$client->DELETE($path);
|
||||
}
|
||||
if ($method eq 'POST') {
|
||||
$client->POST($path, encode_json($data));
|
||||
}
|
||||
|
||||
return $client
|
||||
}
|
||||
|
||||
#
|
||||
# Writes the Response and Content to SysLog
|
||||
#
|
||||
sub freenas_api_log_error {
|
||||
my ($client, $method) = @_;
|
||||
syslog("info","[ERROR]FreeNAS::API::" . $method . " : Response code: " . $client->responseCode());
|
||||
syslog("info","[ERROR]FreeNAS::API::" . $method . " : Response content: " . $client->responseContent());
|
||||
return 1;
|
||||
}
|
||||
|
||||
#
|
||||
#
|
||||
#
|
||||
sub freenas_iscsi_get_globalconfiguration {
|
||||
my ($scfg) = @_;
|
||||
my $client = freenas_api_call($scfg, 'GET', "/api/v1.0/services/iscsi/globalconfiguration/", undef);
|
||||
my $code = $client->responseCode();
|
||||
|
||||
if ($code == 200) {
|
||||
my $result = decode_json($client->responseContent());
|
||||
syslog("info","FreeNAS::API::get_globalconfig : target_basename=" . $result->{'iscsi_basename'});
|
||||
return $result;
|
||||
} else {
|
||||
freenas_api_log_error($client, "get_globalconfig");
|
||||
return undef;
|
||||
}
|
||||
}
|
||||
|
||||
#
|
||||
# Returns a list of all extents.
|
||||
# http://api.freenas.org/resources/iscsi/index.html#get--api-v1.0-services-iscsi-extent-
|
||||
#
|
||||
sub freenas_iscsi_get_extent {
|
||||
my ($scfg) = @_;
|
||||
my $client = freenas_api_call($scfg, 'GET', "/api/v1.0/services/iscsi/extent/?limit=0", undef);
|
||||
|
||||
my $code = $client->responseCode();
|
||||
if ($code == 200) {
|
||||
my $result = decode_json($client->responseContent());
|
||||
syslog("info","FreeNAS::API::get_extent : sucessfull");
|
||||
return $result;
|
||||
} else {
|
||||
freenas_api_log_error($client, "get_extent");
|
||||
return undef;
|
||||
}
|
||||
}
|
||||
|
||||
#
|
||||
# Create an extent on FreeNas
|
||||
# http://api.freenas.org/resources/iscsi/index.html#create-resource
|
||||
# Parameters:
|
||||
# - target config (scfg)
|
||||
# - lun_path
|
||||
#
|
||||
sub freenas_iscsi_create_extent {
|
||||
my ($scfg, $lun_path) = @_;
|
||||
|
||||
my $name = $lun_path;
|
||||
$name =~ s/^.*\///; # all from last /
|
||||
$name = $scfg->{'pool'} . '/' . $name;
|
||||
|
||||
my $device = $lun_path;
|
||||
$device =~ s/^\/dev\///; # strip /dev/
|
||||
|
||||
my $request = {
|
||||
"iscsi_target_extent_type" => "Disk",
|
||||
"iscsi_target_extent_name" => $name,
|
||||
"iscsi_target_extent_disk" => $device,
|
||||
};
|
||||
|
||||
my $client = freenas_api_call($scfg, 'POST', "/api/v1.0/services/iscsi/extent/", $request);
|
||||
my $code = $client->responseCode();
|
||||
if ($code == 201) {
|
||||
my $result = decode_json($client->responseContent());
|
||||
syslog("info", "FreeNAS::API::create_extent(lun_path=" . $result->{'iscsi_target_extent_path'} . ") : sucessfull");
|
||||
return $result;
|
||||
} else {
|
||||
freenas_api_log_error($client, "create_extent");
|
||||
return undef;
|
||||
}
|
||||
}
|
||||
|
||||
#
|
||||
# Remove an extent by it's id
|
||||
# http://api.freenas.org/resources/iscsi/index.html#delete-resource
|
||||
# Parameters:
|
||||
# - scfg
|
||||
# - extent_id
|
||||
#
|
||||
sub freenas_iscsi_remove_extent {
|
||||
my ($scfg, $extent_id) = @_;
|
||||
|
||||
my $client = freenas_api_call($scfg, 'DELETE', "/api/v1.0/services/iscsi/extent/$extent_id/", undef);
|
||||
my $code = $client->responseCode();
|
||||
if ($code == 204) {
|
||||
syslog("info","FreeNAS::API::remove_extent(extent_id=$extent_id) : sucessfull");
|
||||
return 1;
|
||||
} else {
|
||||
freenas_api_log_error($client, "remove_extent");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
#
|
||||
# Returns a list of all targets
|
||||
# http://api.freenas.org/resources/iscsi/index.html#get--api-v1.0-services-iscsi-target-
|
||||
#
|
||||
sub freenas_iscsi_get_target {
|
||||
my ($scfg) = @_;
|
||||
|
||||
my $client = freenas_api_call($scfg, 'GET', "/api/v1.0/services/iscsi/target/?limit=0", undef);
|
||||
my $code = $client->responseCode();
|
||||
if ($code == 200) {
|
||||
my $result = decode_json($client->responseContent());
|
||||
syslog("info","FreeNAS::API::get_target() : sucessfull");
|
||||
return $result;
|
||||
} else {
|
||||
freenas_api_log_error($client, "get_target");
|
||||
return undef;
|
||||
}
|
||||
}
|
||||
|
||||
#
|
||||
# Returns a list of associated extents to targets
|
||||
# http://api.freenas.org/resources/iscsi/index.html#get--api-v1.0-services-iscsi-targettoextent-
|
||||
#
|
||||
sub freenas_iscsi_get_target_to_extent {
|
||||
my ($scfg) = @_;
|
||||
|
||||
my $client = freenas_api_call($scfg, 'GET', "/api/v1.0/services/iscsi/targettoextent/?limit=0", undef);
|
||||
my $code = $client->responseCode();
|
||||
if ($code == 200) {
|
||||
my $result = decode_json($client->responseContent());
|
||||
syslog("info","FreeNAS::API::get_target_to_extent() : sucessfull");
|
||||
# If 'iscsi_lunid' is undef then it is set to 'Auto' in FreeNAS
|
||||
# which should be '0' in our eyes.
|
||||
# This gave Proxmox 5.x and FreeNAS 11.1 a few issues.
|
||||
foreach my $item (@$result) {
|
||||
if (!defined($item->{'iscsi_lunid'})) {
|
||||
$item->{'iscsi_lunid'} = 0;
|
||||
syslog("info", "FreeNAS::API::get_target_to_extent() : change undef iscsi_lunid to 0");
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
} else {
|
||||
freenas_api_log_error($client, "get_target_to_extent");
|
||||
return undef;
|
||||
}
|
||||
}
|
||||
|
||||
#
|
||||
# Associate a FreeNas extent to a FreeNas Target
|
||||
# http://api.freenas.org/resources/iscsi/index.html#post--api-v1.0-services-iscsi-targettoextent-
|
||||
# Parameters:
|
||||
# - target config (scfg)
|
||||
# - FreeNas Target ID
|
||||
# - FreeNas Extent ID
|
||||
# - Lun ID
|
||||
#
|
||||
sub freenas_iscsi_create_target_to_extent {
|
||||
my ($scfg, $target_id, $extent_id, $lun_id) = @_;
|
||||
|
||||
my $request = {
|
||||
"iscsi_target" => $target_id,
|
||||
"iscsi_extent" => $extent_id,
|
||||
"iscsi_lunid" => $lun_id
|
||||
};
|
||||
|
||||
my $client = freenas_api_call($scfg, 'POST', "/api/v1.0/services/iscsi/targettoextent/", $request);
|
||||
my $code = $client->responseCode();
|
||||
if ($code == 201) {
|
||||
my $result = decode_json($client->responseContent());
|
||||
syslog("info","FreeNAS::API::create_target_to_extent(target_id=$target_id, extent_id=$extent_id, lun_id=$lun_id) : sucessfull");
|
||||
return $result;
|
||||
} else {
|
||||
freenas_api_log_error($client, "create_target_to_extent");
|
||||
return undef;
|
||||
}
|
||||
}
|
||||
|
||||
#
|
||||
# Remove a Target to extent by it's id
|
||||
# http://api.freenas.org/resources/iscsi/index.html#delete--api-v1.0-services-iscsi-targettoextent-(int-id)-
|
||||
# Parameters:
|
||||
# - scfg
|
||||
# - link_id
|
||||
#
|
||||
sub freenas_iscsi_remove_target_to_extent {
|
||||
my ($scfg, $link_id) = @_;
|
||||
|
||||
my $client = freenas_api_call($scfg, 'DELETE', "/api/v1.0/services/iscsi/targettoextent/$link_id/", undef);
|
||||
my $code = $client->responseCode();
|
||||
if ($code == 204) {
|
||||
syslog("info","FreeNAS::API::remove_target_to_extent(link_id=$link_id) : sucessfull");
|
||||
return 1;
|
||||
} else {
|
||||
freenas_api_log_error($client, "remove_target_to_extent");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
#
|
||||
# Returns all luns associated to the current target defined by $scfg->{target}
|
||||
# This method returns an array reference like "freenas_iscsi_get_extent" do
|
||||
# but with an additionnal hash entry "iscsi_lunid" retrieved from "freenas_iscsi_get_target_to_extent"
|
||||
#
|
||||
sub freenas_list_lu {
|
||||
my ($scfg) = @_;
|
||||
|
||||
my $targets = freenas_iscsi_get_target($scfg);
|
||||
my $target_id = freenas_get_targetid($scfg);
|
||||
|
||||
my @luns = ();
|
||||
my $iscsi_lunid = undef;
|
||||
|
||||
if(defined($target_id)) {
|
||||
my $target2extents = freenas_iscsi_get_target_to_extent($scfg);
|
||||
my $extents = freenas_iscsi_get_extent($scfg);
|
||||
|
||||
foreach my $item (@$target2extents) {
|
||||
if($item->{'iscsi_target'} == $target_id) {
|
||||
foreach my $node (@$extents) {
|
||||
if($node->{'id'} == $item->{'iscsi_extent'}) {
|
||||
if ($item->{'iscsi_lunid'} =~ /(\d+)/) {
|
||||
$iscsi_lunid = "$1";
|
||||
} else {
|
||||
syslog("info", "FreeNAS::API::freenas_list_lu : iscsi_lunid did not pass tainted testing");
|
||||
next;
|
||||
}
|
||||
$node->{'iscsi_lunid'} .= $iscsi_lunid;
|
||||
push(@luns , $node);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
syslog("info", "FreeNAS::API::freenas_list_lu : sucessfull");
|
||||
return \@luns;
|
||||
}
|
||||
|
||||
#
|
||||
# Returns the first available "lunid" (in all targets namespaces)
|
||||
#
|
||||
sub freenas_get_first_available_lunid {
|
||||
my ($scfg) = @_;
|
||||
|
||||
my $target_id = freenas_get_targetid($scfg);
|
||||
my $target2extents = freenas_iscsi_get_target_to_extent($scfg);
|
||||
my @luns = ();
|
||||
|
||||
foreach my $item (@$target2extents) {
|
||||
push(@luns, $item->{'iscsi_lunid'}) if ($item->{'iscsi_target'} == $target_id);
|
||||
}
|
||||
|
||||
my @sorted_luns = sort {$a <=> $b} @luns;
|
||||
my $lun_id = 0;
|
||||
|
||||
# find the first hole, if not, give the +1 of the last lun
|
||||
foreach my $lun (@sorted_luns) {
|
||||
last if $lun != $lun_id;
|
||||
$lun_id = $lun_id + 1;
|
||||
}
|
||||
|
||||
syslog("info", "FreeNAS::API::freenas_get_first_available_lunid : return $lun_id");
|
||||
return $lun_id;
|
||||
}
|
||||
|
||||
#
|
||||
# Returns the target id on FreeNas of the currently configured target of this PVE storage
|
||||
#
|
||||
sub freenas_get_targetid {
|
||||
my ($scfg) = @_;
|
||||
|
||||
my $global = freenas_iscsi_get_globalconfiguration($scfg);
|
||||
my $targets = freenas_iscsi_get_target($scfg);
|
||||
my $target_id = undef;
|
||||
|
||||
foreach my $target (@$targets) {
|
||||
my $iqn = $global->{'iscsi_basename'} . ':' . $target->{'iscsi_target_name'};
|
||||
if($iqn eq $scfg->{target}) {
|
||||
$target_id = $target->{'id'};
|
||||
last;
|
||||
}
|
||||
}
|
||||
|
||||
return $target_id;
|
||||
}
|
||||
|
||||
|
||||
1;
|
||||
|
|
@ -1,140 +0,0 @@
|
|||
--- ZFSPlugin.pm.orig 2018-10-03 11:02:46.000000000 -0400
|
||||
+++ ZFSPlugin.pm 2018-10-28 21:27:44.794732848 -0400
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
use base qw(PVE::Storage::ZFSPoolPlugin);
|
||||
use PVE::Storage::LunCmd::Comstar;
|
||||
+use PVE::Storage::LunCmd::FreeNAS;
|
||||
use PVE::Storage::LunCmd::Istgt;
|
||||
use PVE::Storage::LunCmd::Iet;
|
||||
use PVE::Storage::LunCmd::LIO;
|
||||
@@ -32,7 +33,7 @@
|
||||
my $zfs_unknown_scsi_provider = sub {
|
||||
my ($provider) = @_;
|
||||
|
||||
- die "$provider: unknown iscsi provider. Available [comstar, istgt, iet, LIO]";
|
||||
+ die "$provider: unknown iscsi provider. Available [comstar, freenas, istgt, iet, LIO]";
|
||||
};
|
||||
|
||||
my $zfs_get_base = sub {
|
||||
@@ -40,6 +41,8 @@
|
||||
|
||||
if ($scfg->{iscsiprovider} eq 'comstar') {
|
||||
return PVE::Storage::LunCmd::Comstar::get_base;
|
||||
+ } elsif ($scfg->{iscsiprovider} eq 'freenas') {
|
||||
+ return PVE::Storage::LunCmd::FreeNAS::get_base;
|
||||
} elsif ($scfg->{iscsiprovider} eq 'istgt') {
|
||||
return PVE::Storage::LunCmd::Istgt::get_base;
|
||||
} elsif ($scfg->{iscsiprovider} eq 'iet') {
|
||||
@@ -62,6 +65,8 @@
|
||||
if ($lun_cmds->{$method}) {
|
||||
if ($scfg->{iscsiprovider} eq 'comstar') {
|
||||
$msg = PVE::Storage::LunCmd::Comstar::run_lun_command($scfg, $timeout, $method, @params);
|
||||
+ } elsif ($scfg->{iscsiprovider} eq 'freenas') {
|
||||
+ $msg = PVE::Storage::LunCmd::FreeNAS::run_lun_command($scfg, $timeout, $method, @params);
|
||||
} elsif ($scfg->{iscsiprovider} eq 'istgt') {
|
||||
$msg = PVE::Storage::LunCmd::Istgt::run_lun_command($scfg, $timeout, $method, @params);
|
||||
} elsif ($scfg->{iscsiprovider} eq 'iet') {
|
||||
@@ -160,6 +165,15 @@
|
||||
return $class->zfs_request($scfg, undef, 'list_view', $guid);
|
||||
}
|
||||
|
||||
+# Part of the multipath enhancement
|
||||
+sub zfs_get_wwid_number {
|
||||
+ my ($class, $scfg, $guid) = @_;
|
||||
+
|
||||
+ die "could not find lun_number for guid $guid" if !$guid;
|
||||
+
|
||||
+ return $class->zfs_request($scfg, undef, 'list_extent', $guid);
|
||||
+}
|
||||
+
|
||||
# Configuration
|
||||
|
||||
sub type {
|
||||
@@ -178,6 +192,24 @@
|
||||
description => "iscsi provider",
|
||||
type => 'string',
|
||||
},
|
||||
+ # This is for FreeNAS iscsi and API intergration
|
||||
+ # And some enhancements asked by the community
|
||||
+ freenas_user => {
|
||||
+ description => "FreeNAS API Username",
|
||||
+ type => 'string',
|
||||
+ },
|
||||
+ freenas_password => {
|
||||
+ description => "FreeNAS API Password",
|
||||
+ type => 'string',
|
||||
+ },
|
||||
+ freenas_use_ssl => {
|
||||
+ description => "FreeNAS API access via SSL",
|
||||
+ type => 'boolean',
|
||||
+ },
|
||||
+ freenas_apiv4_host => {
|
||||
+ description => "FreeNAS API Host",
|
||||
+ type => 'string',
|
||||
+ },
|
||||
# this will disable write caching on comstar and istgt.
|
||||
# it is not implemented for iet. iet blockio always operates with
|
||||
# writethrough caching when not in readonly mode
|
||||
@@ -205,14 +237,18 @@
|
||||
nodes => { optional => 1 },
|
||||
disable => { optional => 1 },
|
||||
portal => { fixed => 1 },
|
||||
- target => { fixed => 1 },
|
||||
- pool => { fixed => 1 },
|
||||
+ target => { fixed => 0 },
|
||||
+ pool => { fixed => 0 },
|
||||
blocksize => { fixed => 1 },
|
||||
iscsiprovider => { fixed => 1 },
|
||||
nowritecache => { optional => 1 },
|
||||
sparse => { optional => 1 },
|
||||
comstar_hg => { optional => 1 },
|
||||
comstar_tg => { optional => 1 },
|
||||
+ freenas_user => { optional => 1 },
|
||||
+ freenas_password => { optional => 1 },
|
||||
+ freenas_use_ssl => { optional => 1 },
|
||||
+ freenas_apiv4_host => { optional => 1 },
|
||||
lio_tpg => { optional => 1 },
|
||||
content => { optional => 1 },
|
||||
bwlimit => { optional => 1 },
|
||||
@@ -237,6 +273,40 @@
|
||||
|
||||
my $path = "iscsi://$portal/$target/$lun";
|
||||
|
||||
+ # Multipath enhancement
|
||||
+ eval {
|
||||
+ my $wwid = $class->zfs_get_wwid_number($scfg, $guid);
|
||||
+# syslog(info,"JD: path get_lun_number guid $guid");
|
||||
+
|
||||
+ if ($wwid =~ /^([-\@\w.]+)$/) {
|
||||
+ $wwid = $1; # $data now untainted
|
||||
+ } else {
|
||||
+ die "Bad data in '$wwid'"; # log this somewhere
|
||||
+ }
|
||||
+ my $wwid_end = substr $wwid, 16;
|
||||
+
|
||||
+ my $mapper = '';
|
||||
+ sleep 3;
|
||||
+ run_command("iscsiadm -m session --rescan");
|
||||
+ sleep 3;
|
||||
+ my $line = `multipath -ll | grep \"$wwid_end\"`;
|
||||
+ my ($mapper_device) = split(' ', $line);
|
||||
+ $mapper_device = "" unless $mapper_device;
|
||||
+ $mapper .= $mapper_device;
|
||||
+
|
||||
+ if ($mapper =~ /^([-\@\w.]+)$/) {
|
||||
+ $mapper = $1; # $data now untainted
|
||||
+ } else {
|
||||
+ $mapper = '';
|
||||
+ }
|
||||
+
|
||||
+# syslog(info,"Multipath mapper found: $mapper\n");
|
||||
+ if ($mapper ne "") {
|
||||
+ $path = "/dev/mapper/$mapper";
|
||||
+ sleep 5;
|
||||
+ }
|
||||
+ };
|
||||
+
|
||||
return ($path, $vmid, $vtype);
|
||||
}
|
||||
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
--- ZFSPlugin-5.0-33.pm 2018-12-15 09:29:56.586101170 -0500
|
||||
+++ ZFSPlugin.pm 2018-12-15 09:24:08.393984276 -0500
|
||||
@@ -27,6 +27,7 @@
|
||||
modify_lu => 1,
|
||||
add_view => 1,
|
||||
list_view => 1,
|
||||
+ list_extent => 1,
|
||||
list_lu => 1,
|
||||
};
|
||||
|
||||
|
|
@ -1,147 +0,0 @@
|
|||
--- ZFSPlugin.pm.orig 2018-11-27 07:23:15.000000000 -0500
|
||||
+++ ZFSPlugin.pm 2018-12-15 09:24:08.393984276 -0500
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
use base qw(PVE::Storage::ZFSPoolPlugin);
|
||||
use PVE::Storage::LunCmd::Comstar;
|
||||
+use PVE::Storage::LunCmd::FreeNAS;
|
||||
use PVE::Storage::LunCmd::Istgt;
|
||||
use PVE::Storage::LunCmd::Iet;
|
||||
use PVE::Storage::LunCmd::LIO;
|
||||
@@ -26,13 +27,14 @@
|
||||
modify_lu => 1,
|
||||
add_view => 1,
|
||||
list_view => 1,
|
||||
+ list_extent => 1,
|
||||
list_lu => 1,
|
||||
};
|
||||
|
||||
my $zfs_unknown_scsi_provider = sub {
|
||||
my ($provider) = @_;
|
||||
|
||||
- die "$provider: unknown iscsi provider. Available [comstar, istgt, iet, LIO]";
|
||||
+ die "$provider: unknown iscsi provider. Available [comstar, freenas, istgt, iet, LIO]";
|
||||
};
|
||||
|
||||
my $zfs_get_base = sub {
|
||||
@@ -40,6 +42,8 @@
|
||||
|
||||
if ($scfg->{iscsiprovider} eq 'comstar') {
|
||||
return PVE::Storage::LunCmd::Comstar::get_base;
|
||||
+ } elsif ($scfg->{iscsiprovider} eq 'freenas') {
|
||||
+ return PVE::Storage::LunCmd::FreeNAS::get_base;
|
||||
} elsif ($scfg->{iscsiprovider} eq 'istgt') {
|
||||
return PVE::Storage::LunCmd::Istgt::get_base;
|
||||
} elsif ($scfg->{iscsiprovider} eq 'iet') {
|
||||
@@ -62,6 +66,8 @@
|
||||
if ($lun_cmds->{$method}) {
|
||||
if ($scfg->{iscsiprovider} eq 'comstar') {
|
||||
$msg = PVE::Storage::LunCmd::Comstar::run_lun_command($scfg, $timeout, $method, @params);
|
||||
+ } elsif ($scfg->{iscsiprovider} eq 'freenas') {
|
||||
+ $msg = PVE::Storage::LunCmd::FreeNAS::run_lun_command($scfg, $timeout, $method, @params);
|
||||
} elsif ($scfg->{iscsiprovider} eq 'istgt') {
|
||||
$msg = PVE::Storage::LunCmd::Istgt::run_lun_command($scfg, $timeout, $method, @params);
|
||||
} elsif ($scfg->{iscsiprovider} eq 'iet') {
|
||||
@@ -160,6 +166,15 @@
|
||||
return $class->zfs_request($scfg, undef, 'list_view', $guid);
|
||||
}
|
||||
|
||||
+# Part of the multipath enhancement
|
||||
+sub zfs_get_wwid_number {
|
||||
+ my ($class, $scfg, $guid) = @_;
|
||||
+
|
||||
+ die "could not find lun_number for guid $guid" if !$guid;
|
||||
+
|
||||
+ return $class->zfs_request($scfg, undef, 'list_extent', $guid);
|
||||
+}
|
||||
+
|
||||
# Configuration
|
||||
|
||||
sub type {
|
||||
@@ -178,6 +193,24 @@
|
||||
description => "iscsi provider",
|
||||
type => 'string',
|
||||
},
|
||||
+ # This is for FreeNAS iscsi and API intergration
|
||||
+ # And some enhancements asked by the community
|
||||
+ freenas_user => {
|
||||
+ description => "FreeNAS API Username",
|
||||
+ type => 'string',
|
||||
+ },
|
||||
+ freenas_password => {
|
||||
+ description => "FreeNAS API Password",
|
||||
+ type => 'string',
|
||||
+ },
|
||||
+ freenas_use_ssl => {
|
||||
+ description => "FreeNAS API access via SSL",
|
||||
+ type => 'boolean',
|
||||
+ },
|
||||
+ freenas_apiv4_host => {
|
||||
+ description => "FreeNAS API Host",
|
||||
+ type => 'string',
|
||||
+ },
|
||||
# this will disable write caching on comstar and istgt.
|
||||
# it is not implemented for iet. iet blockio always operates with
|
||||
# writethrough caching when not in readonly mode
|
||||
@@ -205,14 +238,18 @@
|
||||
nodes => { optional => 1 },
|
||||
disable => { optional => 1 },
|
||||
portal => { fixed => 1 },
|
||||
- target => { fixed => 1 },
|
||||
- pool => { fixed => 1 },
|
||||
+ target => { fixed => 0 },
|
||||
+ pool => { fixed => 0 },
|
||||
blocksize => { fixed => 1 },
|
||||
iscsiprovider => { fixed => 1 },
|
||||
nowritecache => { optional => 1 },
|
||||
sparse => { optional => 1 },
|
||||
comstar_hg => { optional => 1 },
|
||||
comstar_tg => { optional => 1 },
|
||||
+ freenas_user => { optional => 1 },
|
||||
+ freenas_password => { optional => 1 },
|
||||
+ freenas_use_ssl => { optional => 1 },
|
||||
+ freenas_apiv4_host => { optional => 1 },
|
||||
lio_tpg => { optional => 1 },
|
||||
content => { optional => 1 },
|
||||
bwlimit => { optional => 1 },
|
||||
@@ -237,6 +274,40 @@
|
||||
|
||||
my $path = "iscsi://$portal/$target/$lun";
|
||||
|
||||
+ # Multipath enhancement
|
||||
+ eval {
|
||||
+ my $wwid = $class->zfs_get_wwid_number($scfg, $guid);
|
||||
+# syslog(info,"JD: path get_lun_number guid $guid");
|
||||
+
|
||||
+ if ($wwid =~ /^([-\@\w.]+)$/) {
|
||||
+ $wwid = $1; # $data now untainted
|
||||
+ } else {
|
||||
+ die "Bad data in '$wwid'"; # log this somewhere
|
||||
+ }
|
||||
+ my $wwid_end = substr $wwid, 16;
|
||||
+
|
||||
+ my $mapper = '';
|
||||
+ sleep 3;
|
||||
+ run_command("iscsiadm -m session --rescan");
|
||||
+ sleep 3;
|
||||
+ my $line = `multipath -ll | grep \"$wwid_end\"`;
|
||||
+ my ($mapper_device) = split(' ', $line);
|
||||
+ $mapper_device = "" unless $mapper_device;
|
||||
+ $mapper .= $mapper_device;
|
||||
+
|
||||
+ if ($mapper =~ /^([-\@\w.]+)$/) {
|
||||
+ $mapper = $1; # $data now untainted
|
||||
+ } else {
|
||||
+ $mapper = '';
|
||||
+ }
|
||||
+
|
||||
+# syslog(info,"Multipath mapper found: $mapper\n");
|
||||
+ if ($mapper ne "") {
|
||||
+ $path = "/dev/mapper/$mapper";
|
||||
+ sleep 5;
|
||||
+ }
|
||||
+ };
|
||||
+
|
||||
return ($path, $vmid, $vtype);
|
||||
}
|
||||
|
||||
|
|
@ -1,147 +0,0 @@
|
|||
--- ZFSPlugin-5.0-34.pm 2018-12-21 12:52:21.764194866 -0500
|
||||
+++ ZFSPlugin.pm 2018-12-21 12:53:08.663665081 -0500
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
use base qw(PVE::Storage::ZFSPoolPlugin);
|
||||
use PVE::Storage::LunCmd::Comstar;
|
||||
+use PVE::Storage::LunCmd::FreeNAS;
|
||||
use PVE::Storage::LunCmd::Istgt;
|
||||
use PVE::Storage::LunCmd::Iet;
|
||||
use PVE::Storage::LunCmd::LIO;
|
||||
@@ -26,13 +27,14 @@
|
||||
modify_lu => 1,
|
||||
add_view => 1,
|
||||
list_view => 1,
|
||||
+ list_extent => 1,
|
||||
list_lu => 1,
|
||||
};
|
||||
|
||||
my $zfs_unknown_scsi_provider = sub {
|
||||
my ($provider) = @_;
|
||||
|
||||
- die "$provider: unknown iscsi provider. Available [comstar, istgt, iet, LIO]";
|
||||
+ die "$provider: unknown iscsi provider. Available [comstar, freenas, istgt, iet, LIO]";
|
||||
};
|
||||
|
||||
my $zfs_get_base = sub {
|
||||
@@ -40,6 +42,8 @@
|
||||
|
||||
if ($scfg->{iscsiprovider} eq 'comstar') {
|
||||
return PVE::Storage::LunCmd::Comstar::get_base;
|
||||
+ } elsif ($scfg->{iscsiprovider} eq 'freenas') {
|
||||
+ return PVE::Storage::LunCmd::FreeNAS::get_base;
|
||||
} elsif ($scfg->{iscsiprovider} eq 'istgt') {
|
||||
return PVE::Storage::LunCmd::Istgt::get_base;
|
||||
} elsif ($scfg->{iscsiprovider} eq 'iet') {
|
||||
@@ -62,6 +66,8 @@
|
||||
if ($lun_cmds->{$method}) {
|
||||
if ($scfg->{iscsiprovider} eq 'comstar') {
|
||||
$msg = PVE::Storage::LunCmd::Comstar::run_lun_command($scfg, $timeout, $method, @params);
|
||||
+ } elsif ($scfg->{iscsiprovider} eq 'freenas') {
|
||||
+ $msg = PVE::Storage::LunCmd::FreeNAS::run_lun_command($scfg, $timeout, $method, @params);
|
||||
} elsif ($scfg->{iscsiprovider} eq 'istgt') {
|
||||
$msg = PVE::Storage::LunCmd::Istgt::run_lun_command($scfg, $timeout, $method, @params);
|
||||
} elsif ($scfg->{iscsiprovider} eq 'iet') {
|
||||
@@ -160,6 +166,15 @@
|
||||
return $class->zfs_request($scfg, undef, 'list_view', $guid);
|
||||
}
|
||||
|
||||
+# Part of the multipath enhancement
|
||||
+sub zfs_get_wwid_number {
|
||||
+ my ($class, $scfg, $guid) = @_;
|
||||
+
|
||||
+ die "could not find lun_number for guid $guid" if !$guid;
|
||||
+
|
||||
+ return $class->zfs_request($scfg, undef, 'list_extent', $guid);
|
||||
+}
|
||||
+
|
||||
# Configuration
|
||||
|
||||
sub type {
|
||||
@@ -178,6 +193,24 @@
|
||||
description => "iscsi provider",
|
||||
type => 'string',
|
||||
},
|
||||
+ # This is for FreeNAS iscsi and API intergration
|
||||
+ # And some enhancements asked by the community
|
||||
+ freenas_user => {
|
||||
+ description => "FreeNAS API Username",
|
||||
+ type => 'string',
|
||||
+ },
|
||||
+ freenas_password => {
|
||||
+ description => "FreeNAS API Password",
|
||||
+ type => 'string',
|
||||
+ },
|
||||
+ freenas_use_ssl => {
|
||||
+ description => "FreeNAS API access via SSL",
|
||||
+ type => 'boolean',
|
||||
+ },
|
||||
+ freenas_apiv4_host => {
|
||||
+ description => "FreeNAS API Host",
|
||||
+ type => 'string',
|
||||
+ },
|
||||
# this will disable write caching on comstar and istgt.
|
||||
# it is not implemented for iet. iet blockio always operates with
|
||||
# writethrough caching when not in readonly mode
|
||||
@@ -205,14 +238,18 @@
|
||||
nodes => { optional => 1 },
|
||||
disable => { optional => 1 },
|
||||
portal => { fixed => 1 },
|
||||
- target => { fixed => 1 },
|
||||
- pool => { fixed => 1 },
|
||||
+ target => { fixed => 0 },
|
||||
+ pool => { fixed => 0 },
|
||||
blocksize => { fixed => 1 },
|
||||
iscsiprovider => { fixed => 1 },
|
||||
nowritecache => { optional => 1 },
|
||||
sparse => { optional => 1 },
|
||||
comstar_hg => { optional => 1 },
|
||||
comstar_tg => { optional => 1 },
|
||||
+ freenas_user => { optional => 1 },
|
||||
+ freenas_password => { optional => 1 },
|
||||
+ freenas_use_ssl => { optional => 1 },
|
||||
+ freenas_apiv4_host => { optional => 1 },
|
||||
lio_tpg => { optional => 1 },
|
||||
content => { optional => 1 },
|
||||
bwlimit => { optional => 1 },
|
||||
@@ -237,6 +274,40 @@
|
||||
|
||||
my $path = "iscsi://$portal/$target/$lun";
|
||||
|
||||
+ # Multipath enhancement
|
||||
+ eval {
|
||||
+ my $wwid = $class->zfs_get_wwid_number($scfg, $guid);
|
||||
+# syslog(info,"JD: path get_lun_number guid $guid");
|
||||
+
|
||||
+ if ($wwid =~ /^([-\@\w.]+)$/) {
|
||||
+ $wwid = $1; # $data now untainted
|
||||
+ } else {
|
||||
+ die "Bad data in '$wwid'"; # log this somewhere
|
||||
+ }
|
||||
+ my $wwid_end = substr $wwid, 16;
|
||||
+
|
||||
+ my $mapper = '';
|
||||
+ sleep 3;
|
||||
+ run_command("iscsiadm -m session --rescan");
|
||||
+ sleep 3;
|
||||
+ my $line = `multipath -ll | grep \"$wwid_end\"`;
|
||||
+ my ($mapper_device) = split(' ', $line);
|
||||
+ $mapper_device = "" unless $mapper_device;
|
||||
+ $mapper .= $mapper_device;
|
||||
+
|
||||
+ if ($mapper =~ /^([-\@\w.]+)$/) {
|
||||
+ $mapper = $1; # $data now untainted
|
||||
+ } else {
|
||||
+ $mapper = '';
|
||||
+ }
|
||||
+
|
||||
+# syslog(info,"Multipath mapper found: $mapper\n");
|
||||
+ if ($mapper ne "") {
|
||||
+ $path = "/dev/mapper/$mapper";
|
||||
+ sleep 5;
|
||||
+ }
|
||||
+ };
|
||||
+
|
||||
return ($path, $vmid, $vtype);
|
||||
}
|
||||
|
||||
|
|
@ -1,147 +0,0 @@
|
|||
--- ZFSPlugin.pm.orig 2019-02-07 09:14:12.000000000 -0500
|
||||
+++ ZFSPlugin.pm 2019-02-26 20:53:29.096598801 -0500
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
use base qw(PVE::Storage::ZFSPoolPlugin);
|
||||
use PVE::Storage::LunCmd::Comstar;
|
||||
+use PVE::Storage::LunCmd::FreeNAS;
|
||||
use PVE::Storage::LunCmd::Istgt;
|
||||
use PVE::Storage::LunCmd::Iet;
|
||||
use PVE::Storage::LunCmd::LIO;
|
||||
@@ -26,13 +27,14 @@
|
||||
modify_lu => 1,
|
||||
add_view => 1,
|
||||
list_view => 1,
|
||||
+ list_extent => 1,
|
||||
list_lu => 1,
|
||||
};
|
||||
|
||||
my $zfs_unknown_scsi_provider = sub {
|
||||
my ($provider) = @_;
|
||||
|
||||
- die "$provider: unknown iscsi provider. Available [comstar, istgt, iet, LIO]";
|
||||
+ die "$provider: unknown iscsi provider. Available [comstar, freenas, istgt, iet, LIO]";
|
||||
};
|
||||
|
||||
my $zfs_get_base = sub {
|
||||
@@ -40,6 +42,8 @@
|
||||
|
||||
if ($scfg->{iscsiprovider} eq 'comstar') {
|
||||
return PVE::Storage::LunCmd::Comstar::get_base;
|
||||
+ } elsif ($scfg->{iscsiprovider} eq 'freenas') {
|
||||
+ return PVE::Storage::LunCmd::FreeNAS::get_base;
|
||||
} elsif ($scfg->{iscsiprovider} eq 'istgt') {
|
||||
return PVE::Storage::LunCmd::Istgt::get_base;
|
||||
} elsif ($scfg->{iscsiprovider} eq 'iet') {
|
||||
@@ -62,6 +66,8 @@
|
||||
if ($lun_cmds->{$method}) {
|
||||
if ($scfg->{iscsiprovider} eq 'comstar') {
|
||||
$msg = PVE::Storage::LunCmd::Comstar::run_lun_command($scfg, $timeout, $method, @params);
|
||||
+ } elsif ($scfg->{iscsiprovider} eq 'freenas') {
|
||||
+ $msg = PVE::Storage::LunCmd::FreeNAS::run_lun_command($scfg, $timeout, $method, @params);
|
||||
} elsif ($scfg->{iscsiprovider} eq 'istgt') {
|
||||
$msg = PVE::Storage::LunCmd::Istgt::run_lun_command($scfg, $timeout, $method, @params);
|
||||
} elsif ($scfg->{iscsiprovider} eq 'iet') {
|
||||
@@ -160,6 +166,15 @@
|
||||
return $class->zfs_request($scfg, undef, 'list_view', $guid);
|
||||
}
|
||||
|
||||
+# Part of the multipath enhancement
|
||||
+sub zfs_get_wwid_number {
|
||||
+ my ($class, $scfg, $guid) = @_;
|
||||
+
|
||||
+ die "could not find lun_number for guid $guid" if !$guid;
|
||||
+
|
||||
+ return $class->zfs_request($scfg, undef, 'list_extent', $guid);
|
||||
+}
|
||||
+
|
||||
# Configuration
|
||||
|
||||
sub type {
|
||||
@@ -178,6 +193,24 @@
|
||||
description => "iscsi provider",
|
||||
type => 'string',
|
||||
},
|
||||
+ # This is for FreeNAS iscsi and API intergration
|
||||
+ # And some enhancements asked by the community
|
||||
+ freenas_user => {
|
||||
+ description => "FreeNAS API Username",
|
||||
+ type => 'string',
|
||||
+ },
|
||||
+ freenas_password => {
|
||||
+ description => "FreeNAS API Password",
|
||||
+ type => 'string',
|
||||
+ },
|
||||
+ freenas_use_ssl => {
|
||||
+ description => "FreeNAS API access via SSL",
|
||||
+ type => 'boolean',
|
||||
+ },
|
||||
+ freenas_apiv4_host => {
|
||||
+ description => "FreeNAS API Host",
|
||||
+ type => 'string',
|
||||
+ },
|
||||
# this will disable write caching on comstar and istgt.
|
||||
# it is not implemented for iet. iet blockio always operates with
|
||||
# writethrough caching when not in readonly mode
|
||||
@@ -205,14 +238,18 @@
|
||||
nodes => { optional => 1 },
|
||||
disable => { optional => 1 },
|
||||
portal => { fixed => 1 },
|
||||
- target => { fixed => 1 },
|
||||
- pool => { fixed => 1 },
|
||||
+ target => { fixed => 0 },
|
||||
+ pool => { fixed => 0 },
|
||||
blocksize => { fixed => 1 },
|
||||
iscsiprovider => { fixed => 1 },
|
||||
nowritecache => { optional => 1 },
|
||||
sparse => { optional => 1 },
|
||||
comstar_hg => { optional => 1 },
|
||||
comstar_tg => { optional => 1 },
|
||||
+ freenas_user => { optional => 1 },
|
||||
+ freenas_password => { optional => 1 },
|
||||
+ freenas_use_ssl => { optional => 1 },
|
||||
+ freenas_apiv4_host => { optional => 1 },
|
||||
lio_tpg => { optional => 1 },
|
||||
content => { optional => 1 },
|
||||
bwlimit => { optional => 1 },
|
||||
@@ -237,6 +274,40 @@
|
||||
|
||||
my $path = "iscsi://$portal/$target/$lun";
|
||||
|
||||
+ # Multipath enhancement
|
||||
+ eval {
|
||||
+ my $wwid = $class->zfs_get_wwid_number($scfg, $guid);
|
||||
+# syslog(info,"JD: path get_lun_number guid $guid");
|
||||
+
|
||||
+ if ($wwid =~ /^([-\@\w.]+)$/) {
|
||||
+ $wwid = $1; # $data now untainted
|
||||
+ } else {
|
||||
+ die "Bad data in '$wwid'"; # log this somewhere
|
||||
+ }
|
||||
+ my $wwid_end = substr $wwid, 16;
|
||||
+
|
||||
+ my $mapper = '';
|
||||
+ sleep 3;
|
||||
+ run_command("iscsiadm -m session --rescan");
|
||||
+ sleep 3;
|
||||
+ my $line = `multipath -ll | grep \"$wwid_end\"`;
|
||||
+ my ($mapper_device) = split(' ', $line);
|
||||
+ $mapper_device = "" unless $mapper_device;
|
||||
+ $mapper .= $mapper_device;
|
||||
+
|
||||
+ if ($mapper =~ /^([-\@\w.]+)$/) {
|
||||
+ $mapper = $1; # $data now untainted
|
||||
+ } else {
|
||||
+ $mapper = '';
|
||||
+ }
|
||||
+
|
||||
+# syslog(info,"Multipath mapper found: $mapper\n");
|
||||
+ if ($mapper ne "") {
|
||||
+ $path = "/dev/mapper/$mapper";
|
||||
+ sleep 5;
|
||||
+ }
|
||||
+ };
|
||||
+
|
||||
return ($path, $vmid, $vtype);
|
||||
}
|
||||
|
||||
|
|
@ -1,147 +0,0 @@
|
|||
--- ZFSPlugin.pm.orig 2019-04-30 09:54:55.000000000 -0400
|
||||
+++ ZFSPlugin.pm 2019-05-11 10:32:36.326734352 -0400
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
use base qw(PVE::Storage::ZFSPoolPlugin);
|
||||
use PVE::Storage::LunCmd::Comstar;
|
||||
+use PVE::Storage::LunCmd::FreeNAS;
|
||||
use PVE::Storage::LunCmd::Istgt;
|
||||
use PVE::Storage::LunCmd::Iet;
|
||||
use PVE::Storage::LunCmd::LIO;
|
||||
@@ -26,13 +27,14 @@
|
||||
modify_lu => 1,
|
||||
add_view => 1,
|
||||
list_view => 1,
|
||||
+ list_extent => 1,
|
||||
list_lu => 1,
|
||||
};
|
||||
|
||||
my $zfs_unknown_scsi_provider = sub {
|
||||
my ($provider) = @_;
|
||||
|
||||
- die "$provider: unknown iscsi provider. Available [comstar, istgt, iet, LIO]";
|
||||
+ die "$provider: unknown iscsi provider. Available [comstar, freenas, istgt, iet, LIO]";
|
||||
};
|
||||
|
||||
my $zfs_get_base = sub {
|
||||
@@ -40,6 +42,8 @@
|
||||
|
||||
if ($scfg->{iscsiprovider} eq 'comstar') {
|
||||
return PVE::Storage::LunCmd::Comstar::get_base;
|
||||
+ } elsif ($scfg->{iscsiprovider} eq 'freenas') {
|
||||
+ return PVE::Storage::LunCmd::FreeNAS::get_base;
|
||||
} elsif ($scfg->{iscsiprovider} eq 'istgt') {
|
||||
return PVE::Storage::LunCmd::Istgt::get_base;
|
||||
} elsif ($scfg->{iscsiprovider} eq 'iet') {
|
||||
@@ -62,6 +66,8 @@
|
||||
if ($lun_cmds->{$method}) {
|
||||
if ($scfg->{iscsiprovider} eq 'comstar') {
|
||||
$msg = PVE::Storage::LunCmd::Comstar::run_lun_command($scfg, $timeout, $method, @params);
|
||||
+ } elsif ($scfg->{iscsiprovider} eq 'freenas') {
|
||||
+ $msg = PVE::Storage::LunCmd::FreeNAS::run_lun_command($scfg, $timeout, $method, @params);
|
||||
} elsif ($scfg->{iscsiprovider} eq 'istgt') {
|
||||
$msg = PVE::Storage::LunCmd::Istgt::run_lun_command($scfg, $timeout, $method, @params);
|
||||
} elsif ($scfg->{iscsiprovider} eq 'iet') {
|
||||
@@ -160,6 +166,15 @@
|
||||
return $class->zfs_request($scfg, undef, 'list_view', $guid);
|
||||
}
|
||||
|
||||
+# Part of the multipath enhancement
|
||||
+sub zfs_get_wwid_number {
|
||||
+ my ($class, $scfg, $guid) = @_;
|
||||
+
|
||||
+ die "could not find lun_number for guid $guid" if !$guid;
|
||||
+
|
||||
+ return $class->zfs_request($scfg, undef, 'list_extent', $guid);
|
||||
+}
|
||||
+
|
||||
# Configuration
|
||||
|
||||
sub type {
|
||||
@@ -178,6 +193,24 @@
|
||||
description => "iscsi provider",
|
||||
type => 'string',
|
||||
},
|
||||
+ # This is for FreeNAS iscsi and API intergration
|
||||
+ # And some enhancements asked by the community
|
||||
+ freenas_user => {
|
||||
+ description => "FreeNAS API Username",
|
||||
+ type => 'string',
|
||||
+ },
|
||||
+ freenas_password => {
|
||||
+ description => "FreeNAS API Password",
|
||||
+ type => 'string',
|
||||
+ },
|
||||
+ freenas_use_ssl => {
|
||||
+ description => "FreeNAS API access via SSL",
|
||||
+ type => 'boolean',
|
||||
+ },
|
||||
+ freenas_apiv4_host => {
|
||||
+ description => "FreeNAS API Host",
|
||||
+ type => 'string',
|
||||
+ },
|
||||
# this will disable write caching on comstar and istgt.
|
||||
# it is not implemented for iet. iet blockio always operates with
|
||||
# writethrough caching when not in readonly mode
|
||||
@@ -205,14 +238,18 @@
|
||||
nodes => { optional => 1 },
|
||||
disable => { optional => 1 },
|
||||
portal => { fixed => 1 },
|
||||
- target => { fixed => 1 },
|
||||
- pool => { fixed => 1 },
|
||||
+ target => { fixed => 0 },
|
||||
+ pool => { fixed => 0 },
|
||||
blocksize => { fixed => 1 },
|
||||
iscsiprovider => { fixed => 1 },
|
||||
nowritecache => { optional => 1 },
|
||||
sparse => { optional => 1 },
|
||||
comstar_hg => { optional => 1 },
|
||||
comstar_tg => { optional => 1 },
|
||||
+ freenas_user => { optional => 1 },
|
||||
+ freenas_password => { optional => 1 },
|
||||
+ freenas_use_ssl => { optional => 1 },
|
||||
+ freenas_apiv4_host => { optional => 1 },
|
||||
lio_tpg => { optional => 1 },
|
||||
content => { optional => 1 },
|
||||
bwlimit => { optional => 1 },
|
||||
@@ -237,6 +274,40 @@
|
||||
|
||||
my $path = "iscsi://$portal/$target/$lun";
|
||||
|
||||
+ # Multipath enhancement
|
||||
+ eval {
|
||||
+ my $wwid = $class->zfs_get_wwid_number($scfg, $guid);
|
||||
+# syslog(info,"JD: path get_lun_number guid $guid");
|
||||
+
|
||||
+ if ($wwid =~ /^([-\@\w.]+)$/) {
|
||||
+ $wwid = $1; # $data now untainted
|
||||
+ } else {
|
||||
+ die "Bad data in '$wwid'"; # log this somewhere
|
||||
+ }
|
||||
+ my $wwid_end = substr $wwid, 16;
|
||||
+
|
||||
+ my $mapper = '';
|
||||
+ sleep 3;
|
||||
+ run_command("iscsiadm -m session --rescan");
|
||||
+ sleep 3;
|
||||
+ my $line = `multipath -ll | grep \"$wwid_end\"`;
|
||||
+ my ($mapper_device) = split(' ', $line);
|
||||
+ $mapper_device = "" unless $mapper_device;
|
||||
+ $mapper .= $mapper_device;
|
||||
+
|
||||
+ if ($mapper =~ /^([-\@\w.]+)$/) {
|
||||
+ $mapper = $1; # $data now untainted
|
||||
+ } else {
|
||||
+ $mapper = '';
|
||||
+ }
|
||||
+
|
||||
+# syslog(info,"Multipath mapper found: $mapper\n");
|
||||
+ if ($mapper ne "") {
|
||||
+ $path = "/dev/mapper/$mapper";
|
||||
+ sleep 5;
|
||||
+ }
|
||||
+ };
|
||||
+
|
||||
return ($path, $vmid, $vtype);
|
||||
}
|
||||
|
||||
|
|
@ -1,147 +0,0 @@
|
|||
--- ZFSPlugin.pm.orig 2019-06-25 06:54:50.000000000 -0400
|
||||
+++ ZFSPlugin.pm 2019-07-16 13:36:48.817831174 -0400
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
use base qw(PVE::Storage::ZFSPoolPlugin);
|
||||
use PVE::Storage::LunCmd::Comstar;
|
||||
+use PVE::Storage::LunCmd::FreeNAS;
|
||||
use PVE::Storage::LunCmd::Istgt;
|
||||
use PVE::Storage::LunCmd::Iet;
|
||||
use PVE::Storage::LunCmd::LIO;
|
||||
@@ -26,13 +27,14 @@
|
||||
modify_lu => 1,
|
||||
add_view => 1,
|
||||
list_view => 1,
|
||||
+ list_extent => 1,
|
||||
list_lu => 1,
|
||||
};
|
||||
|
||||
my $zfs_unknown_scsi_provider = sub {
|
||||
my ($provider) = @_;
|
||||
|
||||
- die "$provider: unknown iscsi provider. Available [comstar, istgt, iet, LIO]";
|
||||
+ die "$provider: unknown iscsi provider. Available [comstar, freenas, istgt, iet, LIO]";
|
||||
};
|
||||
|
||||
my $zfs_get_base = sub {
|
||||
@@ -40,6 +42,8 @@
|
||||
|
||||
if ($scfg->{iscsiprovider} eq 'comstar') {
|
||||
return PVE::Storage::LunCmd::Comstar::get_base;
|
||||
+ } elsif ($scfg->{iscsiprovider} eq 'freenas') {
|
||||
+ return PVE::Storage::LunCmd::FreeNAS::get_base;
|
||||
} elsif ($scfg->{iscsiprovider} eq 'istgt') {
|
||||
return PVE::Storage::LunCmd::Istgt::get_base;
|
||||
} elsif ($scfg->{iscsiprovider} eq 'iet') {
|
||||
@@ -62,6 +66,8 @@
|
||||
if ($lun_cmds->{$method}) {
|
||||
if ($scfg->{iscsiprovider} eq 'comstar') {
|
||||
$msg = PVE::Storage::LunCmd::Comstar::run_lun_command($scfg, $timeout, $method, @params);
|
||||
+ } elsif ($scfg->{iscsiprovider} eq 'freenas') {
|
||||
+ $msg = PVE::Storage::LunCmd::FreeNAS::run_lun_command($scfg, $timeout, $method, @params);
|
||||
} elsif ($scfg->{iscsiprovider} eq 'istgt') {
|
||||
$msg = PVE::Storage::LunCmd::Istgt::run_lun_command($scfg, $timeout, $method, @params);
|
||||
} elsif ($scfg->{iscsiprovider} eq 'iet') {
|
||||
@@ -160,6 +166,15 @@
|
||||
return $class->zfs_request($scfg, undef, 'list_view', $guid);
|
||||
}
|
||||
|
||||
+# Part of the multipath enhancement
|
||||
+sub zfs_get_wwid_number {
|
||||
+ my ($class, $scfg, $guid) = @_;
|
||||
+
|
||||
+ die "could not find lun_number for guid $guid" if !$guid;
|
||||
+
|
||||
+ return $class->zfs_request($scfg, undef, 'list_extent', $guid);
|
||||
+}
|
||||
+
|
||||
# Configuration
|
||||
|
||||
sub type {
|
||||
@@ -178,6 +193,24 @@
|
||||
description => "iscsi provider",
|
||||
type => 'string',
|
||||
},
|
||||
+ # This is for FreeNAS iscsi and API intergration
|
||||
+ # And some enhancements asked by the community
|
||||
+ freenas_user => {
|
||||
+ description => "FreeNAS API Username",
|
||||
+ type => 'string',
|
||||
+ },
|
||||
+ freenas_password => {
|
||||
+ description => "FreeNAS API Password",
|
||||
+ type => 'string',
|
||||
+ },
|
||||
+ freenas_use_ssl => {
|
||||
+ description => "FreeNAS API access via SSL",
|
||||
+ type => 'boolean',
|
||||
+ },
|
||||
+ freenas_apiv4_host => {
|
||||
+ description => "FreeNAS API Host",
|
||||
+ type => 'string',
|
||||
+ },
|
||||
# this will disable write caching on comstar and istgt.
|
||||
# it is not implemented for iet. iet blockio always operates with
|
||||
# writethrough caching when not in readonly mode
|
||||
@@ -205,14 +238,18 @@
|
||||
nodes => { optional => 1 },
|
||||
disable => { optional => 1 },
|
||||
portal => { fixed => 1 },
|
||||
- target => { fixed => 1 },
|
||||
- pool => { fixed => 1 },
|
||||
+ target => { fixed => 0 },
|
||||
+ pool => { fixed => 0 },
|
||||
blocksize => { fixed => 1 },
|
||||
iscsiprovider => { fixed => 1 },
|
||||
nowritecache => { optional => 1 },
|
||||
sparse => { optional => 1 },
|
||||
comstar_hg => { optional => 1 },
|
||||
comstar_tg => { optional => 1 },
|
||||
+ freenas_user => { optional => 1 },
|
||||
+ freenas_password => { optional => 1 },
|
||||
+ freenas_use_ssl => { optional => 1 },
|
||||
+ freenas_apiv4_host => { optional => 1 },
|
||||
lio_tpg => { optional => 1 },
|
||||
content => { optional => 1 },
|
||||
bwlimit => { optional => 1 },
|
||||
@@ -237,6 +274,40 @@
|
||||
|
||||
my $path = "iscsi://$portal/$target/$lun";
|
||||
|
||||
+ # Multipath enhancement
|
||||
+ eval {
|
||||
+ my $wwid = $class->zfs_get_wwid_number($scfg, $guid);
|
||||
+# syslog(info,"JD: path get_lun_number guid $guid");
|
||||
+
|
||||
+ if ($wwid =~ /^([-\@\w.]+)$/) {
|
||||
+ $wwid = $1; # $data now untainted
|
||||
+ } else {
|
||||
+ die "Bad data in '$wwid'"; # log this somewhere
|
||||
+ }
|
||||
+ my $wwid_end = substr $wwid, 16;
|
||||
+
|
||||
+ my $mapper = '';
|
||||
+ sleep 3;
|
||||
+ run_command("iscsiadm -m session --rescan");
|
||||
+ sleep 3;
|
||||
+ my $line = `multipath -ll | grep \"$wwid_end\"`;
|
||||
+ my ($mapper_device) = split(' ', $line);
|
||||
+ $mapper_device = "" unless $mapper_device;
|
||||
+ $mapper .= $mapper_device;
|
||||
+
|
||||
+ if ($mapper =~ /^([-\@\w.]+)$/) {
|
||||
+ $mapper = $1; # $data now untainted
|
||||
+ } else {
|
||||
+ $mapper = '';
|
||||
+ }
|
||||
+
|
||||
+# syslog(info,"Multipath mapper found: $mapper\n");
|
||||
+ if ($mapper ne "") {
|
||||
+ $path = "/dev/mapper/$mapper";
|
||||
+ sleep 5;
|
||||
+ }
|
||||
+ };
|
||||
+
|
||||
return ($path, $vmid, $vtype);
|
||||
}
|
||||
|
||||
|
|
@ -1,147 +0,0 @@
|
|||
--- ZFSPlugin.pm.orig 2019-06-25 06:54:50.000000000 -0400
|
||||
+++ ZFSPlugin.pm 2019-07-16 13:36:48.817831174 -0400
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
use base qw(PVE::Storage::ZFSPoolPlugin);
|
||||
use PVE::Storage::LunCmd::Comstar;
|
||||
+use PVE::Storage::LunCmd::FreeNAS;
|
||||
use PVE::Storage::LunCmd::Istgt;
|
||||
use PVE::Storage::LunCmd::Iet;
|
||||
use PVE::Storage::LunCmd::LIO;
|
||||
@@ -26,13 +27,14 @@
|
||||
modify_lu => 1,
|
||||
add_view => 1,
|
||||
list_view => 1,
|
||||
+ list_extent => 1,
|
||||
list_lu => 1,
|
||||
};
|
||||
|
||||
my $zfs_unknown_scsi_provider = sub {
|
||||
my ($provider) = @_;
|
||||
|
||||
- die "$provider: unknown iscsi provider. Available [comstar, istgt, iet, LIO]";
|
||||
+ die "$provider: unknown iscsi provider. Available [comstar, freenas, istgt, iet, LIO]";
|
||||
};
|
||||
|
||||
my $zfs_get_base = sub {
|
||||
@@ -40,6 +42,8 @@
|
||||
|
||||
if ($scfg->{iscsiprovider} eq 'comstar') {
|
||||
return PVE::Storage::LunCmd::Comstar::get_base;
|
||||
+ } elsif ($scfg->{iscsiprovider} eq 'freenas') {
|
||||
+ return PVE::Storage::LunCmd::FreeNAS::get_base;
|
||||
} elsif ($scfg->{iscsiprovider} eq 'istgt') {
|
||||
return PVE::Storage::LunCmd::Istgt::get_base;
|
||||
} elsif ($scfg->{iscsiprovider} eq 'iet') {
|
||||
@@ -62,6 +66,8 @@
|
||||
if ($lun_cmds->{$method}) {
|
||||
if ($scfg->{iscsiprovider} eq 'comstar') {
|
||||
$msg = PVE::Storage::LunCmd::Comstar::run_lun_command($scfg, $timeout, $method, @params);
|
||||
+ } elsif ($scfg->{iscsiprovider} eq 'freenas') {
|
||||
+ $msg = PVE::Storage::LunCmd::FreeNAS::run_lun_command($scfg, $timeout, $method, @params);
|
||||
} elsif ($scfg->{iscsiprovider} eq 'istgt') {
|
||||
$msg = PVE::Storage::LunCmd::Istgt::run_lun_command($scfg, $timeout, $method, @params);
|
||||
} elsif ($scfg->{iscsiprovider} eq 'iet') {
|
||||
@@ -160,6 +166,15 @@
|
||||
return $class->zfs_request($scfg, undef, 'list_view', $guid);
|
||||
}
|
||||
|
||||
+# Part of the multipath enhancement
|
||||
+sub zfs_get_wwid_number {
|
||||
+ my ($class, $scfg, $guid) = @_;
|
||||
+
|
||||
+ die "could not find lun_number for guid $guid" if !$guid;
|
||||
+
|
||||
+ return $class->zfs_request($scfg, undef, 'list_extent', $guid);
|
||||
+}
|
||||
+
|
||||
# Configuration
|
||||
|
||||
sub type {
|
||||
@@ -178,6 +193,24 @@
|
||||
description => "iscsi provider",
|
||||
type => 'string',
|
||||
},
|
||||
+ # This is for FreeNAS iscsi and API intergration
|
||||
+ # And some enhancements asked by the community
|
||||
+ freenas_user => {
|
||||
+ description => "FreeNAS API Username",
|
||||
+ type => 'string',
|
||||
+ },
|
||||
+ freenas_password => {
|
||||
+ description => "FreeNAS API Password",
|
||||
+ type => 'string',
|
||||
+ },
|
||||
+ freenas_use_ssl => {
|
||||
+ description => "FreeNAS API access via SSL",
|
||||
+ type => 'boolean',
|
||||
+ },
|
||||
+ freenas_apiv4_host => {
|
||||
+ description => "FreeNAS API Host",
|
||||
+ type => 'string',
|
||||
+ },
|
||||
# this will disable write caching on comstar and istgt.
|
||||
# it is not implemented for iet. iet blockio always operates with
|
||||
# writethrough caching when not in readonly mode
|
||||
@@ -205,14 +238,18 @@
|
||||
nodes => { optional => 1 },
|
||||
disable => { optional => 1 },
|
||||
portal => { fixed => 1 },
|
||||
- target => { fixed => 1 },
|
||||
- pool => { fixed => 1 },
|
||||
+ target => { fixed => 0 },
|
||||
+ pool => { fixed => 0 },
|
||||
blocksize => { fixed => 1 },
|
||||
iscsiprovider => { fixed => 1 },
|
||||
nowritecache => { optional => 1 },
|
||||
sparse => { optional => 1 },
|
||||
comstar_hg => { optional => 1 },
|
||||
comstar_tg => { optional => 1 },
|
||||
+ freenas_user => { optional => 1 },
|
||||
+ freenas_password => { optional => 1 },
|
||||
+ freenas_use_ssl => { optional => 1 },
|
||||
+ freenas_apiv4_host => { optional => 1 },
|
||||
lio_tpg => { optional => 1 },
|
||||
content => { optional => 1 },
|
||||
bwlimit => { optional => 1 },
|
||||
@@ -237,6 +274,40 @@
|
||||
|
||||
my $path = "iscsi://$portal/$target/$lun";
|
||||
|
||||
+ # Multipath enhancement
|
||||
+ eval {
|
||||
+ my $wwid = $class->zfs_get_wwid_number($scfg, $guid);
|
||||
+# syslog(info,"JD: path get_lun_number guid $guid");
|
||||
+
|
||||
+ if ($wwid =~ /^([-\@\w.]+)$/) {
|
||||
+ $wwid = $1; # $data now untainted
|
||||
+ } else {
|
||||
+ die "Bad data in '$wwid'"; # log this somewhere
|
||||
+ }
|
||||
+ my $wwid_end = substr $wwid, 16;
|
||||
+
|
||||
+ my $mapper = '';
|
||||
+ sleep 3;
|
||||
+ run_command("iscsiadm -m session --rescan");
|
||||
+ sleep 3;
|
||||
+ my $line = `multipath -ll | grep \"$wwid_end\"`;
|
||||
+ my ($mapper_device) = split(' ', $line);
|
||||
+ $mapper_device = "" unless $mapper_device;
|
||||
+ $mapper .= $mapper_device;
|
||||
+
|
||||
+ if ($mapper =~ /^([-\@\w.]+)$/) {
|
||||
+ $mapper = $1; # $data now untainted
|
||||
+ } else {
|
||||
+ $mapper = '';
|
||||
+ }
|
||||
+
|
||||
+# syslog(info,"Multipath mapper found: $mapper\n");
|
||||
+ if ($mapper ne "") {
|
||||
+ $path = "/dev/mapper/$mapper";
|
||||
+ sleep 5;
|
||||
+ }
|
||||
+ };
|
||||
+
|
||||
return ($path, $vmid, $vtype);
|
||||
}
|
||||
|
||||
|
|
@ -1 +0,0 @@
|
|||
/apidoc.js
|
||||
|
|
@ -1,66 +0,0 @@
|
|||
--- apidoc.js.orig 2018-08-19 10:18:11.715767285 -0400
|
||||
+++ apidoc.js.new 2018-08-19 10:17:09.364282178 -0400
|
||||
@@ -27584,6 +27584,31 @@
|
||||
"type" : "string",
|
||||
"typetext" : "<string>"
|
||||
},
|
||||
+ "freenas_user" : {
|
||||
+ "description" : "FreeNAS user for API access",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
+ },
|
||||
+ "freenas_password" : {
|
||||
+ "description" : "FreeNAS password for API access",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
+ },
|
||||
+ "freenas_use_ssl" : {
|
||||
+ "description" : "FreeNAS API access via SSL",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "boolean",
|
||||
+ "typetext" : "<boolean>"
|
||||
+ },
|
||||
+ "freenas_apiv4_host" : {
|
||||
+ "description" : "FreeNAS API Host via IPv4",
|
||||
+ "format" : "address",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
+ },
|
||||
"content" : {
|
||||
"description" : "Allowed content types.\n\nNOTE: the value 'rootdir' is used for Containers, and value 'images' for VMs.\n",
|
||||
"format" : "pve-storage-content-list",
|
||||
@@ -27948,6 +27973,31 @@
|
||||
"optional" : 1,
|
||||
"type" : "string",
|
||||
"typetext" : "<string>"
|
||||
+ },
|
||||
+ "freenas_user" : {
|
||||
+ "description" : "FreeNAS user for API access",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
+ },
|
||||
+ "freenas_password" : {
|
||||
+ "description" : "FreeNAS password for API access",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
+ },
|
||||
+ "freenas_use_ssl" : {
|
||||
+ "description" : "FreeNAS API access via SSL",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "boolean",
|
||||
+ "typetext" : "<boolean>"
|
||||
+ },
|
||||
+ "freenas_apiv4_host" : {
|
||||
+ "description" : "FreeNAS API Host via IPv4",
|
||||
+ "format" : "address",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
},
|
||||
"content" : {
|
||||
"description" : "Allowed content types.\n\nNOTE: the value 'rootdir' is used for Containers, and value 'images' for VMs.\n",
|
||||
|
|
@ -1,79 +0,0 @@
|
|||
--- apidoc-5.2-9.js 2018-11-10 14:28:44.022355849 -0500
|
||||
+++ apidoc.js 2018-11-10 15:49:38.873720011 -0500
|
||||
@@ -32448,6 +32448,31 @@
|
||||
"type" : "string",
|
||||
"typetext" : "<string>"
|
||||
},
|
||||
+ "freenas_user" : {
|
||||
+ "description" : "FreeNAS user for API access",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
+ },
|
||||
+ "freenas_password" : {
|
||||
+ "description" : "FreeNAS password for API access",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
+ },
|
||||
+ "freenas_use_ssl" : {
|
||||
+ "description" : "FreeNAS API access via SSL",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "boolean",
|
||||
+ "typetext" : "<boolean>"
|
||||
+ },
|
||||
+ "freenas_apiv4_host" : {
|
||||
+ "description" : "FreeNAS API Host via IPv4",
|
||||
+ "format" : "address",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
+ },
|
||||
"fuse" : {
|
||||
"description" : "Mount CephFS through FUSE.",
|
||||
"optional" : 1,
|
||||
@@ -32604,6 +32629,12 @@
|
||||
"type" : "boolean",
|
||||
"typetext" : "<boolean>"
|
||||
},
|
||||
+ "target" : {
|
||||
+ "description" : "iSCSI target.",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
+ },
|
||||
"transport" : {
|
||||
"description" : "Gluster transport: tcp or rdma",
|
||||
"enum" : [
|
||||
@@ -32810,6 +32841,31 @@
|
||||
"optional" : 1,
|
||||
"type" : "string",
|
||||
"typetext" : "<string>"
|
||||
+ },
|
||||
+ "freenas_user" : {
|
||||
+ "description" : "FreeNAS user for API access",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
+ },
|
||||
+ "freenas_password" : {
|
||||
+ "description" : "FreeNAS password for API access",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
+ },
|
||||
+ "freenas_use_ssl" : {
|
||||
+ "description" : "FreeNAS API access via SSL",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "boolean",
|
||||
+ "typetext" : "<boolean>"
|
||||
+ },
|
||||
+ "freenas_apiv4_host" : {
|
||||
+ "description" : "FreeNAS API Host via IPv4",
|
||||
+ "format" : "address",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
},
|
||||
"fuse" : {
|
||||
"description" : "Mount CephFS through FUSE.",
|
||||
|
|
@ -1,79 +0,0 @@
|
|||
--- apidoc-5.3-1.js 2018-12-21 12:46:55.459865478 -0500
|
||||
+++ apidoc.js 2018-12-06 15:50:21.487987807 -0500
|
||||
@@ -33477,6 +33477,31 @@
|
||||
"type" : "string",
|
||||
"typetext" : "<string>"
|
||||
},
|
||||
+ "freenas_user" : {
|
||||
+ "description" : "FreeNAS user for API access",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
+ },
|
||||
+ "freenas_password" : {
|
||||
+ "description" : "FreeNAS password for API access",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
+ },
|
||||
+ "freenas_use_ssl" : {
|
||||
+ "description" : "FreeNAS API access via SSL",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "boolean",
|
||||
+ "typetext" : "<boolean>"
|
||||
+ },
|
||||
+ "freenas_apiv4_host" : {
|
||||
+ "description" : "FreeNAS API Host via IPv4",
|
||||
+ "format" : "address",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
+ },
|
||||
"fuse" : {
|
||||
"description" : "Mount CephFS through FUSE.",
|
||||
"optional" : 1,
|
||||
@@ -33633,6 +33658,12 @@
|
||||
"type" : "boolean",
|
||||
"typetext" : "<boolean>"
|
||||
},
|
||||
+ "target" : {
|
||||
+ "description" : "iSCSI target.",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
+ },
|
||||
"transport" : {
|
||||
"description" : "Gluster transport: tcp or rdma",
|
||||
"enum" : [
|
||||
@@ -33840,6 +33871,31 @@
|
||||
"optional" : 1,
|
||||
"type" : "string",
|
||||
"typetext" : "<string>"
|
||||
+ },
|
||||
+ "freenas_user" : {
|
||||
+ "description" : "FreeNAS user for API access",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
+ },
|
||||
+ "freenas_password" : {
|
||||
+ "description" : "FreeNAS password for API access",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
+ },
|
||||
+ "freenas_use_ssl" : {
|
||||
+ "description" : "FreeNAS API access via SSL",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "boolean",
|
||||
+ "typetext" : "<boolean>"
|
||||
+ },
|
||||
+ "freenas_apiv4_host" : {
|
||||
+ "description" : "FreeNAS API Host via IPv4",
|
||||
+ "format" : "address",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
},
|
||||
"fuse" : {
|
||||
"description" : "Mount CephFS through FUSE.",
|
||||
|
|
@ -1,79 +0,0 @@
|
|||
--- apidoc.js.orig 2019-02-20 06:20:05.000000000 -0500
|
||||
+++ apidoc.js 2019-02-26 20:51:26.857204965 -0500
|
||||
@@ -33709,6 +33709,31 @@
|
||||
"type" : "string",
|
||||
"typetext" : "<string>"
|
||||
},
|
||||
+ "freenas_user" : {
|
||||
+ "description" : "FreeNAS user for API access",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
+ },
|
||||
+ "freenas_password" : {
|
||||
+ "description" : "FreeNAS password for API access",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
+ },
|
||||
+ "freenas_use_ssl" : {
|
||||
+ "description" : "FreeNAS API access via SSL",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "boolean",
|
||||
+ "typetext" : "<boolean>"
|
||||
+ },
|
||||
+ "freenas_apiv4_host" : {
|
||||
+ "description" : "FreeNAS API Host via IPv4",
|
||||
+ "format" : "address",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
+ },
|
||||
"fuse" : {
|
||||
"description" : "Mount CephFS through FUSE.",
|
||||
"optional" : 1,
|
||||
@@ -33865,6 +33890,12 @@
|
||||
"type" : "boolean",
|
||||
"typetext" : "<boolean>"
|
||||
},
|
||||
+ "target" : {
|
||||
+ "description" : "iSCSI target.",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
+ },
|
||||
"transport" : {
|
||||
"description" : "Gluster transport: tcp or rdma",
|
||||
"enum" : [
|
||||
@@ -34071,6 +34102,31 @@
|
||||
"optional" : 1,
|
||||
"type" : "string",
|
||||
"typetext" : "<string>"
|
||||
+ },
|
||||
+ "freenas_user" : {
|
||||
+ "description" : "FreeNAS user for API access",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
+ },
|
||||
+ "freenas_password" : {
|
||||
+ "description" : "FreeNAS password for API access",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
+ },
|
||||
+ "freenas_use_ssl" : {
|
||||
+ "description" : "FreeNAS API access via SSL",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "boolean",
|
||||
+ "typetext" : "<boolean>"
|
||||
+ },
|
||||
+ "freenas_apiv4_host" : {
|
||||
+ "description" : "FreeNAS API Host via IPv4",
|
||||
+ "format" : "address",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
},
|
||||
"fuse" : {
|
||||
"description" : "Mount CephFS through FUSE.",
|
||||
|
|
@ -1,79 +0,0 @@
|
|||
--- apidoc.js.orig 2019-04-08 02:02:09.000000000 -0400
|
||||
+++ apidoc.js 2019-05-11 10:34:28.305764709 -0400
|
||||
@@ -34385,6 +34385,31 @@
|
||||
"type" : "string",
|
||||
"typetext" : "<string>"
|
||||
},
|
||||
+ "freenas_user" : {
|
||||
+ "description" : "FreeNAS user for API access",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
+ },
|
||||
+ "freenas_password" : {
|
||||
+ "description" : "FreeNAS password for API access",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
+ },
|
||||
+ "freenas_use_ssl" : {
|
||||
+ "description" : "FreeNAS API access via SSL",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "boolean",
|
||||
+ "typetext" : "<boolean>"
|
||||
+ },
|
||||
+ "freenas_apiv4_host" : {
|
||||
+ "description" : "FreeNAS API Host via IPv4",
|
||||
+ "format" : "address",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
+ },
|
||||
"fuse" : {
|
||||
"description" : "Mount CephFS through FUSE.",
|
||||
"optional" : 1,
|
||||
@@ -34541,6 +34566,12 @@
|
||||
"type" : "boolean",
|
||||
"typetext" : "<boolean>"
|
||||
},
|
||||
+ "target" : {
|
||||
+ "description" : "iSCSI target.",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
+ },
|
||||
"transport" : {
|
||||
"description" : "Gluster transport: tcp or rdma",
|
||||
"enum" : [
|
||||
@@ -34748,6 +34779,31 @@
|
||||
"optional" : 1,
|
||||
"type" : "string",
|
||||
"typetext" : "<string>"
|
||||
+ },
|
||||
+ "freenas_user" : {
|
||||
+ "description" : "FreeNAS user for API access",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
+ },
|
||||
+ "freenas_password" : {
|
||||
+ "description" : "FreeNAS password for API access",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
+ },
|
||||
+ "freenas_use_ssl" : {
|
||||
+ "description" : "FreeNAS API access via SSL",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "boolean",
|
||||
+ "typetext" : "<boolean>"
|
||||
+ },
|
||||
+ "freenas_apiv4_host" : {
|
||||
+ "description" : "FreeNAS API Host via IPv4",
|
||||
+ "format" : "address",
|
||||
+ "optional" : 1,
|
||||
+ "type" : "string",
|
||||
+ "typetext" : "<string>"
|
||||
},
|
||||
"fuse" : {
|
||||
"description" : "Mount CephFS through FUSE.",
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue