How to update and remediate hostprofile

Learn how to manage hostprofile with vRO

Problem

Every so often, we need to assign specific tasks to someone, but often, we have to provide detailed instructions like "Don't forget to do A if A happens and B if B happens". For example, "When applying host profile 1, remember to set the hostname in FQDN format and assign the IP address with the subnet mask." However, this approach usually doesn't work in most cases. That's why developing bullet-proof solutions is the best way to minimize human errors. Today, we will explore how to automate host profile management using intelligent techniques, which can be tailored and expanded to any use case.

We're going to create a new workflow which will support the following use cases:

  1. See the current host status
  2. Update the currently attached profile: hostname, IP address, and subnet mask
  3. See all available host profiles
  4. Be able to attach another host profile
  5. Validate provided inputs

The following diagram shows the logic of the upcoming workflow.

Diagram
Diagram
The original size PDF of the diagram can be found here.

Solution

Host profile preparation

To update the host profile, several variables must be defined first:

  • Host profile manager configuration
const sdkConnection: VcSdkConnection = vmHost.sdkConnection;
const hostProfileManager: VcProfileManager = sdkConnection.hostProfileManager;
const hostToConfigSpecMap = [];
const vcHostProfileManagerHostToConfigSpecMap =
  new VcHostProfileManagerHostToConfigSpecMap();
const configSpec = new VcAnswerFileOptionsCreateSpec();
configSpec.validating = true;
vcHostProfileManagerHostToConfigSpecMap.configSpec = configSpec;
vcHostProfileManagerHostToConfigSpecMap.host = vmHost;
const userInput: Array<VcProfileDeferredPolicyOptionParameter> = [];
  • Host name policy configuration

To set the hostname, the HostNamePolicy policyId is used and hostName variable is provided as a hostname.

const hostNameInput = new VcProfileDeferredPolicyOptionParameter();
hostNameInput.inputPath = new VcProfilePropertyPath();
hostNameInput.inputPath.policyId = "HostNamePolicy";
hostNameInput.inputPath.profilePath =
  'network.GenericNetStackInstanceProfile["key-vim-profile-host-GenericNetStackInstanceProfile-defaultTcpipStack"].GenericDnsConfigProfile';
const hostNameParameter = new VcKeyAnyValue();
hostNameParameter.key = "hostName";
hostNameParameter.value_AnyValue = hostFQDN;
hostNameInput.parameter = [hostNameParameter];
Input.push(hostNameInput);
  • IP address policy configuration

The same for the hostSubnet subnetmask and hostIP IP address.

const ipInput = new VcProfileDeferredPolicyOptionParameter();
const subnetParameter = new VcKeyAnyValue();
subnetParameter.key = "subnetmask";
subnetParameter.value_AnyValue = hostSubnet;
const ipParameter = new VcKeyAnyValue();
ipParameter.key = "address";
ipParameter.value_AnyValue = hostIP;
ipInput.inputPath = new VcProfilePropertyPath();
ipInput.inputPath.policyId = "IpAddressPolicy";
ipInput.inputPath.profilePath = 'network.hostPortGroup["key-vim-profile-host-HostPortgroupProfile-ManagementNetwork"].ipConfig';
ipInput.parameter = [subnetParameter, ipParameter];
userInput.push(ipInput);
configSpec.userInput = userInput;
hostToConfigSpecMap.push(vcHostProfileManagerHostToConfigSpecMap);

Update host with provided values

To do so, we have updateHostCustomization method.

func.updateHostCustomization(hostToConfigSpecMap, hostProfileManager);

The method takes a previously prepared variables and execute updateHostCustomizations_Task() method.

public updateHostCustomization(hostToConfigSpecMap: Array<any>, hostProfileManager: VcProfileManager) {
  try {
    const task = hostProfileManager.updateHostCustomizations_Task(hostToConfigSpecMap);
    System.getModule("com.vmware.library.vc.basic").vim3WaitTaskEnd(task, true, 2);
    return;
  } catch (error) {
    throw new Error(`updateHostCustomization: ${error}`);
  }
}

If everything goes well, we should see the similar logs in the console:

Profiles

Firstly, to make our life easier (informative), we're showing the currently attached host profile in the custom form. This will help to understand what to do next. To achieve that, we have a small function called getAttachedProfile.

This simple function gets vmHost as an input and shows the attached profile.

/**
 *
 *
 * @param {VC:HostSystem} vmHost - The name of the ESXi host to check.
 * @returns {string} - Host profile name.
 */
(function getAttachedProfile(vmHost: VcHostSystem): string | null {
  const hostProfileManager: VcProfileManager = vmHost.sdkConnection.hostProfileManager;
  try {
    const profiles: Array<VcProfile> = hostProfileManager.findAssociatedProfile(vmHost);
    return profiles.length > 0 ? profiles[0].name : null;
  } catch (error) {
    throw new Error("Unable to find attached host profile");
  }
});
In vRBT, the object type should be the same as in vRO. For example, in vRBT, it's `VcHostSystem` when used inside the code, but it should be `VC:HostSystem` if we want to use it in the Actions params. In addition, we would like to show the administrator all available profiles in the vCenter to which the ESXi host is connected. To make it work, we have a function called `getAllAvailableHostProfiles`. The ideas are the same, but we return an array of profiles in that case and will show them in the dropdown box.
/**
 *
 *
 * @param {VC:HostSystem} vmHost - The name of the ESXi host to check.
 * @returns {Array/string} - Host profile details
 */
(function getAllAvailableHostProfiles(vmHost: VcHostSystem) {
  const hostProfileManager: VcProfileManager = vmHost.sdkConnection.hostProfileManager;
  const profileDetails: Array<string> = [];
  let customProperties;
  const profiles: VcProfile[] = hostProfileManager.profile;
  if (isArrayNotEmpty(profiles)) {
    profiles.forEach((element) => {
      profileDetails.push(element.name);
    });
  }
  return profileDetails.sort();

  function isArrayNotEmpty<T>(array: T[]): array is [T, ...T[]] {
    return array.length > 0;
  }
});

After selecting the profile, we aim to display details about that profile in a custom form to assist the administrator in making the proper selection. To achieve this, we have a function called getHostProfileDetails. This function retrieves an array of profiles along with their properties (customProperties). The critical property, profileObject_value, contains a VcProfile object that we will utilize later.

/**
 *
 *
 * @param {VC:HostSystem} vmHost - The name of the ESXi host to check.
 * @param {string} hostProfileName
 * @returns {Array/Properties} - Host profile details
 */
(function getHostProfileDetails(vmHost: VcHostSystem, hostProfileName: string): Properties[] {
  const hostProfileManager: VcProfileManager = vmHost.sdkConnection.hostProfileManager;
  const profileDetails: Array<Properties> = [];
  let customProperties;
  let filteredProfile: VcProfile[] = [];
  const profiles: VcProfile[] = hostProfileManager.profile;
  if (isArrayNotEmpty(profiles)) {
    filteredProfile = profiles.filter((profile) => {
      return profile.name === hostProfileName;
    });
  }

  if (isArrayNotEmpty(filteredProfile)) {
    filteredProfile.forEach((element) => {
      customProperties = {
        profileName: element.name,
        profileValidationState: element.validationState,
        profileDescription: element.config.annotation,
        profileObject: element**
      };
      profileDetails.push(customProperties);
    });
  }
  return profileDetails.sort();

  function isArrayNotEmpty<T>(array: T[]): array is [T, ...T[]] {
    return array.length > 0;
  }
});

Profile remediation

To remediate the host profile on the host, we first need to check if the selected profile is the same one that is already attached. If it is, we just remediate the profile with updated values. However, if the selected profile differs, we should first attach it to the host. To make it work, we'll need to follow a few simple steps:

  • Check if the selected profile is not empty
const selectedProfile: Properties[] = System.getModule("com.clouddepth.host_profiles.actions").getHostProfileDetails(vmHost, availableHostProfiles);
if (!func.isArrayNotEmpty(selectedProfile)) throw new Error(`${selectedProfile} is not an array`);
  • Get the current profile and selected profile names and compare them. If they are different, we'll attach the selected profile to the host.
const selectedProfileDetails: Properties = selectedProfile[0];
const attachedProfile: VcProfile | null = func.findAssociatedProfile(vmHost, hostProfileManager);
if (attachedProfile && selectedProfileDetails.profileName != attachedProfile.name) {
  func.associateHostProfile(vmHost, selectedProfileDetails.profileObject);
}
  • Remediate the host
const profileExecuteResult: VcProfileExecuteResult = func.executeHostProfile(vmHost, userInput, selectedProfileDetails.profileObject);
func.applyHostConfig(vmHost, profileExecuteResult);

Final result

In our vCenter we have two host profiles: Host Profile 1 and Host Profile 2

Let's start the workflow and select one of the ESXi hosts to update. We can see that this host already has Host Profile 2 attached. This profile doesn't require additional input.

Although we can view the list of all available profiles.

If we don't remember what is Host Profile 2 about, we can select it and see more details.

Now we can see that this profile is probably related to the vSAN hosts. Let's assume we attach another profile because our host will not be a vSAN host anymore. To do so, select from the list of available profiles, profile number 1. In addition, we can see some placeholders.

As shown above, this profile is related to the VDI, and we must provide some inputs. As we mentioned at the beginning, we want to make our flows bulletproof flows. We are making our inputs mandatory only if profile number 1 is selected. This can be easily done with a custom form.

Let's fill the inputs out. As described before, we're using some regular expressions to validate our inputs and prevent human mistakes.

Example of Host FQDN input with regular expression

After all the mistakes were fixed, the workflows should run as expected and be completed successfully.

Summary

Today, we tried to automate the ESXi host profile remediation by developing a self-service form, which can be safely delegated to the team members. In addition, we are trying to cover our code base with some unit tests to make it as safe and stable as possible.

Any feedback is highly appreciated.

Source Code

The source code with the unit tests can be found here.

The vRO package is also available here and the external ECMASCRIPT package here.

All packages should be imported
Table of Contents
Great! Next, complete checkout for full access to CloudDepth.
Welcome back! You've successfully signed in.
You've successfully subscribed to CloudDepth.
Success! Your account is fully activated, you now have access to all content.
Success! Your billing info has been updated.
Your billing was not updated.

This work by Leonid Belenkiy is licensed under Creative Commons Attribution 4.0 International