Пример #1
0
    def configure_bios(self, config_file, profile=None):
        """ Apply BIOS settings from a JSON configuration file """

        self.poll_lc_ready()
        self.normalize_job_queue()  # Delete any pending jobs

        (_, settings) = self._select_configuration(config_file, profile)

        self._clear_pending_configuration()

        # Flag for if we had to change a setting
        pending_settings = False

        for key in settings:
            self._logger.info("Getting current setting for %s", key)
            fqdd = "{}:{}".format(self._Target, key)
            bios_setting = self._client.DCIM_BIOSEnumerationFactory.get(fqdd)

            current_value = bios_setting.CurrentValue.value
            possible_values = [x.value for x in bios_setting.PossibleValues]
            expected_value = settings[key]

            if current_value != expected_value:
                if bios_setting.IsReadOnly.value == "true":
                    message = ("Attempt to change read only BIOS "
                               "setting: {}").format(key)
                    self._logger.error(message)
                    raise RecipeConfigurationError(message)

                elif expected_value in possible_values:
                    self._logger.info("Changing %s from %s to %s", key,
                                      current_value, expected_value)
                    self._client.DCIM_BIOSService.SetAttribute(
                        self._Target, key, expected_value)
                    pending_settings = True
                else:
                    message = ("Changing BIOS setting {} to {} is not "
                               "in the list of allowed values: {}").format(
                                   key, expected_value, possible_values)
                    self._logger.error(message)
                    raise RecipeConfigurationError(message)
            else:
                self._logger.info("BIOS setting %s is already %s", key,
                                  current_value)

        # Apply the BIOS settings
        if pending_settings:
            result = self._client.DCIM_BIOSService.CreateTargetedConfigJob(
                Target=self._Target)
            job_id = result['Job'].value
            self._logger.info("Created BIOS configuration job %s", job_id)
            self.queue_jobs_and_reboot([job_id])
Пример #2
0
    def _sanity_spares(self):
        """ Sanity check hot spares """

        disk_intersection = set(self._raid_fqdds).intersection(set(self._global_spare_fqdds))
        if disk_intersection:
            message = ("Configuration '{}': Drives are configured for "
                       "both RAID and hot spare: {}"
                      ).format(self.name, disk_intersection)

            self._logger.error(message)
            raise RecipeConfigurationError(message)

        disk_intersection = set(self._jbod_fqdds).intersection(set(self._global_spare_fqdds))
        if disk_intersection:
            message = ("Configuration '{}': Drives are configured for "
                       "both Non-RAID and hot spare: {}"
                      ).format(self.name, disk_intersection)

            self._logger.error(message)
            raise RecipeConfigurationError(message)
Пример #3
0
    def _sanity_raid(self):
        """ Sanity check RAID vs NON-RAID.  There should be no intersection """

        disk_intersection = set(self._raid_fqdds).intersection(set(self._jbod_fqdds))
        if disk_intersection:
            message = ("Configuration '{}': Drives are configured for "
                       "both raid and non-raid: {}").format(self.name,
                                                            disk_intersection)

            self._logger.error(message)
            raise RecipeConfigurationError(message)
Пример #4
0
    def _santiy_present(self):
        """
        Sanity check that all disks are present (our profile selection should
        catch this, but if a profile is forced, we should cry loudly)
        """

        disks_not_present = self.all_drives - self._physical_drive_fqdds

        if disks_not_present:
            message = ("Configuration '{}': Drives not present on physical "
                       "host: {}").format(self.name, disks_not_present)
            self._logger.error(message)
            raise RecipeConfigurationError(message)
Пример #5
0
    def _controller_set_from_drives(self):
        """ Take a list of controllers and return a set of controllers """

        controllers = set()
        for drive in self.all_drives:
            match = re.match(r'Disk\.Bay\.[0-9]+:Enclosure.\w+\.[0-9]-[0-9]:'
                             r'(RAID\.\w+\.[0-9]-[0-9])$', drive)
            if match:
                controllers.add(match.group(1))
            else:
                message = "Malformed drive: {}".format(drive)
                self._logger.error(message)
                raise RecipeConfigurationError(message)

        self._controllers = controllers
Пример #6
0
    def __init__(self, name, configuration, physical_drive_fqdds):
        """ Break down the configuration into an intermediate form """

        self._logger = logging.getLogger(__name__)

        self.name = name
        self._physical_drive_fqdds = set(physical_drive_fqdds)
        self._controllers = None
        self._raid_fqdds = []
        self._jbod_fqdds = []
        self._global_spare_fqdds = []
        self._virtual_disks = {}
        self._explicit_jbod = False # Are any drives explicitly set to JBOD

        for key, setting in configuration['Settings'].items():

            if key.startswith("Disk.Bay"):
                if setting.get('RaidStatus') == 'Non-RAID':
                    self._jbod_fqdds.append(key)
                    self._explicit_jbod = True
                elif setting.get('RaidStatus') == 'Spare':
                    self._global_spare_fqdds.append(key)
                else:
                    raise RecipeConfigurationError("Unknown configuration settings"
                                                   "for {}".format(key))
            elif key.startswith("Disk.Virtual"):
                # Create a VirtualDisk object to represent the virtual disk
                self._virtual_disks[key] = VirtualDisk(name, key, setting)

                # Add the virtual disk members to the list of RAID PDs
                self._raid_fqdds.extend(self._virtual_disks[key].drive_fqdds)
            else:
                self._logger.warning("Configuration '%s': Unknown key in "
                                     "settings: %s", name, key)


        # Perform sanity checks
        self._sanity_raid()
        self._sanity_spares()
        self._santiy_present()

        # Map unmentioned drives to JBOD
        self._implied_drives()

        # Populate controllers
        self._controller_set_from_drives()
Пример #7
0
    def __init__(self, config_name, virtual_disk, vdict):

        self._config_name = config_name
        self._virtual_disk = virtual_disk

        # We need to make sure to not pass PhysicalDiskIDs to the DRAC
        self._drive_fqdds = vdict.pop('PhysicalDiskIDs')
        self._vdict = vdict
        self._logger = logging.getLogger(__name__)

        mode_string = vdict.pop('Mode')
        if not hasattr(self, mode_string):
            message = ("Configuration {}: Unknown RAID level "
                       "{}").format(config_name, mode_string)
            self._logger.error(message)
            raise RecipeConfigurationError(message)

        self._vdict['RAIDLevel'] = getattr(self, mode_string)

        names, values = dict_to_prop_array(vdict)

        self._names = names
        self._values = values
Пример #8
0
    def _load_configuration(self, config_file):
        """ Load and validate a configuration file """

        # To keep working with lc_worker, we check if we
        # have a directory or regular file
        if os.path.exists(config_file):
            if os.path.isdir(config_file):
                configuration_path = "{}/*.json".format(config_file)
                self._logger.info("Looking for configurations in %s",
                                  configuration_path)

                config_files = glob.glob(configuration_path)
                if len(config_files) == 0:
                    message = "No configuration files found!"
                    self._logger.error(message)
                    raise RecipeConfigurationError(message)
            else:
                # Just a plain, old file
                config_files = [config_file]

        else:
            message = "Configuration file or path '{}' does not exist".format(
                config_file)
            self._logger.error(message)
            raise RecipeConfigurationError(message)

        configurations = {}

        for config_file in config_files:
            try:
                with open(config_file) as inventory:
                    new_configuration = json.load(inventory)
            except:
                message = "Failed to read configuration file '{}'".format(
                    config_file)
                self._logger.exception(message)
                raise RecipeConfigurationError(message)
            else:
                # Validate
                try:
                    jsonschema.validate(new_configuration, self.JSON_SCHEMA)

                except jsonschema.ValidationError:
                    message = "JSON validation failed for configuration file '{}'".format(
                        config_file)
                    self._logger.exception(message)
                    raise RecipeConfigurationError(message)

                except Exception as e:
                    message = ("Unexpected error '{}' while validating "
                               "configuration file '{}'").format(
                                   e, config_file)
                    self._logger.exception(message)
                    raise RecipeConfigurationError(message)

                # Check for duplicate keys
                key_intersect = set(configurations.keys()).intersection(
                    set(new_configuration.keys()))
                if key_intersect:
                    # We should only get duplicates if we have a path
                    # passed in as config_file
                    raise RecipeConfigurationError(
                        "Duplicate configuration '{}' "
                        "in path '{}'".format(key_intersect, config_file))

                # Ok, add to the configuration dictionary
                configurations.update(new_configuration)

        return configurations
Пример #9
0
    def configure_raid(self, config_file, profile=None):
        """ Configure RAID """

        self.poll_lc_ready()
        self.normalize_job_queue()

        #
        # Load Configuration
        #
        (my_config_name, my_config) = self._select_configuration(config_file, profile)

        if my_config is None:
            raise RecipeConfigurationError("Somehow got None from _select_configuration.  This"
                                           " should not happen.")

        raid_conf = RAIDConfiguration(my_config_name, my_config, self._pdisks.keys())

        #
        # Clear foreign config
        #
        self._clear_foreign_config()

        #
        # Basic Drive, Enclosure, and Controller Health Check
        #
        self._check_health()

        #
        # Clean up controller, normalize drives
        #
        self._clear_pending_configuration(raid_conf.controllers)
        self._clear_virtual_disks(raid_conf.controllers)
        self._normalize_hotspares(raid_conf)
        self._normalize_raid_drives(raid_conf)

        #
        # Create virtual disks
        #
        self._create_virtual_disks(raid_conf)

        #
        # Hot spares must be assigned after the Vdisks are created
        #
        self._assign_hot_spares(raid_conf)

        #
        # Create any JBODs or single drive RAID0 vds based on configuration
        #
        self._assign_jbods(raid_conf)


        #
        # Create the RAID configuration jobs
        #
        raid_jobs = []
        for target in raid_conf.controllers:
            self._logger.info("Creating configuration job for target %s",
                              target)
            try:
                result = self._client.DCIM_RAIDService.CreateTargetedConfigJob(Target=target)
                raid_jobs.append(result['Job'])
            except DCIMCommandError as exc:
                if exc.message_id == 'STOR026':
                    self._logger.info('No configuration changes were necessary for %s', target)
                else:
                    raise RecipeExecutionError(exc)

        if raid_jobs:
            self.queue_jobs_and_reboot(raid_jobs)
Пример #10
0
    def _select_configuration(self, config_file, profile=None):
        """ Filter out the list of potential RAID configurations by
            applying a series of filter functions against them """

        # We need introspection to get our config
        self._get_enumerations()

        self._logger.info("Picking RAID configuration")

        #
        # Load the confiuration JSON
        #
        config = None
        config_name = None

        configurations = self._load_configuration(config_file)

        # Flatten expands shorthand for raid, enclosure, disk if possible
        configurations = self._flatten_raid_configurations(configurations)

        # If a specific profile was requested, we use that
        if profile:
            if profile in configurations:
                self._logger.info("Picking specified profile '%s'",
                                  profile)
                return (profile, configurations[profile])
            else:
                raise RecipeConfigurationError("The requested profile '{}' does not"
                                               "exist.".format(profile))

        self._logger.debug("Starting with %s potential configurations.  "
                           "Checking for service tag match against %s",
                           len(configurations),
                           self.service_tag)

        for func in [self._filter_by_service_tag, self._filter_by_hardware]:

            (implicit_matches, explicit_matches) = func(configurations)

            if explicit_matches:
                self._logger.info("Proceeding with explicit matches")
                configurations = explicit_matches
            elif implicit_matches:
                self._logger.info("Proceeding with implicit matches")
                configurations = implicit_matches
            else:
                message = "No RAID configuration matches found!"
                self._logger.error(message)
                raise RecipeConfigurationError(message)

        if len(configurations) == 1:
            # This returns a tuple of key, value
            (config_name, config) = configurations.popitem()
            self._logger.info("RAID configuration '%s' matches!",
                              config_name)
        else:
            message = ("Multiple configurations ({}) match host {}!").format(
                ",".join(configurations.keys()), self.service_tag)
            self._logger.error(message)
            raise RecipeConfigurationError(message)

        return (config_name, config)
Пример #11
0
    def _select_configuration(self, config_file, profile):
        """ With BIOS settings, we can stack configurations """

        configurations = self._load_configuration(config_file)

        # Allow the specification of a particular profile for testing
        if profile:
            if profile in configurations:
                return configurations[profile]['Settings']
            else:
                raise RecipeConfigurationError("Unknown profile specified")

        matching_profiles = []

        #
        # Since we are stacking configurations, we don't differentiate between
        # implicit and explicit matches when filtering.  We do check for
        # the distinct selector at the end of the filtering and then
        # will only apply configurations containing that.
        #
        for func in [self._filter_by_service_tag, self._filter_by_system_id]:
            (implicit_profiles, explicit_profiles) = func(configurations)
            configurations = dict(
                list(implicit_profiles.items()) +
                list(explicit_profiles.items()))

        #
        # If profiles have the distinct selector, we get rid of everything else
        #
        (_, distinct_matches) = self._filter_by_distinct(configurations)
        if distinct_matches:
            self._logger.info(
                "Distinct selector found, using only profiles %s",
                distinct_matches.keys())
            configurations = distinct_matches

        #
        # Now order the profiles by priority
        #
        for profile in configurations:
            priority = configurations[profile]['Selectors'].get('Priority', 0)
            profile_tuple = (priority, profile)
            matching_profiles.append(profile_tuple)

        #
        # Now stack the configurations
        #
        stacked_config = OrderedDict()
        if matching_profiles:
            heapq.heapify(matching_profiles)
            while matching_profiles:
                profile = heapq.heappop(matching_profiles)[1]
                self._logger.info("Applying settings from '%s'", profile)

                for key in configurations[profile]['Settings']:
                    stacked_config[key] = configurations[profile]['Settings'][
                        key]
        else:
            message = "No matching BIOS configuration found!"
            self._logger.error(message)
            raise RecipeConfigurationError(message)

        return ("Stacked Configuration", stacked_config)