Nutanix LCM v4 APIs: Managing Inventory and Planning Software Updates – Revisited

Nutanix.dev-NutanixLCMv4APIs_ManagingInventoryandP

Table of Contents

Note: This is a repost of an article published in March 2023. This revisited version contains updated LCM SDK usage that is being progressively rolled out in various LCM versions. The previous version of this article will redirect here. The changes are as follows:

  • Replacement of the PlanApi with the NotificationsApi
  • Replacement of LCM TaskApi with the Prism TaskApi

To ensure you have access to the updated usage, please make sure you run an LCM Inventory in your environment; this will update LCM to the latest version.


In our recent article titled Nutanix® v4 API Namespace Update, we highlighted the latest release of the Nutanix v4 APIs and SDKs. Part of this announcement included the EA release of the new Nutanix Lifecycle Manager™ (LCM) v4 APIs.

The importance of efficient software and firmware upgrades as part of an overall management workflow can’t be overstated. In today’s article, we’ll look at how parts of that strategy can be managed via Nutanix v4 APIs & SDKs.

Introduction to LCM

From the LCM product page:

Intelligent infrastructure software and firmware upgrades with comprehensive dependency management and 1-click simplicity.

https://www.nutanix.com/products/life-cycle-manager

Digging a little deeper into this, a few of the many things LCM can achieve are as follows.

  • Keeping track of the software and firmware versions installed across an environment (“Inventory”)
  • Make sure an environment is ready to have updates applied (“Pre-check”)
  • Produce a list of recommendations, that is, a list of software and firmware that can be upgraded (“Recommendations”)
  • Generate upgrade notifications describing the potential impact on an environment if a specific set of updates were to be applied (“Notifications”)
  • Execute the update plan and bring specified software and firmware up to specified versions (“Update”)

Today’s article will cover all of the above (inventory, pre-check, recommendations, notifications, and updating). For demonstration purposes, the above steps will be completed using the Nutanix v4 Python SDKs although the same overall approaches will apply regardless of the method used.

Important note: Prism Central will be used as the connection endpoint for all demos in this article. At the time of writing this article Prism Element requires a separate connection to carry out the same steps as Prism Central does not currently update registered AOS clusters.

Demonstration Script

The complete demonstration script used in this article can be accessed in the Nutanix.dev Code Samples Repository: LCM Updates with Prism Central and Prism Element (Python SDK).

To use this script, clone the entire code samples GitHub repository, ensuring all sub-directories are cloned at the same time. Many of the Python demonstrations scripts in this repository make use of a shared Python module named tme that will be required for successful script execution.

Where can this script be run?

This article’s demonstration script can be used by connecting to either Prism Central or Prism Element.

Workflow

A typical order of operations could be:

  • Perform LCM inventory: Collect information about the currently installed software versions. LCM will carry out a pre-check step as part of this process, during which the environment will be checked for update readiness.
    • Note: If required, an LCM pre-check can be manually initiated without first initiating an LCM inventory action.
  • Collect information about the supported LCM entities. In this demonstration the available entity list will be used to provide human-readable product names and versions.
  • Collect LCM recommendations: Collect information about updates that can be applied. The results of this step can be used as input for the next step, generating LCM upgrade notifications.
  • Generate LCM upgrade notifications: Gather information about which services, if any, will need to be restarted during an upcoming update step.
  • Apply updates: Install updates based on the recommendation and notification information gathered in previous steps.

APIs Used

To complete the typical workflow outlined in the previous section, these Nutanix v4 LCM APIs will be used. These APIs are all provided via the lcm namespace.

  • InventoryApi
  • PrecheckApi
  • EntityApi
  • RecommendationsApi
  • NotificationsApi
  • UpdateApi

In addition, the demo script will use TaskApi from the Prism SDK.

Each API has been clearly named to indicate the functions each one provides.

Executing the Workflow

Each step in the above process will be outlined in detail here, including information on the payloads used when required.

Environment Setup

If you intend to follow this article in your own environment, ensure your Python development environment is already configured before continuing. These steps are covered in the Nutanix v4 API User Guide: Configuring Your Python Development Environment.

Create Prism Central connection configuration

Before executing any LCM-specific operations it is necessary to prepare the script for Prism Central connection. All current Nutanix v4 SDKs manage this via the Configuration module, as shown below. In this demonstration the Configuration module has been aliased as LCMConfiguration; this improves readability and avoids import clashes when using additional namespaces. During this step the script will also import a number of other modules required for use later in the demonstration.

# only the ntnx_lcm_py_client namespace is required for this code sample
import ntnx_lcm_py_client
from ntnx_lcm_py_client import ApiClient as LCMClient
from ntnx_lcm_py_client import Configuration as LCMConfiguration
from ntnx_lcm_py_client.rest import ApiException as LCMException

from ntnx_lcm_py_client.Ntnx.lcm.v4.common.PrecheckSpec import PrecheckSpec
from ntnx_lcm_py_client.Ntnx.lcm.v4.common.EntityUpdateSpec import EntityUpdateSpec
from ntnx_lcm_py_client.Ntnx.lcm.v4.common.EntityUpdateSpecs import EntityUpdateSpecs
from ntnx_lcm_py_client.Ntnx.lcm.v4.resources.RecommendationSpec import (
    RecommendationSpec,
)

With the LCM namespace correctly imported and aliased, the Prism Central connection configuration can be prepared. Make sure you replace pc_ip, username and cluster_password with values appropriate for your environment. Additionally, verify_ssl can be set to True if your environment used correctly configured SSL certificates.

# build the connection configuration
lcm_config = LCMConfiguration()
lcm_config.host = pc_ip
lcm_config.username = username
lcm_config.password = cluster_password
lcm_config.verify_ssl = False

The last preparation step before LCM-specific operations is to create the LCMClient instance, allowing the script to make use of the configuration created earlier.

# create the connection client using configuration created earlier
lcm_client = LCMClient(configuration=lcm_config)
# we can also specify connection-specific headers, if required
lcm_client.add_default_header(
    header_name="Accept-Encoding", header_value="gzip, deflate, br"
)

Sync vs Async Requests

Throughout this demonstration the code blocks will often include a parameter as shown below:

async_req=False

This parameter is used to specify the script should create a synchronous request that will wait before returning control to the script. Synchronous requests can be submitted without specifying the async_req or by setting its value to True, although this script will use asynchronous requests only.

tme Module

To package functions for easy reuse, a Python module named tme has been used throughout this script. This module is provides the functions listed in this section.

confirm Function

The confirm function asks the user to confirm with a yes/NO answer before continuing with the next step.

The complete confirm function is shown below.

def confirm(self, message: str):
    """
    method to request a yes/NO confirmation from the user
    used to run or skip precheck, inventory (etc)
    """
    yes_no = input(f"{message} (yes/NO): ").lower()
    return yes_no == "yes"

monitor_task Function

The monitor_task function is used to display task progress. This function accepts a collection of Prism Element or Prism Central credentials and a unique Prism Element or Prism Central task UUID (extId). The function then displays a flushed series of . characters until the task is complete. By default the progress is updated each second, although an optional poll_timeout parameter can be used to updated at a specified interval in seconds.

At the conclusion of the monitored task, a human-readable task duration is shown.

Note: The poll_timeout parameter should be used in environments sensitive to sending a large number of API requests.

This code block shows the complete monitor_task function.

def monitor_task(
    self, task_ext_id, task_name, pc_ip, username, password, poll_timeout, prefix = "ZXJnb24=:"
):
    """
    method used to monitor Prism Central tasks
    will print a series of period characters and re-check task
    status at the specified interval
    this version uses the Prism SDK
    """
    start = timer()
    # print message until specified  task is finished
    prism_config = PrismConfiguration()
    prism_config.host = pc_ip
    prism_config.username = username
    prism_config.password = password
    prism_config.verify_ssl = False
    prism_client = PrismClient(configuration=prism_config)
    prism_client.add_default_header(
        header_name="Accept-Encoding", header_value="gzip, deflate, br"
    )
    prism_instance = ntnx_prism_py_client.api.TaskApi(api_client=prism_client)
    task = prism_instance.task_get(f"{prefix}{task_ext_id}")
    units = "second" if poll_timeout == 1 else "seconds"
    print(
        f"{task_name} running, checking progress every {poll_timeout} {units} ...",
        end="",
    )
    while True:
        if task.data.status == "RUNNING":
            print(".", end="", flush=True)
        else:
            print(" finished.")
            break
        time.sleep(int(poll_timeout))
        task = prism_instance.task_get(f"{prefix}{task_ext_id}")
    end = timer()
    elapsed_time = end - start
    if elapsed_time <= 60:
        duration = f"{round(elapsed_time, 0)} seconds"
    else:
        duration = f"{round(elapsed_time // 60, 0)} minutes"
    return duration

InventoryApi: Collect Inventory

With the environment prepared, the LCM Inventory process can be initiated. This process will instruct LCM to examine the connected environment, prepare a list of current software and hardware versions that are installed and make that list available for upcoming operations.

In this example the tme module has been used to confirm the user’s intention. For the remainder of this article the confirmation requests will be omitted.

# create utils instance for re-use later
utils = Utils(pc_ip=pc_ip, username=username, password=cluster_password)

# the confirm function simply asks for a yes/NO confirmation before
# continuing with the specified action
run_inventory = utils.confirm(
    "Run LCM Inventory?  This can take some time, depending on 
environment configuration."
)
if run_inventory:
    # initiate the LCM inventory task
    lcm_instance = ntnx_lcm_py_client.api.InventoryApi(api_client=lcm_client)
    print("Starting LCM inventory ...")
    inventory = lcm_instance.inventory(async_req=False)
    # grab the unique identifier for the LCM inventory task
    inventory_task_ext_id = inventory.data.ext_id
    # use the task'd identifier to continuously poll the task, watching for it to complete
    monitor_task_lcm(
        task_ext_id=inventory_task_ext_id,
        task_name="Inventory",
        client=lcm_client,
        poll_timeout=poll_timeout,
    )
else:
    print("LCM Inventory skipped.")

When the script is executed, the process so far will be similar to the screenshot shown below.

Demonstration script requesting user confirmation then initiating the LCM inventory process

EntityApi: Collect Support Entity Details

Before collection information about available updates, we will first build a list of the entity types LCM supports. In this demonstration the information will primarily be used to show human-readable information vs the machine-specific UUIDs used by the RecommendationApi.

# gather a list of supported entities
# this will be used to show human-readable update info shortly
lcm_instance = ntnx_lcm_py_client.api.EntityApi(api_client=lcm_client)
entities = lcm_instance.get_entities(async_req=False)

RecommendationsApi: Collect Available Update Details

With the inventory step complete, LCM now has the latest details on the different software and hardware versions running in this environment. For the remainder of this article we will work with software updates only.

Because of the way the Nutanix v4 APIs have been designed, most requests can be created in a very similar way to what we’ve already done. However, in addition to create the instance of our required API, RecommendationsApi, we also need to provide details on what sort of updates we are interested in.

The code block below will create the API instance and create the instance of the recommendation spec (the type of updates we are interested in). It will then iterate over the list of available updates and display appropriate information to the user.

"""
gather LCM update recommendations
IMPORTANT NOTE: the way recommendations are collected will change
before the v4 LCM APIs and SDKs are released as Generally Available
(GA); this script should be used for demonstration purposes only
"""
lcm_instance = ntnx_lcm_py_client.api.RecommendationsApi(api_client=lcm_client)
print("Getting LCM Recommendations ...")
rec_spec = RecommendationSpec()
# specify that this script should only look for available software updates
rec_spec.entity_types = ["software"]
recommendations = lcm_instance.get_recommendations(
    async_req=False, body=rec_spec
)
update_info = []
for rec in recommendations.data["entityUpdateSpecs"]:
    entity_matches = [
        entity for entity in entities.data if entity.uuid == rec["entityUuid"]
    ]
    if len(entity_matches) > 0:
        update_info.append(
            {
                "product_name": entity_matches[0].entity_model,
                "version": entity_matches[0].version,
                "entity_uuid": entity_matches[0].uuid,
            }
        )
print(
    f"{len(recommendations.data['entityUpdateSpecs'])} software 
components can be updated:"
)
pprint(update_info)

Important note: The response returned from the get_recommendations function is documented as an instance of GetUpdateRecApiResponse containing a property named data. The data property is documented as being an instance of lcm.v4.resources.RecommendationResult although it is currently returned as a Python list. This is a known issue and is being tracked by Nutanix engineering. For the purposes of this demonstration we will work the data property as a Python list.

The output from collecting recommendations is shown in this screenshot:

Using RecommendationsApi to collect available LCM updates

Quick Summary

Up to this point these actions have been completed.

  • LCM has run an Inventory and is aware of the currently running software and hardware versions
  • LCM is aware of the entities that are supported for update
  • LCM has used the inventory and entity information to create a list of available updates

The final two steps are to generate an upgrade notifications and then, optionally, instruct LCM to perform the updates.

NotificationsApi: Generate LCM Upgrade Notifications

The LCM notifications step instructs LCM to generate information about which services are impacted when updates are applied, if any. This code block generates the upgrade notifications based on information generated in the inventory and recommendations steps.

if not update_info:
    print("No updates available, skipping LCM Update planning.\n")
else:
    # generate LCM upgrade notifications
    # note this is the new way of doing this; we previously used PlanApi
    # for this demo we'll do this for all recommendations returned
    # in the previous request
    lcm_instance = ntnx_lcm_py_client.api.NotificationsApi(api_client=lcm_client)
    print("Generating LCM Upgrade Notifications ...")
    entity_update_specs = EntityUpdateSpecs()
    entity_update_specs.entity_update_specs = []
    for recommendation in recommendations.data["entityUpdateSpecs"]:
        spec = EntityUpdateSpec()
        spec.entity_uuid = recommendation["entityUuid"]
        spec.version = recommendation["version"]
        entity_update_specs.entity_update_specs.append(spec)
    if len(entity_update_specs.entity_update_specs) > 0:
        notifications = lcm_instance.gen_upgrade_notifications(async_req=False, body=entity_update_specs)
        print(
            f"{len(notifications.data.upgrade_plan)} upgrade notifications generated:"
        )
        pprint(notifications.data.upgrade_plan)
    else:
        print("No upgrade notifications available.")
        sys.exit()

This screenshot shows the output from creating the LCM upgrade notifications:

Use NotificationsApi to generate LCM Upgrade Notifications

As can be seen above, user workloads would not be disrupted by these particular updates, although specific services would need to be restarted for the updates to complete.

UpdateApi: Install Updates

Before continuing with this step, ensure these steps are only being carried out in a demonstration or test environment. This script is being shown for demonstration purposes only and should not be used in a production environment.

The final step in this process to take all available info generated up to this point and install all available updates. Further processing may be required if the user would prefer to install a subset of the available updates.

This code block checks there updates to install and, if so, prompts the user to confirm they would like to proceed.

# make sure there are entities to update
if entity_update_specs.entity_update_specs is not None:
    print(
        f"{len(entity_update_specs.entity_update_specs)} \
updates available."
    )
    # make sure the user wants to install updates
    install_updates = utils.confirm("Install updates?")
    if install_updates:
        # do the actual update
        lcm_instance = ntnx_lcm_py_client.api.UpdateApi(
            api_client=lcm_client
        )
        print("Updating software via LCM ...")
        update_spec = UpdateSpec()
        # configure the update properties, timing etc
        update_spec.entity_update_specs = (
            entity_update_specs.entity_update_specs
        )
        # skip the pinned VM prechecks
        # WARNING: consider the implications of doing this in production
        update_spec.skipped_precheck_flags = ["powerOffUvms"]
        update_spec.wait_in_sec_for_app_up = 60
        update = lcm_instance.update(async_req=False, body=update_spec)
        update_task_ext_id = update.data.ext_id
        update_duration = utils.monitor_task(
            task_ext_id=update_task_ext_id,
            task_name="Update",
            pc_ip=utils.prism_config.host,
            username=utils.prism_config.username,
            password=utils.prism_config.password,
            poll_timeout=poll_timeout
        )
        print(f"Update duration: {update_duration}.")
    else:
        print("Updates cancelled.")
else:
    print(
        "No updates available at this time.  Make sure \
available updates weren't skipped due to development script exclusions."
    )

This screenshot shows the complete process from inventory to prompting for update install.

Complete LCM process using the Nutanix v4 Python SDK

Complete Script Run

Up this point this article has demonstrated using LCM SDKs with Prism Central. This screenshot shows the script running from start to finish, this time using LCM on Prism Element. Installing updates has been skipped in this example but follows the same concept and monitoring process as all other steps.

Complete LCM Update script run, using Prism Central

Conclusion

This demonstration has shown how Nutanix LCM, which is itself a collection of automated update processes, can be automated via the new Nutanix v4 APIs. The entire script is publicly available and can be downloaded from the Nutanix.dev Code Samples Repository: LCM Updates with Prism Central and Prism Element (Python SDK).

© 2024 Nutanix, Inc. All rights reserved. Nutanix, the Nutanix logo and all Nutanix product, feature and service names mentioned herein are registered trademarks or trademarks of Nutanix, Inc. in the United States and other countries. Other brand names mentioned herein are for identification purposes only and may be the trademarks of their respective holder(s). This post may contain links to external websites that are not part of Nutanix.com. Nutanix does not control these sites and disclaims all responsibility for the content or accuracy of any external site. Our decision to link to an external site should not be considered an endorsement of any content on such a site. Certain information contained in this post may relate to or be based on studies, publications, surveys and other data obtained from third-party sources and our own internal estimates and research. While we believe these third-party studies, publications, surveys and other data are reliable as of the date of this post, they have not independently verified, and we make no representation as to the adequacy, fairness, accuracy, or completeness of any information obtained from third-party sources.