1 package requestservice
2
3 import (
4 "context"
5 "database/sql"
6 "errors"
7 "fmt"
8 "slices"
9
10 "github.com/hashicorp/go-version"
11
12 "edge-infra.dev/pkg/lib/fog"
13 "edge-infra.dev/pkg/sds/emergencyaccess/eaconst"
14 "edge-infra.dev/pkg/sds/emergencyaccess/msgdata"
15 "edge-infra.dev/pkg/sds/emergencyaccess/types"
16 )
17
18 type versionsMap map[string]eaconst.MessageVersion
19
20 var (
21 defaultEdgeOSToMessageVersion = versionsMap{
22
23 "0.0": eaconst.MessageVersion1_0,
24
25 "1.16": eaconst.MessageVersion2_0,
26 }
27 )
28
29 type versionMapping struct {
30 edgeOS *version.Version
31 message *version.Version
32 }
33
34 func messageVersionMappings(versions versionsMap) (mappings []versionMapping, err error) {
35 for k, v := range versions {
36 edgeOSVersion, err := version.NewVersion(k)
37 if err != nil {
38 return nil, err
39 }
40 messageVersion, err := version.NewVersion(string(v))
41 if err != nil {
42 return nil, err
43 }
44 mappings = append(mappings, versionMapping{
45 edgeOS: edgeOSVersion,
46 message: messageVersion,
47 })
48 }
49 slices.SortFunc(mappings, func(a, b versionMapping) int {
50
51 return b.edgeOS.Compare(a.edgeOS)
52 })
53 return mappings, nil
54 }
55
56 type RequestService struct {
57 db *sql.DB
58
59
60
61
62
63 versions []versionMapping
64 versionCache versionCache
65 minimumVersion *version.Version
66 }
67
68 func New(db *sql.DB) (*RequestService, error) {
69 versionMappings, err := messageVersionMappings(defaultEdgeOSToMessageVersion)
70 if err != nil {
71 return nil, fmt.Errorf("error failed to create message version mappings: %w", err)
72 }
73 minimumVersion, err := version.NewVersion(string(eaconst.MinimumSupportedMessageVersion))
74 if err != nil {
75 return nil, fmt.Errorf("error failed to parse minimum supported message version: %w", err)
76 }
77 return &RequestService{
78 db: db,
79 minimumVersion: minimumVersion,
80 versions: versionMappings,
81 versionCache: versionCache{
82 cache: make(map[types.Target]*version.Version),
83 expiration: defaultExpiration,
84 },
85 }, nil
86 }
87
88 type Config struct {
89 Target types.Target
90 }
91
92 func (rs *RequestService) CreateRequest(ctx context.Context, payload string, config Config) (request msgdata.Request, err error) {
93 messageVersion, err := rs.getMessageVersion(ctx, config.Target)
94 if err != nil {
95 return nil, fmt.Errorf("error getting compatible message version for target: %w", err)
96 }
97 return newRequest(payload, messageVersion)
98 }
99
100 func (rs *RequestService) getMessageVersion(ctx context.Context, target types.Target) (messageVersion *version.Version, err error) {
101 log := fog.FromContext(ctx).WithValues(
102 "projectID", target.Projectid,
103 "bannerID", target.Bannerid,
104 "storeID", target.Storeid,
105 "terminalID", target.Terminalid,
106 )
107 ctx = fog.IntoContext(ctx, log)
108
109 messageVersion, ok := rs.versionCache.Get(target)
110 if ok {
111 return messageVersion, nil
112 }
113 rows := rs.db.QueryRowContext(ctx, getEdgeOSVersionQuery, target.StoreID(), target.TerminalID())
114 var edgeOSVersion string
115 err = rows.Scan(&edgeOSVersion)
116
117
118 if err == sql.ErrNoRows {
119 log.Info("Could not find a target edgeOS version. Attempting a request with minimum supported message version", "messageVersion", rs.minimumVersion.Original())
120 messageVersion = rs.minimumVersion
121 rs.versionCache.Insert(target, messageVersion)
122 return messageVersion, nil
123 }
124 if err != nil {
125 return nil, fmt.Errorf("error scanning edgeOS version results: %w", err)
126 }
127 log.Info("Target edgeOS version queried", "edgeOSVersion", edgeOSVersion)
128 messageVersion, err = rs.pickMessageVersion(ctx, edgeOSVersion)
129 if err != nil {
130 return nil, fmt.Errorf("error picking message version: %w", err)
131 }
132 rs.versionCache.Insert(target, messageVersion)
133 return messageVersion, nil
134 }
135
136 func (rs *RequestService) pickMessageVersion(ctx context.Context, edgeOSVersion string) (messageVersion *version.Version, err error) {
137 log := fog.FromContext(ctx)
138
139 targetVersion, err := version.NewVersion(edgeOSVersion)
140 if err != nil {
141 log.Error(err,
142 "Target edgeOS version could not be parsed. Attempting a request with minimum supported message version",
143 "edgeOSVersion", edgeOSVersion,
144 "messageVersion", rs.minimumVersion.Original(),
145 )
146 return rs.minimumVersion, nil
147 }
148
149
150
151
152
153 for _, v := range rs.versions {
154 if targetVersion.GreaterThanOrEqual(v.edgeOS) {
155 return v.message, nil
156 }
157 }
158 return nil, errors.New("failed to find matching message version for target edge os version")
159 }
160
161 func newRequest(payload string, messageVersion *version.Version) (req msgdata.Request, err error) {
162 switch messageVersion.Original() {
163 case string(eaconst.MessageVersion1_0):
164 req, err = msgdata.NewV1_0Request(payload)
165 case string(eaconst.MessageVersion2_0):
166 req, err = msgdata.NewV2_0Request(payload)
167 default:
168 return nil, fmt.Errorf("unsupported message version %s", messageVersion.Original())
169 }
170 if err != nil {
171 return nil, fmt.Errorf("failed to create v%s request: %w", messageVersion.Original(), err)
172 }
173 return req, nil
174 }
175
View as plain text