server_test.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513
  1. /*
  2. * EliasDB
  3. *
  4. * Copyright 2016 Matthias Ladkau. All rights reserved.
  5. *
  6. * This Source Code Form is subject to the terms of the Mozilla Public
  7. * License, v. 2.0. If a copy of the MPL was not distributed with this
  8. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
  9. */
  10. package server
  11. import (
  12. "errors"
  13. "flag"
  14. "fmt"
  15. "io/ioutil"
  16. "log"
  17. "net/http"
  18. "os"
  19. "runtime"
  20. "strings"
  21. "testing"
  22. "time"
  23. "devt.de/krotik/common/fileutil"
  24. "devt.de/krotik/common/httputil"
  25. "devt.de/krotik/eliasdb/api"
  26. "devt.de/krotik/eliasdb/api/ac"
  27. "devt.de/krotik/eliasdb/cluster"
  28. "devt.de/krotik/eliasdb/cluster/manager"
  29. "devt.de/krotik/eliasdb/config"
  30. "devt.de/krotik/eliasdb/graph"
  31. "devt.de/krotik/eliasdb/graph/graphstorage"
  32. )
  33. /*
  34. Flag to enable / disable long running tests.
  35. (Only used for test development - should never be false)
  36. */
  37. const RunLongRunningTests = true
  38. const testdb = "testdb"
  39. const invalidFileName = "**" + "\x00"
  40. var printLog = []string{}
  41. var errorLog = []string{}
  42. var printLogging = false
  43. func TestMain(m *testing.M) {
  44. flag.Parse()
  45. basepath = testdb + "/"
  46. // Log all print and error messages
  47. print = func(v ...interface{}) {
  48. if printLogging {
  49. fmt.Println(v...)
  50. }
  51. printLog = append(printLog, fmt.Sprint(v...))
  52. }
  53. fatal = func(v ...interface{}) {
  54. if printLogging {
  55. fmt.Println(v...)
  56. }
  57. errorLog = append(errorLog, fmt.Sprint(v...))
  58. }
  59. defer func() {
  60. fatal = log.Fatal
  61. basepath = ""
  62. }()
  63. if res, _ := fileutil.PathExists(testdb); res {
  64. if err := os.RemoveAll(testdb); err != nil {
  65. fmt.Print("Could not remove test directory:", err.Error())
  66. }
  67. }
  68. ensurePath(testdb)
  69. // Run the tests
  70. res := m.Run()
  71. if res, _ := fileutil.PathExists(testdb); res {
  72. if err := os.RemoveAll(testdb); err != nil {
  73. fmt.Print("Could not remove test directory:", err.Error())
  74. }
  75. }
  76. os.Exit(res)
  77. }
  78. func TestMainNormalCase(t *testing.T) {
  79. if !RunLongRunningTests {
  80. return
  81. }
  82. // Make sure to reset the DefaultServeMux
  83. defer func() { http.DefaultServeMux = http.NewServeMux() }()
  84. // Make sure to remove any files
  85. defer func() {
  86. if err := os.RemoveAll(testdb); err != nil {
  87. fmt.Print("Could not remove test directory:", err.Error())
  88. }
  89. time.Sleep(time.Duration(100) * time.Millisecond)
  90. ensurePath(testdb)
  91. }()
  92. // Reset logs
  93. printLog = []string{}
  94. errorLog = []string{}
  95. errorChan := make(chan error)
  96. // Load default configuration
  97. config.LoadDefaultConfig()
  98. // Start ECAL scripting by default
  99. config.Config[config.EnableECALScripts] = true
  100. // Start cluster by default
  101. config.Config[config.EnableCluster] = true
  102. config.Config[config.EnableClusterTerminal] = true
  103. // Enable access control
  104. config.Config[config.EnableAccessControl] = true
  105. // Kick off main function
  106. go func() {
  107. out, _ := runServer()
  108. config.Config[config.EnableCluster] = false
  109. config.Config[config.EnableClusterTerminal] = false
  110. config.Config[config.EnableAccessControl] = false
  111. ac.ACL.Close()
  112. os.Remove("access.db")
  113. lines := strings.Split(strings.TrimSpace(out), "\n")
  114. errorChan <- nil
  115. // stderr should contain one line from the rpc code
  116. if len(lines) != 1 || !strings.Contains(lines[0], "rpc.Serve: accept") {
  117. t.Error("Unexpected stderr:", out)
  118. return
  119. }
  120. }()
  121. // To exit the main function the lock watcher thread
  122. // has to recognise that the lockfile was modified
  123. shutdown := false
  124. go func() {
  125. filename := basepath + config.Str(config.LockFile)
  126. for !shutdown {
  127. // Do a normal shutdown with a log file - don't check for errors
  128. shutdownWithLogFile(filename)
  129. time.Sleep(time.Duration(200) * time.Millisecond)
  130. }
  131. }()
  132. // Wait for the main function to end
  133. if err := <-errorChan; err != nil || len(errorLog) != 0 {
  134. t.Error("Unexpected ending of main thread:", err, errorLog)
  135. return
  136. }
  137. shutdown = true
  138. // Check the print log
  139. logString := strings.Join(printLog, "\n")
  140. if runtime.GOOS == "windows" {
  141. // Very primitive but good enough
  142. logString = strings.Replace(logString, "\\", "/", -1)
  143. }
  144. if logString != `
  145. EliasDB `[1:]+config.ProductVersion+`
  146. Starting datastore in testdb/db
  147. Reading cluster config
  148. Opening cluster state info
  149. Starting cluster (log history: 100)
  150. [Cluster] member1: Starting member manager member1 rpc server on: 127.0.0.1:9030
  151. Creating GraphManager instance
  152. Loading ECAL scripts in testdb/scripts
  153. Creating key (key.pem) and certificate (cert.pem) in: ssl
  154. Ensuring web folder: testdb/web
  155. Ensuring login page: testdb/web/login.html
  156. Ensuring web terminal: testdb/web/db/term.html
  157. Ensuring cluster terminal: testdb/web/db/cluster.html
  158. Starting HTTPS server on: 127.0.0.1:9090
  159. Writing fingerprint file: testdb/web/fingerprint.json
  160. Waiting for shutdown
  161. Lockfile was modified
  162. Shutting down
  163. [Cluster] member1: Housekeeping stopped
  164. [Cluster] member1: Shutdown rpc server on: 127.0.0.1:9030
  165. [Cluster] member1: Connection closed: 127.0.0.1:9030
  166. Closing datastore` {
  167. t.Error("Unexpected log:", logString)
  168. return
  169. }
  170. }
  171. func TestMainErrorCases(t *testing.T) {
  172. if !RunLongRunningTests {
  173. return
  174. }
  175. // Make sure to reset the DefaultServeMux
  176. defer func() { http.DefaultServeMux = http.NewServeMux() }()
  177. // Make sure to remove any files
  178. defer func() {
  179. if err := os.RemoveAll(testdb); err != nil {
  180. fmt.Print("Could not remove test directory:", err.Error())
  181. }
  182. time.Sleep(time.Duration(100) * time.Millisecond)
  183. ensurePath(testdb)
  184. }()
  185. // Setup config and logs
  186. data := make(map[string]interface{})
  187. for k, v := range config.DefaultConfig {
  188. data[k] = v
  189. }
  190. config.Config = data
  191. printLog = []string{}
  192. errorLog = []string{}
  193. // Test db access error
  194. config.Config[config.MemoryOnlyStorage] = true
  195. config.Config[config.EnableReadOnly] = true
  196. config.Config[config.EnableECALScripts] = true
  197. config.Config[config.ECALEntryScript] = invalidFileName
  198. runServer()
  199. // Check that an error happened
  200. if len(errorLog) != 1 ||
  201. !strings.Contains(errorLog[0], "Failed to start ECAL scripting interpreter") {
  202. t.Error("Unexpected error:", errorLog)
  203. return
  204. }
  205. // Set back logs
  206. printLog = []string{}
  207. errorLog = []string{}
  208. // Test db access error
  209. config.Config[config.EnableECALScripts] = false
  210. config.Config[config.MemoryOnlyStorage] = false
  211. config.Config[config.LocationDatastore] = invalidFileName
  212. config.Config[config.EnableReadOnly] = true
  213. runServer()
  214. // Check that an error happened
  215. if len(errorLog) != 2 ||
  216. !strings.Contains(errorLog[0], "Could not create directory") ||
  217. !strings.Contains(errorLog[1], "Failed to open graph storage") {
  218. t.Error("Unexpected error:", errorLog)
  219. return
  220. }
  221. // Set back logs
  222. printLog = []string{}
  223. errorLog = []string{}
  224. // Use memory only storage and the ignored readonly flag
  225. config.Config[config.MemoryOnlyStorage] = true
  226. config.Config[config.EnableReadOnly] = true
  227. // Test failed ssl key generation
  228. config.Config[config.HTTPSKey] = invalidFileName
  229. runServer()
  230. // Check that an error happened
  231. if len(errorLog) != 1 ||
  232. !strings.Contains(errorLog[0], "Failed to generate ssl key and certificate") {
  233. t.Error("Unexpected error:", errorLog)
  234. return
  235. }
  236. config.Config[config.HTTPSKey] = config.DefaultConfig[config.HTTPSKey]
  237. // Set back logs
  238. printLog = []string{}
  239. errorLog = []string{}
  240. // Special error when closing the store
  241. graphstorage.MgsRetClose = errors.New("Testerror")
  242. // Use 9090
  243. config.Config[config.HTTPSPort] = "9090"
  244. ths := httputil.HTTPServer{}
  245. go ths.RunHTTPServer(":9090", nil)
  246. time.Sleep(time.Duration(1) * time.Second)
  247. runServer()
  248. ths.Shutdown()
  249. time.Sleep(time.Duration(1) * time.Second)
  250. if ths.Running {
  251. t.Error("Server should not be running")
  252. return
  253. }
  254. if len(errorLog) != 2 || (errorLog[0] != "listen tcp :9090"+
  255. ": bind: address already in use" && errorLog[0] != "listen tcp :9090"+
  256. ": bind: Only one usage of each socket address (protocol/network address/port) is normally permitted.") ||
  257. errorLog[1] != "Testerror" {
  258. t.Error("Unexpected error:", errorLog)
  259. return
  260. }
  261. // Set back logs
  262. printLog = []string{}
  263. errorLog = []string{}
  264. config.Config[config.EnableCluster] = true
  265. cluster.DSRetNew = errors.New("testerror")
  266. defer func() {
  267. cluster.DSRetNew = nil
  268. }()
  269. runServer()
  270. if len(errorLog) != 1 ||
  271. !strings.Contains(errorLog[0], "testerror") {
  272. t.Error("Unexpected error:", errorLog)
  273. return
  274. }
  275. // Set back logs
  276. printLog = []string{}
  277. errorLog = []string{}
  278. config.Config[config.EnableCluster] = true
  279. config.Config[config.ClusterStateInfoFile] = invalidFileName
  280. runServer()
  281. if len(errorLog) != 1 ||
  282. !strings.Contains(errorLog[0], "Failed to load cluster state info") {
  283. t.Error("Unexpected error:", errorLog)
  284. return
  285. }
  286. // Set back logs
  287. printLog = []string{}
  288. errorLog = []string{}
  289. config.Config[config.ClusterConfigFile] = invalidFileName
  290. runServer()
  291. if len(errorLog) != 1 ||
  292. !strings.Contains(errorLog[0], "Failed to load cluster config") {
  293. t.Error("Unexpected error:", errorLog)
  294. return
  295. }
  296. // Call to debug log function
  297. manager.LogDebug("test debug text")
  298. if logout := api.DDLog.String(); !strings.Contains(logout, "test debug text") {
  299. t.Error("Unexpected error:", logout)
  300. return
  301. }
  302. config.Config = nil
  303. SOPExecuted := false
  304. // Test single operation
  305. StartServerWithSingleOp(func(gm *graph.Manager) bool {
  306. SOPExecuted = true
  307. return true
  308. })
  309. if !SOPExecuted {
  310. t.Error("Single operation function was not executed")
  311. return
  312. }
  313. config.Config = nil
  314. }
  315. func shutdownWithLogFile(filename string) error {
  316. file, err := os.OpenFile(filename, os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0660)
  317. defer file.Close()
  318. if err != nil {
  319. fmt.Println(errorLog)
  320. return err
  321. }
  322. _, err = file.Write([]byte("a"))
  323. if err != nil {
  324. return err
  325. }
  326. return nil
  327. }
  328. /*
  329. Run the server and capture the output.
  330. */
  331. func runServer() (string, error) {
  332. defer func() {
  333. if r := recover(); r != nil {
  334. fmt.Println("Server execution caused a panic.")
  335. out, err := ioutil.ReadFile("out.txt")
  336. if err != nil {
  337. fmt.Println(err)
  338. }
  339. fmt.Println(out)
  340. }
  341. }()
  342. // Exchange stderr to a file
  343. origStdErr := os.Stderr
  344. outFile, err := os.Create("out.txt")
  345. if err != nil {
  346. return "", err
  347. }
  348. defer func() {
  349. outFile.Close()
  350. os.RemoveAll("out.txt")
  351. // Put Stderr back
  352. os.Stderr = origStdErr
  353. log.SetOutput(os.Stderr)
  354. }()
  355. os.Stderr = outFile
  356. log.SetOutput(outFile)
  357. StartServer()
  358. // Reset flags
  359. outFile.Sync()
  360. out, err := ioutil.ReadFile("out.txt")
  361. if err != nil {
  362. return "", err
  363. }
  364. return string(out), nil
  365. }