hostinfohelper.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476
  1. // Copyright 2019 Yunion
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package hostinfo
  15. import (
  16. "bufio"
  17. "context"
  18. "fmt"
  19. "os"
  20. "strconv"
  21. "strings"
  22. "time"
  23. "github.com/shirou/gopsutil/cpu"
  24. "github.com/shirou/gopsutil/mem"
  25. "yunion.io/x/jsonutils"
  26. "yunion.io/x/log"
  27. "yunion.io/x/pkg/errors"
  28. "yunion.io/x/pkg/util/netutils"
  29. "yunion.io/x/pkg/util/regutils"
  30. computeapi "yunion.io/x/onecloud/pkg/apis/compute"
  31. hostapi "yunion.io/x/onecloud/pkg/apis/host"
  32. "yunion.io/x/onecloud/pkg/cloudcommon/types"
  33. "yunion.io/x/onecloud/pkg/hostman/hostinfo/hostbridge"
  34. "yunion.io/x/onecloud/pkg/hostman/hostinfo/hostdhcp"
  35. "yunion.io/x/onecloud/pkg/hostman/hostutils"
  36. "yunion.io/x/onecloud/pkg/hostman/options"
  37. "yunion.io/x/onecloud/pkg/httperrors"
  38. modules "yunion.io/x/onecloud/pkg/mcclient/modules/compute"
  39. "yunion.io/x/onecloud/pkg/util/fileutils2"
  40. "yunion.io/x/onecloud/pkg/util/netutils2"
  41. "yunion.io/x/onecloud/pkg/util/procutils"
  42. "yunion.io/x/onecloud/pkg/util/sysutils"
  43. )
  44. type SCPUInfo struct {
  45. CpuCount int
  46. cpuFreq int64 // MHZ
  47. cpuFeatures []string
  48. CpuArchitecture string
  49. cpuInfoProc *types.SCPUInfo
  50. cpuInfoDmi *types.SDMICPUInfo
  51. }
  52. func DetectCpuInfo() (*SCPUInfo, error) {
  53. cpuinfo := new(SCPUInfo)
  54. cpuCount, _ := cpu.Counts(true)
  55. cpuinfo.CpuCount = cpuCount
  56. spec, err := cpuinfo.fetchCpuSpecs()
  57. if err != nil {
  58. return nil, err
  59. }
  60. var freq float64
  61. strCpuFreq, ok := spec["cpu_freq"]
  62. if ok {
  63. freq, err = strconv.ParseFloat(strCpuFreq, 64)
  64. if err != nil {
  65. log.Errorln(err)
  66. return nil, err
  67. }
  68. }
  69. cpuinfo.cpuFreq = int64(freq)
  70. log.Infof("cpuinfo freq %d", cpuinfo.cpuFreq)
  71. cpuinfo.cpuFeatures = strings.Split(spec["flags"], " ")
  72. // cpu.Percent(interval, false)
  73. ret, err := fileutils2.FileGetContents("/proc/cpuinfo")
  74. if err != nil {
  75. return nil, errors.Wrap(err, "get cpuinfo")
  76. }
  77. cpuinfo.cpuInfoProc, err = sysutils.ParseCPUInfo(strings.Split(ret, "\n"))
  78. if err != nil {
  79. return nil, errors.Wrap(err, "parse cpu info")
  80. }
  81. bret, err := procutils.NewCommand("dmidecode", "-t", "4").Output()
  82. if err != nil {
  83. log.Errorf("dmidecode -t 4 error: %s(%s)", err, string(bret))
  84. cpuinfo.cpuInfoDmi = &types.SDMICPUInfo{Nodes: 1}
  85. } else {
  86. cpuinfo.cpuInfoDmi = sysutils.ParseDMICPUInfo(strings.Split(string(bret), "\n"))
  87. }
  88. cpuArch, err := procutils.NewCommand("uname", "-m").Output()
  89. if err != nil {
  90. return nil, errors.Wrap(err, "get cpu architecture")
  91. }
  92. cpuinfo.CpuArchitecture = strings.TrimSpace(string(cpuArch))
  93. return cpuinfo, nil
  94. }
  95. func (c *SCPUInfo) fetchCpuSpecs() (map[string]string, error) {
  96. f, err := os.Open("/proc/cpuinfo")
  97. if err != nil {
  98. return nil, err
  99. }
  100. defer f.Close()
  101. var spec = make(map[string]string, 0)
  102. scanner := bufio.NewScanner(f)
  103. for scanner.Scan() {
  104. line := scanner.Text()
  105. colon := strings.Index(line, ":")
  106. if colon > 0 {
  107. key := strings.TrimSpace(line[:colon])
  108. val := strings.TrimSpace(line[colon+1:])
  109. if key == "cpu MHz" {
  110. spec["cpu_freq"] = val
  111. } else if key == "flags" {
  112. spec["flags"] = val
  113. }
  114. }
  115. }
  116. if err := scanner.Err(); err != nil {
  117. log.Errorln(err)
  118. return nil, err
  119. }
  120. return spec, nil
  121. }
  122. type SMemory struct {
  123. Total int
  124. Free int
  125. Used int
  126. MemInfo *types.SDMIMemInfo
  127. }
  128. func DetectMemoryInfo() (*SMemory, error) {
  129. var smem = new(SMemory)
  130. info, err := mem.VirtualMemory()
  131. if err != nil {
  132. return nil, err
  133. }
  134. smem.Total = int(info.Total / 1024 / 1024)
  135. smem.Free = int(info.Available / 1024 / 1024)
  136. smem.Used = smem.Total - smem.Free
  137. ret, err := procutils.NewCommand("dmidecode", "-t", "17").Output()
  138. if err != nil {
  139. // ignore
  140. log.Errorf("dmidecode fail %s: %s", err, ret)
  141. smem.MemInfo = &types.SDMIMemInfo{}
  142. } else {
  143. smem.MemInfo = sysutils.ParseDMIMemInfo(strings.Split(string(ret), "\n"))
  144. }
  145. if smem.MemInfo.Total == 0 {
  146. // in case dmidecode is not work, use gopsutil
  147. smem.MemInfo.Total = smem.Total
  148. }
  149. return smem, nil
  150. }
  151. func (m *SMemory) GetHugepages() (sysutils.THugepages, error) {
  152. return sysutils.GetHugepages()
  153. }
  154. type SNIC struct {
  155. Inter string
  156. Bridge string
  157. Ip string
  158. Ip6 string
  159. Wire string
  160. WireId string
  161. Mask int
  162. Mask6 int
  163. Bandwidth int
  164. BridgeDev hostbridge.IBridgeDriver
  165. dhcpServer *hostdhcp.SGuestDHCPServer
  166. dhcpServer6 *hostdhcp.SGuestDHCP6Server
  167. }
  168. func (n *SNIC) EnableDHCPRelay() bool {
  169. if len(n.Ip) == 0 {
  170. return false
  171. }
  172. v4Ip, err := netutils.NewIPV4Addr(n.Ip)
  173. if err != nil {
  174. log.Errorf("EnableDHCPRelay netutils.NewIPV4Addr(%s) error: %v", n.Ip, err)
  175. return false
  176. }
  177. if len(options.HostOptions.DhcpRelay) == 2 && !netutils.IsExitAddress(v4Ip) {
  178. return true
  179. } else {
  180. return false
  181. }
  182. }
  183. func (n *SNIC) EnableDHCP6Relay() bool {
  184. if len(n.Ip6) == 0 {
  185. return false
  186. }
  187. _, err := netutils.NewIPV6Addr(n.Ip6)
  188. if err != nil {
  189. log.Errorf("EnableDHCP6Relay netutils.NewIPV6Addr(%s) error: %v", n.Ip6, err)
  190. return false
  191. }
  192. if len(options.HostOptions.Dhcp6Relay) == 2 {
  193. return true
  194. } else {
  195. return false
  196. }
  197. }
  198. func (n *SNIC) SetupDhcpRelay() error {
  199. if n.EnableDHCPRelay() {
  200. log.Infof("Enable dhcp relay on nic: %#v", n)
  201. if err := n.dhcpServer.RelaySetup(n.Ip); err != nil {
  202. return errors.Wrapf(err, "setup dhcp relay on ip: %s", n.Ip)
  203. }
  204. }
  205. if n.EnableDHCP6Relay() {
  206. log.Infof("Enable dhcpv6 relay on nic: %#v", n)
  207. if err := n.dhcpServer6.RelaySetup(n.Ip6); err != nil {
  208. return errors.Wrapf(err, "setup dhcpv6 relay on ip: %s", n.Ip6)
  209. }
  210. }
  211. return nil
  212. }
  213. func (n *SNIC) SetWireId(wire, wireId string, bandwidth int64) error {
  214. if len(n.Wire) == 0 {
  215. n.Wire = wire
  216. } else if n.Wire != wire {
  217. return errors.Wrapf(httperrors.ErrConflict, "expect wire %s != assign wire %s", n.Wire, wire)
  218. } else {
  219. // match
  220. }
  221. n.WireId = wireId
  222. n.Bandwidth = int(bandwidth)
  223. return nil
  224. }
  225. func (n *SNIC) ExitCleanup() {
  226. n.BridgeDev.CleanupConfig()
  227. log.Infof("Stop DHCP Server")
  228. // TODO stop dhcp server
  229. }
  230. func NewNIC(desc string) (*SNIC, error) {
  231. nic := new(SNIC)
  232. data := strings.Split(desc, "/")
  233. if len(data) < 3 {
  234. return nil, fmt.Errorf("Parse nic conf %s failed, too short", desc)
  235. }
  236. nic.Inter = data[0]
  237. nic.Bridge = data[1]
  238. if regutils.MatchIP4Addr(data[2]) {
  239. nic.Ip = data[2]
  240. if len(data) > 3 && regutils.MatchIP6Addr(data[3]) {
  241. nic.Ip6 = data[3]
  242. }
  243. } else if regutils.MatchIP6Addr(data[2]) {
  244. nic.Ip6 = data[2]
  245. if len(data) > 3 && regutils.MatchIP4Addr(data[3]) {
  246. nic.Ip = data[3]
  247. }
  248. } else {
  249. nic.Wire = data[2]
  250. }
  251. nic.Bandwidth = 1000
  252. log.Infof("IP %s/%s/%s/%s", nic.Ip, nic.Ip6, nic.Bridge, nic.Inter)
  253. // fetch ip and ip6 netmask from interface and bridge
  254. if len(nic.Ip) > 0 || len(nic.Ip6) > 0 {
  255. // waiting for interface assign ip
  256. // in case nic bonding is too slow
  257. var max, wait = 30, 0
  258. for wait < max {
  259. inf := netutils2.NewNetInterfaceWithExpectIp(nic.Inter, nic.Ip, nic.Ip6, nil)
  260. if len(nic.Ip) > 0 && inf.Addr == nic.Ip {
  261. mask, _ := inf.Mask.Size()
  262. if mask > 0 {
  263. nic.Mask = mask
  264. }
  265. }
  266. if len(nic.Ip6) > 0 && inf.Addr6 == nic.Ip6 {
  267. mask, _ := inf.Mask6.Size()
  268. if mask > 0 {
  269. nic.Mask6 = mask
  270. }
  271. }
  272. br := netutils2.NewNetInterface(nic.Bridge)
  273. if br.Addr == nic.Ip {
  274. mask, _ := br.Mask.Size()
  275. if nic.Mask == 0 && mask > 0 {
  276. nic.Mask = mask
  277. }
  278. }
  279. if br.Addr6 == nic.Ip6 {
  280. mask, _ := br.Mask6.Size()
  281. if nic.Mask6 == 0 && mask > 0 {
  282. nic.Mask6 = mask
  283. }
  284. }
  285. if nic.Mask > 0 || nic.Mask6 > 0 {
  286. break
  287. }
  288. time.Sleep(time.Second * 2)
  289. wait += 1
  290. }
  291. if wait >= max {
  292. // if ip not found in inter or bridge
  293. return nil, fmt.Errorf("Ip %s is not configure on %s/%s ?", nic.Ip, nic.Bridge, nic.Inter)
  294. }
  295. }
  296. var err error
  297. nic.BridgeDev, err = hostbridge.NewDriver(options.HostOptions.BridgeDriver,
  298. nic.Bridge, nic.Inter, nic.Ip, nic.Mask, nic.Ip6, nic.Mask6)
  299. if err != nil {
  300. return nil, errors.Wrapf(err, "hostbridge.NewDriver driver: %s, bridge: %s, interface: %s, ip: %s/%d, ip6: %s/%d", options.HostOptions.BridgeDriver, nic.Bridge, nic.Inter, nic.Ip, nic.Mask, nic.Ip6, nic.Mask6)
  301. }
  302. confirm, msg, err := nic.BridgeDev.ConfirmToConfig()
  303. if err != nil {
  304. return nil, errors.Wrapf(err, "nic.BridgeDev.ConfirmToConfig %#v", nic.BridgeDev)
  305. }
  306. if !confirm {
  307. log.Infof("Not confirm to configuration, bridge %s reason: %s", nic.Bridge, msg)
  308. if err = nic.BridgeDev.Setup(nic.BridgeDev); err != nil {
  309. return nil, errors.Wrapf(err, "nic.BridgeDev.Setup %v", nic.BridgeDev)
  310. }
  311. time.Sleep(time.Second * 1)
  312. } else {
  313. log.Infof("Confirm to configuration!! To migrate physical interface configs")
  314. err := nic.BridgeDev.MigrateSlaveConfigs(nic.BridgeDev)
  315. if err != nil {
  316. log.Errorf("fail to migrate configs: %s", err)
  317. }
  318. }
  319. if err := nic.BridgeDev.PersistentConfig(); err != nil {
  320. return nil, errors.Wrapf(err, "nic.BridgeDev.PersistentConfig %v", nic.BridgeDev)
  321. }
  322. if isDHCP, err := nic.BridgeDev.DisableDHCPClient(); err != nil {
  323. return nil, errors.Wrap(err, "disable dhcp client")
  324. } else if isDHCP {
  325. Instance().AppendHostError("dhcp client is enabled before host agent start, please disable it")
  326. }
  327. var relayConf *hostdhcp.SDHCPRelayUpstream
  328. if nic.EnableDHCPRelay() {
  329. log.Infof("EnableDHCPRelay on nic %#v", nic)
  330. relayConf = &hostdhcp.SDHCPRelayUpstream{}
  331. relayConf.IP = options.HostOptions.DhcpRelay[0]
  332. relayConf.Port, err = strconv.Atoi(options.HostOptions.DhcpRelay[1])
  333. if err != nil {
  334. return nil, errors.Wrapf(err, "invalid relay port %s", options.HostOptions.DhcpRelay[1])
  335. }
  336. }
  337. nic.dhcpServer, err = hostdhcp.NewGuestDHCPServer(nic.Bridge, options.HostOptions.DhcpServerPort, relayConf)
  338. if err != nil {
  339. return nil, errors.Wrapf(err, "NewGuestDHCPServer(%s, %d, %#v)", nic.Bridge, options.HostOptions.DhcpServerPort, relayConf)
  340. }
  341. var relayConf6 *hostdhcp.SDHCPRelayUpstream
  342. if nic.EnableDHCP6Relay() {
  343. log.Infof("EnableDHCP6Relay on nic %#v", nic)
  344. relayConf6 = &hostdhcp.SDHCPRelayUpstream{}
  345. relayConf6.IP = options.HostOptions.Dhcp6Relay[0]
  346. relayConf6.Port, err = strconv.Atoi(options.HostOptions.Dhcp6Relay[1])
  347. if err != nil {
  348. return nil, errors.Wrapf(err, "invalid relay port %s", options.HostOptions.Dhcp6Relay[1])
  349. }
  350. }
  351. if !nic.BridgeDev.IsV4Only() {
  352. nic.dhcpServer6, err = hostdhcp.NewGuestDHCP6Server(nic.Bridge, options.HostOptions.Dhcp6ServerPort, relayConf6)
  353. if err != nil {
  354. return nil, errors.Wrapf(err, "NewGuestDHCP6Server(%s, %d, %#v)", nic.Bridge, options.HostOptions.Dhcp6ServerPort, relayConf6)
  355. }
  356. }
  357. // dhcp server start after guest manager init
  358. return nic, nil
  359. }
  360. func (n *SNIC) IsHostLocal() bool {
  361. if n.Bridge == options.HostOptions.HostLocalBridgeName {
  362. n.setupHostLocal()
  363. return true
  364. }
  365. return false
  366. }
  367. func (n *SNIC) setupHostLocal() {
  368. if n.WireId == "" {
  369. n.Wire = computeapi.DEFAULT_HOST_LOCAL_WIRE_NAME
  370. n.WireId = computeapi.DEFAULT_HOST_LOCAL_WIRE_ID
  371. }
  372. }
  373. type SSysInfo struct {
  374. *types.SSystemInfo
  375. Nest string `json:"nest,omitempty"`
  376. OsDistribution string `json:"os_distribution"`
  377. OsVersion string `json:"os_version"`
  378. KernelVersion string `json:"kernel_version"`
  379. QemuVersion string `json:"qemu_version"`
  380. OvsVersion string `json:"ovs_version"`
  381. OvsKmodVersion string `json:"ovs_kmod_version"`
  382. KvmModule string `json:"kvm_module"`
  383. CpuModelName string `json:"cpu_model_name"`
  384. CpuMicrocode string `json:"cpu_microcode"`
  385. CgroupVersion string `json:"cgroup_version"`
  386. StorageType string `json:"storage_type"`
  387. HugepagesOption string `json:"hugepages_option"`
  388. HugepageSizeKb int `json:"hugepage_size_kb"`
  389. HugepageNr *int `json:"hugepage_nr"`
  390. NodeHugepages []hostapi.HostNodeHugepageNr `json:"node_hugepages"`
  391. EnableKsm bool `json:"enable_ksm"`
  392. HostAgentCpuNumaAllocate bool `json:"host_agent_cpu_numa_allocate"`
  393. Topology *hostapi.HostTopology `json:"topology"`
  394. CPUInfo *hostapi.HostCPUInfo `json:"cpu_info"`
  395. MotherboardInfo *types.SSystemInfo `json:"motherboard_info"`
  396. }
  397. func StartDetachStorages(hs []jsonutils.JSONObject) {
  398. for len(hs) > 0 {
  399. hostId, _ := hs[0].GetString("host_id")
  400. storageId, _ := hs[0].GetString("storage_id")
  401. _, err := modules.Hoststorages.Detach(
  402. hostutils.GetComputeSession(context.Background()),
  403. hostId, storageId, nil)
  404. if err != nil {
  405. log.Errorf("Host %s detach storage %s failed: %s",
  406. hostId, storageId, err)
  407. time.Sleep(30 * time.Second)
  408. } else {
  409. hs = hs[1:]
  410. }
  411. }
  412. }
  413. func IsRootPartition(path string) bool {
  414. if !strings.HasPrefix(path, "/") {
  415. return false
  416. }
  417. path = strings.TrimSuffix(path, "/")
  418. pathSegs := strings.Split(path, "/")
  419. for len(pathSegs) > 1 {
  420. err := procutils.NewRemoteCommandAsFarAsPossible("mountpoint", path).Run()
  421. if err != nil {
  422. pathSegs = pathSegs[:len(pathSegs)-1]
  423. path = strings.Join(pathSegs, "/")
  424. continue
  425. } else {
  426. return false
  427. }
  428. }
  429. return true
  430. }