Add artifacts/database package 48/163348/5
authorKatarzyna Gorska <k.gorska@samsung.com>
Tue, 5 Dec 2017 10:49:11 +0000 (11:49 +0100)
committerKatarzyna Gorska <k.gorska@samsung.com>
Thu, 14 Dec 2017 12:15:10 +0000 (13:15 +0100)
Package database is responsible for Weles' artifact storage.

This patch introduces ArtifactDB struct responsible for database
connection and queries.

Change-Id: Ie74be2d5bb62a54c222351ae556fabe8e2be5f4c
Signed-off-by: Katarzyna Gorska <k.gorska@samsung.com>
artifacts/database/database.go [new file with mode: 0644]
artifacts/database/database_suite_test.go [new file with mode: 0644]
artifacts/database/database_test.go [new file with mode: 0644]
artifacts/database/errors.go [new file with mode: 0644]

diff --git a/artifacts/database/database.go b/artifacts/database/database.go
new file mode 100644 (file)
index 0000000..3245c89
--- /dev/null
@@ -0,0 +1,119 @@
+/*
+ *  Copyright (c) 2017 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ *  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
+ */
+
+// Package database is responsible for Weles system's job artifact storage.
+package database
+
+import (
+       "database/sql"
+
+       . "git.tizen.org/tools/weles"
+
+       "github.com/go-gorp/gorp"
+       // sqlite3 is imported for side-effects and will be used
+       // with the standard library sql interface.
+       _ "github.com/mattn/go-sqlite3"
+)
+
+type artifactInfoRecord struct {
+       ID int64 `db:",primarykey, autoincrement"`
+       ArtifactInfo
+}
+
+// ArtifactDB is responsible for database connection and queries.
+type ArtifactDB struct {
+       handler *sql.DB
+       dbmap   *gorp.DbMap
+}
+
+// Open opens database connection.
+func (aDB *ArtifactDB) Open(dbPath string) error {
+       var err error
+       aDB.handler, err = sql.Open("sqlite3", dbPath)
+       if err != nil {
+               return err
+       }
+
+       aDB.dbmap = &gorp.DbMap{Db: aDB.handler, Dialect: gorp.SqliteDialect{}}
+       return aDB.initDB()
+}
+
+// initDB initializes tables.
+func (aDB *ArtifactDB) initDB() error {
+       // Add tables.
+       aDB.dbmap.AddTableWithName(artifactInfoRecord{}, "artifacts").SetKeys(true, "ID")
+
+       return aDB.dbmap.CreateTablesIfNotExists()
+}
+
+// Close closes the database.
+func (aDB *ArtifactDB) Close() error {
+       return aDB.handler.Close()
+}
+
+// InsertArtifactInfo inserts information about artifact to database.
+func (aDB *ArtifactDB) InsertArtifactInfo(ai *ArtifactInfo) error {
+       ar := newArtifactInfoRecord(ai)
+       return aDB.dbmap.Insert(&ar)
+}
+
+// SelectPath selects artifact from database based on its path.
+func (aDB *ArtifactDB) SelectPath(path ArtifactPath) (ArtifactInfo, error) {
+       ar := artifactInfoRecord{}
+       err := aDB.dbmap.SelectOne(&ar, "select * from artifacts where Path=?", path)
+       if err != nil {
+               return ArtifactInfo{}, err
+       }
+       return artifactRecordToInfo(ar), nil
+}
+
+// Select fetches artifacts from ArtifactDB.
+func (aDB *ArtifactDB) Select(arg interface{}) (artifacts []ArtifactInfo, err error) {
+       var (
+               results []artifactInfoRecord
+               query   string
+       )
+       // TODO prepare efficient way of executing generic select.
+       switch arg.(type) {
+       case JobID:
+               query = "select * from artifacts where JobID = ?"
+       default:
+               return nil, ErrUnsupportedQueryType
+       }
+
+       _, err = aDB.dbmap.Select(&results, query, arg)
+       if err != nil {
+               return nil, err
+       }
+       artifacts = make([]ArtifactInfo, len(results))
+       for i, res := range results {
+               artifacts[i] = artifactRecordToInfo(res)
+       }
+       return artifacts, nil
+}
+
+// newArtifactInfoRecord prepares new ArtifactInfoRecord ready to be inserted to database.
+func newArtifactInfoRecord(ai *ArtifactInfo) artifactInfoRecord {
+       return artifactInfoRecord{
+               ArtifactInfo: *ai,
+       }
+}
+
+// artifactRecordToInfo converts ArtifactInfoRecord struct used by ArtifactDB dbmap
+// to ArtifactInfo used by interfaces.
+func artifactRecordToInfo(ar artifactInfoRecord) ArtifactInfo {
+       return ar.ArtifactInfo
+}
diff --git a/artifacts/database/database_suite_test.go b/artifacts/database/database_suite_test.go
new file mode 100644 (file)
index 0000000..71b3c43
--- /dev/null
@@ -0,0 +1,13 @@
+package database_test
+
+import (
+       . "github.com/onsi/ginkgo"
+       . "github.com/onsi/gomega"
+
+       "testing"
+)
+
+func TestDatabase(t *testing.T) {
+       RegisterFailHandler(Fail)
+       RunSpecs(t, "Database Suite")
+}
diff --git a/artifacts/database/database_test.go b/artifacts/database/database_test.go
new file mode 100644 (file)
index 0000000..544e7d3
--- /dev/null
@@ -0,0 +1,197 @@
+/*
+ *  Copyright (c) 2017 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ *  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
+ */
+
+// Package database is responsible for Weles system's job artifact storage.
+package database
+
+import (
+       "database/sql"
+       "io/ioutil"
+       "os"
+       "path/filepath"
+       "time"
+
+       "git.tizen.org/tools/weles"
+       . "github.com/onsi/ginkgo"
+       . "github.com/onsi/ginkgo/extensions/table"
+       . "github.com/onsi/gomega"
+)
+
+var _ = Describe("ArtifactDB", func() {
+       var (
+               job           weles.JobID        = 58008
+               invalidJob    weles.JobID        = 1
+               invalidPath   weles.ArtifactPath = "invalidPath"
+               goldenUnicorn ArtifactDB
+               tmpDir        string
+
+               artifact = weles.ArtifactInfo{
+                       weles.ArtifactDescription{
+                               job,
+                               weles.AM_IMAGEFILE,
+                               "some alias",
+                               "http://example.com",
+                       },
+                       "path1",
+                       weles.AM_PENDING,
+                       time.Now().UTC(),
+               }
+
+               aImageReady = weles.ArtifactInfo{
+                       weles.ArtifactDescription{
+                               job + 1,
+                               weles.AM_IMAGEFILE,
+                               "other alias",
+                               "http://example.com/1",
+                       },
+                       "path2",
+                       weles.AM_READY,
+                       time.Now().UTC(),
+               }
+
+               aYamlFailed = weles.ArtifactInfo{
+                       weles.ArtifactDescription{
+                               job + 1,
+                               weles.AM_YAMLFILE,
+                               "other alias",
+                               "http://example.com/2",
+                       },
+                       "path3",
+                       weles.AM_FAILED,
+                       time.Now().UTC(),
+               }
+
+               aTestFailed = weles.ArtifactInfo{
+                       weles.ArtifactDescription{
+                               job + 2,
+                               weles.AM_TESTFILE,
+                               "alias",
+                               "http://example.com/2",
+                       },
+                       "path4",
+                       weles.AM_FAILED,
+                       time.Unix(3000, 60).UTC(),
+               }
+
+               testArtifacts = []weles.ArtifactInfo{artifact, aImageReady, aYamlFailed, aTestFailed}
+       )
+
+       jobsInDB := func(job weles.JobID) int64 {
+               n, err := goldenUnicorn.dbmap.SelectInt(`SELECT COUNT(*)
+               FROM artifacts
+               WHERE JobID = ?`, job)
+               Expect(err).ToNot(HaveOccurred())
+               return n
+       }
+
+       BeforeEach(func() {
+               var err error
+               tmpDir, err = ioutil.TempDir("", "weles-")
+               Expect(err).ToNot(HaveOccurred())
+               err = goldenUnicorn.Open(filepath.Join(tmpDir, "test.db"))
+               Expect(err).ToNot(HaveOccurred())
+       })
+
+       AfterEach(func() {
+               err := goldenUnicorn.Close()
+               Expect(err).ToNot(HaveOccurred())
+               err = os.RemoveAll(tmpDir)
+               Expect(err).ToNot(HaveOccurred())
+       })
+
+       It("should open new database, with artifact table", func() {
+               n, err := goldenUnicorn.dbmap.SelectInt(`SELECT COUNT(*)
+               FROM sqlite_master
+               WHERE name = 'artifacts'
+               AND type = 'table'`)
+               Expect(err).ToNot(HaveOccurred())
+               Expect(n).To(BeNumerically("==", 1))
+       })
+
+       It("should fail to open database on invalid path", func() {
+               // sql.Open only validates arguments.
+               // db.Ping must be called to check the connection.
+               invalidDatabasePath := filepath.Join(tmpDir, "invalid", "test.db")
+               err := goldenUnicorn.Open(invalidDatabasePath)
+               Expect(err).To(HaveOccurred())
+               Expect(invalidDatabasePath).ToNot(BeAnExistingFile())
+       })
+
+       It("should insert new artifact to database", func() {
+               Expect(jobsInDB(job)).To(BeNumerically("==", 0))
+
+               err := goldenUnicorn.InsertArtifactInfo(&artifact)
+               Expect(err).ToNot(HaveOccurred())
+
+               Expect(jobsInDB(artifact.JobID)).To(BeNumerically("==", 1))
+       })
+
+       Describe("SelectPath", func() {
+
+               BeforeEach(func() {
+                       err := goldenUnicorn.InsertArtifactInfo(&artifact)
+                       Expect(err).ToNot(HaveOccurred())
+
+                       Expect(jobsInDB(artifact.JobID)).To(BeNumerically("==", 1))
+               })
+
+               DescribeTable("database selectpath",
+                       func(path weles.ArtifactPath, expectedErr error, expectedArtifact weles.ArtifactInfo) {
+                               result, err := goldenUnicorn.SelectPath(path)
+
+                               if expectedErr != nil {
+                                       Expect(err).To(Equal(expectedErr))
+                               } else {
+                                       Expect(err).ToNot(HaveOccurred())
+                               }
+                               Expect(result).To(Equal(expectedArtifact))
+                       },
+                       Entry("retrieve artifact based on path", artifact.Path, nil, artifact),
+                       Entry("retrieve artifact based on invalid path", invalidPath, sql.ErrNoRows, weles.ArtifactInfo{}),
+               )
+       })
+
+       Describe("Select", func() {
+
+               BeforeEach(func() {
+                       for _, a := range testArtifacts {
+                               err := goldenUnicorn.InsertArtifactInfo(&a)
+                               Expect(err).ToNot(HaveOccurred())
+                       }
+               })
+
+               DescribeTable("database select",
+                       func(lookedFor interface{}, expectedErr error, expectedResult ...weles.ArtifactInfo) {
+                               result, err := goldenUnicorn.Select(lookedFor)
+
+                               if expectedErr != nil {
+                                       Expect(err).To(Equal(expectedErr))
+                                       Expect(result).To(BeNil())
+                               } else {
+                                       Expect(err).ToNot(HaveOccurred())
+                                       Expect(result).To(Equal(expectedResult))
+                               }
+                       },
+                       // type supported by select.
+                       Entry("select JobID", artifact.JobID, nil, artifact),
+                       // type bool is not supported by select.
+                       Entry("select unsupported value", true, ErrUnsupportedQueryType),
+                       // test query itsef.
+                       Entry("select multiple entries for JobID", aImageReady.JobID, nil, aImageReady, aYamlFailed),
+                       Entry("select no entries for invalid JobID", invalidJob, nil),
+               )
+       })
+})
diff --git a/artifacts/database/errors.go b/artifacts/database/errors.go
new file mode 100644 (file)
index 0000000..7f2d8a4
--- /dev/null
@@ -0,0 +1,29 @@
+/*
+ *  Copyright (c) 2017 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ *  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
+ */
+
+// File errors.go provides definitions of errors for Weles' database package.
+
+package database
+
+import (
+       "errors"
+)
+
+var (
+       // ErrUnsupportedQueryType is returned when wrong type of argument is passed to
+       // ArtifactDB's Select().
+       ErrUnsupportedQueryType = errors.New("unsupported argument type")
+)