 efbbc86f53
			
		
	
	efbbc86f53
	
	
	
		
			
			Fix H904 "Delay string interpolations at logging calls" errors Change-Id: I331808d0132094faf739998a6984440787d3ebf8
		
			
				
	
	
		
			198 lines
		
	
	
		
			8.4 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			198 lines
		
	
	
		
			8.4 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| # Copyright 2015 Rackspace, Inc.
 | |
| #
 | |
| # Licensed under the Apache License, Version 2.0 (the "License");
 | |
| # you may not use this file except in compliance with the License.
 | |
| # You may obtain a copy of the License at
 | |
| #
 | |
| #   http://www.apache.org/licenses/LICENSE-2.0
 | |
| #
 | |
| # Unless required by applicable law or agreed to in writing, software
 | |
| # distributed under the License is distributed on an "AS IS" BASIS,
 | |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | |
| # See the License for the specific language governing permissions and
 | |
| # limitations under the License.
 | |
| 
 | |
| import time
 | |
| 
 | |
| from oslo_log import log
 | |
| 
 | |
| from ironic_python_agent import errors
 | |
| from ironic_python_agent import hardware
 | |
| 
 | |
| LOG = log.getLogger()
 | |
| 
 | |
| 
 | |
| class ExampleBusinessLogicHardwareManager(hardware.HardwareManager):
 | |
|     """Example hardware manager to enforce business logic"""
 | |
| 
 | |
|     # All hardware managers have a name and a version.
 | |
|     # Version should be bumped anytime a change is introduced. This will
 | |
|     # signal to Ironic that if automatic node cleaning is in progress to
 | |
|     # restart it from the beginning, to ensure consistency. The value can
 | |
|     # be anything; it's checked for equality against previously seen
 | |
|     # name:manager pairs.
 | |
|     HARDWARE_MANAGER_NAME = 'ExampleBusinessLogicHardwareManager'
 | |
|     HARDWARE_MANAGER_VERSION = '1'
 | |
| 
 | |
|     def evaluate_hardware_support(self):
 | |
|         """Declare level of hardware support provided.
 | |
| 
 | |
|         Since this example is explicitly about enforcing business logic during
 | |
|         cleaning, we want to return a static value.
 | |
| 
 | |
|         :returns: HardwareSupport level for this manager.
 | |
|         """
 | |
|         return hardware.HardwareSupport.SERVICE_PROVIDER
 | |
| 
 | |
|     def get_clean_steps(self, node, ports):
 | |
|         """Get a list of clean steps with priority.
 | |
| 
 | |
|         Define any clean steps added by this manager here. These will be mixed
 | |
|         with other loaded managers that support this hardware, and ordered by
 | |
|         priority. Higher priority steps run earlier.
 | |
| 
 | |
|         Note that out-of-band clean steps may also be provided by Ironic.
 | |
|         These will follow the same priority ordering even though they are not
 | |
|         executed by IPA.
 | |
| 
 | |
|         There is *no guarantee whatsoever* that steps defined here will be
 | |
|         executed by this HardwareManager. When it comes time to run these
 | |
|         steps, they'll be called using dispatch_to_managers() just like any
 | |
|         other IPA HardwareManager method. This means if they are unique to
 | |
|         your hardware, they should be uniquely named. For example,
 | |
|         upgrade_firmware would be a bad step name. Whereas
 | |
|         upgrade_foobar_device_firmware would be better.
 | |
| 
 | |
|         :param node: The node object as provided by Ironic.
 | |
|         :param ports: Port objects as provided by Ironic.
 | |
|         :returns: A list of cleaning steps, as a list of dicts.
 | |
|         """
 | |
|         # While obviously you could actively run code here, generally this
 | |
|         # should just return a static value, as any initialization and
 | |
|         # detection should've been done in evaluate_hardware_support().
 | |
|         return [{
 | |
|             'step': 'companyx_verify_device_lifecycle',
 | |
|             'priority': 472,
 | |
|             # If you need Ironic to coordinate a reboot after this step
 | |
|             # runs, but before continuing cleaning, this should be true.
 | |
|             'reboot_requested': False,
 | |
|             # If it's safe for Ironic to abort cleaning while this step
 | |
|             # runs, this should be true.
 | |
|             'abortable': True
 | |
|         }]
 | |
| 
 | |
|     # Other examples of interesting cleaning steps for this kind of hardware
 | |
|     # manager would include verifying node.properties matches current state of
 | |
|     # the node, checking smart stats to ensure the disk is not soon to fail,
 | |
|     # or enforcing security policies.
 | |
|     def companyx_verify_device_lifecycle(self, node, ports):
 | |
|         """Verify node is not beyond useful life of 3 years."""
 | |
|         create_date = node.get('created_at')
 | |
|         if create_date is not None:
 | |
|             server_age = time.time() - time.mktime(time.strptime(create_date))
 | |
|             if server_age > (60 * 60 * 24 * 365 * 3):
 | |
|                 raise errors.CleaningError(
 | |
|                     'Server is too old to pass cleaning!')
 | |
|             else:
 | |
|                 LOG.info('Node is %s seconds old, younger than 3 years, '
 | |
|                          'cleaning passes.', server_age)
 | |
| 
 | |
|     def get_deploy_steps(self, node, ports):
 | |
|         """Get a list of deploy steps with priority.
 | |
| 
 | |
|         Returns a list of steps. Each step is represented by a dict::
 | |
| 
 | |
|           {
 | |
|            'interface': the name of the driver interface that should execute
 | |
|                         the step.
 | |
|            'step': the HardwareManager function to call.
 | |
|            'priority': the order steps will be run in. Ironic will sort all
 | |
|                        the deploy steps from all the drivers, with the largest
 | |
|                        priority step being run first. If priority is set to 0,
 | |
|                        the step will not be run during deployment
 | |
|                        automatically, but may be requested via deploy
 | |
|                        templates.
 | |
|            'reboot_requested': Whether the agent should request Ironic reboots
 | |
|                                the node via the power driver after the
 | |
|                                operation completes.
 | |
|            'argsinfo': arguments specification.
 | |
|           }
 | |
| 
 | |
| 
 | |
|         Deploy steps are executed by similar logic to clean steps, but during
 | |
|         deploy time. The following priority ranges should be used:
 | |
| 
 | |
|         * 81 to 99 - deploy steps to run before the image is written.
 | |
|         * 61 to 79 - deploy steps to run after the image is written but before
 | |
|           the bootloader is installed.
 | |
|         * 41 to 59 - steps to run after the image is written and the bootloader
 | |
|           is installed.
 | |
| 
 | |
|         If priority is zero, the step must be explicitly selected via
 | |
|         an applied deploy template.
 | |
| 
 | |
|         Note that each deploy step makes deployments longer. Try to use clean
 | |
|         steps for anything that is not required to be run just before an
 | |
|         instance is ready.
 | |
| 
 | |
|         :param node: Ironic node object
 | |
|         :param ports: list of Ironic port objects
 | |
|         :return: a list of deploying steps, where each step is described as a
 | |
|                  dict as defined above
 | |
| 
 | |
|         """
 | |
|         return [
 | |
|             {
 | |
|                 'step': 'companyx_verify_memory',
 | |
|                 'priority': 90,  # always run before the image is written
 | |
|                 'interface': 'deploy',
 | |
|                 'reboot_requested': False,
 | |
|             },
 | |
|             {
 | |
|                 'step': 'companyx_apply_something',
 | |
|                 'priority': 0,  # only run explicitly
 | |
|                 'interface': 'deploy',
 | |
|                 'reboot_requested': False,
 | |
|                 'argsinfo': {
 | |
|                     "required_value": {
 | |
|                         "description": "An example required argument.",
 | |
|                         "required": True,
 | |
|                     },
 | |
|                     "optional_value": {
 | |
|                         "description": "An example optional argument.",
 | |
|                         "required": False,
 | |
|                     }
 | |
| 
 | |
|                 }
 | |
|             },
 | |
|         ]
 | |
| 
 | |
|     def companyx_verify_memory(self, node, ports):
 | |
|         expected = node.get('properties', {}).get('memory_mb')
 | |
|         if expected is None:
 | |
|             LOG.warning('The node does not have memory, cannot verify')
 | |
|             return
 | |
|         else:
 | |
|             expected = int(expected)
 | |
| 
 | |
|         # Use dispatch_to_managers to avoid tight coupling between hardware
 | |
|         # managers. It would make sense even if this hardware manager
 | |
|         # implemented get_memory, because a more specific hardware managers
 | |
|         # could do it better.
 | |
|         current = hardware.dispatch_to_managers('get_memory')
 | |
|         if current.physical_mb < expected:
 | |
|             # Raising an exception will fail deployment and set the node's
 | |
|             # last_error accordingly.
 | |
|             raise errors.DeploymentError(
 | |
|                 'Memory too low, expected {}, got {}'.format(
 | |
|                     expected, current.physical_mb))
 | |
| 
 | |
|     # Make sure to provide default values for optional arguments.
 | |
|     def companyx_apply_something(self, node, ports, required_value,
 | |
|                                  optional_value=None):
 | |
|         LOG.info('apply_something called with '
 | |
|                  'required_value=%(required_value)s and '
 | |
|                  'optional_value=%(optional_value)s',
 | |
|                  {'required_value': required_value,
 | |
|                   'optional_value': optional_value})
 |