blob: b30363ec4c8f73085aef747998e9c1104dda5aaa [file] [log] [blame] [edit]
// Copyright 2017 Google 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.
package apiGatewayDeploy
import (
"encoding/json"
"net/url"
"time"
"net/http"
"net/http/httptest"
"io/ioutil"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("bundle", func() {
Context("download", func() {
It("should timeout connection and retry", func() {
defer func() {
bundleDownloadConnTimeout = time.Second
}()
bundleDownloadConnTimeout = 100 * time.Millisecond
firstTime := true
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if firstTime {
firstTime = false
time.Sleep(1 * time.Second)
w.WriteHeader(500)
} else {
w.Write([]byte("/bundles/longfail"))
}
}))
defer ts.Close()
uri, err := url.Parse(ts.URL)
Expect(err).ShouldNot(HaveOccurred())
uri.Path = "/bundles/longfail"
tx, err := getDB().Begin()
Expect(err).ShouldNot(HaveOccurred())
deploymentID := "bundle_download_fail"
dep := DataDeployment{
ID: deploymentID,
DataScopeID: deploymentID,
BundleURI: uri.String(),
BundleChecksum: testGetChecksum("crc32", uri.String()),
BundleChecksumType: "crc32",
}
err = InsertDeployment(tx, dep)
Expect(err).ShouldNot(HaveOccurred())
err = tx.Commit()
Expect(err).ShouldNot(HaveOccurred())
queueDownloadRequest(dep)
var listener = make(chan deploymentsResult)
addSubscriber <- listener
result := <-listener
Expect(result.err).NotTo(HaveOccurred())
Expect(len(result.deployments)).To(Equal(1))
d := result.deployments[0]
Expect(d.ID).To(Equal(deploymentID))
})
It("should timeout deployment, mark status as failed, then finish", func() {
proceed := make(chan bool)
failedOnce := false
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if failedOnce {
proceed <- true
time.Sleep(markDeploymentFailedAfter)
w.Write([]byte("/bundles/longfail"))
} else {
failedOnce = true
time.Sleep(markDeploymentFailedAfter)
w.WriteHeader(500)
}
}))
defer ts.Close()
deploymentID := "bundle_download_fail"
uri, err := url.Parse(ts.URL)
Expect(err).ShouldNot(HaveOccurred())
uri.Path = "/bundles/longfail"
bundleUri := uri.String()
bundle := bundleConfigJson{
Name: uri.Path,
URI: bundleUri,
ChecksumType: "crc32",
}
bundle.Checksum = testGetChecksum(bundle.ChecksumType, bundleUri)
bundleJson, err := json.Marshal(bundle)
Expect(err).ShouldNot(HaveOccurred())
tx, err := getDB().Begin()
Expect(err).ShouldNot(HaveOccurred())
dep := DataDeployment{
ID: deploymentID,
BundleConfigID: deploymentID,
ApidClusterID: deploymentID,
DataScopeID: deploymentID,
BundleConfigJSON: string(bundleJson),
ConfigJSON: string(bundleJson),
Created: "",
CreatedBy: "",
Updated: "",
UpdatedBy: "",
BundleName: deploymentID,
BundleURI: bundle.URI,
BundleChecksum: bundle.Checksum,
BundleChecksumType: bundle.ChecksumType,
LocalBundleURI: "",
DeployStatus: "",
DeployErrorCode: 0,
DeployErrorMessage: "",
}
err = InsertDeployment(tx, dep)
Expect(err).ShouldNot(HaveOccurred())
err = tx.Commit()
Expect(err).ShouldNot(HaveOccurred())
trackerHit := false
tracker := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer GinkgoRecover()
b, err := ioutil.ReadAll(r.Body)
Expect(err).ShouldNot(HaveOccurred())
var received apiDeploymentResults
json.Unmarshal(b, &received)
expected := apiDeploymentResults{
{
ID: deploymentID,
Status: RESPONSE_STATUS_FAIL,
ErrorCode: TRACKER_ERR_BUNDLE_DOWNLOAD_TIMEOUT,
Message: "bundle download failed",
},
}
Expect(received).To(Equal(expected))
trackerHit = true
w.Write([]byte("OK"))
}))
defer tracker.Close()
apiServerBaseURI, err = url.Parse(tracker.URL)
Expect(err).ShouldNot(HaveOccurred())
queueDownloadRequest(dep)
<-proceed
// get error state deployment
deployments, err := getDeployments("WHERE id=$1", deploymentID)
Expect(err).ShouldNot(HaveOccurred())
Expect(len(deployments)).To(Equal(1))
d := deployments[0]
Expect(d.ID).To(Equal(deploymentID))
Expect(d.DeployStatus).To(Equal(RESPONSE_STATUS_FAIL))
Expect(d.DeployErrorCode).To(Equal(TRACKER_ERR_BUNDLE_DOWNLOAD_TIMEOUT))
Expect(d.DeployErrorMessage).ToNot(BeEmpty())
Expect(d.LocalBundleURI).To(BeEmpty())
Expect(trackerHit).To(BeTrue())
var listener = make(chan deploymentsResult)
addSubscriber <- listener
<-listener
// get finished deployment
// still in error state (let client update), but with valid local bundle
deployments, err = getDeployments("WHERE id=$1", deploymentID)
Expect(err).ShouldNot(HaveOccurred())
Expect(len(deployments)).To(Equal(1))
d = deployments[0]
Expect(d.ID).To(Equal(deploymentID))
Expect(d.DeployStatus).To(Equal(RESPONSE_STATUS_FAIL))
Expect(d.DeployErrorCode).To(Equal(TRACKER_ERR_BUNDLE_DOWNLOAD_TIMEOUT))
Expect(d.DeployErrorMessage).ToNot(BeEmpty())
Expect(d.LocalBundleURI).To(BeAnExistingFile())
})
It("should not continue attempts if deployment has been deleted", func() {
deploymentID := "bundle_download_deployment_deleted"
uri, err := url.Parse(testServer.URL)
Expect(err).ShouldNot(HaveOccurred())
uri.Path = "/bundles/failonce"
bundleUri := uri.String()
bundle := bundleConfigJson{
Name: uri.Path,
URI: bundleUri,
ChecksumType: "crc32",
}
bundle.Checksum = testGetChecksum(bundle.ChecksumType, bundleUri)
bundleJson, err := json.Marshal(bundle)
Expect(err).ShouldNot(HaveOccurred())
tx, err := getDB().Begin()
Expect(err).ShouldNot(HaveOccurred())
dep := DataDeployment{
ID: deploymentID,
BundleConfigID: deploymentID,
ApidClusterID: deploymentID,
DataScopeID: deploymentID,
BundleConfigJSON: string(bundleJson),
ConfigJSON: string(bundleJson),
Created: "",
CreatedBy: "",
Updated: "",
UpdatedBy: "",
BundleName: deploymentID,
BundleURI: bundle.URI,
BundleChecksum: bundle.Checksum,
BundleChecksumType: bundle.ChecksumType,
LocalBundleURI: "",
DeployStatus: "",
DeployErrorCode: 0,
DeployErrorMessage: "",
}
err = InsertDeployment(tx, dep)
Expect(err).ShouldNot(HaveOccurred())
err = tx.Commit()
Expect(err).ShouldNot(HaveOccurred())
queueDownloadRequest(dep)
// skip first try
time.Sleep(bundleRetryDelay)
// delete deployment
tx, err = getDB().Begin()
Expect(err).ShouldNot(HaveOccurred())
deleteDeployment(tx, dep.ID)
err = tx.Commit()
Expect(err).ShouldNot(HaveOccurred())
// wait for final
time.Sleep(bundleRetryDelay)
// No way to test this programmatically currently
// search logs for "never mind, deployment bundle_download_deployment_deleted was deleted"
})
})
Context("checksums", func() {
It("should download with empty checksumType", func() {
checksumDownloadValid("")
})
It("should fail download with bad empty checksumType", func() {
checksumDownloadInvalid("")
})
It("should fail download with bad md5", func() {
checksumDownloadInvalid("md5")
})
It("should download with good md5", func() {
checksumDownloadValid("md5")
})
It("should fail download with bad md5", func() {
checksumDownloadInvalid("md5")
})
It("should download with good crc32", func() {
checksumDownloadValid("crc32")
})
It("should fail download with bad crc32", func() {
checksumDownloadInvalid("crc32")
})
It("should download with good sha256", func() {
checksumDownloadValid("sha256")
})
It("should fail download with bad sha256", func() {
checksumDownloadInvalid("sha256")
})
It("should download correctly with sha512", func() {
checksumDownloadValid("sha512")
})
It("should fail download with bad sha512", func() {
checksumDownloadInvalid("sha512")
})
})
})
func checksumDownloadValid(checksumType string) {
defer GinkgoRecover()
uri, err := url.Parse(testServer.URL)
Expect(err).ShouldNot(HaveOccurred())
uri.Path = "/bundles/checksum"
checksum := testGetChecksum(checksumType, uri.String())
hash, err := getHashWriter(checksumType)
Expect(err).NotTo(HaveOccurred())
_, err = downloadFromURI(uri.String(), hash, checksum)
Expect(err).NotTo(HaveOccurred())
}
func checksumDownloadInvalid(checksumType string) {
defer GinkgoRecover()
uri, err := url.Parse(testServer.URL)
Expect(err).ShouldNot(HaveOccurred())
uri.Path = "/bundles/checksum"
checksum := "invalidchecksum"
hash, err := getHashWriter(checksumType)
Expect(err).NotTo(HaveOccurred())
_, err = downloadFromURI(uri.String(), hash, checksum)
Expect(err).To(HaveOccurred())
}