idrac.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385
  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 idrac
  15. import (
  16. "context"
  17. "fmt"
  18. "strings"
  19. "time"
  20. "yunion.io/x/jsonutils"
  21. "yunion.io/x/log"
  22. "yunion.io/x/pkg/errors"
  23. "yunion.io/x/pkg/util/httputils"
  24. "yunion.io/x/onecloud/pkg/httperrors"
  25. "yunion.io/x/onecloud/pkg/util/redfish"
  26. "yunion.io/x/onecloud/pkg/util/redfish/bmconsole"
  27. "yunion.io/x/onecloud/pkg/util/redfish/generic"
  28. )
  29. type SIDracRedfishApiFactory struct {
  30. }
  31. func (f *SIDracRedfishApiFactory) Name() string {
  32. return "iDRAC"
  33. }
  34. func (f *SIDracRedfishApiFactory) NewApi(endpoint, username, password string, debug bool) redfish.IRedfishDriver {
  35. return NewIDracRedfishApi(endpoint, username, password, debug)
  36. }
  37. func init() {
  38. redfish.RegisterApiFactory(&SIDracRedfishApiFactory{})
  39. }
  40. type SIDracRefishApi struct {
  41. generic.SGenericRefishApi
  42. }
  43. func NewIDracRedfishApi(endpoint, username, password string, debug bool) redfish.IRedfishDriver {
  44. api := &SIDracRefishApi{
  45. SGenericRefishApi: generic.SGenericRefishApi{
  46. SBaseRedfishClient: redfish.NewBaseRedfishClient(endpoint, username, password, debug),
  47. },
  48. }
  49. api.SetVirtualObject(api)
  50. return api
  51. }
  52. func (r *SIDracRefishApi) ParseRoot(root jsonutils.JSONObject) error {
  53. accountUrl, _ := root.GetString("AccountService", "@odata.id")
  54. if strings.Contains(accountUrl, "iDRAC.Embedded") {
  55. return nil
  56. }
  57. return errors.Error("not iDrac")
  58. }
  59. func (r *SIDracRefishApi) GetVirtualCdromInfo(ctx context.Context) (string, redfish.SCdromInfo, error) {
  60. cdInfo := redfish.SCdromInfo{}
  61. path, jsonResp, err := r.GetVirtualCdromJSON(ctx)
  62. if err != nil {
  63. return "", cdInfo, errors.Wrap(err, "r.GetVirtualCdromJSON")
  64. }
  65. imgPath, _ := jsonResp.GetString("Image")
  66. if imgPath == "null" {
  67. imgPath = ""
  68. }
  69. cdInfo.Image = imgPath
  70. if jsonResp.Contains("Actions") {
  71. cdInfo.SupportAction = true
  72. }
  73. return path, cdInfo, nil
  74. }
  75. func (r *SIDracRefishApi) MountVirtualCdrom(ctx context.Context, path string, cdromUrl string, boot bool) error {
  76. info := jsonutils.NewDict()
  77. info.Set("Image", jsonutils.NewString(cdromUrl))
  78. path = httputils.JoinPath(path, "Actions/VirtualMedia.InsertMedia")
  79. _, _, err := r.Post(ctx, path, info)
  80. if err != nil {
  81. return errors.Wrap(err, "r.Post")
  82. }
  83. // log.Debugf("%s", resp.PrettyString())
  84. if boot {
  85. err = r.SetNextBootVirtualCdrom(ctx)
  86. if err != nil {
  87. return errors.Wrap(err, "r.SetNextBootVirtualCdrom")
  88. }
  89. }
  90. return nil
  91. }
  92. func (r *SIDracRefishApi) UmountVirtualCdrom(ctx context.Context, path string) error {
  93. info := jsonutils.NewDict()
  94. path = httputils.JoinPath(path, "Actions/VirtualMedia.EjectMedia")
  95. _, _, err := r.Post(ctx, path, info)
  96. if err != nil {
  97. return errors.Wrap(err, "r.Post")
  98. }
  99. // log.Debugf("%s", resp.PrettyString())
  100. return nil
  101. }
  102. func (r *SIDracRefishApi) SetNextBootVirtualCdrom(ctx context.Context) error {
  103. idracConf := iDRACConfig{}
  104. idracConf.SystemConfiguration.Components = []iDracComponenct{
  105. {
  106. FQDD: "iDRAC.Embedded.1",
  107. Attributes: []iDracAttribute{
  108. {
  109. Name: "ServerBoot.1#BootOnce",
  110. Value: "Enabled",
  111. },
  112. {
  113. Name: "ServerBoot.1#FirstBootDevice",
  114. Value: "VCD-DVD",
  115. },
  116. },
  117. },
  118. }
  119. return r.doImportConfig(ctx, idracConf)
  120. }
  121. type iDracComponenct struct {
  122. FQDD string `json:"FQDD"`
  123. Attributes []iDracAttribute `json:"Attributes"`
  124. }
  125. type iDracAttribute struct {
  126. Name string `json:"Name"`
  127. Value string `json:"Value"`
  128. }
  129. type iDRACConfig struct {
  130. SystemConfiguration struct {
  131. Model string `json:"Model"`
  132. ServiceTag string `json:"ServiceTag"`
  133. // TimeStamp time.Time `json:"TimeStamp"`
  134. Components []iDracComponenct `json:"Components"`
  135. } `json:"SystemConfiguration"`
  136. }
  137. func (r iDRACConfig) getConfig(fqdd string, name string) (string, error) {
  138. for _, comp := range r.SystemConfiguration.Components {
  139. if comp.FQDD == fqdd {
  140. for _, attr := range comp.Attributes {
  141. if attr.Name == name {
  142. return attr.Value, nil
  143. }
  144. }
  145. }
  146. }
  147. return "", httperrors.ErrNotFound
  148. }
  149. func (r iDRACConfig) toXml() string {
  150. buf := strings.Builder{}
  151. buf.WriteString("<SystemConfiguration>")
  152. for _, comp := range r.SystemConfiguration.Components {
  153. buf.WriteString(fmt.Sprintf(`<Component FQDD="%s">`, comp.FQDD))
  154. for _, attr := range comp.Attributes {
  155. buf.WriteString(fmt.Sprintf(`<Attribute Name="%s">`, attr.Name))
  156. buf.WriteString(attr.Value)
  157. buf.WriteString("</Attribute>")
  158. }
  159. buf.WriteString("</Component>")
  160. }
  161. buf.WriteString("</SystemConfiguration>")
  162. return buf.String()
  163. }
  164. func (r *SIDracRefishApi) GetNTPConf(ctx context.Context) (redfish.SNTPConf, error) {
  165. ntpConf := redfish.SNTPConf{}
  166. eConf, err := r.fetchExportConfig(ctx, "IDRAC")
  167. if err != nil {
  168. return ntpConf, errors.Wrap(err, "fetchExportConfig")
  169. }
  170. ntpConf.NTPServers = make([]string, 0)
  171. ntp1, _ := eConf.getConfig("iDRAC.Embedded.1", "NTPConfigGroup.1#NTP1")
  172. if len(ntp1) > 0 {
  173. ntpConf.NTPServers = append(ntpConf.NTPServers, ntp1)
  174. }
  175. ntp2, _ := eConf.getConfig("iDRAC.Embedded.1", "NTPConfigGroup.1#NTP2")
  176. if len(ntp2) > 0 {
  177. ntpConf.NTPServers = append(ntpConf.NTPServers, ntp2)
  178. }
  179. ntp3, _ := eConf.getConfig("iDRAC.Embedded.1", "NTPConfigGroup.1#NTP3")
  180. if len(ntp3) > 0 {
  181. ntpConf.NTPServers = append(ntpConf.NTPServers, ntp3)
  182. }
  183. ntpEnable, _ := eConf.getConfig("iDRAC.Embedded.1", "NTPConfigGroup.1#NTPEnable")
  184. if ntpEnable == "Enabled" {
  185. ntpConf.ProtocolEnabled = true
  186. }
  187. tz, _ := eConf.getConfig("iDRAC.Embedded.1", "Time.1#TimeZone")
  188. if len(tz) > 0 {
  189. ntpConf.TimeZone = tz
  190. }
  191. return ntpConf, nil
  192. }
  193. func ntpConf2idrac(conf redfish.SNTPConf) iDRACConfig {
  194. idracConf := iDRACConfig{}
  195. idracConf.SystemConfiguration.Components = []iDracComponenct{
  196. {
  197. FQDD: "iDRAC.Embedded.1",
  198. Attributes: []iDracAttribute{
  199. {
  200. Name: "Time.1#TimeZone",
  201. Value: conf.TimeZone,
  202. },
  203. {
  204. Name: "NTPConfigGroup.1#NTPEnable",
  205. Value: "Enabled",
  206. },
  207. },
  208. },
  209. }
  210. for i, srv := range conf.NTPServers {
  211. idracConf.SystemConfiguration.Components[0].Attributes = append(idracConf.SystemConfiguration.Components[0].Attributes, iDracAttribute{
  212. Name: fmt.Sprintf("NTPConfigGroup.1#NTP%d", i+1),
  213. Value: srv,
  214. })
  215. }
  216. return idracConf
  217. }
  218. func (r *SIDracRefishApi) SetNTPConf(ctx context.Context, conf redfish.SNTPConf) error {
  219. iDracConf := ntpConf2idrac(conf)
  220. return r.doImportConfig(ctx, iDracConf)
  221. }
  222. /*
  223. * target: "ALL", "IDRAC", "BIOS", "NIC", "RAID"
  224. */
  225. func (r *SIDracRefishApi) fetchExportConfig(ctx context.Context, target string) (*iDRACConfig, error) {
  226. _, manager, err := r.GetResource(ctx, "Managers", "0")
  227. if err != nil {
  228. return nil, errors.Wrap(err, "GetResource")
  229. }
  230. oemJson, err := manager.GetMap("Actions", "Oem")
  231. if err != nil {
  232. return nil, errors.Wrap(err, "GetMap Actions Oem")
  233. }
  234. var urlPath string
  235. for k, conf := range oemJson {
  236. if strings.HasSuffix(k, "OemManager.ExportSystemConfiguration") {
  237. urlPath, err = conf.GetString("target")
  238. if err != nil {
  239. return nil, errors.Wrap(err, "find OemManager.ExportSystemConfiguration target")
  240. }
  241. break
  242. }
  243. }
  244. if len(urlPath) == 0 {
  245. return nil, errors.Wrap(httperrors.ErrNotFound, "Key OemManager.ExportSystemConfiguration not found")
  246. }
  247. params := jsonutils.NewDict()
  248. params.Add(jsonutils.NewString("JSON"), "ExportFormat")
  249. params.Add(jsonutils.NewString("Default"), "IncludeInExport")
  250. params.Add(jsonutils.NewString(target), "ShareParameters", "Target")
  251. hdr, _, err := r.Post(ctx, urlPath, params)
  252. if err != nil {
  253. return nil, errors.Wrapf(err, "r.Post %s", urlPath)
  254. }
  255. jobUrl := hdr.Get("Location")
  256. if r.IsDebug {
  257. log.Debugf("%s", jobUrl)
  258. }
  259. taskStatus := "Running"
  260. maxWait := 3600 // 1 hour
  261. interval := 5
  262. for waited := 0; taskStatus == "Running" && waited < maxWait; waited += interval {
  263. time.Sleep(time.Duration(interval) * time.Second)
  264. resp, err := r.Get(ctx, jobUrl)
  265. if err != nil {
  266. return nil, errors.Wrapf(err, "r.Get %s", jobUrl)
  267. }
  268. log.Debugf("probe: %d", waited)
  269. // log.Debugf("%s", resp.PrettyString())
  270. taskStatus, _ = resp.GetString("TaskState")
  271. if taskStatus == "" {
  272. conf := iDRACConfig{}
  273. err = resp.Unmarshal(&conf)
  274. if err != nil {
  275. return nil, errors.Wrap(err, "Unmarshal iDRACConfig")
  276. }
  277. // log.Debugf("%s", jsonutils.Marshal(&conf).PrettyString())
  278. return &conf, nil
  279. }
  280. }
  281. return nil, httperrors.ErrTimeout
  282. }
  283. func (r *SIDracRefishApi) doImportConfig(ctx context.Context, conf iDRACConfig) error {
  284. _, manager, err := r.GetResource(ctx, "Managers", "0")
  285. if err != nil {
  286. return errors.Wrap(err, "GetResource")
  287. }
  288. oemJson, err := manager.GetMap("Actions", "Oem")
  289. if err != nil {
  290. return errors.Wrap(err, "GetMap Actions Oem")
  291. }
  292. var urlPath string
  293. for k, conf := range oemJson {
  294. if strings.HasSuffix(k, "OemManager.ImportSystemConfiguration") {
  295. urlPath, err = conf.GetString("target")
  296. if err != nil {
  297. return errors.Wrap(err, "OemManager.ImportSystemConfiguration target")
  298. }
  299. break
  300. }
  301. }
  302. if len(urlPath) == 0 {
  303. return errors.Wrap(httperrors.ErrNotFound, "Key OemManager.ImportSystemConfiguration not found")
  304. }
  305. payload := jsonutils.NewDict()
  306. payload.Add(jsonutils.NewString(conf.toXml()), "ImportBuffer")
  307. payload.Add(jsonutils.NewString("ALL"), "ShareParameters", "Target")
  308. hdr, _, err := r.Post(ctx, urlPath, payload)
  309. if err != nil {
  310. return errors.Wrapf(err, "r.Post %s", urlPath)
  311. }
  312. jobUrl := hdr.Get("Location")
  313. if r.IsDebug {
  314. log.Debugf("%s", jobUrl)
  315. }
  316. taskStatus := "Running"
  317. maxWait := 3600 // 1 hour
  318. interval := 5
  319. for waited := 0; taskStatus == "Running" && waited < maxWait; waited += interval {
  320. time.Sleep(time.Duration(interval) * time.Second)
  321. resp, err := r.Get(ctx, jobUrl)
  322. if err != nil {
  323. return errors.Wrapf(err, "r.Get %s", jobUrl)
  324. }
  325. // log.Debugf("%s", resp.PrettyString())
  326. taskStatus, _ = resp.GetString("TaskState")
  327. }
  328. return nil
  329. }
  330. func (r *SIDracRefishApi) GetConsoleJNLP(ctx context.Context) (string, error) {
  331. bmc := bmconsole.NewBMCConsole(r.GetHost(), r.GetUsername(), r.GetPassword(), r.IsDebug)
  332. return bmc.GetIdracConsoleJNLP(ctx, "", "")
  333. }
  334. func (r *SIDracRefishApi) GetSystemLogsPath() string {
  335. return "/redfish/v1/Managers/iDRAC.Embedded.1/Logs/Sel"
  336. }
  337. func (r *SIDracRefishApi) GetManagerLogsPath() string {
  338. return "/redfish/v1/Managers/iDRAC.Embedded.1/Logs/Lclog"
  339. }
  340. func (r *SIDracRefishApi) GetClearSystemLogsPath() string {
  341. return "/redfish/v1/Managers/iDRAC.Embedded.1/LogServices/Sel/Actions/LogService.ClearLog"
  342. }
  343. func (r *SIDracRefishApi) GetClearManagerLogsPath() string {
  344. return "/redfish/v1/Managers/iDRAC.Embedded.1/LogServices/Lclog/Actions/LogService.ClearLog"
  345. }
  346. func (r *SIDracRefishApi) GetPowerPath() string {
  347. return "/redfish/v1/Chassis/System.Embedded.1/Power"
  348. }
  349. func (r *SIDracRefishApi) GetThermalPath() string {
  350. return "/redfish/v1/Chassis/System.Embedded.1/Thermal"
  351. }