Source code for saltext.vcf.clients.nsx_upgrade

"""NSX upgrade workflow (``/api/v1/upgrade/...``).

NSX-T's upgrade surface is two-layered: an *upgrade plan* drives an
ordered set of *upgrade unit groups* (host, edge, manager), each
containing the actual *upgrade units* (the hosts / edges / managers
being upgraded). Bundles supply the bits.

Endpoints used here (all NSX manager HTTP basic auth, via
:mod:`saltext.vcf.utils.nsx`):

* ``GET    /api/v1/upgrade/status-summary``         — overall progress + state
* ``GET    /api/v1/upgrade/upgrade-unit-groups``    — list groups
* ``GET    /api/v1/upgrade/upgrade-unit-groups/{id}``
* ``POST   /api/v1/upgrade/upgrade-unit-groups``    — create custom group
* ``PUT    /api/v1/upgrade/upgrade-unit-groups/{id}``
* ``DELETE /api/v1/upgrade/upgrade-unit-groups/{id}``
* ``GET    /api/v1/upgrade/upgrade-units``          — list units across groups
* ``GET    /api/v1/upgrade/upgrade-units/{id}``
* ``POST   /api/v1/upgrade/plan?action={start,pause,resume,reset}``
* ``GET    /api/v1/upgrade/plan/settings``          — current plan settings
* ``PUT    /api/v1/upgrade/plan/settings``          — update plan settings
* ``GET    /api/v1/upgrade/upgrade-bundles``        — uploaded bundles
* ``GET    /api/v1/upgrade/upgrade-bundles/{id}``
* ``POST   /api/v1/upgrade/upgrade-bundles?action=upload`` (multipart) — uploaded out-of-band today
* ``GET    /api/v1/upgrade/history``                — previous runs

The bundle upload itself is multipart and isn't wrapped here — drop a
bundle in via the NSX UI or the standalone ``curl`` recipe; this
client manages the workflow once a bundle is present.
"""

import time

import requests

from saltext.vcf.utils import nsx

_BASE = "/api/v1/upgrade"
_STATUS = f"{_BASE}/status-summary"
_PLAN = f"{_BASE}/plan"
_PLAN_SETTINGS = f"{_PLAN}/settings"
_GROUPS = f"{_BASE}/upgrade-unit-groups"
_UNITS = f"{_BASE}/upgrade-units"
_BUNDLES = f"{_BASE}/upgrade-bundles"
_HISTORY = f"{_BASE}/history"

# Terminal NSX overall_upgrade_status values. NSX historically uses both
# ``SUCCESS`` and ``COMPLETED`` depending on the version path; tolerate
# both.
_TERMINAL_OK = {"SUCCESS", "COMPLETED"}
_TERMINAL_BAD = {"FAILED", "PAUSED_DUE_TO_FAILURE"}


# ---------------------------------------------------------------------------
# Status + history
# ---------------------------------------------------------------------------


[docs] def status_summary(opts, profile=None): """Return the overall upgrade status (state, progress %, etc.).""" return nsx.api_get(opts, _STATUS, profile=profile)
[docs] def history(opts, profile=None): """Return the list of previous upgrade runs.""" return nsx.api_get(opts, _HISTORY, profile=profile)
# --------------------------------------------------------------------------- # Plan controls # --------------------------------------------------------------------------- def _plan_action(opts, action, profile=None): return nsx.api_post(opts, _PLAN, params={"action": action}, body={}, profile=profile)
[docs] def start(opts, profile=None): """Start the upgrade plan.""" return _plan_action(opts, "start", profile=profile)
[docs] def pause(opts, profile=None): """Pause the upgrade plan.""" return _plan_action(opts, "pause", profile=profile)
[docs] def resume(opts, profile=None): """Resume a paused plan.""" return _plan_action(opts, "resume", profile=profile)
[docs] def reset(opts, profile=None): """Reset the plan back to initial state (discards group ordering).""" return _plan_action(opts, "reset", profile=profile)
[docs] def get_plan_settings(opts, profile=None): """Return the plan-level settings (parallelism, pre-checks, etc.).""" return nsx.api_get(opts, _PLAN_SETTINGS, profile=profile)
[docs] def update_plan_settings(opts, settings, profile=None): """Replace the plan-level settings document.""" return nsx.api_put(opts, _PLAN_SETTINGS, body=settings, profile=profile)
# --------------------------------------------------------------------------- # Upgrade unit groups # --------------------------------------------------------------------------- def list_groups(opts, profile=None): return nsx.api_get(opts, _GROUPS, profile=profile) def get_group(opts, group_id, profile=None): return nsx.api_get(opts, f"{_GROUPS}/{group_id}", profile=profile) def get_group_or_none(opts, group_id, profile=None): try: return get_group(opts, group_id, profile=profile) except requests.HTTPError as exc: if exc.response is not None and exc.response.status_code == 404: return None raise
[docs] def create_group(opts, group_spec, profile=None): """Create a custom upgrade unit group. *group_spec* keys: ``display_name``, ``type`` (``HOST``/``EDGE``/``MP``), ``upgrade_units`` (list of unit refs), ``enabled``, ``parallel``, ``extended_configuration``. """ return nsx.api_post(opts, _GROUPS, body=group_spec, profile=profile)
def update_group(opts, group_id, group_spec, profile=None): return nsx.api_put(opts, f"{_GROUPS}/{group_id}", body=group_spec, profile=profile) def delete_group(opts, group_id, profile=None): return nsx.api_delete(opts, f"{_GROUPS}/{group_id}", profile=profile) # --------------------------------------------------------------------------- # Upgrade units # --------------------------------------------------------------------------- def list_units(opts, group_id=None, profile=None): params = {"upgrade_unit_group_id": group_id} if group_id else None return nsx.api_get(opts, _UNITS, params=params, profile=profile) def get_unit(opts, unit_id, profile=None): return nsx.api_get(opts, f"{_UNITS}/{unit_id}", profile=profile) def get_unit_or_none(opts, unit_id, profile=None): try: return get_unit(opts, unit_id, profile=profile) except requests.HTTPError as exc: if exc.response is not None and exc.response.status_code == 404: return None raise # --------------------------------------------------------------------------- # Bundles (read-only here) # --------------------------------------------------------------------------- def list_bundles(opts, profile=None): return nsx.api_get(opts, _BUNDLES, profile=profile) def get_bundle(opts, bundle_id, profile=None): return nsx.api_get(opts, f"{_BUNDLES}/{bundle_id}", profile=profile) def get_bundle_or_none(opts, bundle_id, profile=None): try: return get_bundle(opts, bundle_id, profile=profile) except requests.HTTPError as exc: if exc.response is not None and exc.response.status_code == 404: return None raise # --------------------------------------------------------------------------- # Waiter # ---------------------------------------------------------------------------
[docs] def wait_for_completion(opts, *, timeout=14400, poll_interval=30, profile=None): """Block until ``status-summary`` reports a terminal state. Returns the final ``status-summary`` body. Raises ``TimeoutError`` if no terminal state in *timeout* seconds, or ``RuntimeError`` on terminal failure / paused-due-to-failure. """ deadline = time.monotonic() + float(timeout) while True: summary = status_summary(opts, profile=profile) state = (summary.get("overall_upgrade_status") or summary.get("status") or "").upper() if state in _TERMINAL_OK: return summary if state in _TERMINAL_BAD: raise RuntimeError(f"NSX upgrade terminal failure: {state}") if time.monotonic() > deadline: raise TimeoutError( f"NSX upgrade did not finish within {timeout}s (last state: {state!r})" ) time.sleep(poll_interval)