| 
									
										
										
										
											2016-07-24 13:51:12 +08:00
										 |  |  | /* | 
					
						
							|  |  |  |  * Minio Cloud Storage, (C) 2016 Minio, Inc. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * 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 | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  *     http://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. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-19 07:23:42 +08:00
										 |  |  | package cmd | 
					
						
							| 
									
										
										
										
											2016-07-24 13:51:12 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							| 
									
										
										
										
											2017-03-29 23:55:53 +08:00
										 |  |  | 	"encoding/json" | 
					
						
							| 
									
										
										
										
											2017-03-28 02:27:25 +08:00
										 |  |  | 	"fmt" | 
					
						
							| 
									
										
										
										
											2016-08-05 13:01:58 +08:00
										 |  |  | 	"io/ioutil" | 
					
						
							| 
									
										
										
										
											2017-03-31 19:47:40 +08:00
										 |  |  | 	"net" | 
					
						
							| 
									
										
										
										
											2016-07-24 13:51:12 +08:00
										 |  |  | 	"time" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	"github.com/Sirupsen/logrus" | 
					
						
							| 
									
										
										
										
											2017-03-16 23:22:47 +08:00
										 |  |  | 	"github.com/garyburd/redigo/redis" | 
					
						
							| 
									
										
										
										
											2016-07-24 13:51:12 +08:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-03-29 23:55:53 +08:00
										 |  |  | func makeRedisError(msg string, a ...interface{}) error { | 
					
						
							|  |  |  | 	s := fmt.Sprintf(msg, a...) | 
					
						
							|  |  |  | 	return fmt.Errorf("Redis Notifier Error: %s", s) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var ( | 
					
						
							|  |  |  | 	rdNFormatError = makeRedisError(`"format" value is invalid - it must be one of "access" or "namespace".`) | 
					
						
							|  |  |  | 	rdNKeyError    = makeRedisError("Key was not specified in the configuration.") | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-05 13:01:58 +08:00
										 |  |  | // redisNotify to send logs to Redis server
 | 
					
						
							|  |  |  | type redisNotify struct { | 
					
						
							| 
									
										
										
										
											2016-07-24 13:51:12 +08:00
										 |  |  | 	Enable   bool   `json:"enable"` | 
					
						
							| 
									
										
										
										
											2017-03-28 02:27:25 +08:00
										 |  |  | 	Format   string `json:"format"` | 
					
						
							| 
									
										
										
										
											2016-07-24 13:51:12 +08:00
										 |  |  | 	Addr     string `json:"address"` | 
					
						
							|  |  |  | 	Password string `json:"password"` | 
					
						
							|  |  |  | 	Key      string `json:"key"` | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-03-16 07:30:34 +08:00
										 |  |  | func (r *redisNotify) Validate() error { | 
					
						
							|  |  |  | 	if !r.Enable { | 
					
						
							|  |  |  | 		return nil | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2017-03-29 23:55:53 +08:00
										 |  |  | 	if r.Format != formatNamespace && r.Format != formatAccess { | 
					
						
							|  |  |  | 		return rdNFormatError | 
					
						
							| 
									
										
										
										
											2017-03-28 02:27:25 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2017-03-31 19:47:40 +08:00
										 |  |  | 	if _, _, err := net.SplitHostPort(r.Addr); err != nil { | 
					
						
							| 
									
										
										
										
											2017-03-16 07:30:34 +08:00
										 |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2017-03-29 23:55:53 +08:00
										 |  |  | 	if r.Key == "" { | 
					
						
							|  |  |  | 		return rdNKeyError | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2017-03-16 07:30:34 +08:00
										 |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-07-24 13:51:12 +08:00
										 |  |  | type redisConn struct { | 
					
						
							|  |  |  | 	*redis.Pool | 
					
						
							| 
									
										
										
										
											2016-08-05 13:01:58 +08:00
										 |  |  | 	params redisNotify | 
					
						
							| 
									
										
										
										
											2016-07-24 13:51:12 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-10-04 08:29:55 +08:00
										 |  |  | // Dial a new connection to redis instance at addr, optionally with a
 | 
					
						
							|  |  |  | // password if any.
 | 
					
						
							| 
									
										
										
										
											2016-08-05 13:01:58 +08:00
										 |  |  | func dialRedis(rNotify redisNotify) (*redis.Pool, error) { | 
					
						
							| 
									
										
										
										
											2016-07-26 08:53:55 +08:00
										 |  |  | 	// Return error if redis not enabled.
 | 
					
						
							| 
									
										
										
										
											2016-08-05 13:01:58 +08:00
										 |  |  | 	if !rNotify.Enable { | 
					
						
							|  |  |  | 		return nil, errNotifyNotEnabled | 
					
						
							| 
									
										
										
										
											2016-07-26 08:53:55 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2017-03-31 19:47:40 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-05 13:01:58 +08:00
										 |  |  | 	addr := rNotify.Addr | 
					
						
							|  |  |  | 	password := rNotify.Password | 
					
						
							| 
									
										
										
										
											2016-07-24 13:51:12 +08:00
										 |  |  | 	rPool := &redis.Pool{ | 
					
						
							|  |  |  | 		MaxIdle:     3, | 
					
						
							| 
									
										
										
										
											2016-09-20 17:11:17 +08:00
										 |  |  | 		IdleTimeout: 240 * time.Second, // Time 2minutes.
 | 
					
						
							| 
									
										
										
										
											2016-07-24 13:51:12 +08:00
										 |  |  | 		Dial: func() (redis.Conn, error) { | 
					
						
							|  |  |  | 			c, err := redis.Dial("tcp", addr) | 
					
						
							|  |  |  | 			if err != nil { | 
					
						
							|  |  |  | 				return nil, err | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			if password != "" { | 
					
						
							| 
									
										
										
										
											2016-07-26 11:36:56 +08:00
										 |  |  | 				if _, derr := c.Do("AUTH", password); derr != nil { | 
					
						
							| 
									
										
										
										
											2016-07-24 13:51:12 +08:00
										 |  |  | 					c.Close() | 
					
						
							| 
									
										
										
										
											2016-07-26 11:36:56 +08:00
										 |  |  | 					return nil, derr | 
					
						
							| 
									
										
										
										
											2016-07-24 13:51:12 +08:00
										 |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			return c, err | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		TestOnBorrow: func(c redis.Conn, t time.Time) error { | 
					
						
							|  |  |  | 			_, err := c.Do("PING") | 
					
						
							|  |  |  | 			return err | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Test if connection with REDIS can be established.
 | 
					
						
							|  |  |  | 	rConn := rPool.Get() | 
					
						
							|  |  |  | 	defer rConn.Close() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Check connection.
 | 
					
						
							|  |  |  | 	_, err := rConn.Do("PING") | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2017-03-29 23:55:53 +08:00
										 |  |  | 		return nil, makeRedisError("Error connecting to server: %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Test that Key is of desired type
 | 
					
						
							|  |  |  | 	reply, err := redis.String(rConn.Do("TYPE", rNotify.Key)) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, makeRedisError("Error getting type of Key=%s: %v", | 
					
						
							|  |  |  | 			rNotify.Key, err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if reply != "none" { | 
					
						
							|  |  |  | 		expectedType := "hash" | 
					
						
							|  |  |  | 		if rNotify.Format == formatAccess { | 
					
						
							|  |  |  | 			expectedType = "list" | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if reply != expectedType { | 
					
						
							|  |  |  | 			return nil, makeRedisError( | 
					
						
							|  |  |  | 				"Key=%s has type %s, but we expect it to be a %s", | 
					
						
							|  |  |  | 				rNotify.Key, reply, expectedType) | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2016-07-24 13:51:12 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Return pool.
 | 
					
						
							|  |  |  | 	return rPool, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-05 13:01:58 +08:00
										 |  |  | func newRedisNotify(accountID string) (*logrus.Logger, error) { | 
					
						
							| 
									
										
										
										
											2017-02-10 07:20:54 +08:00
										 |  |  | 	rNotify := serverConfig.Notify.GetRedisByID(accountID) | 
					
						
							| 
									
										
										
										
											2016-07-24 13:51:12 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// Dial redis.
 | 
					
						
							| 
									
										
										
										
											2016-08-05 13:01:58 +08:00
										 |  |  | 	rPool, err := dialRedis(rNotify) | 
					
						
							| 
									
										
										
										
											2016-07-24 13:51:12 +08:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2016-08-05 13:01:58 +08:00
										 |  |  | 		return nil, err | 
					
						
							| 
									
										
										
										
											2016-07-24 13:51:12 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	rrConn := redisConn{ | 
					
						
							|  |  |  | 		Pool:   rPool, | 
					
						
							| 
									
										
										
										
											2016-08-05 13:01:58 +08:00
										 |  |  | 		params: rNotify, | 
					
						
							| 
									
										
										
										
											2016-07-24 13:51:12 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-05 13:01:58 +08:00
										 |  |  | 	redisLog := logrus.New() | 
					
						
							| 
									
										
										
										
											2016-07-24 13:51:12 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-05 13:01:58 +08:00
										 |  |  | 	redisLog.Out = ioutil.Discard | 
					
						
							| 
									
										
										
										
											2016-07-24 13:51:12 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// Set default JSON formatter.
 | 
					
						
							| 
									
										
										
										
											2016-08-05 13:01:58 +08:00
										 |  |  | 	redisLog.Formatter = new(logrus.JSONFormatter) | 
					
						
							| 
									
										
										
										
											2016-07-24 13:51:12 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-05 13:01:58 +08:00
										 |  |  | 	redisLog.Hooks.Add(rrConn) | 
					
						
							| 
									
										
										
										
											2016-07-24 13:51:12 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-05 13:01:58 +08:00
										 |  |  | 	// Success, redis enabled.
 | 
					
						
							|  |  |  | 	return redisLog, nil | 
					
						
							| 
									
										
										
										
											2016-07-24 13:51:12 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-05 13:01:58 +08:00
										 |  |  | // Fire is called when an event should be sent to the message broker.
 | 
					
						
							| 
									
										
										
										
											2016-07-24 13:51:12 +08:00
										 |  |  | func (r redisConn) Fire(entry *logrus.Entry) error { | 
					
						
							|  |  |  | 	rConn := r.Pool.Get() | 
					
						
							|  |  |  | 	defer rConn.Close() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-09-20 17:11:17 +08:00
										 |  |  | 	// Fetch event type upon reflecting on its original type.
 | 
					
						
							|  |  |  | 	entryStr, ok := entry.Data["EventType"].(string) | 
					
						
							|  |  |  | 	if !ok { | 
					
						
							|  |  |  | 		return nil | 
					
						
							| 
									
										
										
										
											2016-07-24 13:51:12 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-03-29 23:55:53 +08:00
										 |  |  | 	switch r.params.Format { | 
					
						
							|  |  |  | 	case formatNamespace: | 
					
						
							|  |  |  | 		// Match the event if its a delete request, attempt to delete the key
 | 
					
						
							|  |  |  | 		if eventMatch(entryStr, []string{"s3:ObjectRemoved:*"}) { | 
					
						
							|  |  |  | 			_, err := rConn.Do("HDEL", r.params.Key, entry.Data["Key"]) | 
					
						
							|  |  |  | 			if err != nil { | 
					
						
							|  |  |  | 				return makeRedisError("Error deleting entry: %v", | 
					
						
							|  |  |  | 					err) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			return nil | 
					
						
							|  |  |  | 		} // else save this as new entry or update any existing ones.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		value, err := json.Marshal(map[string]interface{}{ | 
					
						
							|  |  |  | 			"Records": entry.Data["Records"], | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return makeRedisError( | 
					
						
							|  |  |  | 				"Unable to encode event %v to JSON: %v", | 
					
						
							|  |  |  | 				entry.Data["Records"], err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		_, err = rConn.Do("HSET", r.params.Key, entry.Data["Key"], | 
					
						
							|  |  |  | 			value) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return makeRedisError("Error updating hash entry: %v", | 
					
						
							|  |  |  | 				err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	case formatAccess: | 
					
						
							|  |  |  | 		// eventTime is taken from the first entry in the
 | 
					
						
							|  |  |  | 		// records.
 | 
					
						
							|  |  |  | 		events, ok := entry.Data["Records"].([]NotificationEvent) | 
					
						
							|  |  |  | 		if !ok { | 
					
						
							|  |  |  | 			return makeRedisError("unable to extract event time due to conversion error of entry.Data[\"Records\"]=%v", entry.Data["Records"]) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		eventTime := events[0].EventTime | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		listEntry := []interface{}{eventTime, entry.Data["Records"]} | 
					
						
							|  |  |  | 		jsonValue, err := json.Marshal(listEntry) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return makeRedisError("JSON encoding error: %v", err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		_, err = rConn.Do("RPUSH", r.params.Key, jsonValue) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return makeRedisError("Error appending to Redis list: %v", | 
					
						
							|  |  |  | 				err) | 
					
						
							| 
									
										
										
										
											2016-09-20 17:11:17 +08:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2016-07-24 13:51:12 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Required for logrus hook implementation
 | 
					
						
							|  |  |  | func (r redisConn) Levels() []logrus.Level { | 
					
						
							|  |  |  | 	return []logrus.Level{ | 
					
						
							|  |  |  | 		logrus.InfoLevel, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } |