1# This file is part of Ansible 2# 3# Ansible is free software: you can redistribute it and/or modify 4# it under the terms of the GNU General Public License as published by 5# the Free Software Foundation, either version 3 of the License, or 6# (at your option) any later version. 7# 8# Ansible is distributed in the hope that it will be useful, 9# but WITHOUT ANY WARRANTY; without even the implied warranty of 10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11# GNU General Public License for more details. 12# 13# You should have received a copy of the GNU General Public License 14# along with Ansible. If not, see <http://www.gnu.org/licenses/>. 15 16from __future__ import (absolute_import, division, print_function) 17__metaclass__ = type 18 19import re 20import time 21 22from ansible.module_utils.six.moves import reduce 23 24from ansible.module_utils.common.text.formatters import bytes_to_human 25 26from ansible.module_utils.facts.utils import get_file_content, get_mount_size 27 28from ansible.module_utils.facts.hardware.base import Hardware, HardwareCollector 29from ansible.module_utils.facts import timeout 30 31 32class SunOSHardware(Hardware): 33 """ 34 In addition to the generic memory and cpu facts, this also sets 35 swap_reserved_mb and swap_allocated_mb that is available from *swap -s*. 36 """ 37 platform = 'SunOS' 38 39 def populate(self, collected_facts=None): 40 hardware_facts = {} 41 42 # FIXME: could pass to run_command(environ_update), but it also tweaks the env 43 # of the parent process instead of altering an env provided to Popen() 44 # Use C locale for hardware collection helpers to avoid locale specific number formatting (#24542) 45 self.module.run_command_environ_update = {'LANG': 'C', 'LC_ALL': 'C', 'LC_NUMERIC': 'C'} 46 47 cpu_facts = self.get_cpu_facts() 48 memory_facts = self.get_memory_facts() 49 dmi_facts = self.get_dmi_facts() 50 device_facts = self.get_device_facts() 51 uptime_facts = self.get_uptime_facts() 52 53 mount_facts = {} 54 try: 55 mount_facts = self.get_mount_facts() 56 except timeout.TimeoutError: 57 pass 58 59 hardware_facts.update(cpu_facts) 60 hardware_facts.update(memory_facts) 61 hardware_facts.update(dmi_facts) 62 hardware_facts.update(device_facts) 63 hardware_facts.update(uptime_facts) 64 hardware_facts.update(mount_facts) 65 66 return hardware_facts 67 68 def get_cpu_facts(self, collected_facts=None): 69 physid = 0 70 sockets = {} 71 72 cpu_facts = {} 73 collected_facts = collected_facts or {} 74 75 rc, out, err = self.module.run_command("/usr/bin/kstat cpu_info") 76 77 cpu_facts['processor'] = [] 78 79 for line in out.splitlines(): 80 if len(line) < 1: 81 continue 82 83 data = line.split(None, 1) 84 key = data[0].strip() 85 86 # "brand" works on Solaris 10 & 11. "implementation" for Solaris 9. 87 if key == 'module:': 88 brand = '' 89 elif key == 'brand': 90 brand = data[1].strip() 91 elif key == 'clock_MHz': 92 clock_mhz = data[1].strip() 93 elif key == 'implementation': 94 processor = brand or data[1].strip() 95 # Add clock speed to description for SPARC CPU 96 # FIXME 97 if collected_facts.get('ansible_machine') != 'i86pc': 98 processor += " @ " + clock_mhz + "MHz" 99 if 'ansible_processor' not in collected_facts: 100 cpu_facts['processor'] = [] 101 cpu_facts['processor'].append(processor) 102 elif key == 'chip_id': 103 physid = data[1].strip() 104 if physid not in sockets: 105 sockets[physid] = 1 106 else: 107 sockets[physid] += 1 108 109 # Counting cores on Solaris can be complicated. 110 # https://blogs.oracle.com/mandalika/entry/solaris_show_me_the_cpu 111 # Treat 'processor_count' as physical sockets and 'processor_cores' as 112 # virtual CPUs visisble to Solaris. Not a true count of cores for modern SPARC as 113 # these processors have: sockets -> cores -> threads/virtual CPU. 114 if len(sockets) > 0: 115 cpu_facts['processor_count'] = len(sockets) 116 cpu_facts['processor_cores'] = reduce(lambda x, y: x + y, sockets.values()) 117 else: 118 cpu_facts['processor_cores'] = 'NA' 119 cpu_facts['processor_count'] = len(cpu_facts['processor']) 120 121 return cpu_facts 122 123 def get_memory_facts(self): 124 memory_facts = {} 125 126 rc, out, err = self.module.run_command(["/usr/sbin/prtconf"]) 127 128 for line in out.splitlines(): 129 if 'Memory size' in line: 130 memory_facts['memtotal_mb'] = int(line.split()[2]) 131 132 rc, out, err = self.module.run_command("/usr/sbin/swap -s") 133 134 allocated = int(out.split()[1][:-1]) 135 reserved = int(out.split()[5][:-1]) 136 used = int(out.split()[8][:-1]) 137 free = int(out.split()[10][:-1]) 138 139 memory_facts['swapfree_mb'] = free // 1024 140 memory_facts['swaptotal_mb'] = (free + used) // 1024 141 memory_facts['swap_allocated_mb'] = allocated // 1024 142 memory_facts['swap_reserved_mb'] = reserved // 1024 143 144 return memory_facts 145 146 @timeout.timeout() 147 def get_mount_facts(self): 148 mount_facts = {} 149 mount_facts['mounts'] = [] 150 151 # For a detailed format description see mnttab(4) 152 # special mount_point fstype options time 153 fstab = get_file_content('/etc/mnttab') 154 155 if fstab: 156 for line in fstab.splitlines(): 157 fields = line.split('\t') 158 mount_statvfs_info = get_mount_size(fields[1]) 159 mount_info = {'mount': fields[1], 160 'device': fields[0], 161 'fstype': fields[2], 162 'options': fields[3], 163 'time': fields[4]} 164 mount_info.update(mount_statvfs_info) 165 mount_facts['mounts'].append(mount_info) 166 167 return mount_facts 168 169 def get_dmi_facts(self): 170 dmi_facts = {} 171 172 # On Solaris 8 the prtdiag wrapper is absent from /usr/sbin, 173 # but that's okay, because we know where to find the real thing: 174 rc, platform, err = self.module.run_command('/usr/bin/uname -i') 175 platform_sbin = '/usr/platform/' + platform.rstrip() + '/sbin' 176 177 prtdiag_path = self.module.get_bin_path("prtdiag", opt_dirs=[platform_sbin]) 178 rc, out, err = self.module.run_command(prtdiag_path) 179 """ 180 rc returns 1 181 """ 182 if out: 183 system_conf = out.split('\n')[0] 184 185 # If you know of any other manufacturers whose names appear in 186 # the first line of prtdiag's output, please add them here: 187 vendors = [ 188 "Fujitsu", 189 "Oracle Corporation", 190 "QEMU", 191 "Sun Microsystems", 192 "VMware, Inc.", 193 ] 194 vendor_regexp = "|".join(map(re.escape, vendors)) 195 system_conf_regexp = (r'System Configuration:\s+' 196 + r'(' + vendor_regexp + r')\s+' 197 + r'(?:sun\w+\s+)?' 198 + r'(.+)') 199 200 found = re.match(system_conf_regexp, system_conf) 201 if found: 202 dmi_facts['system_vendor'] = found.group(1) 203 dmi_facts['product_name'] = found.group(2) 204 205 return dmi_facts 206 207 def get_device_facts(self): 208 # Device facts are derived for sdderr kstats. This code does not use the 209 # full output, but rather queries for specific stats. 210 # Example output: 211 # sderr:0:sd0,err:Hard Errors 0 212 # sderr:0:sd0,err:Illegal Request 6 213 # sderr:0:sd0,err:Media Error 0 214 # sderr:0:sd0,err:Predictive Failure Analysis 0 215 # sderr:0:sd0,err:Product VBOX HARDDISK 9 216 # sderr:0:sd0,err:Revision 1.0 217 # sderr:0:sd0,err:Serial No VB0ad2ec4d-074a 218 # sderr:0:sd0,err:Size 53687091200 219 # sderr:0:sd0,err:Soft Errors 0 220 # sderr:0:sd0,err:Transport Errors 0 221 # sderr:0:sd0,err:Vendor ATA 222 223 device_facts = {} 224 device_facts['devices'] = {} 225 226 disk_stats = { 227 'Product': 'product', 228 'Revision': 'revision', 229 'Serial No': 'serial', 230 'Size': 'size', 231 'Vendor': 'vendor', 232 'Hard Errors': 'hard_errors', 233 'Soft Errors': 'soft_errors', 234 'Transport Errors': 'transport_errors', 235 'Media Error': 'media_errors', 236 'Predictive Failure Analysis': 'predictive_failure_analysis', 237 'Illegal Request': 'illegal_request', 238 } 239 240 cmd = ['/usr/bin/kstat', '-p'] 241 242 for ds in disk_stats: 243 cmd.append('sderr:::%s' % ds) 244 245 d = {} 246 rc, out, err = self.module.run_command(cmd) 247 if rc != 0: 248 return device_facts 249 250 sd_instances = frozenset(line.split(':')[1] for line in out.split('\n') if line.startswith('sderr')) 251 for instance in sd_instances: 252 lines = (line for line in out.split('\n') if ':' in line and line.split(':')[1] == instance) 253 for line in lines: 254 text, value = line.split('\t') 255 stat = text.split(':')[3] 256 257 if stat == 'Size': 258 d[disk_stats.get(stat)] = bytes_to_human(float(value)) 259 else: 260 d[disk_stats.get(stat)] = value.rstrip() 261 262 diskname = 'sd' + instance 263 device_facts['devices'][diskname] = d 264 d = {} 265 266 return device_facts 267 268 def get_uptime_facts(self): 269 uptime_facts = {} 270 # sample kstat output: 271 # unix:0:system_misc:boot_time 1548249689 272 rc, out, err = self.module.run_command('/usr/bin/kstat -p unix:0:system_misc:boot_time') 273 274 if rc != 0: 275 return 276 277 # uptime = $current_time - $boot_time 278 uptime_facts['uptime_seconds'] = int(time.time() - int(out.split('\t')[1])) 279 280 return uptime_facts 281 282 283class SunOSHardwareCollector(HardwareCollector): 284 _fact_class = SunOSHardware 285 _platform = 'SunOS' 286 287 required_facts = set(['platform']) 288