blob: aa53f8e6bad57b6616c7f7ed908f032fa24c7c07 [file] [log] [blame]
koder aka kdanilovf86d7af2015-05-06 04:01:54 +03001import re
koder aka kdanilov962ee5f2016-12-19 02:40:08 +02002import logging
koder aka kdanilov22d134e2016-11-08 11:33:19 +02003from typing import Dict, Iterable
koder aka kdanilovf86d7af2015-05-06 04:01:54 +03004import xml.etree.ElementTree as ET
koder aka kdanilov73084622016-11-16 21:51:08 +02005from typing import List, Tuple, cast, Optional
koder aka kdanilovf86d7af2015-05-06 04:01:54 +03006
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +03007from . import utils
koder aka kdanilov3d2bc4f2016-11-12 18:31:18 +02008from .node_interfaces import IRPCNode
koder aka kdanilovf86d7af2015-05-06 04:01:54 +03009
10
koder aka kdanilov962ee5f2016-12-19 02:40:08 +020011logger = logging.getLogger("wally")
12
13
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +030014def get_data(rr: str, data: str) -> str:
koder aka kdanilovf86d7af2015-05-06 04:01:54 +030015 match_res = re.search("(?ims)" + rr, data)
16 return match_res.group(0)
17
18
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +030019class HWInfo:
koder aka kdanilov22d134e2016-11-08 11:33:19 +020020 def __init__(self) -> None:
21 self.hostname = None # type: str
22 self.cores = [] # type: List[Tuple[str, int]]
koder aka kdanilovf86d7af2015-05-06 04:01:54 +030023
24 # /dev/... devices
koder aka kdanilov22d134e2016-11-08 11:33:19 +020025 self.disks_info = {} # type: Dict[str, Tuple[str, int]]
koder aka kdanilovf86d7af2015-05-06 04:01:54 +030026
27 # real disks on raid controller
koder aka kdanilov22d134e2016-11-08 11:33:19 +020028 self.disks_raw_info = {} # type: Dict[str, str]
koder aka kdanilovf86d7af2015-05-06 04:01:54 +030029
koder aka kdanilov6ab4d432015-06-22 00:26:28 +030030 # name => (speed, is_full_diplex, ip_addresses)
koder aka kdanilov73084622016-11-16 21:51:08 +020031 self.net_info = {} # type: Dict[str, Tuple[Optional[int], Optional[bool], List[str]]]
koder aka kdanilov6ab4d432015-06-22 00:26:28 +030032
koder aka kdanilov22d134e2016-11-08 11:33:19 +020033 self.ram_size = 0 # type: int
34 self.sys_name = None # type: str
35 self.mb = None # type: str
36 self.raw = None # type: str
koder aka kdanilov6ab4d432015-06-22 00:26:28 +030037
koder aka kdanilov22d134e2016-11-08 11:33:19 +020038 self.storage_controllers = [] # type: List[str]
koder aka kdanilovf86d7af2015-05-06 04:01:54 +030039
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +030040 def get_hdd_count(self) -> Iterable[int]:
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +030041 # SATA HDD COUNT, SAS 10k HDD COUNT, SAS SSD count, PCI-E SSD count
42 return []
43
koder aka kdanilov22d134e2016-11-08 11:33:19 +020044 def get_summary(self) -> Dict[str, int]:
koder aka kdanilovf86d7af2015-05-06 04:01:54 +030045 cores = sum(count for _, count in self.cores)
46 disks = sum(size for _, size in self.disks_info.values())
47
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +030048 return {'cores': cores,
49 'ram': self.ram_size,
50 'storage': disks,
51 'disk_count': len(self.disks_info)}
koder aka kdanilovf86d7af2015-05-06 04:01:54 +030052
53 def __str__(self):
54 res = []
55
56 summ = self.get_summary()
57 summary = "Simmary: {cores} cores, {ram}B RAM, {disk}B storage"
58 res.append(summary.format(cores=summ['cores'],
59 ram=utils.b2ssize(summ['ram']),
60 disk=utils.b2ssize(summ['storage'])))
61 res.append(str(self.sys_name))
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +030062 if self.mb:
koder aka kdanilovf86d7af2015-05-06 04:01:54 +030063 res.append("Motherboard: " + self.mb)
64
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +030065 if not self.ram_size:
koder aka kdanilovf86d7af2015-05-06 04:01:54 +030066 res.append("RAM: Failed to get RAM size")
67 else:
68 res.append("RAM " + utils.b2ssize(self.ram_size) + "B")
69
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +030070 if not self.cores:
koder aka kdanilovf86d7af2015-05-06 04:01:54 +030071 res.append("CPU cores: Failed to get CPU info")
72 else:
73 res.append("CPU cores:")
74 for name, count in self.cores:
75 if count > 1:
76 res.append(" {0} * {1}".format(count, name))
77 else:
78 res.append(" " + name)
79
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +030080 if self.storage_controllers:
koder aka kdanilovf86d7af2015-05-06 04:01:54 +030081 res.append("Disk controllers:")
82 for descr in self.storage_controllers:
83 res.append(" " + descr)
84
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +030085 if self.disks_info:
koder aka kdanilovf86d7af2015-05-06 04:01:54 +030086 res.append("Storage devices:")
87 for dev, (model, size) in sorted(self.disks_info.items()):
88 ssize = utils.b2ssize(size) + "B"
89 res.append(" {0} {1} {2}".format(dev, ssize, model))
90 else:
91 res.append("Storage devices's: Failed to get info")
92
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +030093 if self.disks_raw_info:
koder aka kdanilovf86d7af2015-05-06 04:01:54 +030094 res.append("Disks devices:")
95 for dev, descr in sorted(self.disks_raw_info.items()):
96 res.append(" {0} {1}".format(dev, descr))
97 else:
98 res.append("Disks devices's: Failed to get info")
99
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300100 if self.net_info:
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300101 res.append("Net adapters:")
koder aka kdanilov6ab4d432015-06-22 00:26:28 +0300102 for name, (speed, dtype, _) in self.net_info.items():
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300103 res.append(" {0} {2} duplex={1}".format(name, dtype, speed))
104 else:
105 res.append("Net adapters: Failed to get net info")
106
107 return str(self.hostname) + ":\n" + "\n".join(" " + i for i in res)
108
109
koder aka kdanilov7f59d562016-12-26 01:34:23 +0200110class CephInfo:
111 def __init__(self) -> None:
112 pass
113
114
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300115class SWInfo:
koder aka kdanilov22d134e2016-11-08 11:33:19 +0200116 def __init__(self) -> None:
koder aka kdanilov962ee5f2016-12-19 02:40:08 +0200117 self.mtab = None # type: str
koder aka kdanilov22d134e2016-11-08 11:33:19 +0200118 self.kernel_version = None # type: str
koder aka kdanilov962ee5f2016-12-19 02:40:08 +0200119 self.libvirt_version = None # type: Optional[str]
120 self.qemu_version = None # type: Optional[str]
koder aka kdanilov73084622016-11-16 21:51:08 +0200121 self.OS_version = None # type: utils.OSRelease
koder aka kdanilov7f59d562016-12-26 01:34:23 +0200122 self.ceph_info = None # type: Optional[CephInfo]
123
124
125def get_ceph_services_info(node: IRPCNode) -> CephInfo:
126 # TODO: use ceph-monitoring module
127 return CephInfo()
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300128
129
koder aka kdanilov3d2bc4f2016-11-12 18:31:18 +0200130def get_sw_info(node: IRPCNode) -> SWInfo:
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300131 res = SWInfo()
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300132
koder aka kdanilov22d134e2016-11-08 11:33:19 +0200133 res.OS_version = utils.get_os(node)
koder aka kdanilov73084622016-11-16 21:51:08 +0200134 res.kernel_version = node.get_file_content('/proc/version').decode('utf8').strip()
koder aka kdanilov962ee5f2016-12-19 02:40:08 +0200135 res.mtab = node.get_file_content('/etc/mtab').decode('utf8').strip()
136
137 try:
138 res.libvirt_version = node.run("virsh -v", nolog=True).strip()
139 except OSError:
140 res.libvirt_version = None
141
koder aka kdanilov7f59d562016-12-26 01:34:23 +0200142 # dpkg -l ??
koder aka kdanilov23e6bdf2016-12-24 02:18:54 +0200143
koder aka kdanilov962ee5f2016-12-19 02:40:08 +0200144 try:
145 res.qemu_version = node.run("qemu-system-x86_64 --version", nolog=True).strip()
146 except OSError:
147 res.qemu_version = None
148
koder aka kdanilov7f59d562016-12-26 01:34:23 +0200149 for role in ('ceph-osd', 'ceph-mon', 'ceph-mds'):
150 if role in node.info.roles:
151 res.ceph_info = get_ceph_services_info(node)
152 break
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300153
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300154 return res
155
156
koder aka kdanilov962ee5f2016-12-19 02:40:08 +0200157def get_hw_info(node: IRPCNode) -> Optional[HWInfo]:
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300158
koder aka kdanilov962ee5f2016-12-19 02:40:08 +0200159 try:
160 lshw_out = node.run('sudo lshw -xml 2>/dev/null')
161 except Exception as exc:
162 logger.warning("lshw failed on node %s: %s", node.info.node_id(), exc)
163 return None
164
165 res = HWInfo()
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300166 res.raw = lshw_out
167 lshw_et = ET.fromstring(lshw_out)
168
169 try:
koder aka kdanilov73084622016-11-16 21:51:08 +0200170 res.hostname = cast(str, lshw_et.find("node").attrib['id'])
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300171 except Exception:
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300172 pass
173
174 try:
koder aka kdanilov73084622016-11-16 21:51:08 +0200175
176 res.sys_name = cast(str, lshw_et.find("node/vendor").text) + " " + \
177 cast(str, lshw_et.find("node/product").text)
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300178 res.sys_name = res.sys_name.replace("(To be filled by O.E.M.)", "")
179 res.sys_name = res.sys_name.replace("(To be Filled by O.E.M.)", "")
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300180 except Exception:
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300181 pass
182
183 core = lshw_et.find("node/node[@id='core']")
184 if core is None:
koder aka kdanilov73084622016-11-16 21:51:08 +0200185 return res
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300186
187 try:
koder aka kdanilov73084622016-11-16 21:51:08 +0200188 res.mb = " ".join(cast(str, core.find(node).text)
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300189 for node in ['vendor', 'product', 'version'])
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300190 except Exception:
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300191 pass
192
193 for cpu in core.findall("node[@class='processor']"):
194 try:
koder aka kdanilov73084622016-11-16 21:51:08 +0200195 model = cast(str, cpu.find('product').text)
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300196 threads_node = cpu.find("configuration/setting[@id='threads']")
197 if threads_node is None:
198 threads = 1
199 else:
200 threads = int(threads_node.attrib['value'])
201 res.cores.append((model, threads))
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300202 except Exception:
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300203 pass
204
205 res.ram_size = 0
206 for mem_node in core.findall(".//node[@class='memory']"):
207 descr = mem_node.find('description')
208 try:
209 if descr is not None and descr.text == 'System Memory':
210 mem_sz = mem_node.find('size')
211 if mem_sz is None:
212 for slot_node in mem_node.find("node[@class='memory']"):
213 slot_sz = slot_node.find('size')
214 if slot_sz is not None:
215 assert slot_sz.attrib['units'] == 'bytes'
216 res.ram_size += int(slot_sz.text)
217 else:
218 assert mem_sz.attrib['units'] == 'bytes'
219 res.ram_size += int(mem_sz.text)
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300220 except Exception:
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300221 pass
222
223 for net in core.findall(".//node[@class='network']"):
224 try:
225 link = net.find("configuration/setting[@id='link']")
226 if link.attrib['value'] == 'yes':
koder aka kdanilov73084622016-11-16 21:51:08 +0200227 name = cast(str, net.find("logicalname").text)
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300228 speed_node = net.find("configuration/setting[@id='speed']")
229
230 if speed_node is None:
231 speed = None
232 else:
koder aka kdanilov73084622016-11-16 21:51:08 +0200233 speed = int(speed_node.attrib['value'])
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300234
235 dup_node = net.find("configuration/setting[@id='duplex']")
236 if dup_node is None:
237 dup = None
238 else:
koder aka kdanilov73084622016-11-16 21:51:08 +0200239 dup = cast(str, dup_node.attrib['value']).lower() == 'yes'
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300240
koder aka kdanilov73084622016-11-16 21:51:08 +0200241 ips = [] # type: List[str]
242 res.net_info[name] = (speed, dup, ips)
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300243 except Exception:
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300244 pass
245
246 for controller in core.findall(".//node[@class='storage']"):
247 try:
248 description = getattr(controller.find("description"), 'text', "")
249 product = getattr(controller.find("product"), 'text', "")
250 vendor = getattr(controller.find("vendor"), 'text', "")
251 dev = getattr(controller.find("logicalname"), 'text', "")
252 if dev != "":
253 res.storage_controllers.append(
254 "{0}: {1} {2} {3}".format(dev, description,
255 vendor, product))
256 else:
257 res.storage_controllers.append(
258 "{0} {1} {2}".format(description,
259 vendor, product))
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300260 except Exception:
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300261 pass
262
263 for disk in core.findall(".//node[@class='disk']"):
264 try:
265 lname_node = disk.find('logicalname')
266 if lname_node is not None:
koder aka kdanilov73084622016-11-16 21:51:08 +0200267 dev = cast(str, lname_node.text).split('/')[-1]
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300268
269 if dev == "" or dev[-1].isdigit():
270 continue
271
272 sz_node = disk.find('size')
273 assert sz_node.attrib['units'] == 'bytes'
274 sz = int(sz_node.text)
275 res.disks_info[dev] = ('', sz)
276 else:
277 description = disk.find('description').text
278 product = disk.find('product').text
279 vendor = disk.find('vendor').text
280 version = disk.find('version').text
281 serial = disk.find('serial').text
282
283 full_descr = "{0} {1} {2} {3} {4}".format(
284 description, product, vendor, version, serial)
285
koder aka kdanilov73084622016-11-16 21:51:08 +0200286 businfo = cast(str, disk.find('businfo').text)
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300287 res.disks_raw_info[businfo] = full_descr
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300288 except Exception:
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300289 pass
290
291 return res