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 theNotificationsApi
- Replacement of LCM
TaskApi
with the PrismTaskApi
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.
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:
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:
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 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.
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).