You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

513 lines
14 KiB

  1. /*
  2. * Minio Cloud Storage, (C) 2016 Minio, Inc.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package cmd
  17. import (
  18. "fmt"
  19. "net"
  20. "reflect"
  21. "testing"
  22. "time"
  23. )
  24. // Test InitEventNotifier with faulty disks
  25. func TestInitEventNotifierFaultyDisks(t *testing.T) {
  26. // Prepare for tests
  27. rootPath, err := newTestConfig("us-east-1")
  28. if err != nil {
  29. t.Fatalf("Init Test config failed")
  30. }
  31. // remove the root folder after the test ends.
  32. defer removeAll(rootPath)
  33. disks, err := getRandomDisks(1)
  34. if err != nil {
  35. t.Fatal("Unable to create directories for FS backend. ", err)
  36. }
  37. defer removeAll(disks[0])
  38. endpoints, err := parseStorageEndpoints(disks)
  39. if err != nil {
  40. t.Fatal(err)
  41. }
  42. obj, _, err := initObjectLayer(endpoints, nil)
  43. if err != nil {
  44. t.Fatal("Unable to initialize FS backend.", err)
  45. }
  46. bucketName := "bucket"
  47. if err := obj.MakeBucket(bucketName); err != nil {
  48. t.Fatal("Unexpected error:", err)
  49. }
  50. fs := obj.(fsObjects)
  51. fsstorage := fs.storage.(*posix)
  52. listenARN := "arn:minio:sns:us-east-1:1:listen"
  53. queueARN := "arn:minio:sqs:us-east-1:1:redis"
  54. // Write a notification.xml in the disk
  55. notificationXML := "<NotificationConfiguration>"
  56. notificationXML += "<TopicConfiguration><Event>s3:ObjectRemoved:*</Event><Event>s3:ObjectRemoved:*</Event><Topic>" + listenARN + "</Topic></TopicConfiguration>"
  57. notificationXML += "<QueueConfiguration><Event>s3:ObjectRemoved:*</Event><Event>s3:ObjectRemoved:*</Event><Queue>" + queueARN + "</Queue></QueueConfiguration>"
  58. notificationXML += "</NotificationConfiguration>"
  59. if err := fsstorage.AppendFile(minioMetaBucket, bucketConfigPrefix+"/"+bucketName+"/"+bucketNotificationConfig, []byte(notificationXML)); err != nil {
  60. t.Fatal("Unexpected error:", err)
  61. }
  62. // Test initEventNotifier() with faulty disks
  63. for i := 1; i <= 5; i++ {
  64. fs.storage = newNaughtyDisk(fsstorage, map[int]error{i: errFaultyDisk}, nil)
  65. if err := initEventNotifier(fs); errorCause(err) != errFaultyDisk {
  66. t.Fatal("Unexpected error:", err)
  67. }
  68. }
  69. }
  70. // InitEventNotifierWithAMQP - tests InitEventNotifier when AMQP is not prepared
  71. func TestInitEventNotifierWithAMQP(t *testing.T) {
  72. // initialize the server and obtain the credentials and root.
  73. // credentials are necessary to sign the HTTP request.
  74. rootPath, err := newTestConfig("us-east-1")
  75. if err != nil {
  76. t.Fatalf("Init Test config failed")
  77. }
  78. // remove the root folder after the test ends.
  79. defer removeAll(rootPath)
  80. disks, err := getRandomDisks(1)
  81. defer removeAll(disks[0])
  82. if err != nil {
  83. t.Fatal("Unable to create directories for FS backend. ", err)
  84. }
  85. endpoints, err := parseStorageEndpoints(disks)
  86. if err != nil {
  87. t.Fatal(err)
  88. }
  89. fs, _, err := initObjectLayer(endpoints, nil)
  90. if err != nil {
  91. t.Fatal("Unable to initialize FS backend.", err)
  92. }
  93. serverConfig.SetAMQPNotifyByID("1", amqpNotify{Enable: true})
  94. if err := initEventNotifier(fs); err == nil {
  95. t.Fatal("AMQP config didn't fail.")
  96. }
  97. }
  98. // InitEventNotifierWithElasticSearch - test InitEventNotifier when ElasticSearch is not ready
  99. func TestInitEventNotifierWithElasticSearch(t *testing.T) {
  100. // initialize the server and obtain the credentials and root.
  101. // credentials are necessary to sign the HTTP request.
  102. rootPath, err := newTestConfig("us-east-1")
  103. if err != nil {
  104. t.Fatalf("Init Test config failed")
  105. }
  106. // remove the root folder after the test ends.
  107. defer removeAll(rootPath)
  108. disks, err := getRandomDisks(1)
  109. defer removeAll(disks[0])
  110. if err != nil {
  111. t.Fatal("Unable to create directories for FS backend. ", err)
  112. }
  113. endpoints, err := parseStorageEndpoints(disks)
  114. if err != nil {
  115. t.Fatal(err)
  116. }
  117. fs, _, err := initObjectLayer(endpoints, nil)
  118. if err != nil {
  119. t.Fatal("Unable to initialize FS backend.", err)
  120. }
  121. serverConfig.SetElasticSearchNotifyByID("1", elasticSearchNotify{Enable: true})
  122. if err := initEventNotifier(fs); err == nil {
  123. t.Fatal("ElasticSearch config didn't fail.")
  124. }
  125. }
  126. // InitEventNotifierWithRedis - test InitEventNotifier when Redis is not ready
  127. func TestInitEventNotifierWithRedis(t *testing.T) {
  128. // initialize the server and obtain the credentials and root.
  129. // credentials are necessary to sign the HTTP request.
  130. rootPath, err := newTestConfig("us-east-1")
  131. if err != nil {
  132. t.Fatalf("Init Test config failed")
  133. }
  134. // remove the root folder after the test ends.
  135. defer removeAll(rootPath)
  136. disks, err := getRandomDisks(1)
  137. defer removeAll(disks[0])
  138. if err != nil {
  139. t.Fatal("Unable to create directories for FS backend. ", err)
  140. }
  141. endpoints, err := parseStorageEndpoints(disks)
  142. if err != nil {
  143. t.Fatal(err)
  144. }
  145. fs, _, err := initObjectLayer(endpoints, nil)
  146. if err != nil {
  147. t.Fatal("Unable to initialize FS backend.", err)
  148. }
  149. serverConfig.SetRedisNotifyByID("1", redisNotify{Enable: true})
  150. if err := initEventNotifier(fs); err == nil {
  151. t.Fatal("Redis config didn't fail.")
  152. }
  153. }
  154. type TestPeerRPCServerData struct {
  155. serverType string
  156. testServer TestServer
  157. }
  158. func (s *TestPeerRPCServerData) Setup(t *testing.T) {
  159. s.testServer = StartTestPeersRPCServer(t, s.serverType)
  160. // setup port and minio addr
  161. host, port, err := net.SplitHostPort(s.testServer.Server.Listener.Addr().String())
  162. if err != nil {
  163. t.Fatalf("Initialisation error: %v", err)
  164. }
  165. globalMinioHost = host
  166. globalMinioPort = port
  167. globalMinioAddr = getLocalAddress(
  168. s.testServer.SrvCmdCfg,
  169. )
  170. // initialize the peer client(s)
  171. initGlobalS3Peers(s.testServer.Disks)
  172. }
  173. func (s *TestPeerRPCServerData) TearDown() {
  174. s.testServer.Stop()
  175. _ = removeAll(s.testServer.Root)
  176. for _, d := range s.testServer.Disks {
  177. _ = removeAll(d.Path)
  178. }
  179. }
  180. func TestSetNGetBucketNotification(t *testing.T) {
  181. s := TestPeerRPCServerData{serverType: "XL"}
  182. // setup and teardown
  183. s.Setup(t)
  184. defer s.TearDown()
  185. bucketName := getRandomBucketName()
  186. obj := s.testServer.Obj
  187. if err := initEventNotifier(obj); err != nil {
  188. t.Fatal("Unexpected error:", err)
  189. }
  190. globalEventNotifier.SetBucketNotificationConfig(bucketName, &notificationConfig{})
  191. nConfig := globalEventNotifier.GetBucketNotificationConfig(bucketName)
  192. if nConfig == nil {
  193. t.Errorf("Notification expected to be set, but notification not set.")
  194. }
  195. if !reflect.DeepEqual(nConfig, &notificationConfig{}) {
  196. t.Errorf("Mismatching notification configs.")
  197. }
  198. }
  199. func TestInitEventNotifier(t *testing.T) {
  200. s := TestPeerRPCServerData{serverType: "XL"}
  201. // setup and teardown
  202. s.Setup(t)
  203. defer s.TearDown()
  204. // test if empty object layer arg. returns expected error.
  205. if err := initEventNotifier(nil); err == nil || err != errInvalidArgument {
  206. t.Fatalf("initEventNotifier returned unexpected error value - %v", err)
  207. }
  208. obj := s.testServer.Obj
  209. bucketName := getRandomBucketName()
  210. // declare sample configs
  211. filterRules := []filterRule{
  212. {
  213. Name: "prefix",
  214. Value: "minio",
  215. },
  216. {
  217. Name: "suffix",
  218. Value: "*.jpg",
  219. },
  220. }
  221. sampleSvcCfg := ServiceConfig{
  222. []string{"s3:ObjectRemoved:*", "s3:ObjectCreated:*"},
  223. filterStruct{
  224. keyFilter{filterRules},
  225. },
  226. "1",
  227. }
  228. sampleNotifCfg := notificationConfig{
  229. QueueConfigs: []queueConfig{
  230. {
  231. ServiceConfig: sampleSvcCfg,
  232. QueueARN: "testqARN",
  233. },
  234. },
  235. }
  236. sampleListenCfg := []listenerConfig{
  237. {
  238. TopicConfig: topicConfig{ServiceConfig: sampleSvcCfg,
  239. TopicARN: "testlARN"},
  240. TargetServer: globalMinioAddr,
  241. },
  242. }
  243. // create bucket
  244. if err := obj.MakeBucket(bucketName); err != nil {
  245. t.Fatal("Unexpected error:", err)
  246. }
  247. // bucket is created, now writing should not give errors.
  248. if err := persistNotificationConfig(bucketName, &sampleNotifCfg, obj); err != nil {
  249. t.Fatal("Unexpected error:", err)
  250. }
  251. if err := persistListenerConfig(bucketName, sampleListenCfg, obj); err != nil {
  252. t.Fatal("Unexpected error:", err)
  253. }
  254. // needed to load listener config from disk for testing (in
  255. // single peer mode, the listener config is ingored, but here
  256. // we want to test the loading from disk too.)
  257. globalIsDistXL = true
  258. // test event notifier init
  259. if err := initEventNotifier(obj); err != nil {
  260. t.Fatal("Unexpected error:", err)
  261. }
  262. // fetch bucket configs and verify
  263. ncfg := globalEventNotifier.GetBucketNotificationConfig(bucketName)
  264. if ncfg == nil {
  265. t.Error("Bucket notification was not present for ", bucketName)
  266. }
  267. if len(ncfg.QueueConfigs) != 1 || ncfg.QueueConfigs[0].QueueARN != "testqARN" {
  268. t.Error("Unexpected bucket notification found - ", *ncfg)
  269. }
  270. if globalEventNotifier.GetExternalTarget("testqARN") != nil {
  271. t.Error("A logger was not expected to be found as it was not enabled in the config.")
  272. }
  273. lcfg := globalEventNotifier.GetBucketListenerConfig(bucketName)
  274. if lcfg == nil {
  275. t.Error("Bucket listener was not present for ", bucketName)
  276. }
  277. if len(lcfg) != 1 || lcfg[0].TargetServer != globalMinioAddr || lcfg[0].TopicConfig.TopicARN != "testlARN" {
  278. t.Error("Unexpected listener config found - ", lcfg[0])
  279. }
  280. if globalEventNotifier.GetInternalTarget("testlARN") == nil {
  281. t.Error("A listen logger was not found.")
  282. }
  283. }
  284. func TestListenBucketNotification(t *testing.T) {
  285. s := TestPeerRPCServerData{serverType: "XL"}
  286. // setup and teardown
  287. s.Setup(t)
  288. defer s.TearDown()
  289. // test initialisation
  290. obj := s.testServer.Obj
  291. bucketName := "bucket"
  292. objectName := "object"
  293. // Create the bucket to listen on
  294. if err := obj.MakeBucket(bucketName); err != nil {
  295. t.Fatal("Unexpected error:", err)
  296. }
  297. listenARN := fmt.Sprintf("%s:%s:1:%s-%s",
  298. minioTopic,
  299. serverConfig.GetRegion(),
  300. snsTypeMinio,
  301. s.testServer.Server.Listener.Addr(),
  302. )
  303. lcfg := listenerConfig{
  304. TopicConfig: topicConfig{
  305. ServiceConfig{
  306. []string{"s3:ObjectRemoved:*", "s3:ObjectCreated:*"},
  307. filterStruct{},
  308. "0",
  309. },
  310. listenARN,
  311. },
  312. TargetServer: globalMinioAddr,
  313. }
  314. // write listener config to storage layer
  315. lcfgs := []listenerConfig{lcfg}
  316. if err := persistListenerConfig(bucketName, lcfgs, obj); err != nil {
  317. t.Fatalf("Test Setup error: %v", err)
  318. }
  319. // needed to load listener config from disk for testing (in
  320. // single peer mode, the listener config is ingored, but here
  321. // we want to test the loading from disk too.)
  322. globalIsDistXL = true
  323. // Init event notifier
  324. if err := initEventNotifier(obj); err != nil {
  325. t.Fatal("Unexpected error:", err)
  326. }
  327. // Check if the config is loaded
  328. listenerCfg := globalEventNotifier.GetBucketListenerConfig(bucketName)
  329. if listenerCfg == nil {
  330. t.Fatal("Cannot load bucket listener config")
  331. }
  332. if len(listenerCfg) != 1 {
  333. t.Fatal("Listener config is not correctly loaded. Exactly one listener config is expected")
  334. }
  335. // Check if topic ARN is correct
  336. if listenerCfg[0].TopicConfig.TopicARN != listenARN {
  337. t.Fatal("Configured topic ARN is incorrect.")
  338. }
  339. // Create a new notification event channel.
  340. nEventCh := make(chan []NotificationEvent)
  341. // Close the listener channel.
  342. defer close(nEventCh)
  343. // Add events channel for listener.
  344. if err := globalEventNotifier.AddListenerChan(listenARN, nEventCh); err != nil {
  345. t.Fatalf("Test Setup error: %v", err)
  346. }
  347. // Remove listen channel after the writer has closed or the
  348. // client disconnected.
  349. defer globalEventNotifier.RemoveListenerChan(listenARN)
  350. // Fire an event notification
  351. go eventNotify(eventData{
  352. Type: ObjectRemovedDelete,
  353. Bucket: bucketName,
  354. ObjInfo: ObjectInfo{
  355. Bucket: bucketName,
  356. Name: objectName,
  357. },
  358. ReqParams: map[string]string{
  359. "sourceIPAddress": "localhost:1337",
  360. },
  361. })
  362. // Wait for the event notification here, if nothing is received within 30 seconds,
  363. // test error will be fired
  364. select {
  365. case n := <-nEventCh:
  366. // Check that received event
  367. if len(n) == 0 {
  368. t.Fatal("Unexpected error occurred")
  369. }
  370. if n[0].S3.Object.Key != objectName {
  371. t.Fatalf("Received wrong object name in notification, expected %s, received %s", n[0].S3.Object.Key, objectName)
  372. }
  373. break
  374. case <-time.After(3 * time.Second):
  375. break
  376. }
  377. }
  378. func TestAddRemoveBucketListenerConfig(t *testing.T) {
  379. s := TestPeerRPCServerData{serverType: "XL"}
  380. // setup and teardown
  381. s.Setup(t)
  382. defer s.TearDown()
  383. // test code
  384. obj := s.testServer.Obj
  385. if err := initEventNotifier(obj); err != nil {
  386. t.Fatalf("Failed to initialize event notifier: %v", err)
  387. }
  388. // Make a bucket to store topicConfigs.
  389. randBucket := getRandomBucketName()
  390. if err := obj.MakeBucket(randBucket); err != nil {
  391. t.Fatalf("Failed to make bucket %s", randBucket)
  392. }
  393. // Add a topicConfig to an empty notificationConfig.
  394. accountID := fmt.Sprintf("%d", time.Now().UTC().UnixNano())
  395. accountARN := fmt.Sprintf(
  396. "arn:minio:sqs:%s:%s:listen-%s",
  397. serverConfig.GetRegion(),
  398. accountID,
  399. globalMinioAddr,
  400. )
  401. // Make topic configuration
  402. filterRules := []filterRule{
  403. {
  404. Name: "prefix",
  405. Value: "minio",
  406. },
  407. {
  408. Name: "suffix",
  409. Value: "*.jpg",
  410. },
  411. }
  412. sampleTopicCfg := topicConfig{
  413. TopicARN: accountARN,
  414. ServiceConfig: ServiceConfig{
  415. []string{"s3:ObjectRemoved:*", "s3:ObjectCreated:*"},
  416. filterStruct{
  417. keyFilter{filterRules},
  418. },
  419. "sns-" + accountID,
  420. },
  421. }
  422. sampleListenerCfg := &listenerConfig{
  423. TopicConfig: sampleTopicCfg,
  424. TargetServer: globalMinioAddr,
  425. }
  426. testCases := []struct {
  427. lCfg *listenerConfig
  428. expectedErr error
  429. }{
  430. {sampleListenerCfg, nil},
  431. {nil, errInvalidArgument},
  432. }
  433. for i, test := range testCases {
  434. err := AddBucketListenerConfig(randBucket, test.lCfg, obj)
  435. if err != test.expectedErr {
  436. t.Errorf(
  437. "Test %d: Failed with error %v, expected to fail with %v",
  438. i+1, err, test.expectedErr,
  439. )
  440. }
  441. }
  442. // test remove listener actually removes a listener
  443. RemoveBucketListenerConfig(randBucket, sampleListenerCfg, obj)
  444. // since it does not return errors we fetch the config and
  445. // check
  446. lcSlice := globalEventNotifier.GetBucketListenerConfig(randBucket)
  447. if len(lcSlice) != 0 {
  448. t.Errorf("Remove Listener Config Test: did not remove listener config - %v",
  449. lcSlice)
  450. }
  451. }