|
| 1 | +# Copyright 2015 Rackspace, Inc. |
| 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 | + |
| 15 | +from oslo_log import log |
| 16 | + |
| 17 | +from ironic_python_agent import hardware |
| 18 | + |
| 19 | +LOG = log.getLogger() |
| 20 | + |
| 21 | + |
| 22 | +# All the helper methods should be kept outside of the HardwareManager |
| 23 | +# so they'll never get accidentally called by dispatch_to_managers() |
| 24 | +def _initialize_hardware(): |
| 25 | + """Example method for initalizing hardware.""" |
| 26 | + # Perform any operations here that are required to initialize your |
| 27 | + # hardware. |
| 28 | + LOG.debug('Loading drivers, settling udevs, and generally initalizing') |
| 29 | + pass |
| 30 | + |
| 31 | + |
| 32 | +def _detect_hardware(): |
| 33 | + """Example method for hardware detection.""" |
| 34 | + # For this example, return true if hardware is detected, false if not |
| 35 | + LOG.debug('Looking for example device') |
| 36 | + return True |
| 37 | + |
| 38 | + |
| 39 | +def _is_latest_firmware(): |
| 40 | + """Detect if device is running latest firmware.""" |
| 41 | + # Actually detect the firmware version instead of returning here. |
| 42 | + return True |
| 43 | + |
| 44 | + |
| 45 | +def _upgrade_firmware(): |
| 46 | + """Upgrade firmware on device.""" |
| 47 | + # Actually perform firmware upgrade instead of returning here. |
| 48 | + return True |
| 49 | + |
| 50 | + |
| 51 | +class ExampleDeviceHardwareManager(hardware.HardwareManager): |
| 52 | + """Example hardware manager to support a single device""" |
| 53 | + |
| 54 | + # All hardware managers have a name and a version. |
| 55 | + # Version should be bumped anytime a change is introduced. This will |
| 56 | + # signal to Ironic that if automatic node cleaning is in progress to |
| 57 | + # restart it from the beginning, to ensure consistency. The value can |
| 58 | + # be anything; it's checked for equality against previously seen |
| 59 | + # name:manager pairs. |
| 60 | + HARDWARE_MANAGER_NAME = 'ExampleDeviceHardwareManager' |
| 61 | + HARDWARE_MANAGER_VERSION = '1' |
| 62 | + |
| 63 | + def evaluate_hardware_support(self): |
| 64 | + """Declare level of hardware support provided. |
| 65 | +
|
| 66 | + Since this example covers a case of supporting a specific device, |
| 67 | + this method is where you would do anything needed to initalize that |
| 68 | + device, including loading drivers, and then detect if one exists. |
| 69 | +
|
| 70 | + In some cases, if you expect the hardware to be available on any node |
| 71 | + running this hardware manager, or it's undetectable, you may want to |
| 72 | + return a static value here. |
| 73 | +
|
| 74 | + Be aware all managers' loaded in IPA will run this method before IPA |
| 75 | + performs a lookup or begins heartbeating, so the time needed to |
| 76 | + execute this method will make cleaning and deploying slower. |
| 77 | +
|
| 78 | + :returns: HardwareSupport level for this manager. |
| 79 | + """ |
| 80 | + _initialize_hardware() |
| 81 | + if _detect_hardware(): |
| 82 | + # This actually resolves down to an int. Upstream IPA will never |
| 83 | + # return a value higher than 2 (HardwareSupport.MAINLINE). This |
| 84 | + # means your managers should always be SERVICE_PROVIDER or higher. |
| 85 | + LOG.debug('Found example device, returning SERVICE_PROVIDER') |
| 86 | + return hardware.HardwareSupport.SERVICE_PROVIDER |
| 87 | + else: |
| 88 | + # If the hardware isn't supported, return HardwareSupport.NONE (0) |
| 89 | + # in order to prevent IPA from loading its clean steps or |
| 90 | + # attempting to use any methods inside it. |
| 91 | + LOG.debug('No example devices found, returning NONE') |
| 92 | + return hardware.HardwareSupport.NONE |
| 93 | + |
| 94 | + def get_clean_steps(self, node, ports): |
| 95 | + """Get a list of clean steps with priority. |
| 96 | +
|
| 97 | + Define any clean steps added by this manager here. These will be mixed |
| 98 | + with other loaded managers that support this hardware, and ordered by |
| 99 | + priority. Higher priority steps run earlier. |
| 100 | +
|
| 101 | + Note that out-of-band clean steps may also be provided by Ironic. |
| 102 | + These will follow the same priority ordering even though they are not |
| 103 | + executed by IPA. |
| 104 | +
|
| 105 | + There is *no guarantee whatsoever* that steps defined here will be |
| 106 | + executed by this HardwareManager. When it comes time to run these |
| 107 | + steps, they'll be called using dispatch_to_managers() just like any |
| 108 | + other IPA HardwareManager method. This means if they are unique to |
| 109 | + your hardware, they should be uniquely named. For example, |
| 110 | + upgrade_firmware would be a bad step name. Whereas |
| 111 | + upgrade_foobar_device_firmware would be better. |
| 112 | +
|
| 113 | + :param node: The node object as provided by Ironic. |
| 114 | + :param ports: Port objects as provided by Ironic. |
| 115 | + :returns: A list of cleaning steps, as a list of dicts. |
| 116 | + """ |
| 117 | + # While obviously you could actively run code here, generally this |
| 118 | + # should just return a static value, as any initialization and |
| 119 | + # detection should've been done in evaluate_hardware_support(). |
| 120 | + return [{ |
| 121 | + 'step': 'upgrade_example_device_model1234_firmware', |
| 122 | + 'priority': 37, |
| 123 | + # If you need Ironic to coordinate a reboot after this step |
| 124 | + # runs, but before continuing cleaning, this should be true. |
| 125 | + 'reboot_requested': True, |
| 126 | + # If it's safe for Ironic to abort cleaning while this step |
| 127 | + # runs, this should be true. |
| 128 | + 'abortable': False |
| 129 | + }] |
| 130 | + |
| 131 | + def upgrade_example_device_model1234_firmware(self, node, ports): |
| 132 | + """Upgrade firmware on Example Device Model #1234.""" |
| 133 | + # Any commands needed to perform the firmware upgrade should go here. |
| 134 | + # If you plan on actually flashing firmware every cleaning cycle, you |
| 135 | + # should ensure your device will not experience flash exhaustion. A |
| 136 | + # good practice in some environments would be to check the firmware |
| 137 | + # version against a constant in the code, and noop the method if an |
| 138 | + # upgrade is not needed. |
| 139 | + if _is_latest_firmware(): |
| 140 | + LOG.debug('Latest firmware already flashed, skipping') |
| 141 | + # Return values are ignored here on success |
| 142 | + return True |
| 143 | + else: |
| 144 | + LOG.debug('Firmware version X found, upgrading to Y') |
| 145 | + # Perform firmware upgrade. |
| 146 | + try: |
| 147 | + _upgrade_firmware() |
| 148 | + except Exception as e: |
| 149 | + # Log and pass through the exception so cleaning will fail |
| 150 | + LOG.exception(e) |
| 151 | + raise |
| 152 | + return True |
0 commit comments