The goal of this PR is to figure out how to model Smart Proxy Feature classes in Foreman. You may notice the proposal is not very concrete. That’s because this is a very early stage RFC which seeks to gather input from people. It may not even be a good idea at all.
Starting with the background. The Smart Proxy has features (dhcp, dns, tftp, etc). When a Smart Proxy is registered to Foreman, it stores which features it has found. In Foreman :: Foreman Proxy Registration Protocol v2 explained I’ve explained this in much more detail.
I’ve observed that how we model the code on the Foreman side is inconsistent, especially when we take plugins into account. That’s what I’d like to see solved.
If we look at the model layer, we have a
SmartProxy database model with a
SmartProxyFeature has a
belongs_to relation with
For pure API communication, Foreman has proxy_api but then this is used all over the Foreman code. Most notable in models and services.
If we look at Katello, there’s a large smart_proxy_extensions module.
I would like it if a plugin could register a Smart Proxy Feature.
# It probably shouldn't inherit from the SmartProxyFeature model class class SmartProxyPulpFeature < BaseSmartProxyFeature # Whatever code needed end Foreman::Plugin.register :katello do smart_proxy_feature 'Pulp', SmartProxyPulpFeature end
This means we would end up with a registry of features. This could then be used in database seeding as well.
SmartProxy instance you have a method
get_feature(feature) to get an instance for that feature class (or
features[feature], but that’s implementation details). Note that the ProxyAPI classes can stay and would be called from these feature classes.
The built in features (DHCP, DNS, TFTP, etc) would also get a specific class to give a uniform API.
One result of this is that it is clear what a feature provides and have clear isolation.
Feature classes would also be the right place to perform checks on capabilities. It should be possible to declare that a Smart Proxy Feature must provide some capability, or registration fails. This would allow API changes to happen by introducing a new capability. At some point Foreman can drop support for Smart Proxies without the new API.
For example, let’s say the DNS API has a bad design and needs a rework. It introduces a V2 API and signals this via the
At first the code will look roughly like this (simplified):
class SmartProxyDnsFeature < BaseSmartProxyFeature CAP_V2_API = 'V2_API' def self.required_capabilities  end def create_record(name, type, value) if has_capability?(CAP_V2_API) ProxyAPI::DNSV2 else ProxyAPI::DNS end end end
This will allow Foreman to use both new and old, making it easy to upgrade. You can run Foreman 2.5 with a Foreman Proxy 2.4. Then in a later maintenance window, you upgrade to Foreman Proxy 2.5.
At some point Foreman 3.0 comes around and drops support for the old API. The class is now reduced to:
class SmartProxyDnsFeature < BaseSmartProxyFeature CAP_V2_API = 'V2_API' def self.required_capabilities [CAP_V2_API] end def create_record(name, type, value) ProxyAPI::DNSV2 end end
Whenever a SmartProxyDnsFeature is initialized, it checks the SmartProxyFeature instance for capabilities. If any of the
required_capabilities are not present, it raises an exception. Additionally, any other checks mechanism should also be used. For example, the ping API endpoint can detect the incompatibility. On the Smart Proxy detail page it should also show the problem. The notifications framework can also be used at instance start up. Registration should also fail.
Implementing those checks means that as a user, you get a lot of up front warnings that your setup is going to give problems at some point in the future. In an upgrade procedure you can add a hammer ping at the end to verify everything is running and compatible.