Skip to content

Commit f7a7227

Browse files
committed
When a router is reloaded after a batch of route/ingress changes are
committed, haproxy sometimes fail to reload. This can happen if a new request to delete a route (and so delete the associated certificates) is processed when the haproxy router is reloading. The router does recover on subsequent reloads when the config changes are actually processed. The change here defers the deletes till commit time and ensures we only delete the certificates for the routes that are being processed as part of the current batchset.
1 parent fd83080 commit f7a7227

File tree

4 files changed

+80
-10
lines changed

4 files changed

+80
-10
lines changed

Diff for: pkg/router/template/certmanager.go

+52-10
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,25 @@ import (
1212
routeapi "github.com/openshift/origin/pkg/route/apis/route"
1313
)
1414

15+
// certificateFile represents a certificate file.
16+
type certificateFile struct {
17+
CertDir string
18+
ID string
19+
Contents []byte
20+
}
21+
22+
// certificateFileTag generates a certificate file tag/name. This is used to
23+
// index into the map of deleted certificates.
24+
func (cf certificateFile) Tag() string {
25+
return filepath.Join(cf.CertDir, cf.ID+".pem")
26+
}
27+
1528
// simpleCertificateManager is the default implementation of a certificateManager
1629
type simpleCertificateManager struct {
1730
cfg *certificateManagerConfig
1831
w certificateWriter
32+
33+
deletedCertificates map[string]certificateFile
1934
}
2035

2136
// newSimpleCertificateManager should be used to create a new cert manager. It will return an error
@@ -27,7 +42,7 @@ func newSimpleCertificateManager(cfg *certificateManagerConfig, w certificateWri
2742
if w == nil {
2843
return nil, fmt.Errorf("certificate manager requires a certificate writer")
2944
}
30-
return &simpleCertificateManager{cfg, w}, nil
45+
return &simpleCertificateManager{cfg, w, make(map[string]certificateFile, 0)}, nil
3146
}
3247

3348
// validateCertManagerConfig ensures that the key functions and directories are set as well as
@@ -81,6 +96,8 @@ func (cm *simpleCertificateManager) WriteCertificatesForConfig(config *ServiceAl
8196
buffer.Write([]byte(caCertObj.Contents))
8297
}
8398

99+
certFile := certificateFile{CertDir: cm.cfg.certDir, ID: certObj.ID}
100+
delete(cm.deletedCertificates, certFile.Tag())
84101
if err := cm.w.WriteCertificate(cm.cfg.certDir, certObj.ID, buffer.Bytes()); err != nil {
85102
return err
86103
}
@@ -92,6 +109,8 @@ func (cm *simpleCertificateManager) WriteCertificatesForConfig(config *ServiceAl
92109
destCert, ok := config.Certificates[destCertKey]
93110

94111
if ok {
112+
destCertFile := certificateFile{CertDir: cm.cfg.caCertDir, ID: destCert.ID}
113+
delete(cm.deletedCertificates, destCertFile.Tag())
95114
if err := cm.w.WriteCertificate(cm.cfg.caCertDir, destCert.ID, []byte(destCert.Contents)); err != nil {
96115
return err
97116
}
@@ -101,7 +120,7 @@ func (cm *simpleCertificateManager) WriteCertificatesForConfig(config *ServiceAl
101120
return nil
102121
}
103122

104-
// deleteCertificatesForConfig will delete all certificates for the ServiceAliasConfig
123+
// DeleteCertificatesForConfig will delete all certificates for the ServiceAliasConfig
105124
func (cm *simpleCertificateManager) DeleteCertificatesForConfig(config *ServiceAliasConfig) error {
106125
if config == nil {
107126
return nil
@@ -112,10 +131,8 @@ func (cm *simpleCertificateManager) DeleteCertificatesForConfig(config *ServiceA
112131
certObj, ok := config.Certificates[certKey]
113132

114133
if ok {
115-
err := cm.w.DeleteCertificate(cm.cfg.certDir, certObj.ID)
116-
if err != nil {
117-
return err
118-
}
134+
certFile := certificateFile{CertDir: cm.cfg.certDir, ID: certObj.ID}
135+
cm.deletedCertificates[certFile.Tag()] = certFile
119136
}
120137
}
121138

@@ -124,16 +141,41 @@ func (cm *simpleCertificateManager) DeleteCertificatesForConfig(config *ServiceA
124141
destCert, ok := config.Certificates[destCertKey]
125142

126143
if ok {
127-
err := cm.w.DeleteCertificate(cm.cfg.caCertDir, destCert.ID)
128-
if err != nil {
129-
return err
130-
}
144+
145+
destCertFile := certificateFile{CertDir: cm.cfg.caCertDir, ID: destCert.ID}
146+
cm.deletedCertificates[destCertFile.Tag()] = destCertFile
131147
}
132148
}
133149
}
134150
return nil
135151
}
136152

153+
// Commit applies any pending changes made to the certificateManager.
154+
func (cm *simpleCertificateManager) Commit() error {
155+
// Deletion of certificates that are being referenced in backends or
156+
// config is problematic in that the template router will not
157+
// reload because the config is invalid, so we _do_ need to "stage"
158+
// or commit the removals. Remove all the deleted certificates.
159+
for _, certFile := range cm.deletedCertificates {
160+
err := cm.w.DeleteCertificate(certFile.CertDir, certFile.ID)
161+
if err != nil {
162+
// TODO: Do we care if a file deletion failed?
163+
// Otherwise we will keep hitting this error on
164+
// every commit. Should we just ignore errors here?
165+
// Clean this up based on review comments.
166+
// FIXME: return err
167+
}
168+
}
169+
170+
cm.deletedCertificates = make(map[string]certificateFile, 0)
171+
172+
// If we decide to stage the certificate writes, we can flush the
173+
// write to the disk here. The tradeoff is storing a copy in memory
174+
// until we commit.
175+
176+
return nil
177+
}
178+
137179
// simpleCertificateWriter is the default implementation of a certificateWriter
138180
type simpleCertificateWriter struct{}
139181

Diff for: pkg/router/template/certmanager_test.go

+19
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package templaterouter
22

33
import (
44
"reflect"
5+
"sort"
56
"testing"
67

78
routeapi "github.com/openshift/origin/pkg/route/apis/route"
@@ -119,6 +120,7 @@ func TestCertManager(t *testing.T) {
119120
}
120121

121122
for k, tc := range testCases {
123+
certManager.Commit()
122124
fakeCertWriter.clear()
123125
err := certManager.WriteCertificatesForConfig(tc.cfg)
124126
if err != nil {
@@ -133,14 +135,31 @@ func TestCertManager(t *testing.T) {
133135
t.Errorf("Unexpected number of adds for %s occurred. Expected: %d Got: %d", k, len(tc.expectedAdds), len(fakeCertWriter.addedCerts))
134136
}
135137

138+
if 0 != len(fakeCertWriter.deletedCerts) {
139+
t.Errorf("Unexpected number of deletes prior to certManager.Commit() for %s occurred. Expected: 0 Got: %d", k, len(fakeCertWriter.deletedCerts))
140+
}
141+
142+
err = certManager.Commit()
143+
if err != nil {
144+
t.Fatalf("Unexpected error committing certs for service alias config for %s. Config: %v, err: %v", k, tc.cfg, err)
145+
}
146+
147+
if len(tc.expectedAdds) != len(fakeCertWriter.addedCerts) {
148+
t.Errorf("Unexpected number of adds for %s occurred. Expected: %d Got: %d", k, len(tc.expectedAdds), len(fakeCertWriter.addedCerts))
149+
}
150+
136151
if len(tc.expectedDeletes) != len(fakeCertWriter.deletedCerts) {
137152
t.Errorf("Unexpected number of deletes for %s occurred. Expected: %d Got: %d", k, len(tc.expectedDeletes), len(fakeCertWriter.deletedCerts))
138153
}
139154

155+
sort.Strings(tc.expectedAdds)
156+
sort.Strings(fakeCertWriter.addedCerts)
140157
if !reflect.DeepEqual(tc.expectedAdds, fakeCertWriter.addedCerts) {
141158
t.Errorf("Unexpected adds for %s, wanted: %v, got %v", k, tc.expectedAdds, fakeCertWriter.addedCerts)
142159
}
143160

161+
sort.Strings(tc.expectedDeletes)
162+
sort.Strings(fakeCertWriter.deletedCerts)
144163
if !reflect.DeepEqual(tc.expectedDeletes, fakeCertWriter.deletedCerts) {
145164
t.Errorf("Unexpected deletes for %s, wanted: %v, got %v", k, tc.expectedDeletes, fakeCertWriter.deletedCerts)
146165
}

Diff for: pkg/router/template/router.go

+7
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,13 @@ func (r *templateRouter) writeConfig() error {
394394
r.state[k] = cfg
395395
}
396396

397+
glog.V(4).Infof("Committing router certificate manager changes...")
398+
if err := r.certManager.Commit(); err != nil {
399+
return fmt.Errorf("error committing certificate changes: %v", err)
400+
}
401+
402+
glog.V(4).Infof("Router certificate manager config committed")
403+
397404
for path, template := range r.templates {
398405
file, err := os.Create(path)
399406
if err != nil {

Diff for: pkg/router/template/types.go

+2
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,8 @@ type certificateManager interface {
103103
WriteCertificatesForConfig(config *ServiceAliasConfig) error
104104
// DeleteCertificatesForConfig deletes all certificates for all ServiceAliasConfigs in config
105105
DeleteCertificatesForConfig(config *ServiceAliasConfig) error
106+
// Commit commits all the changes made to the certificateManager.
107+
Commit() error
106108
// CertificateWriter provides direct access to the underlying writer if required
107109
CertificateWriter() certificateWriter
108110
}

0 commit comments

Comments
 (0)