New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add initial version of features mechanism #273
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Really great job. Very clean implementation. I had only really small comments but overall it looks great so far
630e047
to
01403bd
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should it give "False" for system that doesn't support the query?
ibyl --feature HA
WARNING cibyl.plugins.openstack.sources.jenkins Requesting deployment information for 1952 jobs will be based on the job name and approximate, restrict the query for more accurate results
ERROR cibyl.features Couldn't find any enabled source for the system zuul_cp that implements the function get_deployment.
Environment: osp_downstream_jenkins
System: osp_jenkins
HA feature: True
Environment: component_pipeline
System: zuul_cp
HA feature: False
Also, should it be case insensitive? so both cibyl --feature ha
and cibyl --feature HA
will work
cibyl/models/ci/base/system.py
Outdated
'attr_type': Feature, | ||
'attribute_value_class': AttributeDictValue, | ||
'arguments': [ | ||
Argument(name='--feature', arg_type=str, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you think about using "--features" instead of "--feature"? since it suppose to support more than one feature?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense, I'll change it
0015674
to
b3a1b6d
Compare
Probably not, but I'm not sure what would be the best way to mark it. One option would be to print for each feature that failed to query a message, and then maybe a general one.
Another would be to follow the same strategy that we have used until now with the normal queries and simply print a general message:
What do you think, @bregman-arie?
I think that's a good idea and simple change, I'll implement it. |
My concern is that users sometimes focus on very specific lines and when a user sees this line:
What comes into mind (at least for me) is that HA is not tested in that environment, but this is not what it necessarily means. We can print as many warning/error messages as we want before that line, but eventually, sooner or later, some user will take this as "HA is not tested in environment X". |
Using the same message for not available as in the spec is a good idea, I'll use that. Thanks! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Works flawlessly. Amazing job
4df1969
to
f7f8a65
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have found problems with the proposal. The code is tangled at times and I am not really fond of how FeatureTemplate has been designed. Take a look at my comments and we can discuss these.
cibyl/features/__init__.py
Outdated
"""Load all features available, either general or product specific through | ||
some plugin.""" | ||
# __path__ is a list | ||
features_locations = __path__ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again, if you are going to use reflection, try to be as flexible as possible. Hard coding the path does not leave as much room for change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The main way to introduce new paths for features will be thorugh plugins, that's way it's left as a class attribute. That's said I can certainly see the value in having an option to pass paths through the constructor, I'll add it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I still do not like this solution. That is an static variable, which means that if you make a change to it you will be affecting all instances of the class. That is an easy error to happen and one that can be difficult to debug. I would suggest that you either go with a fully encapsulated object or you have this as a python module without classes, just functions and global variables.
The funny thing about Python, is that depending on the type the variable is shared by all instances or not:
- feature_locations = 1 - This will not
- feature_locations = [] - But this will
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I'm aware of that, I did not think that would be a problem really, since I can't imagine a reason to have two instances of the class. Another option would be to make the FeatureLoader a Singleton class, but having everything at the module level is probably simpler and would achieve the same goal.
cibyl/plugins/openstack/__init__.py
Outdated
@@ -68,6 +69,8 @@ def extend_models(self): | |||
self.plugin_attributes_to_add) | |||
setattr(job_class, 'add_deployment', | |||
add_deployment) | |||
FeaturesLoader.features_locations.append(f"{__path__[0]}/features") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another reason as to pass the path on the constructor. How easy it is to miss this change during debugging.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure about this one, this follows the way we have been using the plugins. To make it more explicit I'll move it into a different method, for better visibility.
|
||
class OpenstackFeatureTemplate(FeatureTemplate): | ||
"""Skeleton for an openstack specific feature.""" | ||
method_to_query = "get_deployment" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Have this be given through an abstract method, that is what they are for:
@abstractmethod
def get_method_to_query(self): pass
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the idea, thanks!
Thanks for the thorough review @jsanemet! I've tried to address all the comments, and again sorry for the size of the PR. |
cibyl/features/__init__.py
Outdated
"""Load all features available, either general or product specific through | ||
some plugin.""" | ||
# __path__ is a list | ||
features_locations = __path__ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I still do not like this solution. That is an static variable, which means that if you make a change to it you will be affecting all instances of the class. That is an easy error to happen and one that can be difficult to debug. I would suggest that you either go with a fully encapsulated object or you have this as a python module without classes, just functions and global variables.
The funny thing about Python, is that depending on the type the variable is shared by all instances or not:
- feature_locations = 1 - This will not
- feature_locations = [] - But this will
I have gone through the changes and I still have some beef with the FeatureLoader class. Everything else is good to go I believe. |
I've updated the proposal moving to functionality of the FeatureLoader class to module-level functions |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great, everything is good to go for me.
Introduce a potential mechanism for features in cibyl. Features represent a new mechanism to query for information, particularly for that information that can not be retrieved by a simple call combining cli arguments. Features are classes that inherit from the FeatureTemplate class and are defined inside subpackages called 'features'. They must provide a 'query' method to be called, but the FeatureTemplate class provides on that can be reused with slight tweaks in simple cases. The 'query' method from The FeatureTemplate can be overriden for any given Feature, it's not mandatory to call it. Currently there are two locations for features a general (cibyl/features, empty) and an openstack specific (cibyl/plugins/openstack/features, three features provided, HA, IPV4 and IPV6). Plugins that add features must add their paths to the FeatureLoader class when the plugin is enabled. In the orchestrator the features are loaded and executed (calling the query method). If more than one feature is requested, their results are combined. If the user uses other cli arguments like --jobs, the intersection of the jobs that satisfy each feature will be finally published. If not, for each system it will be simply published whether each feature is present or not. The result of a Feature query without --jobs is to add a Feature model to the system queried. This should not be confused with any particular Feature like (HA or IPV4). The Feature model is a ci model (similar to Job or System) stored in cibyl/models/ci/base/feature.py and serves as store for the result of a Feature query.
This PR is marked as a draft while I finish writing tests, but it's ready for
review.
Features represent a new mechanism to query for information, particularly for
that information that can not be retrieved by a simple call combining
cli arguments.
Features are classes that inherit from the FeatureTemplate class and are
defined inside subpackages called 'features'. They must provide a
'query' method to be called, but the FeatureTemplate class provides on
that can be reused with slight tweaks in simple cases. The 'query' method
from The FeatureTemplate can be overriden for any given Feature, it's
not mandatory to call it.
Currently there are two
locations for features a general (cibyl/features, empty) and an openstack
specific (cibyl/plugins/openstack/features, three features provided, HA,
IPV4 and IPV6). Plugins that add features must add their paths to the
FeatureLoader class when the plugin is enabled.
In the orchestrator the features are loaded and executed (calling the
query method). If more than one feature is requested, their results are
combined. If the user uses other cli arguments like --jobs, the
intersection of the jobs that satisfy each feature will be finally
published. If not, for each system it will be simply published whether
each feature is present or not.
The result of a Feature query without --jobs is to add a Feature model
to the system queried. This should not be confused with any particular
Feature like (HA or IPV4). The Feature model is a ci model (similar to
Job or System) stored in cibyl/models/ci/base/feature.py and serves as
store for the result of a Feature query.