From 18d6573981e588263957fd257df7fe3a8a669ad4 Mon Sep 17 00:00:00 2001 From: Manuel de Brito Fontes Date: Wed, 22 Nov 2017 10:40:54 -0300 Subject: [PATCH] Add fake filesystem for test to avoid temporal files on the local filesystem --- .travis.yml | 9 +- Makefile | 10 +- cmd/nginx/main.go | 2 +- internal/file/bindata.go | 289 ++++++++++++++++++ internal/file/filesystem.go | 127 ++++++++ internal/file/filesystem_test.go | 37 +++ internal/file/structure.go | 26 ++ internal/ingress/controller/controller.go | 5 +- internal/ingress/controller/nginx.go | 27 +- .../ingress/controller/template/template.go | 22 +- .../controller/template/template_test.go | 16 +- internal/watch/dummy.go | 29 ++ internal/watch/file_watcher.go | 14 +- test/e2e/up.sh | 44 --- test/e2e/wait-for-nginx.sh | 61 ++++ 15 files changed, 631 insertions(+), 87 deletions(-) create mode 100644 internal/file/bindata.go create mode 100644 internal/file/filesystem.go create mode 100644 internal/file/filesystem_test.go create mode 100644 internal/file/structure.go create mode 100644 internal/watch/dummy.go create mode 100755 test/e2e/wait-for-nginx.sh diff --git a/.travis.yml b/.travis.yml index c40926a84d..bf40ab59ce 100644 --- a/.travis.yml +++ b/.travis.yml @@ -34,16 +34,21 @@ jobs: - go get github.com/golang/lint/golint - make fmt lint vet - stage: Coverage + before_script: + # start minikube + - test/e2e/up.sh script: - go get github.com/mattn/goveralls - go get github.com/modocache/gover - - if ! go get github.com/golang/tools/cmd/cover; then go get golang.org/x/tools/cmd/cover; - fi + - if ! go get github.com/golang/tools/cmd/cover; then go get golang.org/x/tools/cmd/cover;fi + - if ! go get github.com/jteeuwen/go-bindata/...; then github.com/jteeuwen/go-bindata/...;fi - make cover - stage: e2e before_script: + - if ! go get github.com/jteeuwen/go-bindata/...; then github.com/jteeuwen/go-bindata/...;fi - make e2e-image - test/e2e/up.sh + - test/e2e/wait-for-nginx.sh script: - make e2e-test # split builds to avoid job timeouts diff --git a/Makefile b/Makefile index fd7d37f27a..73ccea0c2b 100644 --- a/Makefile +++ b/Makefile @@ -133,8 +133,12 @@ endif clean: $(DOCKER) rmi -f $(MULTI_ARCH_IMG):$(TAG) || true +.PHONE: gobindata +gobindata: + go-bindata -o internal/file/bindata.go -prefix="rootfs" -pkg=file -ignore=Dockerfile -ignore=".DS_Store" rootfs/... + .PHONY: build -build: clean +build: clean gobindata CGO_ENABLED=0 GOOS=${GOOS} GOARCH=${GOARCH} go build -a -installsuffix cgo \ -ldflags "-s -w -X ${PKG}/version.RELEASE=${TAG} -X ${PKG}/version.COMMIT=${COMMIT} -X ${PKG}/version.REPO=${REPO_INFO}" \ -o ${TEMP_DIR}/rootfs/nginx-ingress-controller ${PKG}/cmd/nginx @@ -150,7 +154,7 @@ lint: @go list -f '{{if len .TestGoFiles}}"golint {{.Dir}}/..."{{end}}' $(shell go list ${PKG}/... | grep -v vendor | grep -v '/test/e2e') | xargs -L 1 sh -c .PHONY: test -test: fmt lint vet +test: fmt lint vet gobindata @echo "+ $@" @go test -v -race -tags "$(BUILDTAGS) cgo" $(shell go list ${PKG}/... | grep -v vendor | grep -v '/test/e2e') @@ -165,7 +169,7 @@ e2e-test: @KUBECONFIG=${HOME}/.kube/config INGRESSNGINXCONFIG=${HOME}/.kube/config ./e2e-tests .PHONY: cover -cover: +cover: gobindata @echo "+ $@" @go list -f '{{if len .TestGoFiles}}"go test -coverprofile={{.Dir}}/.coverprofile {{.ImportPath}}"{{end}}' $(shell go list ${PKG}/... | grep -v vendor | grep -v '/test/e2e') | xargs -L 1 sh -c gover diff --git a/cmd/nginx/main.go b/cmd/nginx/main.go index a34bfb51c7..cbc52573cd 100644 --- a/cmd/nginx/main.go +++ b/cmd/nginx/main.go @@ -118,7 +118,7 @@ func main() { // create the default SSL certificate (dummy) defCert, defKey := ssl.GetFakeSSLCert() - c, err := ssl.AddOrUpdateCertAndKey(fakeCertificate, defCert, defKey, []byte{}, fs) + c, err := ssl.AddOrUpdateCertAndKey(fakeCertificate, defCert, defKey, []byte{}) if err != nil { glog.Fatalf("Error generating self signed certificate: %v", err) } diff --git a/internal/file/bindata.go b/internal/file/bindata.go new file mode 100644 index 0000000000..6fd5810c9a --- /dev/null +++ b/internal/file/bindata.go @@ -0,0 +1,289 @@ +// Code generated by go-bindata. +// sources: +// rootfs/etc/nginx/nginx.conf +// rootfs/etc/nginx/template/nginx.tmpl +// rootfs/ingress-controller/clean-nginx-conf.sh +// DO NOT EDIT! + +package file + +import ( + "bytes" + "compress/gzip" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + "strings" + "time" +) + +func bindataRead(data []byte, name string) ([]byte, error) { + gz, err := gzip.NewReader(bytes.NewBuffer(data)) + if err != nil { + return nil, fmt.Errorf("Read %q: %v", name, err) + } + + var buf bytes.Buffer + _, err = io.Copy(&buf, gz) + clErr := gz.Close() + + if err != nil { + return nil, fmt.Errorf("Read %q: %v", name, err) + } + if clErr != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +type asset struct { + bytes []byte + info os.FileInfo +} + +type bindataFileInfo struct { + name string + size int64 + mode os.FileMode + modTime time.Time +} + +func (fi bindataFileInfo) Name() string { + return fi.name +} +func (fi bindataFileInfo) Size() int64 { + return fi.size +} +func (fi bindataFileInfo) Mode() os.FileMode { + return fi.mode +} +func (fi bindataFileInfo) ModTime() time.Time { + return fi.modTime +} +func (fi bindataFileInfo) IsDir() bool { + return false +} +func (fi bindataFileInfo) Sys() interface{} { + return nil +} + +var _etcNginxNginxConf = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x2c\xca\x41\x0a\x83\x40\x0c\x85\xe1\xfd\x9c\xe2\x41\xf7\x7a\x00\x57\x3d\xca\xa0\x89\x06\x34\x19\x32\x4f\x69\x29\xbd\x7b\x69\xe9\xea\x5f\xfc\xdf\x0d\x77\x5c\x92\x4f\x74\x3b\xda\x2e\xf0\xd5\xfc\x81\x39\x5c\x6d\x3d\xb3\xd2\xc2\xa1\xb6\x0b\xb8\x55\x42\x23\x67\xe9\x7f\xc4\x40\x67\x4d\x0e\xa5\xd9\x82\x31\x4f\x1f\x7f\x63\x68\xb6\x4c\xa5\xc8\x25\xce\x8e\xd7\xbb\x6c\x64\xfb\x76\xa9\x72\x84\x23\x54\xa7\x4f\x00\x00\x00\xff\xff\x75\xb5\xe6\xb8\x77\x00\x00\x00") + +func etcNginxNginxConfBytes() ([]byte, error) { + return bindataRead( + _etcNginxNginxConf, + "etc/nginx/nginx.conf", + ) +} + +func etcNginxNginxConf() (*asset, error) { + bytes, err := etcNginxNginxConfBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "etc/nginx/nginx.conf", size: 119, mode: os.FileMode(420), modTime: time.Unix(1508444716, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _etcNginxTemplateNginxTmpl = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xec\x7d\x6d\x73\xdb\x38\xd2\xe0\xf7\xfc\x0a\x94\xec\x2a\xdb\xa9\x48\x76\xb2\xd9\x79\xe6\xb1\x2b\x57\xe7\xd8\xc9\xda\x37\x4e\xa2\xb2\x9c\xcc\xd4\x5e\x5d\xa9\x20\xb2\x25\x62\x4d\x01\x5c\x00\xb4\xad\xe4\x74\xbf\xfd\x0a\x6f\x24\x08\x82\x94\x9c\xc9\x64\x66\xb6\x86\x1f\x5c\x16\xd9\x68\x34\x1a\x8d\x06\xfa\x05\xc0\x97\x2f\x68\x17\xe7\x39\x3a\x7e\x85\x46\x68\xbd\x7e\xa2\x7e\x0b\xe0\x77\xc0\x85\x7e\x37\xb1\xff\xdb\x4f\xc9\x7c\xa1\x5f\x9f\xcd\x17\xee\xd5\xa5\xb8\x1c\x7f\xfa\xe1\x0d\xc5\xb3\x1c\x52\xfd\xb1\xf9\xc6\x82\x65\x80\x73\x99\x7d\xfe\x78\x7d\xa9\x61\x2e\xea\x9f\x16\x60\x86\x93\x5b\xa0\xa9\xa9\xf6\xb5\xfb\x61\x3f\x16\x9c\x3d\xac\x2e\x00\xa7\x8e\xae\xb1\x7a\x31\x01\xe9\xde\x59\x38\x9c\xa6\x3e\xd4\x69\xfd\x73\xbd\x7e\xa2\x20\xc8\x5c\x37\x62\x64\xa8\x7b\xc7\x52\x01\x49\xc9\x89\x5c\x29\x80\x9c\xe1\x74\xba\x64\x69\x99\x03\x3a\x04\x99\x1c\xd2\x05\xa1\x0f\x87\xe6\x8d\x38\xa4\x8b\x87\x69\x26\x65\xa1\x40\x5c\x31\x0b\x3e\x12\xec\x44\xa1\x07\x9a\xc6\x6b\xfa\x50\x00\x95\x1c\x27\x84\x2e\x1e\x53\x13\xab\x8b\xf5\xd6\xb4\x8f\x69\xda\x55\xdd\x3e\x05\xf3\xe9\x9f\xa4\xb8\x25\xf4\x8c\xe5\x39\x24\x92\xf1\x0b\x26\x24\x1a\x0c\x0e\x0e\x1e\x43\xd0\x67\x8d\xa3\x83\x96\x14\xc3\x92\x51\xc4\xe6\xf3\x93\x27\x4f\xee\x19\xbf\x05\x3e\x2d\x38\x4b\x40\x08\x10\xc8\xca\xcf\xe8\x67\xfd\x61\x5c\xbd\x5f\xaf\x4f\x9e\x14\x24\x45\x87\xbc\xa4\xa6\xda\x51\x41\xd2\x13\xdb\x32\x0a\x68\xf4\x0e\x3f\xa8\x06\xbd\x25\x39\x08\x74\xa4\xaa\xb2\xc8\x79\x4e\x96\x44\x4e\x29\x9b\x93\x1c\x54\x05\x4d\x50\x85\xd9\x90\x67\x38\x75\xf8\x14\xa9\x46\x1c\x1f\xda\x6a\x18\x5f\x1c\x02\x3d\x4c\x59\x62\x5a\x98\x30\x0e\xae\x69\x99\x5c\xe6\x3b\xb6\x1a\x91\x95\x32\x65\xf7\x74\x2a\xc9\x12\x58\x29\xd1\xd3\x43\x2d\x6f\x87\x4f\x11\xbe\x63\x24\x45\xf7\x98\x48\xc5\x6a\xc9\x18\xca\x19\x5d\xa0\xb4\xe4\xea\x37\x46\x1c\x14\x67\x4d\x81\x2e\x6c\x4d\xc6\x4c\xec\xe7\x1b\xfb\x75\xbd\x46\x27\x4f\x9e\xc0\x1d\x50\x29\xd0\x97\x27\x08\x21\xb4\x2c\x73\x49\xa6\x38\x49\xa0\x90\xc8\x3e\x8c\x9e\xe8\x6f\xb6\x92\x84\x51\x0a\x89\x24\x8c\x0a\x54\x55\xf0\x0e\x3f\x98\x3a\xce\xbc\xaf\x8a\x49\xaa\x60\x29\x00\x85\x0f\x14\x2c\xcf\x4f\x9e\xac\x9f\x3c\x51\x7c\xb3\xb5\xeb\x76\xdf\x83\x2e\x20\x33\x40\x77\x38\x2f\x01\xb1\xb9\xfe\x91\xe9\xe1\x86\x7e\x19\xbe\x65\xfc\x1e\xf3\x14\x52\xf5\x1f\x92\x0c\xcd\x00\x29\xc9\x54\xff\xba\x92\x0b\x60\x53\x52\x20\x2b\x76\x9a\x47\xa6\x82\x6a\xf0\x7c\x14\xa0\x47\xfa\x98\x33\xc9\x12\x96\x23\x0b\xc2\x01\xe7\x53\x52\x4c\x6d\x75\xfa\xd1\x3a\x42\xc9\x9b\x86\x3c\x71\xa8\x20\x17\xd0\x57\xcc\x31\xa7\x22\xf8\x2d\xe3\x46\x69\x54\xac\xf1\x44\xdc\xc7\xc2\x95\x0e\x10\xe4\x0e\x3c\xf6\x7f\xf9\x82\x38\xa6\x0b\x40\xbb\x92\x97\x42\x42\xaa\x1a\x78\xfc\xca\x54\xa1\x9b\x72\x0d\x38\xbf\x1c\x9f\x5d\x9e\x5f\x3b\xaa\x04\xc8\xa9\xc3\x39\xe7\x6c\xe9\xa8\xf2\x30\x74\x50\xa2\xbb\x22\xc5\x12\xcf\xb0\x1a\x49\xa5\x80\x54\xf1\x37\x05\x09\x7c\x49\xa8\xe1\x72\xc2\x4a\x2a\xf9\x0a\xa5\x50\x00\x4d\x95\x58\x32\x6a\x3e\xe4\x04\xa8\x44\x97\x63\x84\xd3\x94\x83\x10\x7e\x17\xf4\x8d\x15\xf5\xa1\x56\x09\x0b\x60\xa4\xf0\x87\x4d\x80\x46\x66\x44\x20\x22\x10\x87\x7f\x97\x84\x6b\x01\x48\x70\x9e\x94\x39\x96\x80\x24\xc7\xf3\x39\x49\xd0\x9c\x71\x44\x68\x4a\xee\x48\x5a\xe2\xbc\xa2\xb9\x14\x8a\xde\x7f\x00\xbb\x1c\x23\x62\xa8\x16\x12\xcb\x52\xa0\x02\x2f\x3c\x91\x31\x34\xb8\x52\xe6\xf1\xd4\x98\x46\x30\x4a\xb1\x3c\xf1\xa1\x95\xd6\xaf\x9e\x26\xf4\x15\x91\x70\x46\xe4\x2a\x2c\x63\x64\xac\xee\x78\xd5\xed\xa1\xcc\x1a\x0d\xfc\x49\x8a\x89\xa1\xd4\x92\x78\x97\x31\x21\xa7\xb6\xbd\x53\xd3\x8a\xe9\x67\x46\x01\x89\x0c\x73\x48\x8f\x63\x00\xc7\x4e\x3a\x2b\x74\xff\x64\x14\x26\xe4\x33\x54\x22\x11\xc5\x3b\x27\xb9\x04\x3e\x9d\xad\xa6\x4a\xb8\x6e\x61\x85\x3c\x44\xe7\x30\xc7\x65\x2e\xdf\x6a\x98\x9f\x60\xd5\x25\x5d\x02\x68\xaa\x15\xab\xf7\x54\x0d\xc6\x84\xb5\xd4\x85\xcc\x38\xe0\x54\x9c\x38\x80\xe9\x3d\x27\x12\x62\xa5\x65\x52\x4c\x29\x2b\x4a\x91\x35\x3f\xd6\xdf\x52\xc8\xf1\xaa\x5d\x30\x67\x8b\xa9\x28\x67\x4a\x96\x40\xc8\xe0\x23\x07\xd5\x5a\xa5\x56\x53\x56\x4a\x4f\x07\xd6\x20\xb7\x00\x05\xce\xc9\x1d\x54\xda\xb7\xe2\xcc\x4f\x00\xc5\xa9\xfa\x84\xd6\x6b\xdb\x86\x1a\xda\x56\x28\xda\xd0\xd7\xee\x8b\xe2\xa2\x2e\x65\xc6\x95\x55\x32\xd3\x59\x39\x9f\x2b\xad\xaf\xfa\xac\xa9\x71\xce\x34\x9c\xd1\x35\xaf\x35\x54\xa3\x63\x9b\x68\x2a\x72\xab\x27\x86\xa6\x9e\x34\x6c\x0b\x72\xcc\x17\x30\x8d\x51\x24\x1a\x48\xae\x14\x5c\x9b\x20\x11\x52\x33\x63\xe9\x2a\xd2\xa4\x90\x9a\xd7\x2c\x5d\x75\x37\x49\x23\x69\x37\x28\x86\xa4\xd1\x20\x8d\x43\xe9\x9c\x17\xd3\x25\x7e\x98\xce\x09\xe4\x69\x93\x0a\x0f\xc7\xc5\xcd\xcd\xf8\xc5\x3b\xfc\xf0\x56\x41\x35\xa8\xa8\x31\x58\x86\x04\x28\x42\x0c\x86\x21\x15\x0a\x23\xa4\xab\x02\xc4\x34\xc3\x22\xd3\x88\x42\x22\xd0\x8b\xa3\x97\x3f\x9e\xd8\x51\xa4\x56\xd0\x53\x8a\x97\xf1\x02\xae\x36\xb3\xd2\x7e\x8f\x97\x70\x81\x45\xf6\x0e\x3f\x34\x68\x6e\x63\x99\x95\xc9\x2d\x48\x87\x28\x8e\xe5\xb5\x86\x69\x20\x5a\xe2\x22\x56\xbe\xd5\xf8\x77\xb8\x88\x60\xd0\x28\x8c\x12\x34\xbc\x8b\x35\xc9\xa1\x18\x7b\x8b\xf6\x58\x9b\x22\x78\x1a\x44\x75\xe1\x89\xd1\x74\x87\x39\x51\x6a\x37\xca\x1c\x8f\xa6\x4f\x0e\xae\x83\x3d\x01\x9e\x56\xdf\x46\xf1\xf8\x0d\x33\x6b\x29\xaa\x68\x55\xcb\x49\x31\x25\xd4\x35\xb1\xc1\xe4\xe6\x6c\xf1\xb1\x2e\x70\x49\x6b\x93\x85\xd1\x7a\x01\xc3\xe6\xf3\x4a\x3f\x1b\x5a\xc9\x82\xaa\x05\x2b\xa1\x77\x38\x27\x69\x6f\x2d\x97\x1a\xf4\xd2\x40\x6e\x83\x3f\x3e\xa9\x05\x56\x8c\x56\xbe\xde\x2b\x6f\x11\xd4\x5c\xa5\x7c\x13\x13\x45\xa1\xb2\xe6\x47\xe2\x00\xa6\x6a\xf2\x8b\x48\x6f\x0c\x8f\x63\x5b\x0b\x47\xc1\xf8\x16\x38\xc6\x0a\x2a\xc0\xa1\x46\x25\x49\x40\x0f\xcb\xa8\x22\x33\x38\x26\x06\x4c\x8d\xca\xae\xb9\x96\xd0\x24\x2f\xd3\xa6\xed\x45\x96\x30\xd2\x7a\xc6\x94\x48\xcd\xb4\x3d\x55\xaf\x90\x84\x07\x79\xa8\x16\x5b\xe6\x5b\xbc\xbb\x5e\x73\x26\x73\xe2\x78\x37\x33\xbf\x5c\x27\x99\x9f\xd3\x84\x2d\x8b\x69\x0e\x77\x90\x57\x44\x9b\x62\x57\xfa\x9d\x23\xd7\x42\x6b\x72\x02\xc0\x1b\xfd\xae\x73\x85\xea\xaf\xe5\xff\xf1\x59\xaf\x65\xcd\x82\x4a\xfd\xef\x88\x51\x3f\x7c\x52\xfe\xee\xbd\xd6\x8b\xcc\x3b\xe0\x42\x4d\xe4\xcf\x47\xcf\xbd\x4f\x4b\x42\xa7\x39\xd0\x85\xcc\xd0\x8b\xbf\xff\xe0\x7d\x68\xd2\xa9\xaa\x6d\x52\xa9\x81\x94\xfe\x21\x90\x22\x4c\x57\xde\xdb\x3b\xcc\x57\x5d\x92\xbc\x83\xce\x4a\x21\xd9\x12\xb9\xb1\xa6\x96\xad\x1c\x44\xc1\xa8\x80\x60\xfd\x7f\xfb\x0c\xed\xde\xe9\xa5\x3f\x6e\x78\x21\x14\x14\x4e\xdd\x70\xd5\x34\xde\x2a\x23\xcf\x7b\x06\xea\xe5\x1d\x5a\xaf\x07\x1d\xeb\x32\x3d\x17\x48\x76\x0b\x54\xf8\x1c\x9e\x64\xec\xde\x4c\x00\x37\xe6\xdb\xc6\x11\xbe\x83\x52\x22\xb4\x59\x76\x8f\x39\x25\x74\x21\xac\xfe\x22\x94\x48\x82\x73\xf2\x19\xd2\xa9\xd3\x89\x53\x05\x63\xad\x7b\x53\xf8\x34\x4d\x89\x5a\x5f\xe1\x1c\xe1\x3b\x4c\x72\x8d\xa9\x52\xa1\xc7\x16\x6a\x57\xcf\x5a\x05\x4e\xc0\xbd\x20\x74\xa1\xec\x0d\x3d\x6e\xdc\x3b\x7f\x2c\x55\x4b\xbd\x39\xe3\x4b\x2c\x51\x59\x08\xc9\x01\x2f\x09\x9d\x33\xbf\xc1\x57\x6c\xf1\x56\x43\xbc\x11\x09\x2e\xe0\x7f\x4d\x3e\xbc\x47\xeb\x35\xe8\x1f\xaf\xfe\x25\x18\xad\x59\xb7\xf7\xe5\x0b\x9a\x95\x24\x4f\xab\x32\x1f\x2d\x52\xe3\xd5\x5a\xaf\xf7\x4e\x3c\xa3\x6a\x89\x0b\x54\xf2\x5c\x20\x99\x61\x89\x44\xc6\xca\x3c\x45\x94\x49\x84\x8b\x02\xb0\xb2\x55\x90\xb2\xbf\x85\x18\xe5\x6c\xf1\x75\x76\x93\x6a\x9f\xef\x6c\x30\xf8\xa6\x0d\x7c\x8a\x8c\x5d\xbb\xf6\x9c\x96\x9c\xa0\xdd\x9c\x2d\x16\x9a\xcd\xc6\x18\x6f\xca\x1c\x87\x7f\x7f\xe4\xa4\xb2\x37\x27\xb7\xa4\x38\xd5\x58\xaf\xd8\xe2\xe3\xf5\x55\x25\x7f\x4e\x45\x59\xf8\xf5\x1a\x1d\x9d\xd4\x42\xe6\x20\xac\xc2\x41\x76\xc4\xb5\x47\xf4\xb9\x91\x9d\xaa\x8a\x4a\xbc\xeb\x96\x68\x69\x41\x6d\x4b\xdc\x03\x71\xa3\xb4\x42\x33\xc6\x32\x53\x34\x35\x7a\x9d\xcc\x5f\x55\x6d\x0f\x47\x85\xfa\x05\x9c\x33\xae\xf1\x55\x08\xdf\xa8\x57\x1e\xbe\xf0\x7d\xad\xe1\x1c\x3e\x2d\x20\xd7\x20\x58\xae\x9d\x9e\x1a\xda\xfd\x6c\x1a\xdd\x3f\x67\x40\x41\xbd\xd5\xbd\x8c\x8c\x2a\x11\xda\xe1\x63\x2c\x93\x7b\x22\x33\xb5\x70\xc5\x68\x50\x7b\x5b\x06\x56\x71\x3c\xd3\x96\x6c\xe4\x83\xb2\x93\x05\x48\x65\x23\x0f\x92\x9c\x09\x18\x04\xb2\x75\x9f\x01\x45\x4b\x7c\xab\xbd\x4d\x19\x20\xa9\xd6\xed\xd2\xd5\x3a\x42\xe8\x46\x59\xdb\x4b\xc0\xd4\x8a\xee\x8a\x95\x28\xc1\x54\x89\xae\x20\xcb\x22\x5f\x69\xef\x4b\x13\xe9\xc0\xac\xc4\x94\xf1\x64\xc9\xa8\x29\x43\xbb\x5a\x58\x13\x8f\x54\xa5\xf4\x7e\x86\xd9\x84\xa9\xd5\x13\x12\x65\xa1\x27\xd0\x19\x24\x58\xa1\xd6\x66\x3a\x11\x28\xc1\x02\x4c\x3b\x83\xca\x22\xad\xbe\xd7\xc3\x6b\x06\x55\xdb\x07\xba\xa1\x9a\xc1\x0a\x03\xe3\x64\x41\x94\x96\x71\xdc\x4d\x89\x19\x8d\x19\xbe\x83\x2e\x16\x87\x8c\x23\x49\x66\x2b\x52\xec\x41\x94\xf5\x93\x62\xdd\x0d\x2d\x06\x4f\x08\x4d\xc0\x34\xd1\xc0\xa6\x70\x47\xb0\x04\xa4\x5d\x37\xcd\x4a\xa9\x52\x34\xb9\x95\x91\x19\x64\xf8\x8e\x30\x8e\xee\xc1\xd0\x5d\x79\xc2\x88\x50\x6a\x85\x33\x9c\x64\xa3\x1a\xc3\x0e\xba\x06\x89\x2d\x19\x6e\x2c\x1a\x54\x19\xa6\x69\xae\x57\x5c\x73\x54\x59\xa6\xbd\x12\x57\x6b\x13\xdd\x9d\x65\xb1\xe0\x38\x55\x8b\xae\x0a\xb2\x7a\xf7\xa5\xa5\x00\xaa\xc7\x82\x9c\x54\x10\x7b\x7b\xa8\xf9\x68\xa1\x6d\xe8\x0b\x55\xa9\x1b\x5a\xbe\x8f\xad\xc7\xeb\x86\x76\x65\x06\xce\x25\x56\x79\x1d\x37\x3a\x05\x0d\xd3\xfe\x01\x0d\x97\x96\xee\x15\x0d\x8e\x1c\x7c\x77\xfb\x76\x9b\x3e\xc4\xa9\x42\x12\x55\x5f\xf1\xd2\x1c\x96\x4c\x42\x50\xaa\x56\x51\xd5\x22\x42\x3b\xf6\xf4\x34\x31\x7d\x50\x53\x9c\xe1\x80\xa9\xb6\x5a\x5a\x24\x8c\x73\x48\x64\xbe\xd2\x7e\xb1\x44\x49\x98\x10\xb9\x52\xa8\x39\xc3\x29\xa1\x8b\xa0\x4b\xdb\x98\x76\x0b\x2c\xc4\xd4\x6a\x5a\x91\x64\xb0\xec\xed\xdc\x0e\x34\x3d\x9d\xbd\x6b\x90\xb6\xba\x3b\x82\x49\x69\x08\x43\x8f\x5d\xbd\xe8\x37\x3d\xd4\x74\x20\xe9\xa1\xc6\x05\xae\x3c\xb8\x5e\x9a\xb4\xe9\xb0\x3b\x53\x33\xab\xfe\xa6\x7f\x3f\x8a\x3f\xaa\x44\x1f\x7b\xd4\xc8\xf6\x80\x9a\x13\x28\xce\xf3\xd1\xa5\x98\x4c\xae\xc6\x58\x08\x99\x71\x56\x2e\x32\x2f\x66\x66\x24\x45\x51\x6e\x38\x65\xe2\x74\xa3\x2b\x22\x24\x50\x65\x8b\x88\x91\x2a\xab\x05\x7b\xbd\x56\xba\xe4\xe5\xcb\xbf\x69\xd5\x1c\x71\xc1\x2b\xf8\x9a\x13\xad\x6e\x30\x6f\x82\x1e\xd9\x54\x63\xe3\x79\xf9\xf2\x6f\x27\x7d\xc3\x2a\xa8\xd1\xf1\x03\xb5\x47\xd6\x23\x28\x54\x0d\xfe\x96\x54\x34\x16\xfa\x1f\x66\x5a\xf7\x2a\xf1\xd0\x23\x15\xa9\x7e\x0c\xa4\xc9\x48\x50\xd5\xcb\x9b\x85\x67\x93\xc0\x74\xc9\x8a\xf6\x89\xb1\x65\x51\x4a\x78\x5b\xe6\x79\x43\x8d\x56\xb2\xf2\x33\xa8\x89\x7e\x4f\xea\x49\xc5\x6a\x32\x65\x68\xf8\x12\xab\xe4\xc3\x4d\xd4\x6a\x5a\x51\x3a\xb6\x8a\xc0\x58\x3c\x1c\x8a\x1c\x27\x20\x2c\x40\xa5\xd2\x74\x74\x4b\x30\x46\x7b\xc6\x94\xc2\xbf\x3b\x2f\xf3\xbc\xf5\xb6\x21\x58\x4e\xfe\xcf\x36\x68\xf3\x16\x1b\x07\xd1\x2a\x9f\x45\x15\xf7\xa0\x87\xd3\x83\x0d\x05\xb6\x51\xf7\x9d\xb4\x18\xa6\x4e\x3d\xd6\xf5\x93\xd2\x0b\x5f\xc5\x2f\x3b\x65\xd5\x73\x0d\x4e\x09\x9d\x72\x48\x89\x9a\x38\xea\xa5\xb7\x92\xf5\xc6\x17\xfb\xd4\x96\x9c\x10\x79\xc5\x8a\xda\x6c\x36\xa3\xdd\xbe\xf4\x4c\x46\x59\x2a\x33\x90\x22\x01\x42\xdb\xe4\x09\x4e\x32\x13\xfd\x44\x29\xc7\x42\x92\x04\xe7\x6a\xda\x5a\x16\x9c\xdd\x01\x2a\x80\x6b\x33\x8e\x26\x10\xca\xf4\x64\x72\x35\x31\x48\xce\x70\x92\x55\xec\x56\xe4\x58\xe4\xd3\x44\x7f\x50\xab\x07\x49\xe8\xf1\xf3\xa3\xa3\x23\x17\x2f\x99\x4c\xae\x8e\x3d\x52\x7d\x44\x4d\xd7\xa9\x87\x2d\x0c\xb9\xd6\xe5\x6a\x47\x73\x87\xe5\x8f\xf3\x9c\xdd\xa3\x84\xd1\x39\x59\x98\xe8\xae\x9a\x8e\x1d\x0f\x24\x51\x8b\x61\x11\xa9\x50\xbf\x8f\x37\xfa\xc6\x7e\xdc\xd2\x11\xa7\x16\xbc\xfb\xb0\x2c\xe4\x2a\x8e\xe8\x27\x58\xa1\x83\x18\x0f\x0d\x11\x3a\x0c\xe4\xf9\x97\x2c\x69\xa3\x5b\x58\x75\x34\x59\xe4\x64\x91\xa9\x15\x08\x87\xb4\x4c\x8c\xc6\x50\x1c\x1c\x4a\x36\x9c\x13\x2e\xe4\x70\xb6\x92\x50\x55\xe7\x87\x05\x3c\x06\x07\x71\x80\xfe\x06\x9d\x91\x22\x03\x2e\x0e\x6a\xb5\xd6\xe6\x7b\x62\x7c\x30\x8a\xfd\x89\x01\xaf\x48\xb0\xbf\xd1\x9e\x57\xbf\x45\x69\x2c\xfc\x5a\xda\x41\xd3\x6a\xc6\x8e\x2b\xd6\xeb\xc0\x8c\x50\x7b\x7e\x31\xc6\x1c\x2f\xdb\xd4\x1a\x0a\xcf\x2f\x90\x8e\x9f\x6d\xe9\x10\x50\x74\xf9\x0e\x01\xf5\x3b\xcd\x0a\x55\x43\x45\xb7\xfd\xed\xf3\xd7\x12\xd1\xef\x81\x53\xc4\x7b\x9e\xc1\xf3\x15\xc5\x4b\x92\xdc\x5c\x4d\xae\x21\x61\x3c\x15\xbe\xd8\xa4\x2b\xa5\x2b\x12\xdd\x91\xd3\x9c\xa1\xa3\xb8\x17\x4a\xe4\x53\x48\xd2\x6c\x9a\x94\xfc\xae\xd1\xe1\x6f\xce\xce\x2f\xce\xf4\xcb\xa0\xbf\x47\xc6\x7b\xa6\xed\x6f\x51\xf3\xcc\xfa\xd4\xb4\x05\xaf\xe3\xba\xa6\x3f\x8d\x86\x26\x54\x02\x4f\xa0\x90\x53\x30\xc5\xba\xfb\xc8\x3a\x41\x80\xf3\x33\x96\x42\xe5\x05\x31\xe8\x2f\x6e\x6e\xc6\xcd\x8a\x8d\xc7\x40\xc7\x91\x15\xf1\xae\xd8\x7a\x8d\x5e\xa1\xff\x69\x7a\x70\xda\xfc\x70\x12\xd4\x69\xcd\x66\x6f\xa4\x71\x50\x33\x6b\x2c\x2a\x7c\xaa\x04\xc3\xa6\x35\x19\x1f\x5d\x65\xee\x78\xb8\xf4\x12\xc5\x2e\xe0\x0c\xd4\x23\x04\x52\x35\x71\x42\x49\x51\x80\x3c\x68\x71\x37\x51\x4d\x10\xe6\x6b\x35\x9a\x20\x75\x66\xae\x7b\x83\xb5\xcd\x6f\x7e\x2d\x71\xe1\x6a\x0b\xf1\xa3\x75\x74\x2a\xaa\x3b\x41\xcd\x46\xcf\xd0\xae\x73\xe1\xe8\xce\x98\x79\x49\x5d\x75\x2b\xe0\xdf\x35\xd8\xc8\xea\xb3\xd3\xf9\x9c\x50\x22\x57\x23\xf7\xcf\xcd\xaa\x00\x34\x48\x18\xbb\x25\x30\x70\xe5\x2b\xe4\x6a\xca\xb9\x5d\x0d\x15\x9d\x15\x26\xeb\x6c\xf7\xd6\x1d\x06\x0a\x65\x58\x64\xaf\x1a\xa0\x61\xa5\x67\xba\x9a\xf0\xed\x05\x16\xda\x8b\xa4\x5a\xf6\x35\xe5\x1d\x41\x5a\x1b\x30\x9a\xaf\xac\x8c\xd4\x9c\xd8\x5f\xd8\x31\xea\x3c\x93\x3f\xb9\xf0\xb3\x9f\xaf\x73\x74\xe0\xaf\x4a\xaa\x08\x75\xd5\x4b\xbd\x85\x9d\x8e\x88\xf4\x5d\xb3\xff\x8c\x62\xd4\xdd\x56\xb5\xf3\x0d\x4d\x0b\x46\xa8\x9e\xb0\xec\xf7\x3a\x5d\x70\x74\x6a\x0d\xee\xff\x8b\x8c\xd3\xf6\x72\x8c\xd6\xeb\x63\x0f\xc0\x06\x50\x90\x0e\xdd\x62\x92\x8b\x57\xde\xc7\x77\xf8\xe1\xad\x7a\xa7\x00\xd4\x47\x37\x53\xfb\x30\x0a\x20\x9c\xa6\x23\x0d\xa9\x65\xd1\xbd\x6d\x4a\xcb\x06\x31\xd9\x41\x57\x0c\xa7\x68\x86\x73\xb5\x62\x41\x38\x5f\x30\x4e\x64\xb6\x3c\x41\x66\xa8\x69\x97\x3f\x2b\x69\x8a\x38\x9b\x11\xfa\xcc\x7a\x96\x88\xf0\xfd\x34\x41\xcf\xba\xf0\x96\xc2\xfc\xda\x20\x3e\x75\x78\xd1\x40\x63\x9b\x6a\x6c\x83\xd0\x43\xdb\x59\x6a\x73\x4f\x2a\xcd\x53\xb5\xd3\x49\x85\x0e\x7b\xae\xfc\x5a\xd4\x78\x68\xf2\xa4\x05\xab\xf4\x81\xd0\x66\xa0\xdc\xa2\xd2\xbf\xc4\xf8\xd7\x8a\x71\x5c\x8a\x91\xf3\x2a\x6a\x57\x9a\x96\xb6\x25\x2e\xac\x9b\xf7\x9e\xe4\x39\x9a\xd9\xa4\x3c\x86\x74\x9c\x57\xe7\x57\x65\x80\x7e\xce\x88\x84\x9c\x08\xd9\x48\xb1\xb3\x1c\x22\x34\x85\x87\x67\x0d\x4e\x89\x3a\xe7\xb7\x09\x9b\xb3\xc4\xcc\x0f\x35\xd4\xe8\xca\xbe\xf3\xc1\x77\x0b\x2c\x33\x05\x64\x03\x2e\xb6\x54\x5d\xbe\x39\x8f\x11\xe1\x40\xf4\x24\x09\x69\x13\xb2\x06\x5c\x48\xb4\x9f\x83\x87\x68\x54\x35\x6d\x74\x76\x79\x7e\x7d\x60\x92\x43\xed\xc4\x77\x0e\xd4\x8c\xd6\x2f\x5f\x50\xc1\x09\x95\x15\xcd\x17\x4c\x48\x13\xb2\x35\x94\xd6\x49\x64\x81\xe7\xd1\xba\x2d\x15\x26\x17\xf0\x47\xfb\x1d\xb8\x06\xd3\x81\xc5\x77\xd0\x54\x28\x5e\x18\x25\x22\xa1\x36\x3d\xb0\xa3\x45\xa1\x3a\xd0\xe1\xd3\x56\xb0\x26\x9c\x8b\xb7\xff\x15\x74\x30\xd7\x89\xe0\xfb\x26\x7b\xec\x1a\x4b\xb8\x22\x4b\x22\x45\x2d\x12\xde\xba\x42\x7d\xd6\xe9\xb7\x26\x92\x94\x3b\x55\x1a\x67\xe5\xee\xbd\x6b\xd7\xd4\x82\x5f\x9e\xc7\xd9\x74\x74\xd2\xcd\x25\x9e\xd7\xfc\x89\xb3\xe6\x79\x8b\x35\xdb\x90\xab\x9d\x19\x1d\x14\xee\x9a\x1c\xe3\x0e\xaa\x8f\x6a\x2d\xad\xc0\x94\x92\xfa\x27\xa3\x50\x49\x8b\x3f\xc0\x9f\xa3\xc1\xa0\xc7\xe7\xe4\x8d\x6d\x9c\xe7\xd6\xfb\xa2\x53\x26\x53\xc4\xd5\x58\x36\xe4\x7f\x66\x14\xc4\x08\xbd\xc1\x49\x86\x74\x5c\xc7\x0c\x14\x0b\x2a\x10\x46\x29\x18\x7f\x71\xaa\x61\x83\x90\xc4\xf3\x77\xaf\xd1\xf0\x7f\xa0\xe7\x3f\x20\x99\xb1\x52\x60\x9a\xa2\x1f\x5e\x6a\x3b\x4e\x67\x56\x82\x40\x8c\x23\x3c\x53\xca\xe9\xc7\x1a\xe4\xf9\x8b\x1f\x1b\x30\x11\x45\xa2\xeb\x52\xd2\x63\x82\x68\x4e\x78\x14\x37\x6a\x01\x3a\xf0\xb5\x84\x2e\xd1\x25\x92\x87\x4f\xd1\x6b\xcd\x0b\xab\x98\x9c\xf3\x42\xa0\xfd\x39\x67\xcb\x43\xc9\xd0\xfd\xfd\xfd\x41\x8c\x92\xcc\x0e\xc9\x67\x68\x57\x32\xbd\x0f\xe0\xda\x16\x9e\x34\x35\x9b\xd3\xfe\x11\x81\x73\xe1\x03\x1d\xbf\xb7\x9e\xaa\xd7\x84\xa6\x76\x7a\xb8\x2c\xee\x5e\xfa\x02\x98\xeb\xd9\x11\xd9\x2d\x08\xba\xa4\x9d\x35\x42\x17\xaa\x5a\x44\xa3\xf5\x7a\xb3\x13\x2c\xc8\x67\x0e\x72\x7f\x7a\x2b\xdd\xec\x5e\xde\xe4\xdc\x6d\xd7\x6d\x7c\x13\x5d\x2d\x9a\xfc\xaa\x26\x55\xff\x28\xa3\xb2\xd7\x09\xe7\x37\xf9\x7b\x30\xf6\x3f\x88\x93\xcd\xd8\xbe\xad\xa2\xb5\x55\xe7\x6b\x46\xc2\x0f\x7f\x8d\x84\x6f\xde\x7f\xdb\x8c\x82\xff\x7d\x7c\xfc\x7f\xbe\x13\x67\x5d\x55\xff\x21\xfc\x6c\x8d\x85\xe0\x8d\xe7\x52\xd7\x42\xe5\x66\x94\x06\x22\x0e\xda\x11\xfe\xb7\xa3\xe7\x2e\x10\x7a\x7c\x78\xa8\xf7\x6d\x30\xb4\x5e\xfb\x59\x3b\xbd\x93\xfe\x76\x0b\x71\xb3\x8a\xd9\x51\xf3\x2f\x97\xa8\x6d\xb6\x5c\xd4\x14\xc6\xa7\xb6\xb0\x49\xed\x72\x0d\x2b\x28\x27\xb8\x65\x66\x49\x58\x16\x7a\xd3\xc6\x60\xf2\xe6\xfa\xd3\x9b\xeb\x81\x45\x7a\xa6\xbd\x45\x66\x1f\xa1\x23\xbf\x6d\x16\xb6\xdc\xa7\x1a\x30\xf4\x57\xa1\x6f\xe4\xb3\x42\xad\x9c\xe8\xc0\x73\x15\xe9\x8b\x56\x33\xcf\x3e\x4e\x6e\x3e\xbc\x9b\xbe\xb9\xbe\xfe\x70\x3d\x19\x98\x06\x36\x96\xdc\x3b\x3b\x1a\x41\x7f\x77\xc4\x7d\xeb\x6e\xb9\x6b\x0a\x3c\x33\x7b\x77\x94\xb5\xf2\xfe\x1f\x97\xef\x7f\x41\x66\xbb\x64\x92\x41\x72\x8b\xd4\x02\xcc\x44\xf1\x95\x69\x67\x32\x40\xd4\x3a\x4c\xc4\x7b\x7a\x07\x7d\xb4\xd1\xbd\xee\xc8\xb1\xdb\x99\x82\xf6\x39\xa6\x29\x5b\xda\x4d\x5c\xff\x2a\x85\xce\xbf\x31\xbb\xda\x6e\x29\xbb\xa7\x1a\x89\x38\x40\x58\x54\x24\x6b\xb4\x8a\x54\xe3\xd0\xf6\xfb\x2d\xc3\xea\xdd\xc2\xe4\xb4\x18\x9c\xde\xc2\x34\xc9\xb4\xa8\x13\x7a\xec\x95\xc9\xa4\x2c\xc4\xf1\xe1\xe1\x82\xc8\xac\x9c\x8d\x12\xb6\x3c\xbc\x2d\x67\xc0\x29\x48\x10\x87\x36\x53\x70\x68\x42\x16\xb3\x9c\xcd\x0e\x97\x58\x48\xe0\x87\x09\xa3\x92\xb3\x3c\x07\x2e\x6c\x40\xa3\xb8\x5d\x1c\x26\xcb\xd4\xfb\x62\x5d\xee\x0b\xb6\xcd\x02\xa2\x66\x8a\xcb\xb4\xad\x96\x9e\xa5\x00\x93\xe3\x84\x93\xdb\x9c\x2d\x5e\xb9\xe2\xaf\xcd\xef\x46\xb8\xa9\x67\x76\xdd\xa4\xb7\x7f\x3d\x05\x31\x55\x26\x5d\x74\xd4\x79\x40\x8c\x12\x18\x0c\x07\x9e\x41\x5a\x19\xdc\xc1\x5e\xdd\x86\xb1\x83\x3a\x72\xfb\x02\x8d\xf8\xe2\xc8\xb3\xe1\xd6\x91\x3a\x4c\xb7\xd8\xbd\x4a\x01\xfe\x6e\x7a\x75\x24\x80\xe2\xdc\x27\x1b\xc5\xd2\x9e\x5b\x5b\xaf\xdc\x13\xdd\x2a\x95\x12\x51\xe4\x78\x75\xb2\x35\xa4\x4b\x4a\xad\xb3\xaf\x7d\x7d\x12\x4c\xd5\x9b\x58\x26\x64\x39\x73\x8c\x70\x91\x0d\x1f\x5d\xb3\x33\xa3\xcc\x0c\x18\xd8\x13\x6a\x71\x4f\x2b\xdd\x0f\x21\xf4\xcb\x50\x07\x39\x5e\x1e\xbd\xdc\x44\x44\x7f\x2f\xb9\x9f\x43\x2b\xc2\x43\xeb\xf5\x1f\x9c\x44\x48\x28\xb0\xf0\xf6\x2b\xd8\x30\x59\x17\x86\xa8\x4c\x6d\xaf\xaf\xd7\x4f\x9e\x38\x07\xf0\x13\xd4\x4c\x2f\xd6\x9b\xca\x6a\xe7\x70\x23\xb3\x78\x62\xde\xc7\xf6\x44\xfc\xc6\xe9\xaf\x35\x55\xd1\x18\xd0\xe6\x84\xd7\x2a\x60\x7f\x73\x36\x46\x36\xbd\x5a\x84\xab\x0e\x65\x24\x27\xc5\xa4\x5a\x74\x8c\x6e\xce\xc6\xaf\x83\x40\x4d\xe5\x3a\x97\x49\xa1\xa3\x2c\x55\x09\xe7\x0d\x0d\xde\x5a\x04\xda\xc7\xa2\x13\xbf\xfb\x41\xba\xbf\x3a\x67\xeb\x97\x80\xec\x7f\x3d\x43\xbb\x60\xfd\xb8\x7a\xa9\x54\x17\xf5\xdd\xbb\xc1\xea\x07\xb5\x1e\x1d\xdf\xb3\x05\x2a\xd7\xaf\xb5\x57\xaa\xf7\x8d\x9d\x1f\x31\xcf\xdb\x37\xf7\x23\x44\xe9\x0c\xcc\xa9\x56\x1f\x58\xc9\x8c\x30\xd1\x5f\x2c\x8f\xce\x21\xb1\x61\xce\x8d\x8b\xff\x6e\xd3\x23\x4a\xe0\x77\xa2\xe8\xfb\x1a\xb2\x7f\xbe\xae\xa8\x96\x17\xbf\x57\x7f\x04\x6f\x0c\x92\xee\xfd\xab\xe6\x28\x0f\xad\x60\x62\x61\x91\xd8\x4c\x81\xcc\x36\xe1\xdf\x45\x17\xb5\x56\x79\x9b\xd8\xf9\x86\x5a\x76\x86\x4d\x72\xe6\x6b\xf5\xf8\x0b\x80\x6d\xa2\x41\x3b\xe8\xe3\x79\xaf\x66\x2f\x53\x5f\xb3\x7f\x3c\xef\xd6\xec\x65\x6a\xb8\x59\x95\x68\x70\xb3\x7e\xdb\xc9\xcd\x38\x48\xf7\xd7\xad\x35\x7b\x5d\xf4\x3b\x6b\xf6\xdf\x4f\xb5\xb7\x3a\x41\x75\xcf\x57\xab\xe5\x2d\xb0\xfd\xf1\x54\xea\xb7\x63\x41\xa5\x0e\xbf\x86\x0f\x51\x55\xe6\x36\xfc\xd5\x0a\xe9\x79\xa8\xae\xbe\xb9\xb6\xfb\xbd\xc6\x67\x98\x8d\x6c\x19\xe2\x8e\xd3\x49\x41\x67\xb7\xe8\x23\x16\xe6\xd5\x4a\x5c\xd4\x3e\x04\x0e\x05\x48\x62\x02\xc4\xf6\xd4\x1c\x53\xa8\xbd\x5c\x0f\xe2\x79\x45\x70\xc2\x53\xc7\xa9\x4f\x6d\xa1\xf4\xf2\xbf\xb4\x40\x76\xe5\x7f\x21\xdf\x8a\x8a\x27\x7c\x05\xa6\x95\xb3\x40\x03\x03\xb4\x2b\x51\xad\x4a\xaf\x6d\x02\x36\xed\xae\xda\xf4\x42\x81\xb4\x78\x79\x67\xdb\x61\x31\x06\x8b\x07\x69\x12\x94\xcd\x39\x41\x5b\xe2\xf8\x60\x77\x5a\x0d\x95\xf1\x8f\xcc\xa6\x96\xc0\x8f\xb9\x11\x47\x2d\x7b\x8e\x8e\x6a\x03\xe8\x96\x18\x2e\xad\xd7\xe7\xbd\xdb\x57\xdd\xd8\x31\xba\x25\x12\xbb\xeb\xda\x43\xe2\x6f\x31\x0d\xba\x86\x43\x78\x56\x89\x7e\xf6\x47\x4f\x0f\xd0\x21\x9a\x71\xc0\xb7\x9b\x0d\xd8\xc7\xd9\xb0\xee\xbf\x7a\x54\x35\x8e\xf5\x3a\x7c\x8a\xce\x3e\x5c\x4f\xaa\xfd\x75\x7a\x23\x93\x73\x96\x2d\x49\x92\x11\xc8\x6f\x71\x7e\xbb\xc4\x54\x3b\xcd\xac\x9f\xd4\x3a\xc3\x86\x09\xe3\x62\xc8\x0a\xa0\xc3\x86\x9b\xd4\x3b\xa0\xc7\x1f\x89\x8d\x01\xa8\x55\x15\xb3\x43\xee\x8c\x71\x61\x1d\xbc\xee\xfb\x0e\x52\x2f\xd1\x98\xc3\x5c\x27\x08\xa3\x25\xc8\x8c\xa5\x02\x51\x80\x54\x20\x5c\x6f\x0a\x66\x85\x19\xf9\x98\xa6\x28\x25\xf3\x39\x70\xa0\x12\x5d\x1b\x37\x91\x12\x6e\x83\x90\xcc\xd1\x7e\x25\x67\x06\x19\x7a\x85\xf6\x3e\x8c\x6f\x2e\x3f\xbc\x9f\xec\x1d\x78\xc3\xd0\xdb\x39\xbd\x67\x2c\xe6\xe1\x99\x71\xf7\x0d\x75\xf2\x88\x15\xe0\x3d\x9b\x01\xcc\xb8\xd0\x2d\xd0\xdf\xcc\x27\xb4\x5e\xef\x21\x9c\xdf\xe3\x95\x68\x2d\xe5\x9a\xf0\x67\x1c\x52\xa0\x92\x60\x93\xcf\xb3\xb1\x6a\x0f\x3e\x5a\x7f\x13\x5f\x45\x44\x64\x9e\xd9\x58\xd5\x3b\xc3\xf1\x68\x35\xf6\x5b\xb4\x9d\x1b\x11\x5b\xcd\x1a\x45\x5c\x6b\xdd\x47\x21\x7e\x87\x1f\x86\xa7\x0b\xd8\x43\xcf\xff\xeb\xc5\x8f\x47\xbe\x6f\xd0\x2f\xa4\xa0\x81\xca\xe1\xcd\xaa\x80\x3d\xb4\xa7\xcf\x37\x28\x72\x4c\x28\x4a\x32\xcc\x05\xc8\x57\x1f\x6f\xde\x0e\x7f\xdc\xeb\x2f\x7d\xa5\x0f\x03\xd8\xf3\x93\x48\x2a\xa7\xa4\x73\x6c\x79\xde\xa3\xbf\xa4\xe9\x8f\x25\x4d\x2d\x0d\xd8\x5c\x5f\x98\xc5\xf8\xb0\x72\xf8\x45\x57\x19\xf7\x44\x66\x0e\x12\xeb\x30\x56\xa8\xed\x5c\xf4\x2a\x58\x70\xb8\x63\x30\xdf\x12\xde\xce\x2e\xaa\xa3\x72\xa3\x09\x24\xac\xb5\x50\xfc\xc3\xa7\x8d\xd4\xc9\xd6\x91\xc4\xb5\xaf\x8f\x38\x7c\xf9\xa2\x77\x27\xfd\x6e\x49\x1b\xdf\xb3\x59\x51\x2b\xc9\x44\xc3\xff\xc4\x59\x14\xbf\x19\x07\xff\x10\x99\x0c\xdf\xb7\x75\x9b\xf3\x0a\xb6\x0a\xc6\x69\xd5\x67\xd8\x80\x58\x47\xb0\xd2\x4b\xa9\x70\x1b\x3a\x3b\xc3\xbd\x2e\x8f\x02\x11\x7b\x46\xa4\x0d\xa0\xdf\x5c\x4d\x90\xa0\xc4\x39\x39\xaa\x54\xba\x8a\x06\x7d\x98\x84\xe9\x23\xe0\x68\x59\x0a\x69\x55\xb5\x39\xc0\x20\xf0\x63\x81\x19\x00\xcf\x1a\x3b\x4c\x27\xef\x2f\x6b\x0c\x76\xa7\xa4\xd0\xbb\x07\x59\xc9\x13\x40\xfa\x60\xc7\x39\x53\x24\x11\x39\x0a\x69\x08\x13\x07\x6c\x27\x4e\x26\x57\x67\xc0\x25\x99\xeb\x3c\xc7\x83\xdf\x56\x17\x7f\xeb\x9c\x17\xf4\xfd\x92\xc0\x7e\x5b\xed\xa8\x77\xe0\xb5\xc9\xd4\xc7\xe6\xa1\xf5\x5a\x1f\xb3\xf7\x28\x35\xf0\x17\xb3\x7f\x13\x66\x7f\xc7\x79\xeb\x11\x23\xf6\x3f\x2f\x67\xef\x4f\x20\x0a\xc1\xb8\x7b\x7c\x77\xfd\xe9\x13\x01\xff\x04\x9d\xb4\x69\x15\xa1\xa7\xe6\x84\x2d\x97\x40\x25\x1a\xbf\x79\x87\x44\x86\xbd\x03\x96\xab\x23\xa0\x13\x69\x33\xae\x84\x9b\xf0\x17\x6a\x16\xd6\x3b\x03\x9a\xb9\x73\x98\xea\xdc\xb3\x04\x82\x13\xcb\x5d\x8d\x3b\xae\x9a\x63\x3f\xc9\x4d\xf5\x26\x2c\xcf\x32\x48\x6e\x45\xb9\x6c\xac\x73\x44\x3e\x4d\x6a\xf9\x41\x5d\x4f\x13\x99\x27\x71\x0d\x9e\x04\xd8\xf4\x3e\xf6\xaf\xc7\xd6\x27\xf3\x6f\xcb\x3c\x3f\xcb\x30\xa1\xbe\xf0\x07\xed\x72\xa7\x72\xf7\xb5\xaf\x49\x49\x0c\x6b\xab\x81\x42\xe2\x42\x1f\xe9\xd4\xf3\xf8\xf1\x41\xbf\xcc\xf4\x0e\x38\x99\xc7\x99\xd2\x1d\x53\x44\xbd\x9b\xf8\x94\x48\xec\x6f\xd6\x0c\x07\xb5\x74\x5f\x4c\x6e\x26\x8d\xa5\xd8\x92\x71\xf0\x1c\xb3\xa2\xab\x59\x83\x89\xe4\x24\x91\xc3\x1b\x8e\xa9\x50\x23\x6c\x38\xb1\xb7\x2b\x1c\xa3\x25\x7e\x18\xe2\x05\x54\x63\xcd\x55\xf4\x0e\x3f\x9c\x2e\xa0\xad\x12\xd4\xb7\x4b\x73\x96\xe6\xa4\x9c\xa5\x6c\x89\x89\xd9\x2f\xe8\x4e\xd8\x9c\x94\xb3\x73\xf3\xb6\x1e\x7d\x11\x1c\x63\x3b\x0c\xb4\x46\xd1\xff\x56\xe0\xad\xc3\x39\xea\x3d\xa6\xbd\xf2\xe5\xb1\xed\xb4\x94\xd9\xe8\xec\xf4\x2d\xc9\xf5\xa1\xa0\x41\xf6\x6b\x6c\xac\x85\x85\xc7\xb0\x9c\x5c\x9c\xb6\xc6\x9c\x39\x5f\xb9\x77\xe8\xf5\x20\xad\x29\x6a\x89\xa7\x91\x30\x5b\x41\x47\x3f\xf6\x60\xfe\xa4\x8b\x9b\x63\x9d\xbb\x70\xa7\x50\xc8\xac\x4b\x46\xfa\x70\x9b\xbd\x8e\x84\xd1\x73\x8d\x61\xab\xc1\x1e\x22\xd1\xa1\xa1\x31\x5e\x34\x86\xbb\x77\x42\xc0\xcb\xff\xfe\x3b\x7a\xf9\xdf\x3f\xa0\x57\x7d\x94\x54\x48\xb6\x56\xe9\x5b\x68\xa5\xce\x34\x69\x5f\xc9\x3c\x2e\xc3\xf9\x31\xdb\x39\x5d\x4d\x5b\x6c\xe9\xf4\xc1\x71\x29\xb3\xb1\x5f\x44\x31\xa8\x67\x27\x68\x9c\x07\xd5\xae\xc8\x6b\x13\x9e\x19\x9d\x16\xc5\x35\x63\xd2\xef\x24\x1d\x3c\x28\x39\x41\xaf\xd0\xe1\x41\x10\xb0\xab\x52\xf4\x5f\x68\xa2\xba\xd0\x35\xba\xeb\x91\xdd\xe4\x1a\x7a\x10\x0d\x2b\xbe\x6a\x32\xa3\x27\xa2\xe8\xbf\xed\x76\x13\xc0\x83\x81\x1f\x2a\x94\x40\x25\x31\xf5\x84\x29\xb1\xde\x61\x12\x2e\xac\x32\x63\xa9\x99\x20\x5a\x29\xa8\x1d\x01\x34\xfd\x34\xfd\xea\x7a\xff\x62\xa3\x6c\x4f\x97\xbd\xb1\xa4\xea\x91\x61\xbc\xc8\x07\xf1\x44\x54\x1b\xf1\x09\x9e\x46\x87\x45\x90\x6d\x1b\x17\xd5\x4f\x24\xb0\xa9\x9f\x47\x47\x37\x2d\xb6\x89\x39\x4d\x2f\x78\x22\x07\xee\x75\x25\xd3\x6e\x5d\xd7\x45\xf3\x80\xeb\x2d\xd8\xd3\x38\xec\xfa\xd1\xcc\xb9\xf2\x9a\x53\xed\x6a\xa9\x4f\x2f\xfb\x5a\x86\x55\x35\xbc\xf3\x3b\x3b\x08\xfa\x3d\x06\xa1\x6a\xea\xd0\xde\xbd\x30\x74\xfb\x3b\xbf\xbe\x3f\x95\x90\xbf\xb5\x77\xaf\x54\xcf\xc0\x84\x52\xdd\x1e\x84\x7a\x5f\x41\x7c\xbc\x99\x83\x60\xea\x6d\x36\xe6\x09\xb3\xba\x23\x63\xb3\x5a\x1f\x85\xb0\x76\x3e\x5f\xe2\x07\x73\x67\x82\x7f\x14\xfd\xa0\x21\x01\xda\x0e\x1a\xbd\x66\xe9\xca\x9a\x21\x83\x96\xe4\xe9\x9d\xf6\x7a\xb2\x8c\xde\xd1\x50\xe3\xea\xb8\xc2\x21\x46\x59\xf4\x3a\x88\x06\x61\xbd\xf7\x41\x78\xd4\xc5\xc6\x85\xd6\x83\xf6\x00\xd6\x6e\x79\x57\x32\x1b\x17\x77\x1d\x9d\xb7\x08\xb6\x54\xf0\x8d\x7d\x10\x45\x54\x67\x07\xab\xc5\xf6\x9e\x03\x0b\x50\x51\xfb\x49\x0a\xff\xae\x95\xc7\xdc\xd8\xd2\x85\xe3\xa4\x6d\xcf\x46\x58\x17\x9d\x42\xdc\xa1\x06\xee\x80\x0d\xbd\xdc\x6b\x99\xc0\x95\x61\xab\xb3\x05\xfd\xf9\x3a\x32\x05\xec\x2a\xbb\xe5\xf8\x15\xda\x5f\x80\xb4\xa9\x1b\x97\xd4\xe4\xd6\x37\x66\xfb\x91\xfd\x58\x1f\x93\x10\x60\x3a\x7c\xaa\x71\x29\x0d\x8f\x53\x2c\xb1\x32\x53\xa5\x5e\xc1\x2b\xeb\xd5\x95\x5e\xba\xaf\x0d\x1b\xb5\x6a\x35\x6d\xa6\x9f\x0c\x2c\x81\x8d\x94\xa8\x41\x64\xc2\xf5\x53\x4c\xfc\x72\xd7\x65\xde\x55\x24\x38\xff\xbf\x2a\x62\x53\x4f\xe2\xdc\x52\x0b\x16\xc6\x23\xab\x91\xb7\xca\x02\x9f\x4c\xae\x2a\x6d\xf6\x08\x23\xac\x85\xcc\xc3\x73\xd0\xe2\xf4\x0e\x02\x6a\xec\x7d\x7d\xa4\x2c\x75\x11\x00\x41\x5c\x4a\x86\x7d\xf4\xe2\x2a\x72\x8c\xec\x2b\x9d\x89\x12\x2e\xb7\x50\x73\x57\xa4\x4b\x56\x09\xce\x58\xed\xd6\xd1\xa1\x38\x44\x25\x3b\x3a\xfc\x82\x8b\xfc\x7c\x78\xef\xb2\xbe\xfa\x60\xae\xc8\xc7\x29\x2f\x73\xd0\xe3\x30\xbc\x01\xcf\x41\xf8\xff\x8f\x12\x46\xe7\x31\x15\x1b\x10\xf6\xe1\xe7\xd3\xc9\xf8\x8c\x71\x50\x52\xd4\xda\x88\xb3\xb9\x7a\x76\x8f\x45\x31\xf4\xe0\x86\x89\xdb\xef\x36\xdc\x48\x4e\x64\xe3\x4e\x2f\x53\xb7\x38\x88\xa5\x59\x60\xcb\x03\x59\x42\x89\xfa\x15\xe7\xaa\xf4\x88\xdc\xcb\xa3\xbf\x7d\x85\x38\xb5\x0e\xc7\x0c\xb5\xf7\x47\x01\x63\xc6\xe5\x25\xbd\xae\x4e\xa0\xd8\x78\x5e\x63\x93\x4b\x1b\xcd\x05\x64\x8e\xd5\xd4\xc1\x36\xc7\xef\x7a\xdb\x64\x63\xa5\xdf\x28\xa3\x3e\x4d\x1b\x57\x61\xa1\xd0\xde\x38\xe9\x2c\xa0\xe6\x19\xf5\x4e\x43\x4f\xcd\xd1\x6a\xf5\x29\x49\xf6\x44\x42\x90\xf6\x4b\x80\xa7\x4e\xfc\xa8\x9e\x09\xc8\xe1\x99\xc5\xe2\xa1\x0c\x85\x72\x58\xe5\xa7\xa7\x0f\xcf\xd0\x6e\x4e\xcc\xa9\x21\x95\x8d\x78\x6d\x93\x6b\x5d\x7a\x48\x9f\x04\x9a\xd2\xad\xf7\xc3\x47\x0b\xfe\x66\x03\x66\x42\x16\x94\xd0\x8f\xd7\x57\xad\x8e\xf3\x9d\x04\x47\xcf\x8d\xc5\x57\xb5\x46\x15\x53\xeb\x93\x4d\x58\xb7\x5d\x11\xe9\x29\x92\x98\xab\x16\x2b\xc6\x54\x93\x24\xf6\xcf\x89\xa9\xcf\x86\x79\x86\x12\x0e\xea\x43\xe3\x34\x98\x26\x1f\xf5\x31\x3f\xae\x1f\xaa\xc3\x5b\x7a\xb9\xef\xbc\x08\x1a\x50\x9f\x61\x64\xb0\x74\xe1\xef\x5e\xac\x84\x43\xee\x35\x16\x24\x39\x27\x0b\x10\xd2\xb0\x49\x69\x39\x88\x75\xa9\x75\xe0\x77\x96\x34\xe7\x06\xce\xd4\xdb\x41\x6b\x5b\xa6\x12\x52\xfd\x29\x58\x4b\x87\x48\xae\x01\xe7\xcb\xf6\x0a\xa0\x2e\x3f\x2d\x05\xf0\xa9\xbb\x83\xb4\x1b\xd1\x5b\x92\xc7\xd7\xbe\xb1\x4d\xa3\x0a\x7b\xaa\xcb\xfe\x0a\xf2\x0c\x82\x5f\x4d\x5f\x7b\x38\xb5\xcc\x29\x85\x80\x71\xf2\xd9\x48\xcb\xa0\x6d\x7e\x6c\xd3\xf1\x75\xb2\xa9\xfe\x37\x12\x99\xb4\xa5\xbc\x3d\x9f\x66\xab\x67\x8f\x9c\x3e\x72\xd8\x3b\x35\x3f\x8a\x0d\xf6\xca\xd9\xf4\xff\x9e\xfa\x16\x42\xcf\xac\x14\xf8\x9d\x2c\xee\x2a\xbf\x3c\xfa\x35\xa6\x10\xb6\x6a\x56\x9f\xbd\xe8\x3f\x7f\x5c\xdb\x31\xa8\xf7\xdb\xd8\x91\xe6\x34\xa9\x55\x75\x80\x41\x29\xc0\x18\x61\x08\x0b\xe3\x60\x91\xac\xda\x0a\xf5\x0c\xcd\x4a\x69\xcf\xf1\x65\x77\xc0\x39\x49\xfd\x03\xaf\xb6\x90\x20\x67\x59\x7d\x52\x55\x74\xf8\xbc\xbc\x91\xd3\xe1\xe1\x69\xf6\x51\x03\x67\xb4\x8b\x62\x4a\x64\xdb\xba\x82\x15\x7a\x27\x53\x83\x15\xcb\x58\xd9\xd7\x6a\x2a\x82\x07\xc9\x71\xa2\xe3\x8a\xc6\xc5\xef\x47\x20\x24\xd3\x30\x36\x03\x7e\x03\x13\x1f\x17\x2b\xa9\x71\x74\xc6\x49\xb0\x10\xea\xdd\x0d\xab\xae\x7b\xda\xc4\x23\x21\xf2\xa1\x69\xc6\x50\x35\xa3\xe2\x91\x17\x60\x31\x37\x4c\x99\x18\xe0\xd7\x75\x44\x47\x25\xdd\x7a\x73\x5b\x6c\x7e\x28\xd0\x27\xd9\xbc\xdf\xe0\x13\xf3\xf0\xa4\xde\x56\x2a\x1f\x8f\x98\xa6\x91\x63\x0a\xbe\x61\x93\xb7\x6d\xdc\x63\xca\xf9\x8d\xd9\x72\x6a\xda\x41\xda\x06\x42\xf7\x30\x13\xe6\xa2\x23\xef\x4a\xed\xad\x3d\x8b\xfe\xf3\xd1\xde\xaf\xe3\xf1\xd5\xbf\x8b\x67\x7b\x77\xa5\xff\x78\x17\x35\x39\xa4\xed\x2b\x7d\x36\x6d\x43\xfa\x65\xa8\xd6\x10\xc3\xcb\x71\x13\xb7\x7f\x80\x63\xaf\xa9\xbb\xe1\x6a\x8a\x9e\x7a\x9b\xb7\x84\xbb\x7a\x63\x17\x48\x7c\x9d\xcc\x75\xd5\xd0\xd7\xb2\x6d\x06\x9c\x8f\xd7\xd3\xa9\xbd\x7a\xb4\x17\xc9\xb8\xbe\x83\xb2\xbe\xeb\xe4\x31\xe5\xf5\x8d\x3f\x5e\xf9\x9e\x28\x44\x04\x51\x34\x3a\xf2\x08\x67\x7a\x47\x44\x24\x4a\x4a\xd7\x24\x52\x5d\xf2\x15\x74\xda\xd6\xb4\x37\xbb\x3a\x7e\xe7\x94\x13\xd9\x8e\xdb\xde\x03\xda\x96\x44\x92\x85\x9a\xc3\x2e\x6e\x6e\xc6\xec\x61\x85\x3e\x95\x39\x05\x8e\x67\x24\x27\x72\x15\x00\x3b\x77\xd7\xfd\xfd\xfd\xc8\x9c\x3b\x94\xb0\xe5\xe1\x2c\x67\x8b\x43\x8b\x87\xd0\xc5\x50\x66\x30\xd4\x27\x80\x3f\xac\x86\x77\x3e\xb6\xe1\x3d\x91\x99\x3d\xe7\xa8\xbf\xc9\x26\x85\xaa\xf5\xb4\x82\x73\xad\xdb\x2a\x25\x43\xee\xce\x4b\x33\x5d\x76\x98\x74\xde\xd5\x95\x79\xde\xb7\xe9\x32\x4a\xde\x97\xc8\x8d\x96\x15\x8d\x5f\x9a\x37\x5b\x7a\x75\x77\x07\xc6\xac\x42\x8b\xde\x18\x5d\x15\x0f\x97\xb3\x56\x31\xb6\x2e\xc4\x0e\xc9\xa6\x69\x0f\xde\x38\xea\x09\xd0\x74\x03\x5e\x0e\xf8\xf1\x78\xaf\x01\xa7\xed\xfb\xae\xbd\x02\xd6\x73\xbc\xdf\x30\x79\x4d\x59\xfd\xd7\x19\x10\x3a\x90\x35\xb0\xab\xdd\xc1\xc1\xb6\x05\xd8\x7c\x3e\x68\xfb\x8a\x5d\x83\x9a\x77\xc7\x6c\xd5\xa0\x76\x1d\x5b\x1a\xbf\xdf\xb8\xce\x8d\x70\x37\xec\x51\x66\xaf\x31\x5a\xfa\x92\xb5\x3a\x82\xec\x7d\xd6\x8e\x79\xa2\x96\x99\x6f\xf0\x44\x57\x3d\xfe\x8d\xee\xf1\xe7\xe5\x57\x62\xae\xd2\x07\x3a\x9b\x1c\xc3\x6b\xe3\xb2\xaf\xab\x42\xed\x40\x88\xbd\xfc\xdb\xbf\xdb\x37\xfa\xe8\x0b\x7f\xa3\x4a\x81\xdd\x12\x98\x9a\x0c\xaf\x68\xc9\xa8\x52\x50\x85\x4c\xfe\x57\x47\xcc\xd0\xe2\xd5\x86\x7d\xfc\xe9\xc4\xdb\x3c\x76\xc9\x3d\x3b\xe8\x92\xea\xcb\x30\x11\x9b\x23\xbb\x35\x5c\xf2\x95\x9e\xf2\x28\x3c\x48\xef\x1a\x0b\x13\x84\x99\xc1\x9c\x71\xb0\xfe\x03\xc5\x3c\x4c\x4d\xb9\x08\xb5\x0a\x41\x15\xe0\xeb\xa2\x56\x4f\x83\xef\xe1\xc1\xbb\xf1\x36\xa0\xbf\xf9\xd1\xcd\x91\xd7\x20\xf9\xea\x3d\xa3\x97\x29\x2c\x0b\x26\x5d\xea\x56\xdb\xb4\x76\x9b\xa7\x19\xcd\x57\xe8\x9e\xf1\x5b\xe1\x7c\x94\x89\xc9\x1f\x41\x44\x68\x2b\x2f\x61\xcb\x82\x83\x10\x90\x76\x19\xd4\x91\x1c\x9d\x34\x7d\x8d\x05\x18\x9f\xc8\x57\xad\x91\x4f\xf5\x06\xf8\xa1\x3e\x87\xc5\x49\xf0\xd6\x0e\xaa\xc3\xa7\xe8\x34\xd5\x77\x44\xfb\x1b\x9b\x9b\xe9\xb3\x66\x63\x61\xbc\x4d\xbe\x73\xcb\x2b\xe3\xe5\x6a\x6d\x8a\x18\xd8\xde\x70\xa1\x99\x58\x2a\x18\xfa\x86\xa7\x66\xa2\x7a\x3b\x64\xac\xde\xad\x1d\x6c\xc6\x51\x7d\x0f\x08\x73\x40\x6a\x9e\x75\x57\xd6\xba\x80\x85\x64\x08\xbb\x9b\x92\x9c\x67\xc6\x3a\x09\x9e\xe9\x72\x69\xda\x3c\x04\xdd\x2d\x63\x3a\x44\x67\x3f\xc3\x62\xcc\x61\x4e\x1e\x1a\x2e\x4e\x8d\x10\x0d\x4c\x45\xe1\x7e\xfc\xe1\x60\xb3\x7f\xc6\x3c\x91\x43\x1a\xfe\x1e\x06\x9c\x7e\xd3\x93\x19\xfe\x24\xa7\x2a\x74\xc8\xc6\xb6\xbe\x33\xdb\x5f\x31\x2f\x8f\x56\x63\x26\xdf\x5f\xe7\x75\x6c\x9f\xb2\xb0\xcd\x92\x63\x07\xbd\x67\x08\xaa\x13\x86\xea\xeb\xcc\xe7\x8c\xfb\x62\xdb\x28\x64\x7d\xbc\x2d\x41\xe8\x8e\xbd\x46\x6b\xae\xf2\x22\x53\xa0\x04\xd2\x11\xba\x06\x2c\x18\x3d\x6e\xea\x8f\x73\xfd\x31\x2c\xbd\x3d\x09\xcd\x34\xc6\x78\x66\x63\xc7\x6e\x88\x66\x7e\xb2\x39\x1e\x14\xe9\xc3\x69\xf5\xd6\x82\x24\x67\x65\xaa\x44\xe6\x8e\xe8\x01\x6a\x47\xac\x66\x5b\x69\x26\xbe\xde\xbd\x89\xd1\xd4\x49\x07\x7c\xf1\xdb\x1c\x46\x6a\xe3\xad\xc1\xb6\x09\xb3\x9d\xdc\xdc\x44\xa6\x44\x54\x9f\xb5\x4b\x04\x9a\x81\xd2\x5e\x4b\x46\x89\x64\x1c\x52\x0f\x8b\xde\x6e\xae\x66\x68\x1b\xd7\x43\x82\xcd\xe5\xbd\xd2\x79\xfb\x39\xb9\x05\x24\x56\x22\x25\x8b\x83\x76\xfb\xfa\x0e\x41\x35\xde\xe7\xe7\x2f\xfe\x6b\x74\x34\x3a\x1a\x3d\xef\x74\xbe\x84\x1b\xb8\x4c\xb9\xe3\xe3\xf0\x16\x0c\xf7\xa4\xa0\xa6\xb1\x3c\x3c\xc0\xe6\x6b\x0e\x28\x8d\x4b\x53\xfd\xef\xff\x0f\x00\x00\xff\xff\x2e\xe3\xb9\xdd\x59\x92\x00\x00") + +func etcNginxTemplateNginxTmplBytes() ([]byte, error) { + return bindataRead( + _etcNginxTemplateNginxTmpl, + "etc/nginx/template/nginx.tmpl", + ) +} + +func etcNginxTemplateNginxTmpl() (*asset, error) { + bytes, err := etcNginxTemplateNginxTmplBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "etc/nginx/template/nginx.tmpl", size: 37465, mode: os.FileMode(420), modTime: time.Unix(1511380479, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _ingressControllerCleanNginxConfSh = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x64\x92\x41\x6b\xe3\x48\x10\x85\xef\xfa\x15\x6f\x2d\x83\x77\x17\x47\x9d\x78\x0f\x0b\xc9\xc9\x9b\x64\x19\x91\x60\x43\xe4\x4c\x08\x98\x40\xbb\x55\x96\x0a\xa4\x6e\x4d\x75\x2b\xb6\x99\x99\xff\x3e\xb4\xe2\xcc\xc4\x8c\x8e\x55\x4f\xfd\xbe\x7a\x55\xe9\x1f\x6a\xc3\x56\x6d\xb4\xaf\x93\x24\xc5\xb5\xeb\x0e\xc2\x55\x1d\x30\x3b\xbf\xf8\x17\xab\x9a\x70\xd7\x6f\x48\x2c\x05\xf2\x98\xf7\xa1\x76\xe2\xb3\x24\x4d\x52\xdc\xb3\x21\xeb\xa9\x44\x6f\x4b\x12\x84\x9a\x30\xef\xb4\xa9\xe9\xbd\x33\xc5\x67\x12\xcf\xce\x62\x96\x9d\xe3\xcf\x28\x18\x1d\x5b\xa3\xbf\xae\x92\x14\x07\xd7\xa3\xd5\x07\x58\x17\xd0\x7b\x42\xa8\xd9\x63\xcb\x0d\x81\xf6\x86\xba\x00\xb6\x30\xae\xed\x1a\xd6\xd6\x10\x76\x1c\xea\xc1\xe6\xf8\x48\x96\xa4\x78\x3e\x3e\xe1\x36\x41\xb3\x85\x86\x71\xdd\x01\x6e\xfb\x51\x07\x1d\x06\xe0\xf8\xd5\x21\x74\x97\x4a\xed\x76\xbb\x4c\x0f\xb0\x99\x93\x4a\x35\x6f\x42\xaf\xee\xf3\xeb\xdb\x45\x71\x7b\x36\xcb\xce\x87\x5f\x1e\x6d\x43\xde\x43\xe8\x4b\xcf\x42\x25\x36\x07\xe8\xae\x6b\xd8\xe8\x4d\x43\x68\xf4\x0e\x4e\xa0\x2b\x21\x2a\x11\x5c\xe4\xdd\x09\x07\xb6\xd5\x14\xde\x6d\xc3\x4e\x0b\x25\x29\x4a\xf6\x41\x78\xd3\x87\x93\xb0\xde\xe9\xd8\x9f\x08\x9c\x85\xb6\x18\xcd\x0b\xe4\xc5\x08\xff\xcd\x8b\xbc\x98\x26\x29\x9e\xf2\xd5\xa7\xe5\xe3\x0a\x4f\xf3\x87\x87\xf9\x62\x95\xdf\x16\x58\x3e\xe0\x7a\xb9\xb8\xc9\x57\xf9\x72\x51\x60\xf9\x3f\xe6\x8b\x67\xdc\xe5\x8b\x9b\x29\x88\x43\x4d\x02\xda\x77\x12\xf9\x9d\x80\x63\x8c\x54\xc6\xcc\x0a\xa2\x13\x80\xad\x7b\x03\xf2\x1d\x19\xde\xb2\x41\xa3\x6d\xd5\xeb\x8a\x50\xb9\x57\x12\xcb\xb6\x42\x47\xd2\xb2\x8f\xcb\xf4\xd0\xb6\x4c\x52\x34\xdc\x72\xd0\x61\xa8\xfc\x36\x54\x16\x6f\x69\x15\xd7\xe9\x8d\x70\x17\x20\xd4\xba\x57\xf2\x30\xce\x7a\x32\x7d\xe0\x57\x02\xb5\x5d\x38\xa0\x61\x4b\x3e\x26\x67\x2b\xb6\xfb\xcc\x38\xbb\x8d\xc1\xfb\xe8\x1a\xaf\x8b\x3d\x5a\x27\x04\x1f\x27\x88\xe0\xda\xa2\x1f\xba\x1a\x95\x83\x50\x45\xfb\x64\x98\xaa\x8c\xc7\xd2\x6a\x5b\xfa\xcb\x24\xc5\x45\x76\x34\x1d\xb8\x84\x42\x2f\x16\x46\x8b\x30\x09\x4c\xad\x45\x9b\x40\xa2\x7c\x92\x62\xf6\x53\xfa\x01\x29\x49\xf1\x4f\xac\x77\x8d\x36\x84\xb6\x6f\x02\x47\xff\x8f\x8a\x88\x77\x46\x98\x78\xb5\x16\xa5\xaa\x09\xbe\xe1\x57\xe9\x05\xf8\x7b\xac\xd6\x93\xf1\x64\x6d\x4f\x7b\xea\x65\xac\xbe\x2e\xae\xd4\xcb\xda\x8e\xd5\xcd\xd5\xf7\x49\xf2\x23\x00\x00\xff\xff\x3b\xba\x15\x8d\x85\x03\x00\x00") + +func ingressControllerCleanNginxConfShBytes() ([]byte, error) { + return bindataRead( + _ingressControllerCleanNginxConfSh, + "ingress-controller/clean-nginx-conf.sh", + ) +} + +func ingressControllerCleanNginxConfSh() (*asset, error) { + bytes, err := ingressControllerCleanNginxConfShBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "ingress-controller/clean-nginx-conf.sh", size: 901, mode: os.FileMode(493), modTime: time.Unix(1509931394, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +// Asset loads and returns the asset for the given name. +// It returns an error if the asset could not be found or +// could not be loaded. +func Asset(name string) ([]byte, error) { + cannonicalName := strings.Replace(name, "\\", "/", -1) + if f, ok := _bindata[cannonicalName]; ok { + a, err := f() + if err != nil { + return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err) + } + return a.bytes, nil + } + return nil, fmt.Errorf("Asset %s not found", name) +} + +// MustAsset is like Asset but panics when Asset would return an error. +// It simplifies safe initialization of global variables. +func MustAsset(name string) []byte { + a, err := Asset(name) + if err != nil { + panic("asset: Asset(" + name + "): " + err.Error()) + } + + return a +} + +// AssetInfo loads and returns the asset info for the given name. +// It returns an error if the asset could not be found or +// could not be loaded. +func AssetInfo(name string) (os.FileInfo, error) { + cannonicalName := strings.Replace(name, "\\", "/", -1) + if f, ok := _bindata[cannonicalName]; ok { + a, err := f() + if err != nil { + return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err) + } + return a.info, nil + } + return nil, fmt.Errorf("AssetInfo %s not found", name) +} + +// AssetNames returns the names of the assets. +func AssetNames() []string { + names := make([]string, 0, len(_bindata)) + for name := range _bindata { + names = append(names, name) + } + return names +} + +// _bindata is a table, holding each asset generator, mapped to its name. +var _bindata = map[string]func() (*asset, error){ + "etc/nginx/nginx.conf": etcNginxNginxConf, + "etc/nginx/template/nginx.tmpl": etcNginxTemplateNginxTmpl, + "ingress-controller/clean-nginx-conf.sh": ingressControllerCleanNginxConfSh, +} + +// AssetDir returns the file names below a certain +// directory embedded in the file by go-bindata. +// For example if you run go-bindata on data/... and data contains the +// following hierarchy: +// data/ +// foo.txt +// img/ +// a.png +// b.png +// then AssetDir("data") would return []string{"foo.txt", "img"} +// AssetDir("data/img") would return []string{"a.png", "b.png"} +// AssetDir("foo.txt") and AssetDir("notexist") would return an error +// AssetDir("") will return []string{"data"}. +func AssetDir(name string) ([]string, error) { + node := _bintree + if len(name) != 0 { + cannonicalName := strings.Replace(name, "\\", "/", -1) + pathList := strings.Split(cannonicalName, "/") + for _, p := range pathList { + node = node.Children[p] + if node == nil { + return nil, fmt.Errorf("Asset %s not found", name) + } + } + } + if node.Func != nil { + return nil, fmt.Errorf("Asset %s not found", name) + } + rv := make([]string, 0, len(node.Children)) + for childName := range node.Children { + rv = append(rv, childName) + } + return rv, nil +} + +type bintree struct { + Func func() (*asset, error) + Children map[string]*bintree +} +var _bintree = &bintree{nil, map[string]*bintree{ + "etc": &bintree{nil, map[string]*bintree{ + "nginx": &bintree{nil, map[string]*bintree{ + "nginx.conf": &bintree{etcNginxNginxConf, map[string]*bintree{}}, + "template": &bintree{nil, map[string]*bintree{ + "nginx.tmpl": &bintree{etcNginxTemplateNginxTmpl, map[string]*bintree{}}, + }}, + }}, + }}, + "ingress-controller": &bintree{nil, map[string]*bintree{ + "clean-nginx-conf.sh": &bintree{ingressControllerCleanNginxConfSh, map[string]*bintree{}}, + }}, +}} + +// RestoreAsset restores an asset under the given directory +func RestoreAsset(dir, name string) error { + data, err := Asset(name) + if err != nil { + return err + } + info, err := AssetInfo(name) + if err != nil { + return err + } + err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755)) + if err != nil { + return err + } + err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode()) + if err != nil { + return err + } + err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime()) + if err != nil { + return err + } + return nil +} + +// RestoreAssets restores an asset under the given directory recursively +func RestoreAssets(dir, name string) error { + children, err := AssetDir(name) + // File + if err != nil { + return RestoreAsset(dir, name) + } + // Dir + for _, child := range children { + err = RestoreAssets(dir, filepath.Join(name, child)) + if err != nil { + return err + } + } + return nil +} + +func _filePath(dir, name string) string { + cannonicalName := strings.Replace(name, "\\", "/", -1) + return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...) +} + diff --git a/internal/file/filesystem.go b/internal/file/filesystem.go new file mode 100644 index 0000000000..1adaba8ec1 --- /dev/null +++ b/internal/file/filesystem.go @@ -0,0 +1,127 @@ +package file + +import ( + "os" + "path/filepath" + + "github.com/golang/glog" + + "k8s.io/kubernetes/pkg/util/filesystem" +) + +// Filesystem is an interface that we can use to mock various filesystem operations +type Filesystem interface { + filesystem.Filesystem +} + +// NewLocalFS implements Filesystem using same-named functions from "os" and "io/ioutil". +func NewLocalFS() (Filesystem, error) { + fs := filesystem.DefaultFs{} + + err := initialize(false, fs) + if err != nil { + return nil, err + } + + return fs, nil +} + +// NewFakeFS creates an in-memory filesytem with all the required +// paths used by the ingress controller. +// This allows running test without polluting the local machine. +func NewFakeFS() (Filesystem, error) { + fs := filesystem.NewFakeFs() + + err := initialize(true, fs) + if err != nil { + return nil, err + } + + return fs, nil +} + +// initialize creates the required directory structure and when +// runs as virtual filesystem it copies the local files to it +func initialize(isVirtual bool, fs Filesystem) error { + for _, directory := range directories { + err := fs.MkdirAll(directory, 0655) + if err != nil { + return err + } + } + + if !isVirtual { + return nil + } + + for _, file := range files { + f, err := fs.Create(file) + if err != nil { + return err + } + + _, err = f.Write([]byte("")) + if err != nil { + return err + } + + err = f.Close() + if err != nil { + return err + } + } + + err := fs.MkdirAll("/proc", 0655) + if err != nil { + return err + } + + glog.Info("Restoring generated (go-bindata) assets in virtual filesystem...") + for _, assetName := range AssetNames() { + err := restoreAsset("/", assetName, fs) + if err != nil { + return err + } + } + + return nil +} + +// restoreAsset restores an asset under the given directory +func restoreAsset(dir, name string, fs Filesystem) error { + data, err := Asset(name) + if err != nil { + return err + } + info, err := AssetInfo(name) + if err != nil { + return err + } + err = fs.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755)) + if err != nil { + return err + } + + f, err := fs.Create(_filePath(dir, name)) + if err != nil { + return err + } + + _, err = f.Write(data) + if err != nil { + return err + } + + err = f.Close() + if err != nil { + return err + } + + //Missing info.Mode() + + err = fs.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime()) + if err != nil { + return err + } + return nil +} diff --git a/internal/file/filesystem_test.go b/internal/file/filesystem_test.go new file mode 100644 index 0000000000..36e1513c60 --- /dev/null +++ b/internal/file/filesystem_test.go @@ -0,0 +1,37 @@ +/* +Copyright 2017 The Kubernetes Authors. + +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 file + +import ( + "testing" +) + +func TestNewFakeFS(t *testing.T) { + fs, err := NewFakeFS() + if err != nil { + t.Fatalf("unexpected error creating filesystem abstraction: %v", err) + } + + if fs == nil { + t.Fatal("expected a filesystem but none returned") + } + + _, err = fs.Stat("/etc/nginx/nginx.conf") + if err != nil { + t.Fatalf("unexpected error reading default nginx.conf file: %v", err) + } +} diff --git a/internal/file/structure.go b/internal/file/structure.go new file mode 100644 index 0000000000..aa4cd74ddb --- /dev/null +++ b/internal/file/structure.go @@ -0,0 +1,26 @@ +package file + +const ( + // AuthDirectory default directory used to store files + // to authenticate request + AuthDirectory = "/etc/ingress-controller/auth" + + // DefaultSSLDirectory defines the location where the SSL certificates will be generated + // This directory contains all the SSL certificates that are specified in Ingress rules. + // The name of each file is -.pem. The content is the concatenated + // certificate and key. + DefaultSSLDirectory = "/ingress-controller/ssl" +) + +var ( + directories = []string{ + "/etc/nginx/template", + "/run", + DefaultSSLDirectory, + AuthDirectory, + } + + files = []string{ + "/run/nginx.pid", + } +) diff --git a/internal/ingress/controller/controller.go b/internal/ingress/controller/controller.go index 875ffbedc0..12d951e87a 100644 --- a/internal/ingress/controller/controller.go +++ b/internal/ingress/controller/controller.go @@ -76,8 +76,8 @@ type Configuration struct { ConfigMapName string DefaultService string - IngressClass string - Namespace string + + Namespace string ForceNamespaceIsolation bool @@ -87,7 +87,6 @@ type Configuration struct { UDPConfigMapName string DefaultHealthzURL string - DefaultIngressClass string DefaultSSLCertificate string // optional diff --git a/internal/ingress/controller/nginx.go b/internal/ingress/controller/nginx.go index b413ac5c3f..2d7ababb75 100644 --- a/internal/ingress/controller/nginx.go +++ b/internal/ingress/controller/nginx.go @@ -43,6 +43,7 @@ import ( "k8s.io/client-go/util/flowcontrol" "k8s.io/kubernetes/pkg/util/filesystem" + "k8s.io/ingress-nginx/internal/file" "k8s.io/ingress-nginx/internal/ingress" "k8s.io/ingress-nginx/internal/ingress/annotations" "k8s.io/ingress-nginx/internal/ingress/annotations/class" @@ -57,6 +58,7 @@ import ( "k8s.io/ingress-nginx/internal/net/dns" "k8s.io/ingress-nginx/internal/net/ssl" "k8s.io/ingress-nginx/internal/task" + "k8s.io/ingress-nginx/internal/watch" ) type statusModule string @@ -77,7 +79,7 @@ var ( // NewNGINXController creates a new NGINX Ingress controller. // If the environment variable NGINX_BINARY exists it will be used // as source for nginx commands -func NewNGINXController(config *Configuration) *NGINXController { +func NewNGINXController(config *Configuration, fs file.Filesystem) *NGINXController { ngx := os.Getenv("NGINX_BINARY") if ngx == "" { ngx = nginxBinary @@ -114,12 +116,12 @@ func NewNGINXController(config *Configuration) *NGINXController { stopCh: make(chan struct{}), stopLock: &sync.Mutex{}, - fileSystem: filesystem.DefaultFs{}, + fileSystem: fs, } n.listers, n.controllers = n.createListers(n.stopCh) - n.stats = newStatsCollector(config.Namespace, config.IngressClass, n.binary, n.cfg.ListenPorts.Status) + n.stats = newStatsCollector(config.Namespace, class.IngressClass, n.binary, n.cfg.ListenPorts.Status) n.syncQueue = task.NewTaskQueue(n.syncIngress) @@ -131,8 +133,8 @@ func NewNGINXController(config *Configuration) *NGINXController { PublishService: config.PublishService, IngressLister: n.listers.Ingress, ElectionID: config.ElectionID, - IngressClass: config.IngressClass, - DefaultIngressClass: config.DefaultIngressClass, + IngressClass: class.IngressClass, + DefaultIngressClass: class.DefaultClass, UpdateStatusOnShutdown: config.UpdateStatusOnShutdown, UseNodeInternalIP: config.UseNodeInternalIP, }) @@ -142,7 +144,7 @@ func NewNGINXController(config *Configuration) *NGINXController { var onChange func() onChange = func() { - template, err := ngx_template.NewTemplate(tmplPath, onChange) + template, err := ngx_template.NewTemplate(tmplPath, fs) if err != nil { // this error is different from the rest because it must be clear why nginx is not working glog.Errorf(` @@ -153,19 +155,28 @@ Error loading new template : %v return } - n.t.Close() n.t = template glog.Info("new NGINX template loaded") n.SetForceReload(true) } - ngxTpl, err := ngx_template.NewTemplate(tmplPath, onChange) + ngxTpl, err := ngx_template.NewTemplate(tmplPath, fs) if err != nil { glog.Fatalf("invalid NGINX template: %v", err) } n.t = ngxTpl + // TODO: refactor + if _, ok := fs.(filesystem.DefaultFs); !ok { + watch.NewDummyFileWatcher(tmplPath, onChange) + } else { + _, err = watch.NewFileWatcher(tmplPath, onChange) + if err != nil { + glog.Fatalf("unexpected error watching template %v: %v", tmplPath, err) + } + } + return n } diff --git a/internal/ingress/controller/template/template.go b/internal/ingress/controller/template/template.go index b5cea55e63..08ff249927 100644 --- a/internal/ingress/controller/template/template.go +++ b/internal/ingress/controller/template/template.go @@ -29,16 +29,17 @@ import ( text_template "text/template" "github.com/golang/glog" + "github.com/pkg/errors" "github.com/pborman/uuid" extensions "k8s.io/api/extensions/v1beta1" "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/ingress-nginx/internal/file" "k8s.io/ingress-nginx/internal/ingress" "k8s.io/ingress-nginx/internal/ingress/annotations/ratelimit" "k8s.io/ingress-nginx/internal/ingress/controller/config" ing_net "k8s.io/ingress-nginx/internal/net" - "k8s.io/ingress-nginx/internal/watch" ) const ( @@ -50,34 +51,29 @@ const ( // Template ... type Template struct { tmpl *text_template.Template - fw watch.FileWatcher - bp *BufferPool + //fw watch.FileWatcher + bp *BufferPool } //NewTemplate returns a new Template instance or an //error if the specified template file contains errors -func NewTemplate(file string, onChange func()) (*Template, error) { - tmpl, err := text_template.New("nginx.tmpl").Funcs(funcMap).ParseFiles(file) +func NewTemplate(file string, fs file.Filesystem) (*Template, error) { + data, err := fs.ReadFile(file) if err != nil { - return nil, err + return nil, errors.Wrapf(err, "unexpected error reading template %v", file) } - fw, err := watch.NewFileWatcher(file, onChange) + + tmpl, err := text_template.New("nginx.tmpl").Funcs(funcMap).Parse(string(data)) if err != nil { return nil, err } return &Template{ tmpl: tmpl, - fw: fw, bp: NewBufferPool(defBufferSize), }, nil } -// Close removes the file watcher -func (t *Template) Close() { - t.fw.Close() -} - // Write populates a buffer using a template with NGINX configuration // and the servers and upstreams created by Ingress rules func (t *Template) Write(conf config.TemplateConfig) ([]byte, error) { diff --git a/internal/ingress/controller/template/template_test.go b/internal/ingress/controller/template/template_test.go index e147a2e7b3..3fd0cd4e3e 100644 --- a/internal/ingress/controller/template/template_test.go +++ b/internal/ingress/controller/template/template_test.go @@ -26,6 +26,7 @@ import ( "strings" "testing" + "k8s.io/ingress-nginx/internal/file" "k8s.io/ingress-nginx/internal/ingress" "k8s.io/ingress-nginx/internal/ingress/annotations/authreq" "k8s.io/ingress-nginx/internal/ingress/annotations/rewrite" @@ -174,13 +175,13 @@ func TestTemplateWithData(t *testing.T) { if dat.ListenPorts == nil { dat.ListenPorts = &config.ListenPorts{} } - tf, err := os.Open(path.Join(pwd, "../../../../rootfs/etc/nginx/template/nginx.tmpl")) + + fs, err := file.NewFakeFS() if err != nil { - t.Errorf("unexpected error reading json file: %v", err) + t.Fatalf("unexpected error: %v", err) } - defer tf.Close() - ngxTpl, err := NewTemplate(tf.Name(), func() {}) + ngxTpl, err := NewTemplate("/etc/nginx/template/nginx.tmpl", fs) if err != nil { t.Errorf("invalid NGINX template: %v", err) } @@ -207,13 +208,12 @@ func BenchmarkTemplateWithData(b *testing.B) { b.Errorf("unexpected error unmarshalling json: %v", err) } - tf, err := os.Open(path.Join(pwd, "../../../rootfs/etc/nginx/template/nginx.tmpl")) + fs, err := file.NewFakeFS() if err != nil { - b.Errorf("unexpected error reading json file: %v", err) + b.Fatalf("unexpected error: %v", err) } - defer tf.Close() - ngxTpl, err := NewTemplate(tf.Name(), func() {}) + ngxTpl, err := NewTemplate("/etc/nginx/template/nginx.tmpl", fs) if err != nil { b.Errorf("invalid NGINX template: %v", err) } diff --git a/internal/watch/dummy.go b/internal/watch/dummy.go new file mode 100644 index 0000000000..16a607fc2e --- /dev/null +++ b/internal/watch/dummy.go @@ -0,0 +1,29 @@ +/* +Copyright 2017 The Kubernetes Authors. + +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 watch + +// DummyFileWatcher noop implementation of a file watcher +type DummyFileWatcher struct{} + +func NewDummyFileWatcher(file string, onEvent func()) FileWatcher { + return DummyFileWatcher{} +} + +// Close ends the watch +func (f DummyFileWatcher) Close() error { + return nil +} diff --git a/internal/watch/file_watcher.go b/internal/watch/file_watcher.go index 0fea1a1434..91daf06208 100644 --- a/internal/watch/file_watcher.go +++ b/internal/watch/file_watcher.go @@ -24,8 +24,12 @@ import ( "gopkg.in/fsnotify.v1" ) -// FileWatcher defines a watch over a file -type FileWatcher struct { +type FileWatcher interface { + Close() error +} + +// OSFileWatcher defines a watch over a file +type OSFileWatcher struct { file string watcher *fsnotify.Watcher // onEvent callback to be invoked after the file being watched changes @@ -34,7 +38,7 @@ type FileWatcher struct { // NewFileWatcher creates a new FileWatcher func NewFileWatcher(file string, onEvent func()) (FileWatcher, error) { - fw := FileWatcher{ + fw := OSFileWatcher{ file: file, onEvent: onEvent, } @@ -43,12 +47,12 @@ func NewFileWatcher(file string, onEvent func()) (FileWatcher, error) { } // Close ends the watch -func (f *FileWatcher) Close() error { +func (f OSFileWatcher) Close() error { return f.watcher.Close() } // watch creates a fsnotify watcher for a file and create of write events -func (f *FileWatcher) watch() error { +func (f *OSFileWatcher) watch() error { watcher, err := fsnotify.NewWatcher() if err != nil { return err diff --git a/test/e2e/up.sh b/test/e2e/up.sh index fd4839fb90..e98b155d61 100755 --- a/test/e2e/up.sh +++ b/test/e2e/up.sh @@ -35,47 +35,3 @@ until kubectl get nodes -o jsonpath="$JSONPATH" 2>&1 | grep -q "Ready=True"; do sleep 1; done - -echo "deploying NGINX Ingress controller" -cat deploy/namespace.yaml | kubectl apply -f - -cat deploy/default-backend.yaml | kubectl apply -f - -cat deploy/configmap.yaml | kubectl apply -f - -cat deploy/tcp-services-configmap.yaml | kubectl apply -f - -cat deploy/udp-services-configmap.yaml | kubectl apply -f - -cat deploy/without-rbac.yaml | kubectl apply -f - -cat deploy/provider/baremetal/service-nodeport.yaml | kubectl apply -f - - -echo "updating image..." -kubectl set image \ - deployments \ - --namespace ingress-nginx \ - --selector app=ingress-nginx \ - nginx-ingress-controller=quay.io/kubernetes-ingress-controller/nginx-ingress-controller:test - -sleep 5 - -echo "waiting NGINX ingress pod..." - -function waitForPod() { - until kubectl get pods -n ingress-nginx -l app=ingress-nginx -o jsonpath="$JSONPATH" 2>&1 | grep -q "Ready=True"; - do - sleep 1; - done -} - -export -f waitForPod - -timeout 10s bash -c waitForPod - -if kubectl get pods -n ingress-nginx -l app=ingress-nginx -o jsonpath="$JSONPATH" 2>&1 | grep -q "Ready=True"; -then - echo "Kubernetes deployments started" -else - echo "Kubernetes deployments with issues:" - kubectl get pods -n ingress-nginx - - echo "Reason:" - kubectl describe pods -n ingress-nginx - kubectl logs -n ingress-nginx -l app=ingress-nginx - exit 1 -fi diff --git a/test/e2e/wait-for-nginx.sh b/test/e2e/wait-for-nginx.sh new file mode 100755 index 0000000000..19e4d5ebb0 --- /dev/null +++ b/test/e2e/wait-for-nginx.sh @@ -0,0 +1,61 @@ +#!/bin/bash + +# Copyright 2017 The Kubernetes Authors. +# +# 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. + +export JSONPATH='{range .items[*]}{@.metadata.name}:{range @.status.conditions[*]}{@.type}={@.status};{end}{end}' + +echo "deploying NGINX Ingress controller" +cat deploy/namespace.yaml | kubectl apply -f - +cat deploy/default-backend.yaml | kubectl apply -f - +cat deploy/configmap.yaml | kubectl apply -f - +cat deploy/tcp-services-configmap.yaml | kubectl apply -f - +cat deploy/udp-services-configmap.yaml | kubectl apply -f - +cat deploy/without-rbac.yaml | kubectl apply -f - +cat deploy/provider/baremetal/service-nodeport.yaml | kubectl apply -f - + +echo "updating image..." +kubectl set image \ + deployments \ + --namespace ingress-nginx \ + --selector app=ingress-nginx \ + nginx-ingress-controller=quay.io/kubernetes-ingress-controller/nginx-ingress-controller:test + +sleep 5 + +echo "waiting NGINX ingress pod..." + +function waitForPod() { + until kubectl get pods -n ingress-nginx -l app=ingress-nginx -o jsonpath="$JSONPATH" 2>&1 | grep -q "Ready=True"; + do + sleep 1; + done +} + +export -f waitForPod + +timeout 10s bash -c waitForPod + +if kubectl get pods -n ingress-nginx -l app=ingress-nginx -o jsonpath="$JSONPATH" 2>&1 | grep -q "Ready=True"; +then + echo "Kubernetes deployments started" +else + echo "Kubernetes deployments with issues:" + kubectl get pods -n ingress-nginx + + echo "Reason:" + kubectl describe pods -n ingress-nginx + kubectl logs -n ingress-nginx -l app=ingress-nginx + exit 1 +fi