Sushy is a small Python library to communicate with Redfish based systems
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

186 lines
6.0KB

  1. #!/usr/bin/env python
  2. #
  3. # Copyright 2017 Red Hat, Inc.
  4. # All Rights Reserved.
  5. #
  6. # Licensed under the Apache License, Version 2.0 (the "License"); you may
  7. # not use this file except in compliance with the License. You may obtain
  8. # a copy of the License at
  9. #
  10. # http://www.apache.org/licenses/LICENSE-2.0
  11. #
  12. # Unless required by applicable law or agreed to in writing, software
  13. # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  14. # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  15. # License for the specific language governing permissions and limitations
  16. # under the License.
  17. import argparse
  18. import ssl
  19. import xml.etree.ElementTree as ET
  20. import flask
  21. import libvirt
  22. app = flask.Flask(__name__)
  23. # Turn off strict_slashes on all routes
  24. app.url_map.strict_slashes = False
  25. LIBVIRT_CONN = None
  26. BOOT_DEVICE_MAP = {
  27. 'Pxe': 'network',
  28. 'Hdd': 'hd',
  29. 'Cd': 'cdrom',
  30. }
  31. BOOT_DEVICE_MAP_REV = {v: k for k, v in BOOT_DEVICE_MAP.items()}
  32. def _get_libvirt_domain(domain):
  33. try:
  34. return LIBVIRT_CONN.lookupByName(domain)
  35. except libvirt.libvirtError:
  36. flask.abort(404)
  37. @app.route('/redfish/v1/')
  38. def root_resource():
  39. return flask.render_template('root.json')
  40. @app.route('/redfish/v1/Systems')
  41. def system_collection_resource():
  42. domains = LIBVIRT_CONN.listDefinedDomains()
  43. return flask.render_template(
  44. 'system_collection.json', system_count=len(domains), systems=domains)
  45. def _get_total_cpus(domain, tree):
  46. total_cpus = 0
  47. if domain.isActive():
  48. total_cpus = domain.maxVcpus()
  49. else:
  50. # If we can't get it from maxVcpus() try to find it by
  51. # inspecting the domain XML
  52. if total_cpus <= 0:
  53. vcpu_element = tree.find('.//vcpu')
  54. if vcpu_element is not None:
  55. total_cpus = int(vcpu_element.text)
  56. return total_cpus
  57. def _get_boot_source_target(tree):
  58. boot_source_target = None
  59. boot_element = tree.find('.//boot')
  60. if boot_element is not None:
  61. boot_source_target = (
  62. BOOT_DEVICE_MAP_REV.get(boot_element.get('dev')))
  63. return boot_source_target
  64. @app.route('/redfish/v1/Systems/<identity>', methods=['GET', 'PATCH'])
  65. def system_resource(identity):
  66. domain = _get_libvirt_domain(identity)
  67. if flask.request.method == 'GET':
  68. power_state = 'On' if domain.isActive() else 'Off'
  69. total_memory_gb = int(domain.maxMemory() / 1024 / 1024)
  70. tree = ET.fromstring(domain.XMLDesc())
  71. total_cpus = _get_total_cpus(domain, tree)
  72. boot_source_target = _get_boot_source_target(tree)
  73. return flask.render_template(
  74. 'system.json', identity=identity, uuid=domain.UUIDString(),
  75. power_state=power_state, total_memory_gb=total_memory_gb,
  76. total_cpus=total_cpus, boot_source_target=boot_source_target)
  77. elif flask.request.method == 'PATCH':
  78. boot = flask.request.json.get('Boot')
  79. if not boot:
  80. return 'PATCH only works for the Boot element', 400
  81. target = BOOT_DEVICE_MAP.get(boot.get('BootSourceOverrideTarget'))
  82. if not target:
  83. return 'Missing the BootSourceOverrideTarget element', 400
  84. # NOTE(lucasagomes): In libvirt we always set the boot
  85. # device frequency to "continuous" so, we are ignoring the
  86. # BootSourceOverrideEnabled element here
  87. # TODO(lucasagomes): We should allow changing the boot mode from
  88. # BIOS to UEFI (and vice-versa)
  89. tree = ET.fromstring(domain.XMLDesc())
  90. for os_element in tree.findall('os'):
  91. # Remove all "boot" elements
  92. for boot_element in os_element.findall('boot'):
  93. os_element.remove(boot_element)
  94. # Add a new boot element with the request boot device
  95. boot_element = ET.SubElement(os_element, 'boot')
  96. boot_element.set('dev', target)
  97. LIBVIRT_CONN.defineXML(ET.tostring(tree).decode('utf-8'))
  98. return '', 204
  99. @app.route('/redfish/v1/Systems/<identity>/Actions/ComputerSystem.Reset',
  100. methods=['POST'])
  101. def system_reset_action(identity):
  102. domain = _get_libvirt_domain(identity)
  103. reset_type = flask.request.json.get('ResetType')
  104. try:
  105. if reset_type in ('On', 'ForceOn'):
  106. if not domain.isActive():
  107. domain.create()
  108. elif reset_type == 'ForceOff':
  109. if domain.isActive():
  110. domain.destroy()
  111. elif reset_type == 'GracefulShutdown':
  112. if domain.isActive():
  113. domain.shutdown()
  114. elif reset_type == 'GracefulRestart':
  115. if domain.isActive():
  116. domain.reboot()
  117. elif reset_type == 'ForceRestart':
  118. if domain.isActive():
  119. domain.reset()
  120. elif reset_type == 'Nmi':
  121. if domain.isActive():
  122. domain.injectNMI()
  123. except libvirt.libvirtError:
  124. flask.abort(500)
  125. return '', 204
  126. def parse_args():
  127. parser = argparse.ArgumentParser('MockupServerLibvirt')
  128. parser.add_argument('-p', '--port',
  129. type=int,
  130. default=8000,
  131. help='The port to bind the server to')
  132. parser.add_argument('-u', '--libvirt-uri',
  133. type=str,
  134. default='qemu:///system',
  135. help='The libvirt URI')
  136. parser.add_argument('-c', '--ssl-certificate',
  137. type=str,
  138. help='SSL certificate to use for HTTPS')
  139. parser.add_argument('-k', '--ssl-key',
  140. type=str,
  141. help='SSL key to use for HTTPS')
  142. return parser.parse_args()
  143. if __name__ == '__main__':
  144. args = parse_args()
  145. LIBVIRT_CONN = libvirt.open(args.libvirt_uri)
  146. ssl_context = None
  147. if args.ssl_certificate and args.ssl_key:
  148. ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
  149. ssl_context.load_cert_chain(args.ssl_certificate, args.ssl_key)
  150. app.run(host='', port=args.port, ssl_context=ssl_context)