-
Notifications
You must be signed in to change notification settings - Fork 59
/
common.go
498 lines (454 loc) · 18.8 KB
/
common.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
// Package events have the functionality of
// - Create Event Subscription
// - Delete Event Subscription
// - Get Event Subscription
// - Post Event Subscription to destination
// - Post TestEvent (SubmitTestEvent)
// and corresponding unit test cases
package events
import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"runtime"
"strings"
"time"
"github.com/ODIM-Project/ODIM/lib-dmtf/model"
"github.com/ODIM-Project/ODIM/lib-utilities/common"
"github.com/ODIM-Project/ODIM/lib-utilities/errors"
l "github.com/ODIM-Project/ODIM/lib-utilities/logs"
taskproto "github.com/ODIM-Project/ODIM/lib-utilities/proto/task"
"github.com/ODIM-Project/ODIM/lib-utilities/response"
errResponse "github.com/ODIM-Project/ODIM/lib-utilities/response"
"github.com/ODIM-Project/ODIM/lib-utilities/services"
"github.com/ODIM-Project/ODIM/svc-events/evcommon"
"github.com/ODIM-Project/ODIM/svc-events/evmodel"
"github.com/ODIM-Project/ODIM/svc-events/evresponse"
"gopkg.in/go-playground/validator.v9"
)
// ExternalInterfaces struct to inject the pmb client function into the handlers
type ExternalInterfaces struct {
External
DB
}
var (
// UpdateTaskService function pointer for calling the files
UpdateTaskService = services.UpdateTask
// IOUtilReadAllFunc function pointer for calling the files
IOUtilReadAllFunc = ioutil.ReadAll
)
// External struct to inject the contact external function into the handlers
type External struct {
ContactClient func(context.Context, string, string, string, string, interface{}, map[string]string) (*http.Response, error)
Auth func(context.Context, string, []string, []string) (response.RPC, error)
CreateTask func(context.Context, string) (string, error)
UpdateTask func(context.Context, common.TaskData) error
CreateChildTask func(context.Context, string, string) (string, error)
}
// DB struct to inject the contact DB function into the handlers
type DB struct {
GetSessionUserName func(ctx context.Context, sessionToken string) (string, error)
GetEvtSubscriptions func(string) ([]evmodel.SubscriptionResource, error)
SaveEventSubscription func(evmodel.SubscriptionResource) error
GetPluginData func(string) (*common.Plugin, *errors.Error)
GetDeviceSubscriptions func(string) (*common.DeviceSubscription, error)
GetTarget func(string) (*common.Target, error)
GetAllKeysFromTable func(string) ([]string, error)
GetAllFabrics func() ([]string, error)
GetAllMatchingDetails func(string, string, common.DbType) ([]string, *errors.Error)
UpdateDeviceSubscriptionLocation func(common.DeviceSubscription) error
GetFabricData func(string) (evmodel.Fabric, error)
DeleteEvtSubscription func(string) error
DeleteDeviceSubscription func(hostIP string) error
UpdateEventSubscription func(evmodel.SubscriptionResource) error
SaveUndeliveredEvents func(string, []byte) error
SaveDeviceSubscription func(common.DeviceSubscription) error
GetUndeliveredEvents func(string) (string, error)
DeleteUndeliveredEvents func(string) error
GetUndeliveredEventsFlag func(string) (bool, error)
SetUndeliveredEventsFlag func(string) error
DeleteUndeliveredEventsFlag func(string) error
GetAggregateData func(string) (evmodel.Aggregate, error)
SaveAggregateSubscription func(aggregateId string, hostIP []string) error
GetAggregateHosts func(aggregateIP string) ([]string, error)
UpdateAggregateHosts func(aggregateId string, hostIP []string) error
GetAggregateList func(hostIP string) ([]string, error)
GetUndeliveredEventsKeyList func(string, string, common.DbType, int) ([]string, int, *errors.Error)
}
// fillTaskData is to fill task information in TaskData struct
func fillTaskData(taskID, targetURI, request string, resp errResponse.RPC, taskState string, taskStatus string, percentComplete int32, httpMethod string) common.TaskData {
return common.TaskData{
TaskID: taskID,
TargetURI: targetURI,
Response: resp,
TaskRequest: request,
TaskState: taskState,
TaskStatus: taskStatus,
PercentComplete: percentComplete,
HTTPMethod: httpMethod,
}
}
// UpdateTaskData update the task with the given data
func UpdateTaskData(ctx context.Context, taskData common.TaskData) error {
respBody, _ := json.Marshal(taskData.Response.Body)
finalResponse, _ := json.Marshal(taskData.FinalResponse)
payLoad := &taskproto.Payload{
HTTPHeaders: taskData.Response.Header,
HTTPOperation: taskData.HTTPMethod,
JSONBody: taskData.TaskRequest,
StatusCode: taskData.Response.StatusCode,
TargetURI: taskData.TargetURI,
ResponseBody: respBody,
FinalResponseBody: finalResponse,
}
err := UpdateTaskService(ctx, taskData.TaskID, taskData.TaskState, taskData.TaskStatus,
taskData.PercentComplete, payLoad, time.Now())
if err != nil && (err.Error() == common.Cancelling) {
// We cant do anything here as the task has done it work completely, we cant reverse it.
//Unless if we can do opposite/reverse action for delete server which is add server.
UpdateTaskService(ctx, taskData.TaskID, common.Cancelled, taskData.TaskStatus, taskData.PercentComplete, payLoad, time.Now())
if taskData.PercentComplete == 0 {
return fmt.Errorf("error while starting the task: %v", err)
}
l.LogWithFields(ctx).Error("error: task update for " + taskData.TaskID + " failed with err: " + err.Error())
runtime.Goexit()
}
return nil
}
// this function is for to create array of originofresources without odata id
func removeOdataIDfromOriginResources(originResources []model.Link) []string {
var originRes []string
for _, origin := range originResources {
originRes = append(originRes, origin.Oid)
}
return originRes
}
// remove duplicate elements in string slice.
// Takes string slice and length, and updates the same with new values
func removeDuplicatesFromSlice(slc *[]string) {
if len(*slc) > 1 {
uniqueElementsDs := make(map[string]bool)
var uniqueElementsList []string
for _, element := range *slc {
if exist := uniqueElementsDs[element]; !exist {
uniqueElementsList = append(uniqueElementsList, element)
uniqueElementsDs[element] = true
}
}
// length of uniqueElementsList will be less than passed string slice,
// only if duplicates existed, so will assign slc with modified list and update length
if len(uniqueElementsList) < len(*slc) {
*slc = uniqueElementsList
}
}
}
// removeElement will remove the element from the slice return
// slice of remaining elements
func removeElement(slice []string, element string) []string {
var elements []string
for _, val := range slice {
if val != element {
elements = append(elements, val)
}
}
return elements
}
// removeElement will remove the element from the slice return
// slice of remaining elements
func removeLinks(slice []model.Link, element string) []model.Link {
var elements []model.Link
for _, val := range slice {
if val.Oid != element {
elements = append(elements, val)
}
}
return elements
}
// PluginCall method is to call to given url and method
// and validate the response and return
func (e *ExternalInterfaces) PluginCall(ctx context.Context, req evcommon.PluginContactRequest) (errResponse.RPC, string, string, string, error) {
var resp errResponse.RPC
response, err := e.callPlugin(ctx, req)
if err != nil {
if evcommon.GetPluginStatus(ctx, req.Plugin) {
response, err = e.callPlugin(ctx, req)
}
if err != nil {
errorMessage := "Error : " + err.Error()
evcommon.GenErrorResponse(errorMessage, errResponse.InternalError, http.StatusInternalServerError,
[]interface{}{}, &resp)
l.LogWithFields(ctx).Error(errorMessage)
return resp, "", "", "", err
}
}
defer response.Body.Close()
body, err := IOUtilReadAllFunc(response.Body)
if err != nil {
errorMessage := "error while trying to read response body: " + err.Error()
evcommon.GenErrorResponse(errorMessage, errResponse.InternalError, http.StatusInternalServerError,
[]interface{}{}, &resp)
l.LogWithFields(ctx).Error(errorMessage)
return resp, "", "", "", err
}
if response.StatusCode == http.StatusAccepted {
resp.StatusCode = int32(response.StatusCode)
resp.Body = string(body)
return resp, response.Header.Get("Location"), response.Header.Get("X-Auth-Token"), response.Header.Get(common.XForwardedFor), nil
}
if !(response.StatusCode == http.StatusCreated || response.StatusCode == http.StatusOK) {
resp.StatusCode = int32(response.StatusCode)
resp.Body = string(body)
return resp, "", "", "", err
}
var outBody interface{}
json.Unmarshal(body, &outBody)
resp.StatusCode = int32(response.StatusCode)
resp.Body = outBody
return resp, response.Header.Get("location"), response.Header.Get("X-Auth-Token"), response.Header.Get(common.XForwardedFor), nil
}
// validateFields is for validating subscription parameters
func validateFields(request *model.EventDestination) (int32, string, []interface{}, error) {
validEventFormatTypes := map[string]bool{"Event": true, "MetricReport": true}
validEventTypes := map[string]bool{"Alert": true, "MetricReport": true, "ResourceAdded": true, "ResourceRemoved": true, "ResourceUpdated": true, "StatusChange": true, "Other": true}
validate := validator.New()
// if any of the mandatory fields missing in the struct, then it return an error
err := validate.Struct(request)
if err != nil {
for _, err := range err.(validator.ValidationErrors) {
return http.StatusBadRequest, errResponse.PropertyMissing, []interface{}{err.Field()}, fmt.Errorf(err.Field() + " field is missing")
}
}
if request.EventFormatType == "" {
request.EventFormatType = "Event"
}
// if Subscription Name is empty then use default name
if request.Name == "" {
request.Name = evmodel.SubscriptionName
}
if _, ok := validEventFormatTypes[request.EventFormatType]; !ok {
return http.StatusBadRequest, errResponse.PropertyValueNotInList, []interface{}{request.EventFormatType, "EventFormatType"}, fmt.Errorf("Invalid EventFormatType")
}
if len(request.EventTypes) == 0 && request.EventFormatType == "MetricReport" {
request.EventTypes = []string{"MetricReport"}
}
for _, eventType := range request.EventTypes {
if _, ok := validEventTypes[eventType]; !ok {
return http.StatusBadRequest, errResponse.PropertyValueNotInList, []interface{}{eventType, "EventTypes"}, fmt.Errorf("invalid EventTypes")
}
}
if request.EventFormatType == "MetricReport" {
if len(request.EventTypes) > 1 {
return http.StatusBadRequest, errResponse.PropertyValueFormatError, []interface{}{request.EventFormatType, "EventTypes"}, fmt.Errorf("Unsupported EventType")
}
if request.EventTypes[0] != "MetricReport" {
return http.StatusBadRequest, errResponse.PropertyValueNotInList, []interface{}{request.EventTypes[0], "EventType"}, fmt.Errorf("Unsupported EventType")
}
}
if !request.SubscriptionType.IsValidSubscriptionType() {
return http.StatusBadRequest, errResponse.PropertyUnknown, []interface{}{"SubscriptionType"}, fmt.Errorf("invalid SubscriptionType")
}
if !request.SubscriptionType.IsSubscriptionTypeSupported() {
return http.StatusBadRequest, errResponse.PropertyValueNotInList, []interface{}{request.SubscriptionType.ToString(), "SubscriptionType"}, fmt.Errorf("unsupported SubscriptionType")
}
if request.Context == "" {
request.Context = evmodel.Context
}
if request.DeliveryRetryPolicy == "" {
request.DeliveryRetryPolicy = evmodel.DeliveryRetryPolicy
} else {
if !request.DeliveryRetryPolicy.IsValidDeliveryRetryPolicyType() {
return http.StatusBadRequest, errResponse.PropertyUnknown, []interface{}{"DeliveryRetryPolicy"}, fmt.Errorf("invalid deliveryRetryPolicy")
}
if !request.DeliveryRetryPolicy.IsDeliveryRetryPolicyTypeSupported() {
return http.StatusBadRequest, errResponse.PropertyValueNotInList, []interface{}{request.DeliveryRetryPolicy.ToString(), "deliveryRetryPolicy"}, fmt.Errorf("unsupported DeliveryRetryPolicy")
}
}
availableProtocols := []string{"Redfish"}
var validProtocol bool
validProtocol = false
for _, protocol := range availableProtocols {
if request.Protocol == protocol {
validProtocol = true
}
}
if !validProtocol {
return http.StatusBadRequest, errResponse.PropertyValueNotInList, []interface{}{request.Protocol, "Protocol"}, fmt.Errorf("protocol %v is invalid", request.Protocol)
}
// check the All ResourceTypes are supported
for _, resourceType := range request.ResourceTypes {
if _, ok := common.ResourceTypes[resourceType]; !ok {
return http.StatusBadRequest, errResponse.PropertyValueNotInList, []interface{}{resourceType, "ResourceType"}, fmt.Errorf("unsupported ResourceType")
}
}
return http.StatusOK, common.OK, []interface{}{}, nil
}
// GetUUID fetches the UUID from the Origin Resource
func getUUID(origin string) (string, error) {
var uuid string
requestData := strings.SplitN(origin, ".", 2)
if len(requestData) <= 1 {
return "", fmt.Errorf("error: SystemUUID not found")
}
resource := requestData[0]
uuid = resource[strings.LastIndexByte(resource, '/')+1:]
return uuid, nil
}
func createEventSubscriptionResponse() interface{} {
return errors.ErrorClass{
MessageExtendedInfo: []errors.MsgExtendedInfo{
errors.MsgExtendedInfo{
MessageID: response.Created,
},
},
Code: errResponse.Created,
Message: "See @Message.ExtendedInfo for more information.",
}
}
// getPluginToken will verify the if any token present to the plugin else it will create token for the new plugin
func (e *ExternalInterfaces) getPluginToken(ctx context.Context, plugin *common.Plugin) string {
authToken := evcommon.Token.GetToken(plugin.ID)
if authToken == "" {
return e.createToken(ctx, plugin)
}
return authToken
}
func (e *ExternalInterfaces) createToken(ctx context.Context, plugin *common.Plugin) string {
var contactRequest evcommon.PluginContactRequest
contactRequest.Plugin = plugin
contactRequest.HTTPMethodType = http.MethodPost
contactRequest.PostBody = map[string]interface{}{
"Username": plugin.Username,
"Password": string(plugin.Password),
}
contactRequest.URL = "/ODIM/v1/Sessions"
_, _, token, _, err := e.PluginCall(ctx, contactRequest)
if err != nil {
l.LogWithFields(ctx).Error(err.Error())
}
pluginToken := evcommon.PluginToken{
Tokens: make(map[string]string),
}
if token != "" {
pluginToken.StoreToken(plugin.ID, token)
}
return token
}
func (e *ExternalInterfaces) retryEventOperation(ctx context.Context, req evcommon.PluginContactRequest) (errResponse.RPC, string, string, string, error) {
var resp errResponse.RPC
var token = e.createToken(ctx, req.Plugin)
if token == "" {
evcommon.GenErrorResponse("error: Unable to create session with plugin "+req.Plugin.ID, errResponse.NoValidSession, http.StatusUnauthorized,
[]interface{}{}, &resp)
return resp, "", "", "", fmt.Errorf("error: Unable to create session with plugin")
}
req.Token = token
return e.PluginCall(ctx, req)
}
func (e *ExternalInterfaces) retryEventSubscriptionOperation(ctx context.Context, req evcommon.PluginContactRequest) (*http.Response, evresponse.EventResponse, error) {
var resp evresponse.EventResponse
var token = e.createToken(ctx, req.Plugin)
if token == "" {
evcommon.GenEventErrorResponse("error: Unable to create session with plugin "+req.Plugin.ID, errResponse.NoValidSession, http.StatusUnauthorized,
&resp, []interface{}{})
return nil, resp, fmt.Errorf("error: Unable to create session with plugin")
}
req.Token = token
response, err := e.callPlugin(ctx, req)
if err != nil {
errorMessage := "error while unmarshal the body : " + err.Error()
evcommon.GenEventErrorResponse(errorMessage, errResponse.InternalError, http.StatusInternalServerError,
&resp, []interface{}{})
l.LogWithFields(ctx).Error(errorMessage)
return nil, resp, err
}
return response, resp, err
}
// isHostPresent will check if hostIp present in the hosts slice
func isHostPresent(hosts []string, hostIP string) bool {
if len(hosts) < 1 {
return false
}
front := 0
rear := len(hosts) - 1
for front <= rear {
if hosts[front] == hostIP || hosts[rear] == hostIP {
return true
}
front++
rear--
}
return false
}
func getFabricID(origin string) string {
data := strings.Split(origin, "/redfish/v1/Fabrics/")
if len(data) > 1 {
fabricData := strings.Split(data[1], "/")
return fabricData[0]
}
return ""
}
func getAggregateID(origin string) string {
data := strings.Split(origin, "/redfish/v1/AggregationService/Aggregates/")
if len(data) > 1 {
fabricData := strings.Split(data[1], "/")
return fabricData[0]
}
return ""
}
// callPlugin check the given request url and PreferAuth type plugin
func (e *ExternalInterfaces) callPlugin(ctx context.Context, req evcommon.PluginContactRequest) (*http.Response, error) {
var reqURL = "https://" + req.Plugin.IP + ":" + req.Plugin.Port + req.URL
if strings.EqualFold(req.Plugin.PreferredAuthType, "BasicAuth") {
return e.ContactClient(ctx, reqURL, req.HTTPMethodType, "", "", req.PostBody, req.LoginCredential)
}
return e.ContactClient(ctx, reqURL, req.HTTPMethodType, req.Token, "", req.PostBody, nil)
}
// checkCollection verifies if the given origin is collection and extracts all the subordinate resources
func (e *ExternalInterfaces) checkCollection(origin string) ([]string, string, bool, string, bool, error) {
switch origin {
case "/redfish/v1/Systems":
collection, err := e.GetAllKeysFromTable("ComputerSystem")
return collection, "SystemsCollection", true, "", false, err
case "/redfish/v1/Chassis":
return []string{}, "ChassisCollection", true, "", false, nil
case "/redfish/v1/Managers":
//TODO:After Managers implementation need to get all Managers data
return []string{}, "ManagerCollection", true, "", false, nil
case "/redfish/v1/Fabrics":
collection, err := e.GetAllFabrics()
return collection, "FabricsCollection", true, "", false, err
case "/redfish/v1/TaskService/Tasks":
return []string{}, "TasksCollection", true, "", false, nil
}
if strings.Contains(origin, "/AggregationService/Aggregates/") {
aggregateCollection, err := e.GetAggregateData(origin)
if err != nil {
return []string{}, "AggregateCollections", true, "", false, err
}
var collection []string = []string{}
for _, system := range aggregateCollection.Elements {
var systemID string = system.Oid
collection = append(collection, systemID)
}
return collection, "AggregateCollections", true, origin, true, err
}
return []string{}, "", false, "", false, nil
}
// isHostPresentInEventForward will check if hostIp present in the hosts slice
func isHostPresentInEventForward(hosts []string, hostIP string) bool {
if len(hosts) == 0 {
return true
}
front := 0
rear := len(hosts) - 1
for front <= rear {
if hosts[front] == hostIP || hosts[rear] == hostIP || strings.Contains(hosts[rear], "Collection") || strings.Contains(hosts[front], "Collection") {
return true
}
front++
rear--
}
return false
}