1 package services
2
3 import (
4 "context"
5 "database/sql"
6 "errors"
7 "fmt"
8 "regexp"
9 "strconv"
10 "strings"
11
12 "slices"
13
14 "github.com/google/uuid"
15 "github.com/hashicorp/go-multierror"
16
17 sqlerr "edge-infra.dev/pkg/edge/api/apierror/sql"
18 "edge-infra.dev/pkg/edge/api/graph/mapper"
19 "edge-infra.dev/pkg/edge/api/graph/model"
20 sqlquery "edge-infra.dev/pkg/edge/api/sql"
21 "edge-infra.dev/pkg/edge/constants"
22 linkerd "edge-infra.dev/pkg/edge/linkerd"
23 "edge-infra.dev/pkg/lib/fog"
24 "edge-infra.dev/pkg/sds/ien/topology"
25 )
26
27 const (
28
29 AcRelayKey = "ac_relay"
30 PxeEnabledKey = "pxe_enabled"
31 BootstrapAckKey = "bootstrap_ack"
32 VpnEnabledKey = "vpn_enabled"
33 ThickPosKey = "thick_pos"
34 EgressGatewayEnabledKey = "egress_gateway_enabled"
35 GatewayRateLimitingEnabledKey = "gateway_rate_limiting_enabled"
36 UplinkRateLimitKey = "uplink_rate_limit"
37 DownlinkRateLimitKey = "downlink_rate_limit"
38 ClusterLogLevelKey = "cluster_log_level"
39 NamespaceLogLevelsKey = "namespace_log_levels"
40 AutoUpdateEnabledKey = "auto_update_enabled"
41 MaximumLanOutageHoursKey = "maximum_lan_outage_hours"
42 VncReadWriteAuthRequired = "vnc_read_write_auth_required"
43 VncReadAuthRequired = "vnc_read_auth_required"
44 )
45
46 var validClusterLogLevels = []string{
47 "DEBUG",
48 "INFO",
49 "NOTICE",
50 "WARNING",
51 "ERROR",
52 "CRITICAL",
53 "ALERT",
54 "EMERGENCY",
55 }
56
57 var (
58
59 defaultRateLimit = "100mbit"
60 logLevelDefault = "ERROR"
61 )
62
63 var (
64 ErrInvalidRateLimit = errors.New("invalid format for upload or download rate limits")
65 ErrClusterEdgeIDEmpty = errors.New("cluster edge id cannot be empty")
66 ErrInvalidMaximumLanOutageHours = errors.New("maximum lan outage duration must be at least 24 hours")
67 )
68
69 type ErrInvalidLogLevel struct {
70 LogLevel string
71 }
72
73 func (e *ErrInvalidLogLevel) Error() string {
74 return fmt.Sprintf("%s is invalid log level. Valid log levels: %s", e.LogLevel, strings.Join(validClusterLogLevels, ","))
75 }
76
77
78 type ClusterConfigService interface {
79 UpdateClusterConfig(ctx context.Context, clusterEdgeID string, updatedClusterConfig *model.UpdateClusterConfig) (*model.ClusterConfig, error)
80 GetClusterConfig(ctx context.Context, clusterEdgeID string) (*model.ClusterConfig, error)
81 }
82
83 type clusterConfigService struct {
84 SQLDB *sql.DB
85 }
86
87 func NewClusterConfigService(sqlDB *sql.DB) *clusterConfigService {
88 return &clusterConfigService{
89 SQLDB: sqlDB,
90 }
91 }
92
93 func (c *clusterConfigService) UpdateClusterConfig(ctx context.Context, clusterEdgeID string, inputCfg *model.UpdateClusterConfig) (*model.ClusterConfig, error) {
94 clusterCfg, err := c.GetClusterConfig(ctx, clusterEdgeID)
95 if err != nil {
96 return nil, err
97 }
98
99
100 mapUpdateClusterConfig(clusterCfg, inputCfg)
101
102
103 if err := validateClusterConfig(clusterCfg); err != nil {
104 return nil, err
105 }
106
107
108 clusterCfgValues, err := mapUpdateClusterConfigAttributes(inputCfg)
109 if err != nil {
110 return nil, err
111 }
112 return clusterCfg, c.upsertClusterConfigKeys(ctx, clusterEdgeID, clusterCfgValues)
113 }
114
115 func (c *clusterConfigService) GetClusterConfig(ctx context.Context, clusterEdgeID string) (*model.ClusterConfig, error) {
116 if clusterEdgeID == "" {
117 return nil, ErrClusterEdgeIDEmpty
118 }
119
120 rows, err := c.SQLDB.QueryContext(ctx, sqlquery.GetClusterConfig, clusterEdgeID)
121 if err != nil {
122 return nil, err
123 }
124 defer rows.Close()
125
126 return scanClusterConfigRows(clusterEdgeID, rows)
127 }
128
129
130 func (c *clusterConfigService) upsertClusterConfigKeys(ctx context.Context, clusterEdgeID string, clusterCfg map[string]string) error {
131 transaction, err := c.SQLDB.BeginTx(ctx, nil)
132 if err != nil {
133 return err
134 }
135
136 for key, value := range clusterCfg {
137 err = queryClusterConfig(ctx, transaction, sqlquery.UpdateClusterConfig, clusterEdgeID, key, value)
138 if err != nil {
139 return err
140 }
141 }
142
143 return transaction.Commit()
144 }
145
146 func queryClusterConfig(ctx context.Context, transaction *sql.Tx, query string, clusterID, key, val string) error {
147 if _, err := transaction.ExecContext(ctx, query, clusterID, key, val, uuid.NewString()); err != nil {
148 if rollbackErr := transaction.Rollback(); rollbackErr != nil {
149 return multierror.Append(err, rollbackErr)
150 }
151 return err
152 }
153 return nil
154 }
155
156 func mapUpdateClusterConfigAttributes(updateClusterConfig *model.UpdateClusterConfig) (map[string]string, error) {
157 output := map[string]string{}
158
159 if updateClusterConfig.AcRelay != nil {
160 output[AcRelayKey] = strconv.FormatBool(*updateClusterConfig.AcRelay)
161 }
162
163 if updateClusterConfig.PxeEnabled != nil {
164 output[PxeEnabledKey] = strconv.FormatBool(*updateClusterConfig.PxeEnabled)
165 }
166
167 if updateClusterConfig.BootstrapAck != nil {
168 output[BootstrapAckKey] = strconv.FormatBool(*updateClusterConfig.BootstrapAck)
169 }
170
171 if updateClusterConfig.VpnEnabled != nil {
172 output[VpnEnabledKey] = strconv.FormatBool(*updateClusterConfig.VpnEnabled)
173 }
174
175 if updateClusterConfig.ThickPos != nil {
176 output[ThickPosKey] = strconv.FormatBool(*updateClusterConfig.ThickPos)
177 }
178
179 if updateClusterConfig.EgressGatewayEnabled != nil {
180 output[EgressGatewayEnabledKey] = strconv.FormatBool(*updateClusterConfig.EgressGatewayEnabled)
181 }
182
183 if updateClusterConfig.GatewayRateLimitingEnabled != nil {
184 output[GatewayRateLimitingEnabledKey] = strconv.FormatBool(*updateClusterConfig.GatewayRateLimitingEnabled)
185 }
186
187 if updateClusterConfig.UplinkRateLimit != nil {
188 output[UplinkRateLimitKey] = *updateClusterConfig.UplinkRateLimit
189 }
190
191 if updateClusterConfig.DownlinkRateLimit != nil {
192 output[DownlinkRateLimitKey] = *updateClusterConfig.DownlinkRateLimit
193 }
194
195 if updateClusterConfig.ClusterLogLevel != nil {
196 output[ClusterLogLevelKey] = *updateClusterConfig.ClusterLogLevel
197 }
198
199 if updateClusterConfig.NamespaceLogLevels != nil {
200 namespaceLogLevelsJSONStr, err := mapper.NLLPToJSON(updateClusterConfig.NamespaceLogLevels)
201 if err != nil {
202 return nil, fmt.Errorf("Error when marshaling to a string in mapUpdatedClusterConfigAttributes: %s", err)
203 }
204
205 output[NamespaceLogLevelsKey] = namespaceLogLevelsJSONStr
206 }
207
208 if updateClusterConfig.MaximumLanOutageHours != nil {
209 output[MaximumLanOutageHoursKey] = strconv.FormatInt(int64(*updateClusterConfig.MaximumLanOutageHours), 10)
210 }
211
212
213
214 if updateClusterConfig.LinkerdIdentityIssuerCertDuration != nil {
215 output[constants.LinkerdIdentityIssuerCertDuration] = strconv.FormatInt(int64(*updateClusterConfig.LinkerdIdentityIssuerCertDuration), 10)
216 }
217
218
219 if updateClusterConfig.LinkerdIdentityIssuerCertRenewBefore != nil {
220 output[constants.LinkerdIdentityIssuerCertRenewBefore] = strconv.FormatInt(int64(*updateClusterConfig.LinkerdIdentityIssuerCertRenewBefore), 10)
221 }
222
223 if updateClusterConfig.AutoUpdateEnabled != nil {
224 output[AutoUpdateEnabledKey] = strconv.FormatBool(*updateClusterConfig.AutoUpdateEnabled)
225 }
226
227 if updateClusterConfig.VncReadWriteAuthRequired != nil {
228 output[VncReadWriteAuthRequired] = strconv.FormatBool(*updateClusterConfig.VncReadWriteAuthRequired)
229 }
230
231 if updateClusterConfig.VncReadAuthRequired != nil {
232 output[VncReadAuthRequired] = strconv.FormatBool(*updateClusterConfig.VncReadAuthRequired)
233 }
234
235 return output, nil
236 }
237
238 func scanClusterConfigRows(clusterEdgeID string, rows *sql.Rows) (*model.ClusterConfig, error) {
239 clusterConfig := defaultClusterConfig(clusterEdgeID)
240
241 for rows.Next() {
242 var clusterConfigEdgeID, configKey, configValue string
243 if err := rows.Scan(&clusterConfigEdgeID, &clusterConfig.ClusterEdgeID, &configKey, &configValue); err != nil {
244 return nil, err
245 }
246 err := insertValueIntoClusterConfig(clusterConfig, configKey, configValue)
247 if err != nil {
248 return nil, err
249 }
250 }
251 if err := rows.Err(); err != nil {
252 return nil, sqlerr.Wrap(err)
253 }
254
255 return clusterConfig, nil
256 }
257
258
259 func insertValueIntoClusterConfig(clusterConfig *model.ClusterConfig, configKey, configValue string) error {
260 var err error
261 switch configKey {
262 case AcRelayKey:
263 clusterConfig.AcRelay, err = strconv.ParseBool(configValue)
264 if err != nil {
265 return err
266 }
267 case PxeEnabledKey:
268 clusterConfig.PxeEnabled, err = strconv.ParseBool(configValue)
269 if err != nil {
270 return err
271 }
272 case BootstrapAckKey:
273 clusterConfig.BootstrapAck, err = strconv.ParseBool(configValue)
274 if err != nil {
275 return err
276 }
277 case VpnEnabledKey:
278 clusterConfig.VpnEnabled, err = strconv.ParseBool(configValue)
279 if err != nil {
280 return err
281 }
282 case ThickPosKey:
283 clusterConfig.ThickPos, err = strconv.ParseBool(configValue)
284 if err != nil {
285 return err
286 }
287 case EgressGatewayEnabledKey:
288 clusterConfig.EgressGatewayEnabled, err = strconv.ParseBool(configValue)
289 if err != nil {
290 return err
291 }
292 case GatewayRateLimitingEnabledKey:
293 clusterConfig.GatewayRateLimitingEnabled, err = strconv.ParseBool(configValue)
294 if err != nil {
295 return err
296 }
297 case UplinkRateLimitKey:
298 clusterConfig.UplinkRateLimit = configValue
299 case DownlinkRateLimitKey:
300 clusterConfig.DownlinkRateLimit = configValue
301 case ClusterLogLevelKey:
302 clusterConfig.ClusterLogLevel = configValue
303 case NamespaceLogLevelsKey:
304 convertedPayload, err := mapper.JSONtoNLL(configValue)
305 if err != nil {
306 return err
307 }
308 clusterConfig.NamespaceLogLevels = convertedPayload
309 case MaximumLanOutageHoursKey:
310 v, err := strconv.ParseInt(configValue, 10, 64)
311 if err != nil {
312 return err
313 }
314 clusterConfig.MaximumLanOutageHours = int(v)
315
316
317 case constants.LinkerdIdentityIssuerCertDuration:
318 v, err := strconv.ParseInt(configValue, 10, 64)
319 if err != nil {
320 return err
321 }
322 clusterConfig.LinkerdIdentityIssuerCertDuration = int(v)
323
324
325 case constants.LinkerdIdentityIssuerCertRenewBefore:
326 v, err := strconv.ParseInt(configValue, 10, 64)
327 if err != nil {
328 return err
329 }
330 clusterConfig.LinkerdIdentityIssuerCertRenewBefore = int(v)
331 case AutoUpdateEnabledKey:
332 clusterConfig.AutoUpdateEnabled, err = strconv.ParseBool(configValue)
333 if err != nil {
334 return err
335 }
336 case VncReadWriteAuthRequired:
337 val, err := strconv.ParseBool(configValue)
338 if err != nil {
339 return err
340 }
341 clusterConfig.VncReadWriteAuthRequired = &val
342 case VncReadAuthRequired:
343 val, err := strconv.ParseBool(configValue)
344 if err != nil {
345 return err
346 }
347 clusterConfig.VncReadAuthRequired = &val
348 default:
349
350 log := fog.New(fog.WithLevel(1)).WithName("cluster config service")
351
352
353 msg := fmt.Errorf("unknown cluster config key %s", configKey)
354 log.Error(msg, "ensure the new key is added to the cluster configuration API")
355 return nil
356 }
357 return nil
358 }
359
360
361 func validateClusterConfig(cfg *model.ClusterConfig) error {
362 if cfg == nil {
363 return nil
364 }
365
366 if err := validateRateLimit(&cfg.UplinkRateLimit); err != nil {
367 return err
368 }
369 if err := validateRateLimit(&cfg.DownlinkRateLimit); err != nil {
370 return err
371 }
372
373 if err := validateLogLevel(cfg.ClusterLogLevel); err != nil {
374 return err
375 }
376
377 if err := validateNamespaceLogLevel(cfg.NamespaceLogLevels); err != nil {
378 return err
379 }
380
381 if err := validateMaximumLanOutageHours(cfg.MaximumLanOutageHours); err != nil {
382 return err
383 }
384
385
386
387 if err := validateLinkerdIdentityIssuerCertTimes(cfg.LinkerdIdentityIssuerCertDuration, cfg.LinkerdIdentityIssuerCertRenewBefore); err != nil {
388 return err
389 }
390
391 return nil
392 }
393
394
395
396 func validateRateLimit(rateLimit *string) error {
397 if rateLimit == nil {
398 return nil
399 }
400
401 rateLimitRegExp, err := regexp.Compile("^[1-9][0-9]*([kK]bit$|[mM]bit$)")
402 if err != nil {
403 return err
404 }
405 if !rateLimitRegExp.MatchString(*rateLimit) {
406 return ErrInvalidRateLimit
407 }
408
409 return nil
410 }
411
412 func validateNamespaceLogLevel(namespaceLogLevels []*model.NamespaceLogLevel) error {
413 if namespaceLogLevels == nil {
414 return nil
415 }
416 for _, entry := range namespaceLogLevels {
417 if err := validateLogLevel(entry.Level); err != nil {
418 return err
419 }
420 }
421 return nil
422 }
423
424
425 func validateLogLevel(level string) error {
426 levelExists := slices.Contains(validClusterLogLevels, strings.ToUpper(level))
427 if !levelExists {
428 return &ErrInvalidLogLevel{LogLevel: level}
429 }
430 return nil
431 }
432
433
434
435 func validateLinkerdIdentityIssuerCertTimes(duration, renewBefore int) error {
436 if duration <= 0 || renewBefore <= 0 {
437 return topology.ErrInvalidCertificateDurationOrRenewBefore
438 }
439 if duration <= renewBefore {
440 return topology.ErrInvalidCertificateRenewBefore
441 }
442 return nil
443 }
444
445 func validateMaximumLanOutageHours(maxLanOutageHours int) error {
446 if maxLanOutageHours < 24 {
447 return ErrInvalidMaximumLanOutageHours
448 }
449 return nil
450 }
451
452
453 func mapUpdateClusterConfig(clusterCfg *model.ClusterConfig, updateClusterCfg *model.UpdateClusterConfig) {
454 if updateClusterCfg.AcRelay != nil {
455 clusterCfg.AcRelay = *updateClusterCfg.AcRelay
456 }
457 if updateClusterCfg.BootstrapAck != nil {
458 clusterCfg.BootstrapAck = *updateClusterCfg.BootstrapAck
459 }
460 if updateClusterCfg.VpnEnabled != nil {
461 clusterCfg.VpnEnabled = *updateClusterCfg.VpnEnabled
462 }
463 if updateClusterCfg.PxeEnabled != nil {
464 clusterCfg.PxeEnabled = *updateClusterCfg.PxeEnabled
465 }
466 if updateClusterCfg.ThickPos != nil {
467 clusterCfg.ThickPos = *updateClusterCfg.ThickPos
468 }
469 if updateClusterCfg.EgressGatewayEnabled != nil {
470 clusterCfg.EgressGatewayEnabled = *updateClusterCfg.EgressGatewayEnabled
471 }
472 if updateClusterCfg.GatewayRateLimitingEnabled != nil {
473 clusterCfg.GatewayRateLimitingEnabled = *updateClusterCfg.GatewayRateLimitingEnabled
474 }
475 if updateClusterCfg.UplinkRateLimit != nil {
476 clusterCfg.UplinkRateLimit = *updateClusterCfg.UplinkRateLimit
477 }
478 if updateClusterCfg.DownlinkRateLimit != nil {
479 clusterCfg.DownlinkRateLimit = *updateClusterCfg.DownlinkRateLimit
480 }
481 if updateClusterCfg.ClusterLogLevel != nil {
482 clusterCfg.ClusterLogLevel = *updateClusterCfg.ClusterLogLevel
483 }
484 if updateClusterCfg.NamespaceLogLevels != nil {
485 convertedNamespaceLogLevel := mapper.NLLPToNLL(updateClusterCfg.NamespaceLogLevels)
486 clusterCfg.NamespaceLogLevels = convertedNamespaceLogLevel
487 }
488 if updateClusterCfg.MaximumLanOutageHours != nil {
489 clusterCfg.MaximumLanOutageHours = *updateClusterCfg.MaximumLanOutageHours
490 }
491
492
493 if updateClusterCfg.LinkerdIdentityIssuerCertDuration != nil {
494 clusterCfg.LinkerdIdentityIssuerCertDuration = *updateClusterCfg.LinkerdIdentityIssuerCertDuration
495 }
496
497
498 if updateClusterCfg.LinkerdIdentityIssuerCertRenewBefore != nil {
499 clusterCfg.LinkerdIdentityIssuerCertRenewBefore = *updateClusterCfg.LinkerdIdentityIssuerCertRenewBefore
500 }
501 if updateClusterCfg.AutoUpdateEnabled != nil {
502 clusterCfg.AutoUpdateEnabled = *updateClusterCfg.AutoUpdateEnabled
503 }
504 if updateClusterCfg.VncReadWriteAuthRequired != nil {
505 clusterCfg.VncReadWriteAuthRequired = updateClusterCfg.VncReadWriteAuthRequired
506 }
507 if updateClusterCfg.VncReadAuthRequired != nil {
508 clusterCfg.VncReadAuthRequired = updateClusterCfg.VncReadAuthRequired
509 }
510 }
511
512
513 func defaultClusterConfig(clusterEdgeID string) *model.ClusterConfig {
514 return &model.ClusterConfig{
515 ClusterEdgeID: clusterEdgeID,
516 AcRelay: false,
517 VpnEnabled: false,
518 BootstrapAck: true,
519 PxeEnabled: false,
520 ThickPos: true,
521 EgressGatewayEnabled: false,
522 GatewayRateLimitingEnabled: false,
523 UplinkRateLimit: defaultRateLimit,
524 DownlinkRateLimit: defaultRateLimit,
525 ClusterLogLevel: logLevelDefault,
526 NamespaceLogLevels: make([]*model.NamespaceLogLevel, 0),
527 MaximumLanOutageHours: int(linkerd.DefaultThinPosIdentityIssuerCertificateDurationHours),
528
529 LinkerdIdentityIssuerCertDuration: int(linkerd.DefaultThinPosIdentityIssuerCertificateDurationHours),
530 LinkerdIdentityIssuerCertRenewBefore: int(linkerd.DefaultThinPosIdentityIssuerCertificateRenewBeforeHours),
531 VncReadWriteAuthRequired: nil,
532 VncReadAuthRequired: nil,
533 }
534 }
535
View as plain text