summaryrefslogtreecommitdiff
path: root/ironic_python_agent/hardware.py
diff options
context:
space:
mode:
Diffstat (limited to 'ironic_python_agent/hardware.py')
-rw-r--r--ironic_python_agent/hardware.py183
1 files changed, 183 insertions, 0 deletions
diff --git a/ironic_python_agent/hardware.py b/ironic_python_agent/hardware.py
new file mode 100644
index 00000000..f41e306d
--- /dev/null
+++ b/ironic_python_agent/hardware.py
@@ -0,0 +1,183 @@
+"""
+Copyright 2013 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 abc
+import os
+import subprocess
+
+import stevedore
+
+from ironic_python_agent import encoding
+from ironic_python_agent.openstack.common import log
+from ironic_python_agent import utils
+
+_global_manager = None
+
+
+class HardwareSupport(object):
+ """These are just guidelines to suggest values that might be returned by
+ calls to `evaluate_hardware_support`. No HardwareManager in mainline
+ ironic-python-agent will ever offer a value greater than `MAINLINE`.
+ Service Providers should feel free to return values greater than
+ SERVICE_PROVIDER to distinguish between additional levels of support.
+ """
+ NONE = 0
+ GENERIC = 1
+ MAINLINE = 2
+ SERVICE_PROVIDER = 3
+
+
+class HardwareType(object):
+ MAC_ADDRESS = 'mac_address'
+
+
+class HardwareInfo(encoding.Serializable):
+ def __init__(self, type, id):
+ self.type = type
+ self.id = id
+
+ def serialize(self):
+ return utils.get_ordereddict([
+ ('type', self.type),
+ ('id', self.id),
+ ])
+
+
+class BlockDevice(object):
+ def __init__(self, name, size, start_sector):
+ self.name = name
+ self.size = size
+ self.start_sector = start_sector
+
+
+class NetworkInterface(object):
+ def __init__(self, name, mac_addr):
+ self.name = name
+ self.mac_address = mac_addr
+ # TODO(russellhaering): Pull these from LLDP
+ self.switch_port_descr = None
+ self.switch_chassis_descr = None
+
+
+class HardwareManager(object):
+ @abc.abstractmethod
+ def evaluate_hardware_support(cls):
+ pass
+
+ @abc.abstractmethod
+ def list_network_interfaces(self):
+ pass
+
+ @abc.abstractmethod
+ def get_os_install_device(self):
+ pass
+
+ def list_hardware_info(self):
+ hardware_info = []
+ for interface in self.list_network_interfaces():
+ hardware_info.append(HardwareInfo(HardwareType.MAC_ADDRESS,
+ interface.mac_address))
+ return hardware_info
+
+
+class GenericHardwareManager(HardwareManager):
+ def __init__(self):
+ self.sys_path = '/sys'
+
+ if os.path.isdir('/mnt/sys'):
+ self.sys_path = '/mnt/sys'
+
+ def evaluate_hardware_support(cls):
+ return HardwareSupport.GENERIC
+
+ def _get_interface_info(self, interface_name):
+ addr_path = '{0}/class/net/{1}/address'.format(self.sys_path,
+ interface_name)
+ addr_file = open(addr_path, 'r')
+ mac_addr = addr_file.read().strip()
+ return NetworkInterface(interface_name, mac_addr)
+
+ def _is_device(self, interface_name):
+ device_path = '{0}/class/net/{1}/device'.format(self.sys_path,
+ interface_name)
+ return os.path.exists(device_path)
+
+ def list_network_interfaces(self):
+ iface_names = os.listdir('{0}/class/net'.format(self.sys_path))
+ return [self._get_interface_info(name)
+ for name in iface_names
+ if self._is_device(name)]
+
+ def _cmd(self, command):
+ process = subprocess.Popen(command, stdout=subprocess.PIPE)
+ return process.communicate()
+
+ def _list_block_devices(self):
+ report = self._cmd(['blockdev', '--report'])[0]
+ lines = report.split('\n')
+ lines = [line.split() for line in lines if line is not '']
+ startsec_idx = lines[0].index('StartSec')
+ device_idx = lines[0].index('Device')
+ size_idx = lines[0].index('Size')
+ return [BlockDevice(line[device_idx],
+ int(line[size_idx]),
+ int(line[startsec_idx]))
+ for line
+ in lines[1:]]
+
+ def get_os_install_device(self):
+ # Assume anything with a start sector other than 0, is a partition
+ block_devices = [device for device in self._list_block_devices()
+ if device.start_sector == 0]
+
+ # Find the first device larger than 4GB, assume it is the OS disk
+ # TODO(russellhaering): This isn't a valid assumption in all cases,
+ # is there a more reasonable default behavior?
+ block_devices.sort(key=lambda device: device.size)
+ for device in block_devices:
+ if device.size >= (4 * pow(1024, 3)):
+ return device.name
+
+
+def _compare_extensions(ext1, ext2):
+ mgr1 = ext1.obj
+ mgr2 = ext2.obj
+ return mgr1.evaluate_hardware_support() - mgr2.evaluate_hardware_support()
+
+
+def get_manager():
+ global _global_manager
+
+ if not _global_manager:
+ LOG = log.getLogger()
+ extension_manager = stevedore.ExtensionManager(
+ namespace='ironic_python_agent.hardware_managers',
+ invoke_on_load=True)
+
+ # There will always be at least one extension available (the
+ # GenericHardwareManager).
+ preferred_extension = sorted(extension_manager, _compare_extensions)[0]
+ preferred_manager = preferred_extension.obj
+
+ if preferred_manager.evaluate_hardware_support() <= 0:
+ raise RuntimeError('No suitable HardwareManager could be found')
+
+ LOG.info('selected hardware manager {0}'.format(
+ preferred_extension.entry_point_target))
+
+ _global_manager = preferred_manager
+
+ return _global_manager