diff --git a/cmd/osm-controller/osm-controller.go b/cmd/osm-controller/osm-controller.go
index 0c302ec3bb..a99bea8ccb 100644
--- a/cmd/osm-controller/osm-controller.go
+++ b/cmd/osm-controller/osm-controller.go
@@ -5,6 +5,7 @@ import (
"encoding/json"
"flag"
"fmt"
+ "net/http"
"os"
"path"
"strings"
@@ -245,22 +246,34 @@ func main() {
events.GenericEventRecorder().FatalEvent(err, events.InitializationError, "Error initializing ADS server")
}
- // Initialize function and HTTP health probes
- funcProbes := []health.Probes{xdsServer}
- httpProbes := getHTTPHealthProbes()
-
- // Initialize the http server and start it
- httpServer := httpserver.NewHTTPServer(funcProbes, httpProbes, metricsstore.DefaultMetricsStore, constants.MetricsServerPort)
- httpServer.Start()
-
if err := createControllerManagerForOSMResources(certManager); err != nil {
events.GenericEventRecorder().FatalEvent(err, events.InitializationError, "Error creating controller manager to reconcile OSM resources")
}
- // Expose /debug endpoints and data only if the enableDebugServer flag is enabled
+ // Initialize OSM's http service server
+ httpServer := httpserver.NewHTTPServer(constants.OSMServicePort)
+
+ // Health/Liveness probes
+ funcProbes := []health.Probes{xdsServer}
+ httpServer.AddHandlers(map[string]http.Handler{
+ "/health/ready": health.ReadinessHandler(funcProbes, getHTTPHealthProbes()),
+ "/health/alive": health.LivenessHandler(funcProbes, getHTTPHealthProbes()),
+ })
+ // Metrics
+ httpServer.AddHandler("/metrics", metricsstore.DefaultMetricsStore.Handler())
+ // Version
+ httpServer.AddHandler("/version", version.GetVersionHandler())
+
+ // Start HTTP server
+ err = httpServer.Start()
+ if err != nil {
+ log.Fatal().Err(err).Msgf("Failed to start OSM metrics/probes HTTP server")
+ }
+
+ // Create DebugServer and start its config event listener.
+ // Listener takes care to start and stop the debug server as appropriate
debugConfig := debugger.NewDebugConfig(certDebugger, xdsServer, meshCatalog, kubeConfig, kubeClient, cfg, kubernetesClient)
- debugServerInterface := httpserver.NewDebugHTTPServer(debugConfig, constants.DebugPort)
- httpserver.RegisterDebugServer(debugServerInterface, cfg)
+ debugConfig.StartDebugServerConfigListener()
<-stop
log.Info().Msg("Goodbye!")
diff --git a/cmd/osm-controller/osm-controller_test.go b/cmd/osm-controller/osm-controller_test.go
index edbc70d852..dd77624b22 100644
--- a/cmd/osm-controller/osm-controller_test.go
+++ b/cmd/osm-controller/osm-controller_test.go
@@ -2,82 +2,17 @@ package main
import (
"context"
- "strconv"
"testing"
- "time"
-
- "github.com/pkg/errors"
tassert "github.com/stretchr/testify/assert"
- v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
testclient "k8s.io/client-go/kubernetes/fake"
"github.com/openservicemesh/osm/pkg/certificate/providers/tresor"
- "github.com/openservicemesh/osm/pkg/configurator"
"github.com/openservicemesh/osm/pkg/constants"
- "github.com/openservicemesh/osm/pkg/httpserver"
-)
-
-const (
- testOSMNamespace = "-test-osm-namespace-"
- testOSMConfigMapName = "-test-osm-config-map-"
)
-func toggleDebugServer(enable bool, kubeClient *testclient.Clientset) error {
- updatedConfigMap := v1.ConfigMap{
- ObjectMeta: metav1.ObjectMeta{
- Namespace: testOSMNamespace,
- Name: testOSMConfigMapName,
- },
- Data: map[string]string{
- "enable_debug_server": strconv.FormatBool(enable),
- },
- }
- _, err := kubeClient.CoreV1().ConfigMaps(testOSMNamespace).Update(context.TODO(), &updatedConfigMap, metav1.UpdateOptions{})
- return err
-}
-
-func TestDebugServer(t *testing.T) {
- assert := tassert.New(t)
-
- // set up a controller
- stop := make(chan struct{})
- kubeClient, _, cfg, err := setupComponents(testOSMNamespace, testOSMConfigMapName, false, stop)
- assert.NoError(err)
-
- fakeDebugServer := FakeDebugServer{0, 0, nil, false}
- httpserver.RegisterDebugServer(&fakeDebugServer, cfg)
-
- assert.Equal(0, fakeDebugServer.startCount)
- assert.Equal(0, fakeDebugServer.stopCount)
-
- // Start server
- err = toggleDebugServer(true, kubeClient)
- assert.NoError(err)
-
- assert.Eventually(func() bool {
- return fakeDebugServer.running == true
- }, 2*time.Second, 100*time.Millisecond)
- assert.Eventually(func() bool {
- return fakeDebugServer.startCount == 1
- }, 2*time.Second, 100*time.Millisecond)
- assert.Equal(0, fakeDebugServer.stopCount) // No eventually for an non-expected-to-change value
-
- // Stop it
- err = toggleDebugServer(false, kubeClient)
- assert.NoError(err)
-
- assert.Eventually(func() bool {
- return fakeDebugServer.running == false
- }, 2*time.Second, 100*time.Millisecond)
- assert.Eventually(func() bool {
- return fakeDebugServer.stopCount == 1
- }, 2*time.Second, 100*time.Millisecond)
- assert.Equal(1, fakeDebugServer.startCount) // No eventually for an non-expected-to-change value
-}
-
func TestCreateCABundleKubernetesSecret(t *testing.T) {
assert := tassert.New(t)
@@ -122,45 +57,3 @@ func TestJoinURL(t *testing.T) {
assert.Equal(result, ju.expectedOutput)
}
}
-
-type FakeDebugServer struct {
- stopCount int
- startCount int
- stopErr error
- running bool
-}
-
-func (f *FakeDebugServer) Stop() error {
- f.stopCount++
- if f.stopErr != nil {
- return errors.Errorf("Debug server error")
- }
- f.running = false
- return nil
-}
-
-func (f *FakeDebugServer) Start() {
- f.startCount++
- f.running = true
-}
-
-func setupComponents(namespace, configMapName string, initialDebugServerEnabled bool, stop chan struct{}) (*testclient.Clientset, v1.ConfigMap, configurator.Configurator, error) {
- kubeClient := testclient.NewSimpleClientset()
-
- defaultConfigMap := map[string]string{
- "enable_debug_server": strconv.FormatBool(initialDebugServerEnabled),
- }
- configMap := v1.ConfigMap{
- ObjectMeta: metav1.ObjectMeta{
- Namespace: namespace,
- Name: configMapName,
- },
- Data: defaultConfigMap,
- }
- _, err := kubeClient.CoreV1().ConfigMaps(namespace).Create(context.TODO(), &configMap, metav1.CreateOptions{})
- if err != nil {
- return kubeClient, configMap, nil, err
- }
- cfg := configurator.NewConfigurator(kubeClient, stop, namespace, configMapName)
- return kubeClient, configMap, cfg, nil
-}
diff --git a/pkg/constants/constants.go b/pkg/constants/constants.go
index d7d6f36045..0f31920879 100644
--- a/pkg/constants/constants.go
+++ b/pkg/constants/constants.go
@@ -60,8 +60,8 @@ const (
// InjectorWebhookPort is the port on which the sidecar injection webhook listens
InjectorWebhookPort = 9090
- // MetricsServerPort is the port on which OSM exposes its own metrics server
- MetricsServerPort = 9091
+ // OSMServicePort is the port on which OSM exposes HTTP paths for metrics and probes
+ OSMServicePort = 9091
//DebugPort is the port on which OSM exposes its debug server
DebugPort = 9092
diff --git a/pkg/debugger/certificate.go b/pkg/debugger/certificate.go
index e8a0a8f2d8..b8125213c6 100644
--- a/pkg/debugger/certificate.go
+++ b/pkg/debugger/certificate.go
@@ -10,7 +10,7 @@ import (
"github.com/openservicemesh/osm/pkg/certificate"
)
-func (ds debugConfig) getCertHandler() http.Handler {
+func (ds DebugConfig) getCertHandler() http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
certs := ds.certDebugger.ListIssuedCertificates()
diff --git a/pkg/debugger/certificate_test.go b/pkg/debugger/certificate_test.go
index 38758e28da..e47f9a99c7 100644
--- a/pkg/debugger/certificate_test.go
+++ b/pkg/debugger/certificate_test.go
@@ -18,7 +18,7 @@ func TestGetCertHandler(t *testing.T) {
mockCtrl := gomock.NewController(t)
mock := NewMockCertificateManagerDebugger(mockCtrl)
- ds := debugConfig{
+ ds := DebugConfig{
certDebugger: mock,
}
diff --git a/pkg/debugger/debugger_config_listener.go b/pkg/debugger/debugger_config_listener.go
new file mode 100644
index 0000000000..88c5955439
--- /dev/null
+++ b/pkg/debugger/debugger_config_listener.go
@@ -0,0 +1,54 @@
+package debugger
+
+import (
+ "github.com/openservicemesh/osm/pkg/announcements"
+ "github.com/openservicemesh/osm/pkg/constants"
+ "github.com/openservicemesh/osm/pkg/httpserver"
+ "github.com/openservicemesh/osm/pkg/kubernetes/events"
+)
+
+// StartDebugServerConfigListener registers a go routine to listen to configuration and configure debug server as needed
+func (d *DebugConfig) StartDebugServerConfigListener() {
+ // Subscribe to configuration updates
+ ch := events.GetPubSubInstance().Subscribe(
+ announcements.ConfigMapAdded,
+ announcements.ConfigMapDeleted,
+ announcements.ConfigMapUpdated)
+
+ // This is the Debug server
+ httpDebugServer := httpserver.NewHTTPServer(constants.DebugPort)
+ httpDebugServer.AddHandlers(d.GetHandlers())
+
+ // Run config listener
+ go func(cfgSubChannel chan interface{}, dConf *DebugConfig, httpServ *httpserver.HTTPServer) {
+ // Bootstrap after subscribing
+ started := false
+
+ if d.configurator.IsDebugServerEnabled() {
+ if err := httpDebugServer.Start(); err != nil {
+ log.Error().Err(err).Msgf("error starting debug server")
+ }
+ started = true
+ }
+
+ for {
+ <-cfgSubChannel
+ isDbgSrvEnabled := d.configurator.IsDebugServerEnabled()
+
+ if isDbgSrvEnabled && !started {
+ if err := httpDebugServer.Start(); err != nil {
+ log.Error().Err(err).Msgf("error starting debug server")
+ } else {
+ started = true
+ }
+ }
+ if !isDbgSrvEnabled && started {
+ if err := httpDebugServer.Stop(); err != nil {
+ log.Error().Err(err).Msgf("error stoping debug server")
+ } else {
+ started = false
+ }
+ }
+ }
+ }(ch, d, httpDebugServer)
+}
diff --git a/pkg/debugger/envoy.go b/pkg/debugger/envoy.go
index 9e3733c64d..816daf2d3a 100644
--- a/pkg/debugger/envoy.go
+++ b/pkg/debugger/envoy.go
@@ -11,7 +11,7 @@ import (
"github.com/openservicemesh/osm/pkg/certificate"
)
-func (ds debugConfig) getEnvoyConfig(pod *v1.Pod, cn certificate.CommonName, url string) string {
+func (ds DebugConfig) getEnvoyConfig(pod *v1.Pod, cn certificate.CommonName, url string) string {
log.Info().Msgf("Getting Envoy config for CN=%s, podIP=%s", cn, pod.Status.PodIP)
minPort := 16000
diff --git a/pkg/debugger/feature_flags.go b/pkg/debugger/feature_flags.go
index 2ada7b66d6..649c4fed57 100644
--- a/pkg/debugger/feature_flags.go
+++ b/pkg/debugger/feature_flags.go
@@ -8,7 +8,7 @@ import (
"github.com/openservicemesh/osm/pkg/featureflags"
)
-func (ds debugConfig) getFeatureFlags() http.Handler {
+func (ds DebugConfig) getFeatureFlags() http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if featureFlagsJSON, err := json.Marshal(featureflags.Features); err != nil {
log.Error().Err(err).Msgf("Error marshaling feature flags struct: %+v", featureflags.Features)
diff --git a/pkg/debugger/index.go b/pkg/debugger/index.go
index 12f6103587..fc824116a4 100644
--- a/pkg/debugger/index.go
+++ b/pkg/debugger/index.go
@@ -5,7 +5,7 @@ import (
"net/http"
)
-func (ds debugConfig) getDebugIndex(handlers map[string]http.Handler) http.Handler {
+func (ds DebugConfig) getDebugIndex(handlers map[string]http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html")
_, _ = fmt.Fprint(w, `
`)
diff --git a/pkg/debugger/namespace.go b/pkg/debugger/namespace.go
index 4dcef725d5..cc8c24e700 100644
--- a/pkg/debugger/namespace.go
+++ b/pkg/debugger/namespace.go
@@ -10,7 +10,7 @@ type namespaces struct {
Namespaces []string `json:"namespaces"`
}
-func (ds debugConfig) getMonitoredNamespacesHandler() http.Handler {
+func (ds DebugConfig) getMonitoredNamespacesHandler() http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var n namespaces
n.Namespaces = ds.meshCatalogDebugger.ListMonitoredNamespaces()
diff --git a/pkg/debugger/namespace_test.go b/pkg/debugger/namespace_test.go
index 867104ed1c..39a17d99de 100644
--- a/pkg/debugger/namespace_test.go
+++ b/pkg/debugger/namespace_test.go
@@ -16,7 +16,7 @@ func TestMonitoredNamespaceHandler(t *testing.T) {
mockCtrl := gomock.NewController(t)
mock := NewMockMeshCatalogDebugger(mockCtrl)
- ds := debugConfig{
+ ds := DebugConfig{
meshCatalogDebugger: mock,
}
monitoredNamespacesHandler := ds.getMonitoredNamespacesHandler()
diff --git a/pkg/debugger/policy.go b/pkg/debugger/policy.go
index a8276a2ae9..c14d763a68 100644
--- a/pkg/debugger/policy.go
+++ b/pkg/debugger/policy.go
@@ -20,7 +20,7 @@ type policies struct {
TrafficTargets []*target.TrafficTarget `json:"traffic_targets"`
}
-func (ds debugConfig) getOSMConfigHandler() http.Handler {
+func (ds DebugConfig) getOSMConfigHandler() http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
confJSON, err := ds.configurator.GetConfigMap()
if err != nil {
@@ -31,7 +31,7 @@ func (ds debugConfig) getOSMConfigHandler() http.Handler {
})
}
-func (ds debugConfig) getSMIPoliciesHandler() http.Handler {
+func (ds DebugConfig) getSMIPoliciesHandler() http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var p policies
p.TrafficSplits, p.WeightedServices, p.ServiceAccounts, p.RouteGroups, p.TrafficTargets = ds.meshCatalogDebugger.ListSMIPolicies()
diff --git a/pkg/debugger/policy_test.go b/pkg/debugger/policy_test.go
index 5621947319..89be41481d 100644
--- a/pkg/debugger/policy_test.go
+++ b/pkg/debugger/policy_test.go
@@ -22,7 +22,7 @@ func TestGetSMIPolicies(t *testing.T) {
mockCtrl := gomock.NewController(t)
mock := NewMockMeshCatalogDebugger(mockCtrl)
- ds := debugConfig{
+ ds := DebugConfig{
meshCatalogDebugger: mock,
}
diff --git a/pkg/debugger/port_forward.go b/pkg/debugger/port_forward.go
index 42c9964c83..b35fe212d2 100644
--- a/pkg/debugger/port_forward.go
+++ b/pkg/debugger/port_forward.go
@@ -30,7 +30,7 @@ type portForward struct {
Ready chan struct{}
}
-func (ds debugConfig) forwardPort(req portForward) {
+func (ds DebugConfig) forwardPort(req portForward) {
log.Info().Msgf("Start port forward to podIP=%s on PodPort=%d to LocalPort=%d", req.Pod.Status.PodIP, req.PodPort, req.LocalPort)
path := fmt.Sprintf("/api/v1/namespaces/%s/pods/%s/portforward", req.Pod.Namespace, req.Pod.Name)
hostIP := strings.TrimLeft(ds.kubeConfig.Host, "htps:/")
diff --git a/pkg/debugger/proxy.go b/pkg/debugger/proxy.go
index 5013fdc407..67494aede6 100644
--- a/pkg/debugger/proxy.go
+++ b/pkg/debugger/proxy.go
@@ -15,7 +15,7 @@ const (
proxyConfigQueryKey = "cfg"
)
-func (ds debugConfig) getProxies() http.Handler {
+func (ds DebugConfig) getProxies() http.Handler {
// This function is needed to convert the list of connected proxies to
// the type (map) required by the printProxies function.
listConnected := func() map[certificate.CommonName]time.Time {
@@ -59,7 +59,7 @@ func printProxies(w http.ResponseWriter, proxies map[certificate.CommonName]time
_, _ = fmt.Fprint(w, ``)
}
-func (ds debugConfig) getConfigDump(cn certificate.CommonName, w http.ResponseWriter) {
+func (ds DebugConfig) getConfigDump(cn certificate.CommonName, w http.ResponseWriter) {
pod, err := catalog.GetPodFromCertificate(cn, ds.kubeController)
if err != nil {
log.Error().Err(err).Msgf("Error getting Pod from certificate with CN=%s", cn)
@@ -69,7 +69,7 @@ func (ds debugConfig) getConfigDump(cn certificate.CommonName, w http.ResponseWr
_, _ = fmt.Fprintf(w, "%s", envoyConfig)
}
-func (ds debugConfig) getProxy(cn certificate.CommonName, w http.ResponseWriter) {
+func (ds DebugConfig) getProxy(cn certificate.CommonName, w http.ResponseWriter) {
pod, err := catalog.GetPodFromCertificate(cn, ds.kubeController)
if err != nil {
log.Error().Err(err).Msgf("Error getting Pod from certificate with CN=%s", cn)
diff --git a/pkg/debugger/server.go b/pkg/debugger/server.go
index a2b071c1c2..7c0f88c44f 100644
--- a/pkg/debugger/server.go
+++ b/pkg/debugger/server.go
@@ -12,7 +12,7 @@ import (
)
// GetHandlers implements DebugConfig interface and returns the rest of URLs and the handling functions.
-func (ds debugConfig) GetHandlers() map[string]http.Handler {
+func (ds DebugConfig) GetHandlers() map[string]http.Handler {
handlers := map[string]http.Handler{
"/debug/certs": ds.getCertHandler(),
"/debug/xds": ds.getXDSHandler(),
@@ -38,7 +38,7 @@ func (ds debugConfig) GetHandlers() map[string]http.Handler {
// NewDebugConfig returns an implementation of DebugConfig interface.
func NewDebugConfig(certDebugger CertificateManagerDebugger, xdsDebugger XDSDebugger, meshCatalogDebugger MeshCatalogDebugger, kubeConfig *rest.Config, kubeClient kubernetes.Interface, cfg configurator.Configurator, kubeController k8s.Controller) DebugConfig {
- return debugConfig{
+ return DebugConfig{
certDebugger: certDebugger,
xdsDebugger: xdsDebugger,
meshCatalogDebugger: meshCatalogDebugger,
diff --git a/pkg/debugger/types.go b/pkg/debugger/types.go
index a72c88ddbd..5d39240eda 100644
--- a/pkg/debugger/types.go
+++ b/pkg/debugger/types.go
@@ -1,7 +1,6 @@
package debugger
import (
- "net/http"
"time"
target "github.com/servicemeshinterface/smi-sdk-go/pkg/apis/access/v1alpha2"
@@ -20,8 +19,8 @@ import (
var log = logger.New("debugger")
-// debugConfig implements the DebugServer interface.
-type debugConfig struct {
+// DebugConfig implements the DebugServer interface.
+type DebugConfig struct {
certDebugger CertificateManagerDebugger
xdsDebugger XDSDebugger
meshCatalogDebugger MeshCatalogDebugger
@@ -60,9 +59,3 @@ type XDSDebugger interface {
// GetXDSLog returns a log of the XDS responses sent to Envoy proxies.
GetXDSLog() *map[certificate.CommonName]map[envoy.TypeURI][]time.Time
}
-
-// DebugConfig is the interface of the debug config for debug HTTP server
-type DebugConfig interface {
- // GetHandlers returns the HTTP handlers available for the debug server.
- GetHandlers() map[string]http.Handler
-}
diff --git a/pkg/debugger/xds.go b/pkg/debugger/xds.go
index 454b31081d..dd77e8fa6a 100644
--- a/pkg/debugger/xds.go
+++ b/pkg/debugger/xds.go
@@ -10,7 +10,7 @@ import (
"github.com/openservicemesh/osm/pkg/envoy"
)
-func (ds debugConfig) getXDSHandler() http.Handler {
+func (ds DebugConfig) getXDSHandler() http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
xdsLog := ds.xdsDebugger.GetXDSLog()
diff --git a/pkg/httpserver/debugger.go b/pkg/httpserver/debugger.go
deleted file mode 100644
index a7bbcfb320..0000000000
--- a/pkg/httpserver/debugger.go
+++ /dev/null
@@ -1,93 +0,0 @@
-package httpserver
-
-import (
- "context"
- "fmt"
- "net/http"
-
- "github.com/openservicemesh/osm/pkg/announcements"
- "github.com/openservicemesh/osm/pkg/configurator"
- "github.com/openservicemesh/osm/pkg/debugger"
- "github.com/openservicemesh/osm/pkg/kubernetes/events"
-)
-
-// DebugServer is the object wrapper for OSM's HTTP server class
-type DebugServer struct {
- Server *http.Server
-}
-
-// DebugServerInterface is the interface of the Debug HTTP server.
-type DebugServerInterface interface {
- Stop() error
- Start()
-}
-
-// RegisterDebugServer registers a go routine to listen to configuration and configure debug server as needed
-func RegisterDebugServer(dbgServerInterface DebugServerInterface, cfg configurator.Configurator) {
- // Subscribe to configuration updates
- ch := events.GetPubSubInstance().Subscribe(
- announcements.ConfigMapAdded,
- announcements.ConfigMapDeleted,
- announcements.ConfigMapUpdated)
-
- // Run config listener
- go func(cfgSubChannel chan interface{}, cf configurator.Configurator, dbgIf DebugServerInterface) {
- // Bootstrap after subscribing
- dbgSrvRunning := false
- if cfg.IsDebugServerEnabled() {
- dbgIf.Start()
- dbgSrvRunning = true
- }
-
- for {
- <-cfgSubChannel
- isDbgSrvEnabled := cfg.IsDebugServerEnabled()
-
- if isDbgSrvEnabled && !dbgSrvRunning {
- log.Debug().Msgf("Starting DBG server")
- dbgIf.Start()
- dbgSrvRunning = true
- } else if !isDbgSrvEnabled && dbgSrvRunning {
- log.Debug().Msgf("Stopping DBG server")
- err := dbgIf.Stop()
- if err != nil {
- log.Error().Msgf("Error stopping debug server: %v", err)
- continue
- }
- dbgSrvRunning = false
- }
- }
- }(ch, cfg, dbgServerInterface)
-}
-
-// NewDebugHTTPServer creates a new API Server for Debug
-func NewDebugHTTPServer(debugServer debugger.DebugConfig, apiPort int32) DebugServerInterface {
- return &DebugServer{
- Server: &http.Server{
- Addr: fmt.Sprintf(":%d", apiPort),
- Handler: NewHealthMux(debugServer.GetHandlers()),
- },
- }
-}
-
-// Start runs the Serve operations for the DebugServer http.server on a separate go routine context
-func (d *DebugServer) Start() {
- go func() {
- log.Info().Msgf("Starting Debug Server on %s", d.Server.Addr)
- if err := d.Server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
- events.GenericEventRecorder().FatalEvent(err, events.InitializationError,
- "Error starting Debug server")
- }
- }()
-}
-
-// Stop halts the DebugServer http.server
-func (d *DebugServer) Stop() error {
- ctx, cancel := context.WithTimeout(context.Background(), contextTimeoutDuration)
- defer cancel()
- if err := d.Server.Shutdown(ctx); err != nil {
- log.Error().Err(err).Msg("Unable to shutdown Debug server gracefully")
- return err
- }
- return nil
-}
diff --git a/pkg/httpserver/debugger_test.go b/pkg/httpserver/debugger_test.go
deleted file mode 100644
index 9d13c6c657..0000000000
--- a/pkg/httpserver/debugger_test.go
+++ /dev/null
@@ -1,57 +0,0 @@
-package httpserver
-
-import (
- "fmt"
- "io/ioutil"
- "net/http"
- "net/http/httptest"
- "testing"
-
- "github.com/golang/mock/gomock"
- tassert "github.com/stretchr/testify/assert"
-
- "github.com/openservicemesh/osm/pkg/debugger"
-)
-
-const (
- validRoutePath = "/debug/test1"
- responseBody = "OSM rules"
-)
-
-func TestNewDebugHTTPServer(t *testing.T) {
- assert := tassert.New(t)
-
- mockCtrl := gomock.NewController(t)
- mockDebugServer := debugger.NewMockDebugServer(mockCtrl)
- mockDebugServer.EXPECT().GetHandlers().Return(map[string]http.Handler{
- validRoutePath: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- _, _ = fmt.Fprint(w, responseBody)
- }),
- })
- debugServ := NewDebugHTTPServer(mockDebugServer, testPort).(*DebugServer)
- testDebug := &httptest.Server{
- Config: debugServ.Server,
- }
-
- type newDebugHTTPServerTest struct {
- routePath string
- expectedHTTPStatusCode int
- expectedResponseBody string
- }
-
- newDebugHTTPServerTests := []newDebugHTTPServerTest{
- {invalidRoutePath, http.StatusNotFound, "404 page not found\n"},
- {validRoutePath, http.StatusOK, responseBody},
- }
-
- for _, debugTest := range newDebugHTTPServerTests {
- req := httptest.NewRequest("GET", fmt.Sprintf("%s%s", url, debugTest.routePath), nil)
- w := httptest.NewRecorder()
- testDebug.Config.Handler.ServeHTTP(w, req)
- resp := w.Result()
- bodyBytes, _ := ioutil.ReadAll(resp.Body)
-
- assert.Equal(debugTest.expectedHTTPStatusCode, resp.StatusCode)
- assert.Equal(debugTest.expectedResponseBody, string(bodyBytes))
- }
-}
diff --git a/pkg/httpserver/httpserver.go b/pkg/httpserver/httpserver.go
index 81e058c663..e0e2b50711 100644
--- a/pkg/httpserver/httpserver.go
+++ b/pkg/httpserver/httpserver.go
@@ -2,16 +2,12 @@ package httpserver
import (
"context"
- "encoding/json"
"fmt"
"net/http"
"time"
- "github.com/openservicemesh/osm/pkg/health"
"github.com/openservicemesh/osm/pkg/kubernetes/events"
"github.com/openservicemesh/osm/pkg/logger"
- "github.com/openservicemesh/osm/pkg/metricsstore"
- "github.com/openservicemesh/osm/pkg/version"
)
const (
@@ -24,71 +20,85 @@ var (
// HTTPServer is the object wrapper for OSM's HTTP server class
type HTTPServer struct {
- server *http.Server
-}
-
-// NewHealthMux makes a new *http.ServeMux
-func NewHealthMux(handlers map[string]http.Handler) *http.ServeMux {
- router := http.NewServeMux()
- for url, handler := range handlers {
- router.Handle(url, handler)
- }
-
- return router
+ started bool
+ server *http.Server
+ httpServeMux *http.ServeMux // Used to restart the server once stopped
+ port uint16 // Used to restart the server once stopped
+ stopSyncChan chan struct{}
}
// NewHTTPServer creates a new API server
-func NewHTTPServer(probes []health.Probes, httpProbes []health.HTTPProbe, metricsStore *metricsstore.MetricsStore, apiPort int32) *HTTPServer {
- handlers := map[string]http.Handler{
- "/health/ready": health.ReadinessHandler(probes, httpProbes),
- "/health/alive": health.LivenessHandler(probes, httpProbes),
- "/metrics": metricsStore.Handler(),
- "/version": getVersionHandler(),
- }
+func NewHTTPServer(port uint16) *HTTPServer {
+ serverMux := http.NewServeMux()
return &HTTPServer{
+ started: false,
server: &http.Server{
- Addr: fmt.Sprintf(":%d", apiPort),
- Handler: NewHealthMux(handlers),
+ Addr: fmt.Sprintf(":%d", port),
+ Handler: serverMux,
},
+ httpServeMux: serverMux,
+ port: port,
+ stopSyncChan: make(chan struct{}),
}
}
-// Start runs the Serve operations for the http.server on a separate go routine context
-func (s *HTTPServer) Start() {
+// AddHandler adds an HTTP handlers for the given path on the HTTPServer
+// For changes to be effective, server requires restart
+func (s *HTTPServer) AddHandler(url string, handler http.Handler) {
+ s.httpServeMux.Handle(url, handler)
+}
+
+// AddHandlers covenience, multi-value AddHandler
+func (s *HTTPServer) AddHandlers(handlers map[string]http.Handler) {
+ for url, handler := range handlers {
+ s.AddHandler(url, handler)
+ }
+}
+
+// Start starts ListenAndServe on the http server.
+// If already started, does nothing
+func (s *HTTPServer) Start() error {
+ if s.started {
+ return nil
+ }
+
go func() {
log.Info().Msgf("Starting API Server on %s", s.server.Addr)
if err := s.server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
events.GenericEventRecorder().FatalEvent(err, events.InitializationError,
"Error starting HTTP server")
}
+ s.stopSyncChan <- struct{}{}
}()
+
+ s.started = true
+ return nil
}
-// Stop halts the http.server
+// Stop halts and resets the http server
+// If server is already stopped, does nothing
func (s *HTTPServer) Stop() error {
+ if !s.started {
+ return nil
+ }
+
ctx, cancel := context.WithTimeout(context.Background(), contextTimeoutDuration)
defer cancel()
if err := s.server.Shutdown(ctx); err != nil {
log.Error().Err(err).Msg("Unable to shutdown API server gracefully")
return err
}
- return nil
-}
-// getVersionHandler returns an HTTP handler that returns the version info
-func getVersionHandler() http.Handler {
- return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
- versionInfo := version.Info{
- Version: version.Version,
- BuildDate: version.BuildDate,
- GitCommit: version.GitCommit,
- }
+ // Since we want to free the server, if shutdown succeeded wait for ListenAndServe to return
+ <-s.stopSyncChan
- if jsonVersionInfo, err := json.Marshal(versionInfo); err != nil {
- log.Error().Err(err).Msgf("Error marshaling version info struct: %+v", versionInfo)
- } else {
- _, _ = fmt.Fprint(w, string(jsonVersionInfo))
- }
- })
+ // Free and reset the server, so it can be started again
+ s.started = false
+ s.server = &http.Server{
+ Addr: fmt.Sprintf(":%d", s.port),
+ Handler: s.httpServeMux,
+ }
+
+ return nil
}
diff --git a/pkg/httpserver/httpserver_test.go b/pkg/httpserver/httpserver_test.go
index 2026d84154..e7d20c6c9e 100644
--- a/pkg/httpserver/httpserver_test.go
+++ b/pkg/httpserver/httpserver_test.go
@@ -41,7 +41,14 @@ func TestNewHTTPServer(t *testing.T) {
testProbes := []health.Probes{mockProbe}
metricsStore := metricsstore.DefaultMetricsStore
- httpServ := NewHTTPServer(testProbes, nil, metricsStore, testPort)
+ httpServ := NewHTTPServer(testPort)
+
+ httpServ.AddHandlers(map[string]http.Handler{
+ "/health/ready": health.ReadinessHandler(testProbes, nil),
+ "/health/alive": health.LivenessHandler(testProbes, nil),
+ "/metrics": metricsStore.Handler(),
+ })
+
testServer := &httptest.Server{
Config: httpServ.server,
}
diff --git a/pkg/version/version.go b/pkg/version/version.go
index 9a59cb7996..fed937db5c 100644
--- a/pkg/version/version.go
+++ b/pkg/version/version.go
@@ -1,5 +1,15 @@
package version
+import (
+ "encoding/json"
+ "fmt"
+ "net/http"
+
+ "github.com/openservicemesh/osm/pkg/logger"
+)
+
+var log = logger.New("version")
+
// BuildDate is the date when the binary was built
var BuildDate string
@@ -20,3 +30,20 @@ type Info struct {
// BuildDate is the build date of the OSM Controller.
BuildDate string `json:"build_date,omitempty"`
}
+
+// GetVersionHandler returns an HTTP handler that returns the version info
+func GetVersionHandler() http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
+ versionInfo := Info{
+ Version: Version,
+ BuildDate: BuildDate,
+ GitCommit: GitCommit,
+ }
+
+ if jsonVersionInfo, err := json.Marshal(versionInfo); err != nil {
+ log.Error().Err(err).Msgf("Error marshaling version info struct: %+v", versionInfo)
+ } else {
+ _, _ = fmt.Fprint(w, string(jsonVersionInfo))
+ }
+ })
+}
diff --git a/tests/e2e/e2e_debug_server_test.go b/tests/e2e/e2e_debug_server_test.go
index e505a4a3d4..0bba5d1731 100644
--- a/tests/e2e/e2e_debug_server_test.go
+++ b/tests/e2e/e2e_debug_server_test.go
@@ -1,6 +1,7 @@
package e2e
import (
+ "fmt"
"strings"
"time"
@@ -57,36 +58,40 @@ var _ = OSMDescribe("Test Debug Server by toggling enableDebugServer",
Destination: controllerDest,
}
- By("Ensuring debug server is available when enableDebugServer is enabled")
+ iterations := 2
+ for i := 1; i <= iterations; i++ {
+ By(fmt.Sprintf("(%d/%d) Ensuring debug server is available when enableDebugServer is enabled", i, iterations))
- Expect(Td.UpdateOSMConfig("enable_debug_server", "true"))
+ Expect(Td.UpdateOSMConfig("enable_debug_server", "true"))
- cond := Td.WaitForRepeatedSuccess(func() bool {
- result := Td.HTTPRequest(req)
+ cond := Td.WaitForRepeatedSuccess(func() bool {
+ result := Td.HTTPRequest(req)
- if result.Err != nil || result.StatusCode != 200 {
- Td.T.Logf("> REST req failed (status: %d) %v", result.StatusCode, result.Err)
- return false
- }
- Td.T.Logf("> REST req succeeded: %d", result.StatusCode)
- return true
- }, 5 /*consecutive success threshold*/, 90*time.Second /*timeout*/)
- Expect(cond).To(BeTrue())
+ if result.Err != nil || result.StatusCode != 200 {
+ Td.T.Logf("> REST req failed (status: %d) %v", result.StatusCode, result.Err)
+ return false
+ }
+ Td.T.Logf("> REST req succeeded: %d", result.StatusCode)
+ return true
+ }, 5 /*consecutive success threshold*/, 90*time.Second /*timeout*/)
+ Expect(cond).To(BeTrue())
- By("Ensuring debug server is unavailable when enableDebugServer is disabled")
+ By(fmt.Sprintf("(%d/%d) Ensuring debug server is unavailable when enableDebugServer is disabled", i, iterations))
- Expect(Td.UpdateOSMConfig("enable_debug_server", "false"))
- cond = Td.WaitForRepeatedSuccess(func() bool {
- result := Td.HTTPRequest(req)
+ Expect(Td.UpdateOSMConfig("enable_debug_server", "false"))
- if result.Err == nil || !strings.Contains(result.Err.Error(), "command terminated with exit code 7 ") {
- Td.T.Logf("> REST req received unexpected response (status: %d) %v", result.StatusCode, result.Err)
- return false
- }
- Td.T.Logf("> REST req succeeded, got expected error: %v", result.Err)
- return true
- }, 5 /*consecutive success threshold*/, 90*time.Second /*timeout*/)
- Expect(cond).To(BeTrue())
+ cond = Td.WaitForRepeatedSuccess(func() bool {
+ result := Td.HTTPRequest(req)
+
+ if result.Err == nil || !strings.Contains(result.Err.Error(), "command terminated with exit code 7 ") {
+ Td.T.Logf("> REST req received unexpected response (status: %d) %v", result.StatusCode, result.Err)
+ return false
+ }
+ Td.T.Logf("> REST req succeeded, got expected error: %v", result.Err)
+ return true
+ }, 5 /*consecutive success threshold*/, 90*time.Second /*timeout*/)
+ Expect(cond).To(BeTrue())
+ }
})
})
})