Skip to content
2 changes: 2 additions & 0 deletions Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions pkg/controller/add_networkconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ package controller
import (
"github.com/openshift/cluster-network-operator/pkg/controller/clusterconfig"
"github.com/openshift/cluster-network-operator/pkg/controller/operconfig"
"github.com/openshift/cluster-network-operator/pkg/controller/proxyconfig"
)

func init() {
// AddToManagerFuncs is a list of functions to create controllers and add them to a manager.
AddToManagerFuncs = append(AddToManagerFuncs,
proxyconfig.Add,
operconfig.Add,
clusterconfig.Add,
operconfig.AddConfigMapReconciler,
Expand Down
156 changes: 156 additions & 0 deletions pkg/controller/proxyconfig/controller.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
package proxyconfig

import (
"context"
"fmt"
"log"

configv1 "github.com/openshift/api/config/v1"
"github.com/openshift/cluster-network-operator/pkg/controller/statusmanager"
"github.com/openshift/cluster-network-operator/pkg/names"

corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller"
"sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/manager"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
"sigs.k8s.io/controller-runtime/pkg/source"
)

// and Start it when the Manager is Started.
func Add(mgr manager.Manager, status *statusmanager.StatusManager) error {
reconciler := newReconciler(mgr, status)
if reconciler == nil {
return fmt.Errorf("failed to create reconciler")
}

return add(mgr, reconciler)
}

// newReconciler returns a new reconcile.Reconciler
func newReconciler(mgr manager.Manager, status *statusmanager.StatusManager) reconcile.Reconciler {
if err := configv1.Install(mgr.GetScheme()); err != nil {
return &ReconcileProxyConfig{}
}

return &ReconcileProxyConfig{client: mgr.GetClient(), scheme: mgr.GetScheme(), status: status}
}

// add adds a new Controller to mgr with r as the reconcile.Reconciler
func add(mgr manager.Manager, r reconcile.Reconciler) error {
// Create a new controller
c, err := controller.New("proxyconfig-controller", mgr, controller.Options{Reconciler: r})
if err != nil {
return err
}

// Watch for changes to the proxy resource.
err = c.Watch(&source.Kind{Type: &configv1.Proxy{}}, &handler.EnqueueRequestForObject{})
if err != nil {
return err
}

Comment thread
danehans marked this conversation as resolved.
return nil
}

// ReconcileProxyConfig reconciles a Proxy object
type ReconcileProxyConfig struct {
// This client, initialized using mgr.Client() above, is a split client
// that reads objects from the cache and writes to the apiserver.
client client.Client
scheme *runtime.Scheme
status *statusmanager.StatusManager
}

// Reconcile expects request to refer to a proxy object named "cluster"
// in the default namespace and will ensure the proxy object is in the
// desired state.
func (r *ReconcileProxyConfig) Reconcile(request reconcile.Request) (reconcile.Result, error) {
log.Printf("Reconciling proxy '%s'", request.Name)
proxyConfig := &configv1.Proxy{}
err := r.client.Get(context.TODO(), request.NamespacedName, proxyConfig)
if err != nil {
if apierrors.IsNotFound(err) {
// Request object not found, could have been deleted after reconcile request.
// Return and don't requeue
log.Printf("proxy '%s' not found; reconciliation will be skipped", request.Name)
return reconcile.Result{}, nil
}
// Error reading the object - requeue the request.
return reconcile.Result{}, fmt.Errorf("failed to get proxy '%s': %v", request.Name, err)
}

// A nil proxy is generated by upgrades and installs not requiring a proxy.
if !isSpecHTTPProxySet(&proxyConfig.Spec) &&
!isSpecHTTPSProxySet(&proxyConfig.Spec) &&
!isSpecNoProxySet(&proxyConfig.Spec) {
log.Printf("httpProxy, httpsProxy and noProxy not defined for proxy '%s'; validation will be skipped",
request.Name)
}

// Only proceed if the required config objects can be collected.
infraConfig := &configv1.Infrastructure{}
netConfig := &configv1.Network{}
clusterCfgMap := &corev1.ConfigMap{}
if err := r.client.Get(context.TODO(), types.NamespacedName{Name: names.CLUSTER_CONFIG}, infraConfig); err != nil {
log.Printf("failed to get infrastructure config '%s': %v", names.CLUSTER_CONFIG, err)
r.status.SetDegraded(statusmanager.ProxyConfig, "InfraConfigError",
fmt.Sprintf("Error getting infrastructure config %s: %v.", names.CLUSTER_CONFIG, err))
return reconcile.Result{}, nil
}
if err := r.client.Get(context.TODO(), types.NamespacedName{Name: names.CLUSTER_CONFIG}, netConfig); err != nil {
log.Printf("failed to get network config '%s': %v", names.CLUSTER_CONFIG, err)
r.status.SetDegraded(statusmanager.ProxyConfig, "NetworkConfigError",
fmt.Sprintf("Error getting network config '%s': %v.", names.CLUSTER_CONFIG, err))
return reconcile.Result{}, nil
}
if err := r.client.Get(context.TODO(), types.NamespacedName{Name: "cluster-config-v1", Namespace: "kube-system"},
clusterCfgMap); err != nil {
log.Printf("failed to get configmap '%s/%s': %v", clusterCfgMap.Namespace, clusterCfgMap.Name, err)
r.status.SetDegraded(statusmanager.ProxyConfig, "ClusterConfigError",
fmt.Sprintf("Error getting cluster config configmap '%s/%s': %v.", clusterCfgMap.Namespace,
clusterCfgMap.Name, err))
return reconcile.Result{}, nil
}
Comment thread
danehans marked this conversation as resolved.
if err := r.ValidateProxyConfig(&proxyConfig.Spec); err != nil {
log.Printf("Failed to validate proxy '%s': %v", proxyConfig.Name, err)
r.status.SetDegraded(statusmanager.ProxyConfig, "InvalidProxyConfig",
fmt.Sprintf("The configuration is invalid for proxy '%s' (%v). "+
"Use 'oc edit proxy.config.openshift.io %s' to fix.", proxyConfig.Name, proxyConfig.Name, err))
return reconcile.Result{}, nil
}

// Update proxy status.
if err := r.syncProxyStatus(proxyConfig, infraConfig, netConfig, clusterCfgMap); err != nil {
log.Printf("Could not sync proxy '%s' status: %v", proxyConfig.Name, err)
r.status.SetDegraded(statusmanager.ProxyConfig, "StatusError",
fmt.Sprintf("Could not update proxy '%s' status: %v", proxyConfig.Name, err))
return reconcile.Result{}, err
}
log.Printf("Reconciling proxy '%s' complete", request.Name)

r.status.SetNotDegraded(statusmanager.ProxyConfig)

return reconcile.Result{}, nil
}

// isSpecHTTPProxySet returns true if spec.httpProxy of
// proxyConfig is set.
func isSpecHTTPProxySet(proxyConfig *configv1.ProxySpec) bool {
return len(proxyConfig.HTTPProxy) > 0
}

// isSpecHTTPSProxySet returns true if spec.httpsProxy of
// proxyConfig is set.
func isSpecHTTPSProxySet(proxyConfig *configv1.ProxySpec) bool {
Comment thread
danehans marked this conversation as resolved.
return len(proxyConfig.HTTPSProxy) > 0
}

// isSpecNoProxySet returns true if spec.NoProxy of proxyConfig is set.
func isSpecNoProxySet(proxyConfig *configv1.ProxySpec) bool {
return len(proxyConfig.NoProxy) > 0
}
124 changes: 124 additions & 0 deletions pkg/controller/proxyconfig/status.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package proxyconfig

import (
"context"
"fmt"
"net/url"
"strconv"
"strings"

"github.com/ghodss/yaml"

configv1 "github.com/openshift/api/config/v1"

corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/util/sets"
)

// syncProxyStatus computes the current status of proxy and
// updates status of any changes since last sync.
func (r *ReconcileProxyConfig) syncProxyStatus(proxy *configv1.Proxy, infra *configv1.Infrastructure, network *configv1.Network, cluster *corev1.ConfigMap) error {
var err error
var noProxy string
updated := proxy.DeepCopy()

if isSpecNoProxySet(&proxy.Spec) {
if proxy.Spec.NoProxy == noProxyWildcard {
noProxy = proxy.Spec.NoProxy
} else {
noProxy, err = mergeUserSystemNoProxy(proxy, infra, network, cluster)
if err != nil {
return fmt.Errorf("failed to merge user/system noProxy settings: %v", err)
}
}
}

updated.Status.HTTPProxy = proxy.Spec.HTTPProxy
updated.Status.HTTPSProxy = proxy.Spec.HTTPSProxy
updated.Status.NoProxy = noProxy

if !proxyStatusesEqual(proxy.Status, updated.Status) {
if err := r.client.Status().Update(context.TODO(), updated); err != nil {
return fmt.Errorf("failed to update proxy status: %v", err)
}
}

return nil
}

// mergeUserSystemNoProxy merges user-supplied noProxy settings from proxy
// with cluster-wide noProxy settings, returning a merged, comma-separated
// string of noProxy settings.
func mergeUserSystemNoProxy(proxy *configv1.Proxy, infra *configv1.Infrastructure, network *configv1.Network, cluster *corev1.ConfigMap) (string, error) {
Comment thread
danehans marked this conversation as resolved.
apiServerURL, err := url.Parse(infra.Status.APIServerURL)
if err != nil {
return "", fmt.Errorf("failed to parse API server URL")
}

internalAPIServer, err := url.Parse(infra.Status.APIServerInternalURL)
if err != nil {
return "", fmt.Errorf("failed to parse API server internal URL")
}

set := sets.NewString(
"127.0.0.1",
"localhost",
network.Status.ServiceNetwork[0],
apiServerURL.Hostname(),
internalAPIServer.Hostname(),
)

// TODO: This will be flexible when master machine management is more dynamic.
type installConfig struct {
ControlPlane struct {
Replicas string `json:"replicas"`
} `json:"controlPlane"`
Networking struct {
MachineCIDR string `json:"machineCIDR"`
} `json:"networking"`
}
var ic installConfig
data, ok := cluster.Data["install-config"]
Comment thread
danehans marked this conversation as resolved.
if !ok {
return "", fmt.Errorf("missing install-config in configmap")
}
if err := yaml.Unmarshal([]byte(data), &ic); err != nil {
return "", fmt.Errorf("invalid install-config: %v\njson:\n%s", err, data)
}

switch infra.Status.PlatformStatus.Type {
case configv1.AWSPlatformType, configv1.GCPPlatformType, configv1.AzurePlatformType, configv1.OpenStackPlatformType:
set.Insert("169.254.169.254", ic.Networking.MachineCIDR)
}

replicas, err := strconv.Atoi(ic.ControlPlane.Replicas)
if err != nil {
return "", fmt.Errorf("failed to parse install config replicas: %v", err)
}

for i := int64(0); i < int64(replicas); i++ {
etcdHost := fmt.Sprintf("etcd-%d.%s", i, infra.Status.EtcdDiscoveryDomain)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@crawford - will these names be fixed with etcd operator in future?

set.Insert(etcdHost)
}

for _, clusterNetwork := range network.Status.ClusterNetwork {
set.Insert(clusterNetwork.CIDR)
Comment thread
danehans marked this conversation as resolved.
}

for _, userValue := range strings.Split(proxy.Spec.NoProxy, ",") {
set.Insert(userValue)
}

return strings.Join(set.List(), ","), nil
}

// proxyStatusesEqual compares two ProxyStatus values. Returns true if the
// provided values should be considered equal for the purpose of determining
// whether an update is necessary, false otherwise.
func proxyStatusesEqual(a, b configv1.ProxyStatus) bool {
if a.HTTPProxy != b.HTTPProxy || a.HTTPSProxy != b.HTTPSProxy || a.NoProxy != b.NoProxy {
return false
}

return true
}
56 changes: 56 additions & 0 deletions pkg/controller/proxyconfig/validation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package proxyconfig

import (
"fmt"
"net"
"strings"

configv1 "github.com/openshift/api/config/v1"
"github.com/openshift/cluster-network-operator/pkg/util/validation"
)

const (
proxyHTTPScheme = "http"
proxyHTTPSScheme = "https"
// noProxyWildcard is the string used to as a wildcard attached to a
// domain suffix in proxy.spec.noProxy to bypass proxying.
noProxyWildcard = "*"
)

// ValidateProxyConfig ensures that proxyConfig is valid.
func (r *ReconcileProxyConfig) ValidateProxyConfig(proxyConfig *configv1.ProxySpec) error {
Comment thread
danehans marked this conversation as resolved.
if isSpecHTTPProxySet(proxyConfig) {
scheme, err := validation.URI(proxyConfig.HTTPProxy)
if err != nil {
return fmt.Errorf("invalid httpProxy URI: %v", err)
}
if scheme != proxyHTTPScheme {
return fmt.Errorf("httpProxy requires an %q URI scheme", proxyHTTPScheme)
}
}

if isSpecHTTPSProxySet(proxyConfig) {
scheme, err := validation.URI(proxyConfig.HTTPSProxy)
if err != nil {
return fmt.Errorf("invalid httpsProxy URI: %v", err)
}
if scheme != proxyHTTPScheme && scheme != proxyHTTPSScheme {
return fmt.Errorf("httpsProxy requires a %q or %s URI scheme", proxyHTTPScheme, proxyHTTPSScheme)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is one of these %q and the other %s?

}
}

if isSpecNoProxySet(proxyConfig) {
if proxyConfig.NoProxy != noProxyWildcard {
for _, v := range strings.Split(proxyConfig.NoProxy, ",") {
v = strings.TrimSpace(v)
errDomain := validation.DomainName(v, false)
_, _, errCIDR := net.ParseCIDR(v)
if errDomain != nil && errCIDR != nil {
return fmt.Errorf("invalid noProxy: %v", v)
}
}
}
}

return nil
}
1 change: 1 addition & 0 deletions pkg/controller/statusmanager/status_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ type StatusLevel int
const (
ClusterConfig StatusLevel = iota
OperatorConfig StatusLevel = iota
ProxyConfig StatusLevel = iota
PodDeployment StatusLevel = iota
maxStatusLevel StatusLevel = iota
)
Expand Down
13 changes: 13 additions & 0 deletions pkg/names/names.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package names

import "k8s.io/apimachinery/pkg/types"

// some names

// OperatorConfig is the name of the CRD that defines the complete
Expand All @@ -10,6 +12,9 @@ const OPERATOR_CONFIG = "cluster"
// and status object.
const CLUSTER_CONFIG = "cluster"

// PROXY_CONFIG is the name of the default proxy object.
const PROXY_CONFIG = "cluster"

// APPLIED_PREFIX is the prefix applied to the config maps
// where we store previously applied configuration
const APPLIED_PREFIX = "applied-"
Expand All @@ -31,3 +36,11 @@ const SERVICE_CA_CONFIGMAP = "openshift-service-ca"
// MULTUS_VALIDATING_WEBHOOK is the name of the ValidatingWebhookConfiguration for multus-admission-controller
// that is used in multus admission controller deployment
const MULTUS_VALIDATING_WEBHOOK = "multus.openshift.io"

// Proxy returns the namespaced name "cluster" in the
// default namespace.
func Proxy() types.NamespacedName {
return types.NamespacedName{
Name: PROXY_CONFIG,
}
}
File renamed without changes.
File renamed without changes.
Loading