forked from googleforgames/open-match
/
shared.go
146 lines (130 loc) · 4.3 KB
/
shared.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
// Package redispb marshals and unmarshals Open Match Backend protobuf messages
// ('MatchObject') for redis state storage.
// More details about the protobuf messages used in Open Match can be found in
// the api/protobuf-spec/om_messages.proto file.
/*
Copyright 2018 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
All of this can probably be done more succinctly with some more interface and
reflection, this is a hack but works for now.
*/
package redispb
import (
"context"
"errors"
"reflect"
"strings"
"github.com/gogo/protobuf/jsonpb"
"github.com/gogo/protobuf/proto"
"github.com/gomodule/redigo/redis"
log "github.com/sirupsen/logrus"
"github.com/tidwall/gjson"
)
// Logrus structured logging setup
var (
sLogFields = log.Fields{
"app": "openmatch",
"component": "statestorage",
}
sLog = log.WithFields(sLogFields)
)
// MarshalToRedis marshals a protobuf message to a redis hash.
// The protobuf message in question must have an 'id' field.
// If a positive integer TTL is provided, it will also be set.
func MarshalToRedis(ctx context.Context, pool *redis.Pool, pb proto.Message, ttl int) error {
// We want to serialize to redis as JSON, not the typical protobuf string
// serializer, so start by marshalling to json.
this := jsonpb.Marshaler{}
jsonMsg, err := this.MarshalToString(pb)
if err != nil {
sLog.WithFields(log.Fields{
"error": err.Error(),
"component": "statestorage",
"protobuf": pb,
}).Error("failure marshaling protobuf message to JSON")
return err
}
// Get redis key
keyResult := gjson.Get(jsonMsg, "id")
// Return error if the provided protobuf message doesn't have an ID field
if !keyResult.Exists() {
err = errors.New("cannot unmarshal protobuf messages without an id field")
sLog.WithFields(log.Fields{
"error": err.Error(),
"component": "statestorage",
}).Error("failed to retrieve from redis")
return err
}
key := keyResult.String()
// Prepare redis command.
cmd := "HSET"
resultLog := sLog.WithFields(log.Fields{
"key": key,
"cmd": cmd,
})
// Get the Redis connection.
redisConn, err := pool.GetContext(context.Background())
defer redisConn.Close()
if err != nil {
sLog.WithFields(log.Fields{
"error": err.Error(),
"component": "statestorage",
}).Error("failed to connect to redis")
return err
}
redisConn.Send("MULTI")
// Write all non-id fields from the protobuf message to state storage.
// Use reflection to get the field names from the protobuf message.
pbInfo := reflect.ValueOf(pb).Elem()
for i := 0; i < pbInfo.NumField(); i++ {
// TODO: change this to use the json name field from the struct tags
// something like parseTag() in src/encoding/json/tags.go
//field := strings.ToLower(pbInfo.Type().Field(i).Tag.Get("json"))
field := strings.ToLower(pbInfo.Type().Field(i).Name)
value := ""
//value, err = strconv.Unquote(gjson.Get(jsonMsg, field).String())
value = gjson.Get(jsonMsg, field).String()
if err != nil {
resultLog.Error("Issue with Unquoting string", err)
}
if field != "id" {
// This isn't the ID field, so write it to the redis hash.
redisConn.Send(cmd, key, field, value)
if err != nil {
resultLog.WithFields(log.Fields{
"error": err.Error(),
"component": "statestorage",
"field": field,
}).Error("State storage error")
return err
}
resultLog.WithFields(log.Fields{
"component": "statestorage",
"field": field,
"value": value,
}).Info("State storage operation")
}
}
if ttl > 0 {
redisConn.Send("EXPIRE", key, ttl)
resultLog.WithFields(log.Fields{
"component": "statestorage",
"ttl": ttl,
}).Info("State storage expiration set")
} else {
resultLog.WithFields(log.Fields{
"component": "statestorage",
"ttl": ttl,
}).Debug("State storage expiration not set")
}
_, err = redisConn.Do("EXEC")
return err
}