acrn-hypervisor/misc/config_tools/scenario_config/pipeline.py
Junjie Mao 0a7910c7f0 config_tools: composing operations around XMLs as pipelines
There is an increasing demand of composing different operations around XML
schemas and/or data in different ways for different purpose. Today we
already have:

  - Validate XML data, which takes XML schemas and data (board and
    scenario) as inputs.
  - Fill in missing nodes in XML data with default values, which takes XML
    schema and data (scenario only) as inputs.

In the near future we'll extend the operations around XMLs by introducing
XML schema preprocessing and XML data upgrading, adding more possibilities
to construct a larger operation by composing smaller ones.

In order for minimized code repetition and easier composition, this patch
introduces an infrasturcture that abstracts each operation as a pipeline
stage. Each stage defines its own inputs and outputs and can be composed
sequentially as a larger, single operation.

The existing operations listed above, along with XML file loaders, are then
refactored to provide pipeline stages. The main methods are also refined to
complete their tasks by constructing and invoking pipelines.

Tracked-On: #6690
Signed-off-by: Junjie Mao <junjie.mao@intel.com>
2022-03-15 10:22:37 +08:00

73 lines
2.3 KiB
Python

#!/usr/bin/env python3
#
# Copyright (C) 2022 Intel Corporation.
#
# SPDX-License-Identifier: BSD-3-Clause
#
class PipelineObject:
def __init__(self, **kwargs):
self.data = {}
for k,v in kwargs.items():
self.set(k, v)
def set(self, tag, data):
self.data[tag] = data
def get(self, tag):
return self.data[tag]
def has(self, tag):
return tag in self.data.keys()
def consume(self, tag):
return self.data.pop(tag, None)
def dump(self):
print(self.data)
class PipelineStage:
# The following three class variables defines the inputs and outputs of the stage. Each of them can be either a set
# or a string (which is interpreted as a unit set)
consumes = set() # Data consumed by this stage. Consumed data will be unavailable to later stages.
uses = set() # Data used but not consumed by this stage.
provides = set() # Data provided by this stage.
def run(self, obj):
raise NotImplementedError
class PipelineEngine:
def __init__(self, initial_data = []):
self.stages = []
self.initial_data = set(initial_data)
self.available_data = set(initial_data)
def add_stage(self, stage):
consumes = stage.consumes if isinstance(stage.consumes, set) else {stage.consumes}
uses = stage.uses if isinstance(stage.uses, set) else {stage.uses}
provides = stage.provides if isinstance(stage.provides, set) else {stage.provides}
all_uses = consumes.union(uses)
if not all_uses.issubset(self.available_data):
raise Exception(f"Data {uses - self.available_data} need by stage {stage.__class__.__name__} but not provided by the pipeline")
self.stages.append(stage)
self.available_data = self.available_data.difference(consumes).union(provides)
def add_stages(self, stages):
for stage in stages:
self.add_stage(stage)
def run(self, obj):
for tag in self.initial_data:
if not obj.has(tag):
raise AttributeError(f"Data {tag} is needed by the pipeline but not provided by the object")
for stage in self.stages:
stage.run(obj)
consumes = stage.consumes if isinstance(stage.consumes, set) else {stage.consumes}
for tag in consumes:
obj.consume(tag)