qga.go 26 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031
  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 qga
  15. import (
  16. "bufio"
  17. "encoding/base64"
  18. "encoding/json"
  19. "fmt"
  20. "net"
  21. "runtime/debug"
  22. "strconv"
  23. "strings"
  24. "sync"
  25. "time"
  26. "yunion.io/x/jsonutils"
  27. "yunion.io/x/log"
  28. "yunion.io/x/pkg/errors"
  29. "yunion.io/x/onecloud/pkg/cloudcommon/types"
  30. "yunion.io/x/onecloud/pkg/hostman/diskutils/fsutils"
  31. "yunion.io/x/onecloud/pkg/hostman/guestfs"
  32. "yunion.io/x/onecloud/pkg/hostman/monitor"
  33. )
  34. const (
  35. QGA_DEFAULT_READ_TIMEOUT_SECOND int = 5
  36. QGA_EXEC_DEFAULT_WAIT_TIMEOUT int = 300
  37. )
  38. type QGACallback func([]byte)
  39. type QemuGuestAgent struct {
  40. id string
  41. qgaSocketPath string
  42. commandQueue []string
  43. callbackQueue []QGACallback
  44. scanner *bufio.Scanner
  45. rwc net.Conn
  46. mutex *sync.Mutex
  47. writing bool
  48. readTimeout int
  49. }
  50. type TryMutex struct {
  51. mu sync.Mutex
  52. }
  53. func NewQemuGuestAgent(id, qgaSocketPath string) (*QemuGuestAgent, error) {
  54. qga := &QemuGuestAgent{
  55. id: id,
  56. qgaSocketPath: qgaSocketPath,
  57. mutex: &sync.Mutex{},
  58. readTimeout: QGA_DEFAULT_READ_TIMEOUT_SECOND,
  59. }
  60. err := qga.connect()
  61. if err != nil {
  62. return nil, err
  63. }
  64. return qga, nil
  65. }
  66. func (qga *QemuGuestAgent) connect() error {
  67. qga.mutex.Lock()
  68. defer qga.mutex.Unlock()
  69. conn, err := net.Dial("unix", qga.qgaSocketPath)
  70. if err != nil {
  71. return errors.Wrap(err, "dial qga socket")
  72. }
  73. qga.commandQueue = make([]string, 0)
  74. qga.callbackQueue = make([]QGACallback, 0)
  75. qga.rwc = conn
  76. qga.scanner = bufio.NewScanner(conn)
  77. go qga.read()
  78. return nil
  79. }
  80. func (qga *QemuGuestAgent) read() {
  81. defer func() {
  82. if r := recover(); r != nil {
  83. log.Errorf("QemuGuestAgent read %v %v", r, debug.Stack())
  84. }
  85. }()
  86. scanner := qga.scanner
  87. for scanner.Scan() {
  88. res := scanner.Bytes()
  89. if len(res) == 0 {
  90. continue
  91. }
  92. go qga.callBack(res)
  93. }
  94. err := scanner.Err()
  95. if err != nil {
  96. log.Debugf("QGA Disconnected %s: %s", qga.id, err)
  97. }
  98. }
  99. func (qga *QemuGuestAgent) callBack(res []byte) {
  100. qga.mutex.Lock()
  101. if len(qga.callbackQueue) == 0 {
  102. qga.mutex.Unlock()
  103. return
  104. }
  105. cb := qga.callbackQueue[0]
  106. qga.callbackQueue = qga.callbackQueue[1:]
  107. qga.mutex.Unlock()
  108. if cb != nil {
  109. go cb(res)
  110. }
  111. }
  112. func (qga *QemuGuestAgent) Close() error {
  113. qga.mutex.Lock()
  114. defer qga.mutex.Unlock()
  115. if qga.rwc == nil {
  116. return nil
  117. }
  118. err := qga.rwc.Close()
  119. if err != nil {
  120. return err
  121. }
  122. qga.commandQueue = nil
  123. qga.callbackQueue = nil
  124. qga.scanner = nil
  125. qga.rwc = nil
  126. return nil
  127. }
  128. func (qga *QemuGuestAgent) Query(cmd string, cb QGACallback) int {
  129. // push
  130. var cbQueueLength int
  131. qga.mutex.Lock()
  132. qga.commandQueue = append(qga.commandQueue, cmd)
  133. qga.callbackQueue = append(qga.callbackQueue, cb)
  134. cbQueueLength = len(qga.callbackQueue)
  135. qga.mutex.Unlock()
  136. if !qga.writing {
  137. go qga.query()
  138. }
  139. return cbQueueLength
  140. }
  141. func (m *QemuGuestAgent) checkWriting() bool {
  142. m.mutex.Lock()
  143. defer m.mutex.Unlock()
  144. if m.writing {
  145. return false
  146. } else {
  147. m.writing = true
  148. }
  149. return true
  150. }
  151. func (m *QemuGuestAgent) query() {
  152. if !m.checkWriting() {
  153. return
  154. }
  155. for {
  156. if len(m.commandQueue) == 0 {
  157. break
  158. }
  159. //pop
  160. m.mutex.Lock()
  161. cmd := m.commandQueue[0]
  162. m.commandQueue = m.commandQueue[1:]
  163. err := m.write(cmd)
  164. m.mutex.Unlock()
  165. if err != nil {
  166. log.Errorf("Write %s to QGA error %s: %s", cmd, m.id, err)
  167. break
  168. }
  169. }
  170. m.writing = false
  171. }
  172. func (qga *QemuGuestAgent) write(cmd string) error {
  173. log.Debugf("QGA Write %s: %s", qga.id, cmd)
  174. length, index := len(cmd), 0
  175. for index < length {
  176. i, err := qga.rwc.Write([]byte(cmd))
  177. if err != nil {
  178. return err
  179. }
  180. index += i
  181. }
  182. return nil
  183. }
  184. func (qga *QemuGuestAgent) QgaCommand(cmd *monitor.Command, readTimeout int) ([]byte, error) {
  185. info, err := qga.GuestInfo()
  186. if err != nil {
  187. return nil, err
  188. }
  189. if len(info.SupportedCommands) == 0 {
  190. return nil, errors.Errorf("exec guest-info return empty")
  191. }
  192. var i = 0
  193. for ; i < len(info.SupportedCommands); i++ {
  194. if info.SupportedCommands[i].Name == cmd.Execute {
  195. break
  196. }
  197. }
  198. if i > len(info.SupportedCommands) {
  199. return nil, errors.Errorf("unsupported command %s", cmd.Execute)
  200. }
  201. if !info.SupportedCommands[i].Enabled {
  202. return nil, errors.Errorf("command %s not enabled", cmd.Execute)
  203. }
  204. res, err := qga.execCmd(cmd, info.SupportedCommands[i].SuccessResp, readTimeout)
  205. if err != nil {
  206. return nil, err
  207. }
  208. return *res, nil
  209. }
  210. func (qga *QemuGuestAgent) getQGACallback(expectResp bool, resChan chan string) QGACallback {
  211. if !expectResp {
  212. return nil
  213. }
  214. return func(res []byte) {
  215. resChan <- string(res)
  216. }
  217. }
  218. func (qga *QemuGuestAgent) execCmd(cmd *monitor.Command, expectResp bool, readTimeoutSecond int) (*json.RawMessage, error) {
  219. if qga.rwc == nil {
  220. err := qga.connect()
  221. if err != nil {
  222. return nil, errors.Wrap(err, "qga connect")
  223. }
  224. }
  225. var resChan = make(chan string)
  226. var cb = qga.getQGACallback(expectResp, resChan)
  227. rawCmd := jsonutils.Marshal(cmd).String()
  228. cbQueueLength := qga.Query(rawCmd, cb)
  229. if !expectResp {
  230. return nil, nil
  231. }
  232. var res string
  233. if readTimeoutSecond <= 0 {
  234. readTimeoutSecond = qga.readTimeout
  235. }
  236. select {
  237. case <-time.After(time.Duration(readTimeoutSecond) * time.Second):
  238. if cbQueueLength > 30 {
  239. if err := qga.Close(); err != nil {
  240. log.Errorf("failed close qga connection %s", err)
  241. }
  242. }
  243. return nil, errors.Errorf("qga read timeout")
  244. case res = <-resChan:
  245. break
  246. }
  247. var objmap map[string]*json.RawMessage
  248. log.Debugf("qga response %s", res)
  249. if err := json.Unmarshal([]byte(res), &objmap); err != nil {
  250. return nil, errors.Wrap(err, "unmarshal qga res")
  251. }
  252. if val, ok := objmap["return"]; ok {
  253. return val, nil
  254. } else if val, ok := objmap["error"]; ok {
  255. res := &monitor.Error{}
  256. if err := json.Unmarshal(*val, res); err != nil {
  257. return nil, errors.Wrapf(err, "unmarshal qemu error resp: %s", *val)
  258. }
  259. return nil, errors.Errorf("%s", res.Error())
  260. } else {
  261. return nil, nil
  262. }
  263. }
  264. func (qga *QemuGuestAgent) GuestPing(timeout int) error {
  265. cmd := &monitor.Command{
  266. Execute: "guest-ping",
  267. }
  268. _, err := qga.execCmd(cmd, true, timeout)
  269. return err
  270. }
  271. type GuestCommand struct {
  272. Enabled bool `json:"enabled"`
  273. Name string `json:"name"`
  274. // whether command returns a response on success (since 1.7)
  275. SuccessResp bool `json:"success-response"`
  276. }
  277. type GuestInfo struct {
  278. Version string `json:"version"`
  279. SupportedCommands []GuestCommand `json:"supported_commands"`
  280. }
  281. func (qga *QemuGuestAgent) GuestInfo() (*GuestInfo, error) {
  282. cmd := &monitor.Command{
  283. Execute: "guest-info",
  284. }
  285. rawRes, err := qga.execCmd(cmd, true, -1)
  286. if err != nil {
  287. return nil, err
  288. }
  289. if rawRes == nil {
  290. return nil, errors.Errorf("qga no response")
  291. }
  292. res := new(GuestInfo)
  293. err = json.Unmarshal(*rawRes, res)
  294. if err != nil {
  295. return nil, errors.Wrap(err, "unmarshal raw response")
  296. }
  297. return res, nil
  298. }
  299. func (qga *QemuGuestAgent) GuestInfoTask() ([]byte, error) {
  300. info, err := qga.GuestInfo()
  301. if err != nil {
  302. return nil, err
  303. }
  304. cmd := &monitor.Command{
  305. Execute: "guest-info",
  306. }
  307. var i = 0
  308. for ; i < len(info.SupportedCommands); i++ {
  309. if info.SupportedCommands[i].Name == cmd.Execute {
  310. break
  311. }
  312. }
  313. if i > len(info.SupportedCommands) {
  314. return nil, errors.Errorf("unsupported command %s", cmd.Execute)
  315. }
  316. if !info.SupportedCommands[i].Enabled {
  317. return nil, errors.Errorf("command %s not enabled", cmd.Execute)
  318. }
  319. res, err := qga.execCmd(cmd, true, -1)
  320. if err != nil {
  321. return nil, err
  322. }
  323. return *res, nil
  324. }
  325. func (qga *QemuGuestAgent) QgaGetNetwork() ([]byte, error) {
  326. cmd := &monitor.Command{
  327. Execute: "guest-network-get-interfaces",
  328. }
  329. res, err := qga.execCmd(cmd, true, -1)
  330. if err != nil {
  331. return nil, err
  332. }
  333. return *res, nil
  334. }
  335. type GuestDiskPciController struct {
  336. Domain int `json:"domain"`
  337. Bus int `json:"bus"`
  338. Slot int `json:"slot"`
  339. Func int `json:"func"`
  340. }
  341. type GuestFsDisk struct {
  342. PciController GuestDiskPciController `json:"pci-controller"`
  343. BusType string `json:"bus-type"`
  344. Bus int `json:"bus"`
  345. Target int `json:"target"`
  346. Unit int `json:"unit"`
  347. Serial string `json:"serial"`
  348. Dev string `json:"dev"`
  349. }
  350. type GuestFsInfo struct {
  351. Name string `json:"name"`
  352. Mountpoint string `json:"mountpoint"`
  353. Type string `json:"type"`
  354. UsedBytes int64 `json:"used-bytes"`
  355. TotalBytes int64 `json:"total-bytes"`
  356. Disk []GuestFsDisk `json:"disk"`
  357. }
  358. func (qga *QemuGuestAgent) QgaGuestGetFsInfo() ([]GuestFsInfo, error) {
  359. //run guest-get-fsinfo
  360. cmdFsInfo := &monitor.Command{
  361. Execute: "guest-get-fsinfo",
  362. }
  363. rawResFsInfo, err := qga.execCmd(cmdFsInfo, true, -1)
  364. resFsInfo := make([]GuestFsInfo, 0)
  365. err = json.Unmarshal(*rawResFsInfo, &resFsInfo)
  366. if err != nil {
  367. return nil, errors.Wrap(err, "unmarshal raw response")
  368. }
  369. return resFsInfo, nil
  370. }
  371. type GuestOsInfo struct {
  372. Id string `json:"id"`
  373. KernelRelease string `json:"kernel-release"`
  374. KernelVersion string `json:"kernel-version"`
  375. Machine string `json:"machine"`
  376. Name string `json:"name"`
  377. PrettyName string `json:"pretty-name"`
  378. Version string `json:"version"`
  379. VersionId string `json:"version-id"`
  380. }
  381. func (qga *QemuGuestAgent) QgaGuestGetOsInfo() (*GuestOsInfo, error) {
  382. //run guest-get-osinfo
  383. cmdOsInfo := &monitor.Command{
  384. Execute: "guest-get-osinfo",
  385. }
  386. rawResOsInfo, err := qga.execCmd(cmdOsInfo, true, -1)
  387. resOsInfo := new(GuestOsInfo)
  388. err = json.Unmarshal(*rawResOsInfo, resOsInfo)
  389. if err != nil {
  390. return nil, errors.Wrap(err, "unmarshal raw response")
  391. }
  392. return resOsInfo, nil
  393. }
  394. func (qga *QemuGuestAgent) QgaFileOpen(path, mode string) (int, error) {
  395. cmdFileOpen := &monitor.Command{
  396. Execute: "guest-file-open",
  397. Args: map[string]interface{}{
  398. "path": path,
  399. "mode": mode,
  400. },
  401. }
  402. rawResFileOpen, err := qga.execCmd(cmdFileOpen, true, -1)
  403. if err != nil {
  404. return 0, err
  405. }
  406. fileNum, err := strconv.ParseInt(string(*rawResFileOpen), 10, 64)
  407. if err != nil {
  408. return 0, err
  409. }
  410. return int(fileNum), nil
  411. }
  412. type GuestFileWrite struct {
  413. Count int `json:"count"`
  414. Eof bool `json:"eof"`
  415. }
  416. func (qga *QemuGuestAgent) QgaFileWrite(fileNum int, content string) (int, bool, error) {
  417. contentEncode := base64.StdEncoding.EncodeToString([]byte(content))
  418. //write shell to file
  419. cmdFileWrite := &monitor.Command{
  420. Execute: "guest-file-write",
  421. Args: map[string]interface{}{
  422. "handle": fileNum,
  423. "buf-b64": contentEncode,
  424. },
  425. }
  426. rawResFileWrite, err := qga.execCmd(cmdFileWrite, true, -1)
  427. if err != nil {
  428. return -1, false, err
  429. }
  430. resWrite := new(GuestFileWrite)
  431. err = json.Unmarshal(*rawResFileWrite, resWrite)
  432. if err != nil {
  433. return -1, false, errors.Wrap(err, "unmarshal raw response")
  434. }
  435. return resWrite.Count, resWrite.Eof, nil
  436. }
  437. type GuestFileRead struct {
  438. Count int `json:"count"`
  439. BufB64 string `json:"buf-b64"`
  440. Eof bool `json:"eof"`
  441. }
  442. func (qga *QemuGuestAgent) QgaFileRead(fileNum, readCount int) ([]byte, bool, error) {
  443. cmdFileRead := &monitor.Command{
  444. Execute: "guest-file-read",
  445. }
  446. args := map[string]interface{}{
  447. "handle": fileNum,
  448. }
  449. // readCount: maximum number of bytes to read (default is 4KB, maximum is 48MB)
  450. if readCount > 0 {
  451. args["count"] = readCount
  452. }
  453. cmdFileRead.Args = args
  454. rawResFileRead, err := qga.execCmd(cmdFileRead, true, -1)
  455. if err != nil {
  456. return nil, false, err
  457. }
  458. resReadInfo := new(GuestFileRead)
  459. err = json.Unmarshal(*rawResFileRead, resReadInfo)
  460. if err != nil {
  461. return nil, false, errors.Wrap(err, "unmarshal raw response")
  462. }
  463. content, err := base64.StdEncoding.DecodeString(resReadInfo.BufB64)
  464. if err != nil {
  465. return nil, false, errors.Wrap(err, "failed decode base64")
  466. }
  467. return content, resReadInfo.Eof, nil
  468. }
  469. func (qga *QemuGuestAgent) QgaFileClose(fileNum int) error {
  470. //close file
  471. cmdFileClose := &monitor.Command{
  472. Execute: "guest-file-close",
  473. Args: map[string]interface{}{
  474. "handle": fileNum,
  475. },
  476. }
  477. _, err := qga.execCmd(cmdFileClose, true, -1)
  478. if err != nil {
  479. return err
  480. }
  481. return nil
  482. }
  483. func ParseIPAndSubnet(input string) (string, string, error) {
  484. //Converting IP/MASK format to IP and MASK
  485. parts := strings.Split(input, "/")
  486. if len(parts) != 2 {
  487. return "", "", fmt.Errorf("Invalid input format")
  488. }
  489. ip := parts[0]
  490. subnetSizeStr := parts[1]
  491. subnetSize := 0
  492. for _, c := range subnetSizeStr {
  493. if c < '0' || c > '9' {
  494. return "", "", fmt.Errorf("Invalid subnet size")
  495. }
  496. subnetSize = subnetSize*10 + int(c-'0')
  497. }
  498. mask := net.CIDRMask(subnetSize, 32)
  499. subnetMask := net.IP(mask).To4().String()
  500. return ip, subnetMask, nil
  501. }
  502. func ParseIP6AndSubnet(input string) (string, string, error) {
  503. //Converting IP/MASK format to IP and MASK
  504. parts := strings.Split(input, "/")
  505. if len(parts) != 2 {
  506. return "", "", fmt.Errorf("Invalid input format")
  507. }
  508. ip := parts[0]
  509. subnetSizeStr := parts[1]
  510. subnetSize := 0
  511. for _, c := range subnetSizeStr {
  512. if c < '0' || c > '9' {
  513. return "", "", fmt.Errorf("Invalid subnet size")
  514. }
  515. subnetSize = subnetSize*10 + int(c-'0')
  516. }
  517. mask := net.CIDRMask(subnetSize, 32)
  518. subnetMask := net.IP(mask).To16().String()
  519. return ip, subnetMask, nil
  520. }
  521. func (qga *QemuGuestAgent) QgaAddFileExec(filePath string) error {
  522. //Adding execution permissions to file
  523. shellAddAuth := "chmod +x " + filePath
  524. arg := []string{"-c", shellAddAuth}
  525. cmdAddAuth := &monitor.Command{
  526. Execute: "guest-exec",
  527. Args: map[string]interface{}{
  528. "path": "/bin/bash",
  529. "arg": arg,
  530. "env": []string{},
  531. "input-data": "",
  532. "capture-output": true,
  533. },
  534. }
  535. _, err := qga.execCmd(cmdAddAuth, true, -1)
  536. if err != nil {
  537. return err
  538. }
  539. return nil
  540. }
  541. func (qga *QemuGuestAgent) QgaSetWindowsNetwork(qgaNetMod *monitor.NetworkModify) error {
  542. var ip, ip6, mask, mask6 string
  543. var err error
  544. var networkCmd string
  545. if len(qgaNetMod.Ipmask) > 0 {
  546. ip, mask, err = ParseIPAndSubnet(qgaNetMod.Ipmask)
  547. if err != nil {
  548. return err
  549. }
  550. networkCmd += fmt.Sprintf(
  551. "netsh interface ip set address name=\"%s\" source=static addr=%s mask=%s gateway=%s & "+
  552. "netsh interface ip set address name=\"%s\" dhcp",
  553. qgaNetMod.Device, ip, mask, qgaNetMod.Gateway, qgaNetMod.Device,
  554. )
  555. }
  556. if len(qgaNetMod.Ip6mask) > 0 {
  557. ip6, mask6, err = ParseIP6AndSubnet(qgaNetMod.Ip6mask)
  558. if err != nil {
  559. return err
  560. }
  561. if networkCmd != "" {
  562. networkCmd += " & "
  563. }
  564. networkCmd += fmt.Sprintf("netsh interface ipv6 set address interface=\"%s\" address=%s/%s & "+
  565. "netsh interface ipv6 add route ::/0 interface=\"%s\" %s & "+
  566. "netsh interface ipv6 set address interface=\"%s\" source=dhcp",
  567. qgaNetMod.Device, ip6, mask6, qgaNetMod.Device, qgaNetMod.Gateway6, qgaNetMod.Device,
  568. )
  569. }
  570. log.Infof("networkCmd: %s", networkCmd)
  571. arg := []string{"/C", networkCmd}
  572. cmdExecNet := &monitor.Command{
  573. Execute: "guest-exec",
  574. Args: map[string]interface{}{
  575. "path": "C:\\Windows\\System32\\cmd.exe",
  576. "arg": arg,
  577. "env": []string{},
  578. "input-data": "",
  579. "capture-output": true,
  580. },
  581. }
  582. _, err = qga.execCmd(cmdExecNet, true, -1)
  583. if err != nil {
  584. return err
  585. }
  586. return nil
  587. }
  588. var NETWORK_RESTRT_SCRIPT = `#!/bin/bash
  589. set -e
  590. DEV=$1
  591. if systemctl is-active --quiet NetworkManager.service; then
  592. nmcli connection down $DEV && nmcli connection up $DEV
  593. exit 0
  594. fi
  595. if command -v ifup &> /dev/null; then
  596. ifdown $DEV && ifup $DEV
  597. exit 0
  598. fi
  599. if command -v ifconfig &> /dev/null; then
  600. ifconfig $DEV down && ifconfig $DEV up
  601. exit 0
  602. fi
  603. if systemctl is-active --quiet network.service; then
  604. systemctl restart network.service
  605. exit 0
  606. fi
  607. if command -v netplan &>/dev/null; then
  608. netplan try --timeout 0
  609. exit 0
  610. fi
  611. if command -v ip &> /dev/null; then
  612. ip link set $DEV down && ip link set $DEV up
  613. exit 0
  614. fi
  615. echo "No valid method restart network device"
  616. exit 1
  617. `
  618. func (qga *QemuGuestAgent) QgaRestartLinuxNetwork(qgaNetMod *monitor.NetworkModify) error {
  619. scriptPath := "/tmp/qga_restart_network"
  620. if err := qga.FilePutContents(scriptPath, NETWORK_RESTRT_SCRIPT, false); err != nil {
  621. return errors.Wrap(err, "write qga_restart_network script")
  622. }
  623. retCode, stdout, stderr, err := qga.CommandWithTimeout("bash", []string{scriptPath, qgaNetMod.Device}, nil, "", true, 10)
  624. if err != nil {
  625. return errors.Wrap(err, "CommandWithTimeout")
  626. }
  627. if retCode != 0 {
  628. return errors.Errorf("QgaRestartLinuxNetwork failed: %s %s retcode %d", stdout, stderr, retCode)
  629. }
  630. return nil
  631. }
  632. func (qga *QemuGuestAgent) qgaDeployNetworkConfigure(guestNics []*types.SServerNic) error {
  633. qgaPart := NewQGAPartition(qga)
  634. fsDriver, err := guestfs.DetectRootFs(qgaPart)
  635. if err != nil {
  636. return errors.Wrap(err, "qga DetectRootFs")
  637. }
  638. log.Infof("QGA %s DetectRootFs %s", qga.id, fsDriver.String())
  639. return fsDriver.DeployNetworkingScripts(qgaPart, guestNics)
  640. }
  641. func (qga *QemuGuestAgent) QgaSetNetwork(qgaNetMod *monitor.NetworkModify, guestNics []*types.SServerNic) error {
  642. //Getting information about the operating system
  643. resOsInfo, err := qga.QgaGuestGetOsInfo()
  644. if err != nil {
  645. return errors.Wrap(err, "get os info")
  646. }
  647. //Judgement based on id, currently only windows and other systems are judged
  648. switch resOsInfo.Id {
  649. case "mswindows":
  650. return qga.QgaSetWindowsNetwork(qgaNetMod)
  651. default:
  652. // do deploy network configure
  653. if err := qga.qgaDeployNetworkConfigure(guestNics); err != nil {
  654. return errors.Wrap(err, "qgaDeployNetworkConfigure")
  655. }
  656. return qga.QgaRestartLinuxNetwork(qgaNetMod)
  657. }
  658. }
  659. func (qga *QemuGuestAgent) QgaRestartNetwork(qgaNetMod *monitor.NetworkModify) error {
  660. //Getting information about the operating system
  661. resOsInfo, err := qga.QgaGuestGetOsInfo()
  662. if err != nil {
  663. return errors.Wrap(err, "get os info")
  664. }
  665. //Judgement based on id, currently only windows and other systems are judged
  666. switch resOsInfo.Id {
  667. case "mswindows":
  668. return qga.QgaSetWindowsNetwork(qgaNetMod)
  669. default:
  670. return qga.QgaRestartLinuxNetwork(qgaNetMod)
  671. }
  672. }
  673. func (qga *QemuGuestAgent) QgaDeployNics(guestNics []*types.SServerNic) error {
  674. //Getting information about the operating system
  675. resOsInfo, err := qga.QgaGuestGetOsInfo()
  676. if err != nil {
  677. return errors.Wrap(err, "get os info")
  678. }
  679. if resOsInfo.Id == "mswindows" {
  680. return nil
  681. }
  682. if err := qga.qgaDeployNetworkConfigure(guestNics); err != nil {
  683. return errors.Wrap(err, "qgaDeployNetworkConfigure")
  684. }
  685. return nil
  686. }
  687. func (qga *QemuGuestAgent) QgaResizeDisk(diskId string) error {
  688. //Getting information about the operating system
  689. resOsInfo, err := qga.QgaGuestGetOsInfo()
  690. if err != nil {
  691. return errors.Wrap(err, "get os info")
  692. }
  693. //Judgement based on id, currently only windows and other systems are judged
  694. switch resOsInfo.Id {
  695. case "mswindows":
  696. return qga.QgaResizeWindowsDisk(diskId)
  697. default:
  698. return qga.QgaResizeLinuxDisk(diskId)
  699. }
  700. }
  701. func (qga *QemuGuestAgent) QgaResizeWindowsDisk(diskId string) error {
  702. fsInfos, err := qga.QgaGuestGetFsInfo()
  703. if err != nil {
  704. return errors.Wrap(err, "QgaGuestGetFsInfo")
  705. }
  706. diskSerial := strings.ReplaceAll(diskId, "-", "")
  707. for i := range fsInfos {
  708. fsInfo := fsInfos[i]
  709. for j := range fsInfo.Disk {
  710. if len(fsInfo.Disk[j].Serial) > 15 && strings.HasPrefix(diskSerial, fsInfo.Disk[j].Serial) {
  711. mountPoint := fsInfo.Mountpoint
  712. if strings.HasSuffix(mountPoint, ":\\") {
  713. driverLetter := mountPoint[0:1]
  714. log.Infof("disk %s found driver letter %s", mountPoint, driverLetter)
  715. retCode, stdout, stderr, err := qga.CommandWithTimeout("powershell.exe",
  716. []string{"-Command", "Resize-Partition", "-DriveLetter", driverLetter, "-Size", fmt.Sprintf("(Get-PartitionSupportedSize -DriveLetter %s).SizeMax", driverLetter)},
  717. nil, "", true, -1,
  718. )
  719. if err != nil {
  720. return errors.Wrap(err, "qga exec resize")
  721. }
  722. if retCode != 0 {
  723. return errors.Errorf("qga exec resize failed: %s %s, retcode %d", stdout, stderr, retCode)
  724. }
  725. return nil
  726. }
  727. }
  728. }
  729. }
  730. return nil
  731. }
  732. func (qga *QemuGuestAgent) QgaResizeLinuxDisk(diskId string) error {
  733. qgaDriver := NewQgaFsutilDriver(qga)
  734. fsutilDriver := fsutils.NewFsutilDriver(qgaDriver)
  735. err := qga.FilePutContents("/usr/bin/growpart", fsutils.GrowPartScript, false)
  736. if err != nil {
  737. return errors.Wrap(err, "file put content growpart")
  738. }
  739. retCode, stdout, stderr, err := qga.CommandWithTimeout("chmod", []string{"+x", "/usr/bin/growpart"}, nil, "", true, -1)
  740. if err != nil {
  741. return errors.Wrap(err, "chmod +x /usr/bin/growpart failed")
  742. }
  743. if retCode != 0 {
  744. return errors.Errorf("chmod +x /usr/bin/growpart failed: %s %s", stdout, stderr)
  745. }
  746. retCode, stdout, stderr, err = qga.CommandWithTimeout("sh", []string{"-c", "df / | awk 'NR==2{print $1}'"}, nil, "", true, -1)
  747. if err != nil {
  748. return errors.Wrap(err, "df / | awk 'NR==2{print $1}' failed")
  749. }
  750. if retCode != 0 {
  751. return errors.Errorf("df / | awk 'NR==2{print $1}' failed: %s %s", stdout, stderr)
  752. }
  753. rootPartDev := strings.TrimSpace(stdout)
  754. log.Infof("disk %s resize root part dev %s", diskId, rootPartDev)
  755. return fsutilDriver.ResizeDiskWithDiskId(diskId, rootPartDev, true)
  756. }
  757. /*
  758. # @username: the user account whose password to change
  759. # @password: the new password entry string, base64 encoded
  760. # @crypted: true if password is already crypt()d, false if raw
  761. #
  762. # If the @crypted flag is true, it is the caller's responsibility
  763. # to ensure the correct crypt() encryption scheme is used. This
  764. # command does not attempt to interpret or report on the encryption
  765. # scheme. Refer to the documentation of the guest operating system
  766. # in question to determine what is supported.
  767. #
  768. # Not all guest operating systems will support use of the
  769. # @crypted flag, as they may require the clear-text password
  770. #
  771. # The @password parameter must always be base64 encoded before
  772. # transmission, even if already crypt()d, to ensure it is 8-bit
  773. # safe when passed as JSON.
  774. #
  775. # Returns: Nothing on success.
  776. #
  777. # Since: 2.3
  778. */
  779. func (qga *QemuGuestAgent) GuestSetUserPassword(username, password string, crypted bool) error {
  780. password64 := base64.StdEncoding.EncodeToString([]byte(password))
  781. cmd := &monitor.Command{
  782. Execute: "guest-set-user-password",
  783. Args: map[string]interface{}{
  784. "username": username,
  785. "password": password64,
  786. "crypted": crypted,
  787. },
  788. }
  789. _, err := qga.execCmd(cmd, true, -1)
  790. if err != nil {
  791. return err
  792. }
  793. return nil
  794. }
  795. /*
  796. ##
  797. # @GuestExec:
  798. # @pid: pid of child process in guest OS
  799. #
  800. # Since: 2.5
  801. ##
  802. { 'struct': 'GuestExec',
  803. 'data': { 'pid': 'int'} }
  804. */
  805. type GuestExec struct {
  806. Pid int
  807. }
  808. /*
  809. ##
  810. # @guest-exec:
  811. #
  812. # Execute a command in the guest
  813. #
  814. # @path: path or executable name to execute
  815. # @arg: argument list to pass to executable
  816. # @env: environment variables to pass to executable
  817. # @input-data: data to be passed to process stdin (base64 encoded)
  818. # @capture-output: bool flag to enable capture of
  819. # stdout/stderr of running process. defaults to false.
  820. #
  821. # Returns: PID on success.
  822. #
  823. # Since: 2.5
  824. ##
  825. { 'command': 'guest-exec',
  826. 'data': { 'path': 'str', '*arg': ['str'], '*env': ['str'],
  827. '*input-data': 'str', '*capture-output': 'bool' },
  828. 'returns': 'GuestExec' }
  829. */
  830. func (qga *QemuGuestAgent) GuestExecCommand(
  831. cmdPath string, args, env []string, inputData string, captureOutput bool,
  832. ) (*GuestExec, error) {
  833. qgaCmd := &monitor.Command{
  834. Execute: "guest-exec",
  835. Args: map[string]interface{}{
  836. "path": cmdPath,
  837. "arg": args,
  838. "env": env,
  839. "input-data": inputData,
  840. "capture-output": captureOutput,
  841. },
  842. }
  843. rawRes, err := qga.execCmd(qgaCmd, true, -1)
  844. if err != nil {
  845. return nil, err
  846. }
  847. if rawRes == nil {
  848. return nil, errors.Errorf("qga no response")
  849. }
  850. res := new(GuestExec)
  851. err = json.Unmarshal(*rawRes, res)
  852. if err != nil {
  853. return nil, errors.Wrap(err, "unmarshal raw response")
  854. }
  855. return res, nil
  856. }
  857. /*
  858. ##
  859. # @GuestExecStatus:
  860. #
  861. # @exited: true if process has already terminated.
  862. # @exitcode: process exit code if it was normally terminated.
  863. # @signal: signal number (linux) or unhandled exception code
  864. # (windows) if the process was abnormally terminated.
  865. # @out-data: base64-encoded stdout of the process
  866. # @err-data: base64-encoded stderr of the process
  867. # Note: @out-data and @err-data are present only
  868. # if 'capture-output' was specified for 'guest-exec'
  869. # @out-truncated: true if stdout was not fully captured
  870. # due to size limitation.
  871. # @err-truncated: true if stderr was not fully captured
  872. # due to size limitation.
  873. #
  874. # Since: 2.5
  875. ##
  876. { 'struct': 'GuestExecStatus',
  877. 'data': { 'exited': 'bool', '*exitcode': 'int', '*signal': 'int',
  878. '*out-data': 'str', '*err-data': 'str',
  879. '*out-truncated': 'bool', '*err-truncated': 'bool' }}
  880. */
  881. type GuestExecStatus struct {
  882. Exited bool
  883. Exitcode int
  884. Signal int
  885. OutData string `json:"out-data"`
  886. ErrData string `json:"err-data"`
  887. OutTruncated bool `json:"out-truncated"`
  888. ErrTruncated bool `json:"err-truncated"`
  889. }
  890. /*
  891. ##
  892. # @guest-exec-status:
  893. #
  894. # Check status of process associated with PID retrieved via guest-exec.
  895. # Reap the process and associated metadata if it has exited.
  896. #
  897. # @pid: pid returned from guest-exec
  898. #
  899. # Returns: GuestExecStatus on success.
  900. #
  901. # Since: 2.5
  902. ##
  903. { 'command': 'guest-exec-status',
  904. 'data': { 'pid': 'int' },
  905. 'returns': 'GuestExecStatus' }
  906. */
  907. func (qga *QemuGuestAgent) GuestExecStatusCommand(pid int) (*GuestExecStatus, error) {
  908. cmd := &monitor.Command{
  909. Execute: "guest-exec-status",
  910. Args: map[string]interface{}{
  911. "pid": pid,
  912. },
  913. }
  914. rawRes, err := qga.execCmd(cmd, true, -1)
  915. if err != nil {
  916. return nil, err
  917. }
  918. if rawRes == nil {
  919. return nil, errors.Errorf("qga no response")
  920. }
  921. res := new(GuestExecStatus)
  922. err = json.Unmarshal(*rawRes, res)
  923. if err != nil {
  924. return nil, errors.Wrap(err, "unmarshal raw response")
  925. }
  926. return res, nil
  927. }