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.
Background
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 has_many
to SmartProxyFeature
. SmartProxyFeature
has a belongs_to
relation with Feature
.
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.
Proposal
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.
From the 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.
Capabilities
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 V2_API
capability.
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.