Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Display ESXi model, CPU model and code name at deploy_vm failure #554

Merged
merged 3 commits into from
Mar 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 33 additions & 7 deletions common/esxi_get_model.yml
Original file line number Diff line number Diff line change
@@ -1,25 +1,51 @@
# Copyright 2021-2024 VMware, Inc.
# SPDX-License-Identifier: BSD-2-Clause
---
- name: "Initialize the facts of ESXi server model info"
- name: "Initialize facts of ESXi server model info and CPU info"
ansible.builtin.set_fact:
esxi_model_info: ''
esxi_cpu_model_info: ''
esxi_cpu_vendor: ''
esxi_cpu_code_name: ''

# Get ESXi server model and CPU model
- include_tasks: esxi_get_property.yml
- name: "Get ESXi server model and CPU model"
include_tasks: esxi_get_property.yml
vars:
esxi_host_property_list:
- summary.hardware.cpuModel
- summary.hardware.model
- summary.hardware.vendor
- hardware.cpuPkg

- name: "Set fact of ESXi server model and CPU model info"
ansible.builtin.set_fact:
esxi_model_info: "{{ esxi_host_property_results.ansible_facts.summary.hardware.vendor }} {{ esxi_host_property_results.ansible_facts.summary.hardware.model }}"
esxi_cpu_model_info: "{{ esxi_host_property_results.ansible_facts.summary.hardware.cpuModel }}"
esxi_cpu_vendor: "{{ esxi_host_property_results.ansible_facts.hardware.cpuPkg[0].vendor | default('Unknown') }}"

- name: "Display ESXi server model info"
ansible.builtin.debug: var=esxi_model_info
- name: "Display ESXi server CPU model info"
ansible.builtin.debug: var=esxi_cpu_model_info
- name: "Get ESXi server CPU code name"
when: esxi_cpu_vendor | lower == 'intel'
block:
- name: "Get ESXi server CPU code name"
ansible.builtin.script: "../tools/query_cpu.py -n '{{ cpu_name }}'"
vars:
cpu_name: "{{ esxi_cpu_model_info | regex_replace(' CPU.*', '') }}"
ignore_errors: true
register: query_cpu_result

- name: "Set fact of ESXi server CPU code name"
ansible.builtin.set_fact:
esxi_cpu_code_name: "{{ query_cpu_result.stdout.strip() }}"
when:
- query_cpu_result.failed is defined
- not query_cpu_result.failed
- query_cpu_result.stdout is defined
- query_cpu_result.stdout

- name: "Display ESXi server and CPU model info"
ansible.builtin.debug:
msg:
- "ESXi server model info: {{ esxi_model_info }}"
- "ESXi server CPU manufacturer: {{ esxi_cpu_vendor }}"
- "ESXi server CPU model info: {{ esxi_cpu_model_info }}"
- "ESXi server CPU code name: {{ esxi_cpu_code_name }}"
6 changes: 3 additions & 3 deletions common/esxi_get_property.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,17 @@
# Parameters:
# esxi_host_property_list
#
- name: Get ESXi host specified property
- name: "Get ESXi host specified property"
community.vmware.vmware_host_facts:
hostname: "{{ vsphere_host_name }}"
username: "{{ vsphere_host_user }}"
password: "{{ vsphere_host_user_password }}"
esxi_hostname: "{{ esxi_hostname }}"
validate_certs: "{{ validate_certs | default(false) }}"
properties: "{{ esxi_host_property_list }}"
properties: "{{ esxi_host_property_list | default(omit) }}"
schema: vsphere
register: esxi_host_property_results

- name: Display the specified ESXi property result
- name: "Display the specified ESXi property result"
ansible.builtin.debug: var=esxi_host_property_results
when: enable_debug is defined and enable_debug
23 changes: 23 additions & 0 deletions linux/deploy_vm/deploy_vm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,29 @@
ansible.builtin.debug: var=vm_guest_ip
when: vm_guest_ip is defined and vm_guest_ip
rescue:
- name: "Display ESXi and CPU model information at deployment failure"
ansible.builtin.debug:
msg: >-
VM deployment failed on server {{ esxi_model_info }} with
ESXi version {{ esxi_version }} build {{ esxi_build }} installed.
The server's CPU model is {{ esxi_cpu_model_info }}
{{ ' (code name ' ~ esxi_cpu_code_name ~ ')' if esxi_cpu_code_name }}
tags:
- fail_message

- name: "Known issue - Linux guest OS hung"
ansible.builtin.debug:
msg: >-
Linux guest OS could be hung on ESXi {{ esxi_version }} server
based on Intel {{ esxi_cpu_code_name }} CPU. Refer to KB articles
https://kb.vmware.com/s/article/92361 and
https://kb.vmware.com/s/article/91250.
tags:
- known_issue
when:
- esxi_version in ['7.0.0', '7.0.1']
- esxi_cpu_code_name in ['Skylake', 'Ice Lake', 'Cascade Lake']

- name: "Collect serial port log at test failure"
include_tasks: collect_serial_port_log.yml
vars:
Expand Down
14 changes: 11 additions & 3 deletions plugin/ansible_vsphere_gosv_log.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ def __init__(self, product, hostname):
self.build = ''
self.model = ''
self.cpu_model = ''
self.cpu_codename = ''

def __str__(self):
info = {'hostname': self.hostname,
Expand All @@ -90,6 +91,7 @@ def __str__(self):
if self.product.lower() == 'esxi':
info['model'] = self.model
info['cpu_model'] = self.cpu_model
info['cpu_codename'] = self.cpu_codename
return json.dumps(info, indent=4)

def update_property(self, p_name, p_value):
Expand Down Expand Up @@ -139,6 +141,8 @@ def set_esxi_info(self, ansible_gosv_facts=None):
ansible_gosv_facts.get('esxi_model_info', ''))
self.esxi_info.update_property('cpu_model',
ansible_gosv_facts.get('esxi_cpu_model_info', ''))
self.esxi_info.update_property('cpu_codename',
ansible_gosv_facts.get('esxi_cpu_code_name', ''))

def __str__(self):
"""
Expand Down Expand Up @@ -167,8 +171,12 @@ def __str__(self):
hostname_col_width = max(
[len('Hostname or IP'), len(self.vcenter_info.hostname), len(self.esxi_info.hostname)])
# Get server model column width
esxi_cpu_detail = self.esxi_info.cpu_model
if self.esxi_info.cpu_codename:
esxi_cpu_detail += f" ({self.esxi_info.cpu_codename})"

server_model_col_width = max(
[len('Server Model'), len(self.esxi_info.model), len(self.esxi_info.cpu_model)])
[len('Server Model'), len(self.esxi_info.model), len(esxi_cpu_detail)])

# Table width
table_width = sum([9, version_col_width, build_col_width,
Expand Down Expand Up @@ -202,12 +210,12 @@ def __str__(self):
self.esxi_info.build.ljust(build_col_width),
self.esxi_info.hostname.ljust(hostname_col_width),
self.esxi_info.model.ljust(server_model_col_width))
if self.esxi_info.cpu_model:
if esxi_cpu_detail:
msg += row_format.format('',
''.ljust(version_col_width),
''.ljust(build_col_width),
''.ljust(hostname_col_width),
self.esxi_info.cpu_model.ljust(server_model_col_width))
esxi_cpu_detail.ljust(server_model_col_width))
msg += row_border

msg += "\n"
Expand Down
127 changes: 127 additions & 0 deletions tools/query_cpu.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
#!/usr/bin/env python3
# Copyright 2024 VMware, Inc.
# SPDX-License-Identifier: BSD-2-Clause
#
# This script can help to query CPU code name from manufacturer website
#
import sys
import requests
import re
import traceback
from lxml import html
from urllib.parse import quote, urlparse, urlunparse
from argparse import ArgumentParser, RawTextHelpFormatter

ARK_INTEL_HOME = "https://ark.intel.com"

def url_encode(input_string):
encoded_string = quote(input_string)
return encoded_string

class Processor(object):
def __init__(self):
self.cpu_name = None
self.code_name = None

def get_cpu_name(self):
return self.cpu_name

def get_code_name(self):
return self.code_name

class IntelProcessor(Processor):
def __init__(self, cpu_spec_url):
super().__init__()
self._parse_cpu_spec(cpu_spec_url)

def _parse_cpu_spec(self, cpu_spec_url):
"""
Get CPU details from its specification URL
For example,
https://ark.intel.com/content/www/us/en/ark/products/212458/intel-xeon-gold-6330-processor-42m-cache-2-00-ghz.html
"""
try:
response = requests.get(cpu_spec_url)
if response.status_code == 200:
xml_tree = html.fromstring(response.content)
product_title = xml_tree.xpath('//div[contains(@class, "product-family-title-text")]/h1')
if product_title:
cpu_name = re.sub(' *Processor *', '', product_title[0].text)
cpu_name = cpu_name.replace('®', '(R)')
self.cpu_name = cpu_name
# Look for code name
codename_a = xml_tree.xpath('//span[@data-key="CodeNameText"]/a')
if codename_a:
self.code_name = codename_a[0].text.replace('Products formerly ', '').strip()
except Exception as e:
traceback_str = traceback.format_exc()
sys.stderr.write(f"Failed to parse CPU specification {cpu_spec_url}\n" + traceback_str)
sys.exit(1)

def intel_search_cpu(search_name):
"""
Search for CPU by given CPU name pattern
"""
cpu_spec_url = None
# Remove '(R)' from the CPU name
cpu_name_pattern = re.sub('\(\w+\)', '', search_name)
# Construct the search query URL
search_url = f"{ARK_INTEL_HOME}/content/www/us/en/ark/search.html?_charset_=UTF-8&q=" + url_encode(cpu_name_pattern)

# Send a GET request to the search URL
response = requests.get(search_url)
if response.status_code == 200:
xml_tree = html.fromstring(response.content)
# Find the CPU search results
cpu_results = xml_tree.xpath('//h4[@class="result-title"]/a')
if cpu_results and len(cpu_results) > 0:
# It searched more than one CPU results
for cpu_result in cpu_results:
cpu_name = re.sub(r'(Processor)? *\(.*\)', '', cpu_result.text).strip().replace('®', '(R)')
# print(f"Found CPU name: {cpu_name}")
if cpu_name == search_name:
cpu_spec_url = ARK_INTEL_HOME + cpu_result.get('href')
break
else:
# It searched only one CPU result
search_result = xml_tree.xpath('//div[contains(@class,"textSearch")]/input[@id="FormRedirectUrl"]')
if search_result and len(search_result) > 0:
cpu_spec_url = ARK_INTEL_HOME + search_result[0].value

# print(f"Found CPU spec for {search_name}: {cpu_spec_url}")
return cpu_spec_url

def intel_query_cpu_code_name(search_name):
"""
Search CPU and get its code name from CPU specification
"""
cpu_spec_url = intel_search_cpu(search_name)
if cpu_spec_url:
intel_cpu = IntelProcessor(cpu_spec_url)
cpu_name = intel_cpu.get_cpu_name()
code_name = intel_cpu.get_code_name()
if cpu_name and code_name and cpu_name == search_name:
print(code_name)

def parse_arguments():
parser = ArgumentParser(description="A tool to query CPU code name by CPU product name",
usage='%(prog)s [OPTIONS]',
formatter_class=RawTextHelpFormatter)
parser.add_argument("-n", dest="name", required=True,
help="CPU product name\n" +
"e.g. Intel(R) Xeon(R) Gold 6330")

args = parser.parse_args()
return args

if __name__ == "__main__":
args = parse_arguments()
try:
if 'Intel' in args.name:
intel_query_cpu_code_name(args.name)
elif 'AMD' in args.name:
pass
except Exception as e:
traceback_str = traceback.format_exc()
sys.stderr.write(traceback_str)
sys.exit(1)
12 changes: 11 additions & 1 deletion windows/deploy_vm/deploy_vm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,23 @@
vars:
vm_screenshot_local_dir: "{{ current_test_log_folder }}"
rescue:
- name: "Display ESXi and CPU model information at deployment failure"
ansible.builtin.debug:
msg: >-
VM deployment failed on server {{ esxi_model_info }} with
ESXi version {{ esxi_version }} build {{ esxi_build }} installed.
The server's CPU model is {{ esxi_cpu_model_info }}
{{ ' (code name ' ~ esxi_cpu_code_name ~ ')' if esxi_cpu_code_name }}
tags:
- fail_message

- name: "Test case failure"
include_tasks: ../../common/test_rescue.yml
vars:
exit_testing_when_fail: true
always:
- name: "Remove NFS mount point"
when: nfs_mount_dir is defined and nfs_mount_dir
block:
- name: "Umount NFS share point"
include_tasks: ../../common/local_unmount.yml
Expand All @@ -64,4 +75,3 @@
vars:
local_path: "{{ nfs_mount_dir }}"
del_local_file_ignore_errors: true
when: nfs_mount_dir is defined and nfs_mount_dir