<commit_before>// Copyright 2016 Jacques Supcik <jacques.supcik@hefr.ch>
//
// 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.

//   _   _                     _                             _        _
//  | |_| |__  _   _ _ __ ___ (_) ___         ___ __ _ _ __ | |_ __ _(_)_ __
//  | __| '_ \| | | | '_ ` _ \| |/ _ \ _____ / __/ _` | '_ \| __/ _` | | '_ \
//  | |_| | | | |_| | | | | | | | (_) |_____| (_| (_| | |_) | || (_| | | | | |
//   \__|_| |_|\__, |_| |_| |_|_|\___/       \___\__,_| .__/ \__\__,_|_|_| |_|
//             |___/                                  |_|
//

// Frontend
package main

import (
	"crypto/hmac"
	"crypto/sha1"
	"encoding/base64"
	"flag"
	"fmt"
	log "github.com/Sirupsen/logrus"
	"github.com/gorilla/mux"
	"github.com/gorilla/sessions"
	"github.com/kidstuff/mongostore"
	"gopkg.in/mgo.v2"
	"html/template"
	"net/http"
	"path/filepath"
)

const (
	dbName        = "thymio_captain"
	sessionC      = "sessions"
	maxAge        = 24 * 3600
	sessionKey    = "session-key"
	root          = "internal_pages"
	TOKEN_RND_LEN = 20
	TOKEN_SIG_LEN = 20
)

var (
	database       *mgo.Session
	store          *mongostore.MongoStore
	adminSecretKey *string
	startSecretKey *string
	templates      = make(map[string]*template.Template)
)

func initSession(w http.ResponseWriter, r *http.Request) (vars map[string]string, session *sessions.Session, err error) {
	database.Refresh()
	vars = mux.Vars(r)
	session, err = store.Get(r, sessionKey)
	log.Debugf("Session ID = %v", session.ID)
	if err != nil {
		log.Error(err.Error())
		http.Error(w, "Session Error", 500)
	} else {
		w.Header().Set("Cache-Control", "max-age=0, no-cache, no-store")
		w.Header().Set("Pragma", "no-cache")
	}
	return
}

func isValidToken(token string, key string) bool {
	t, err := base64.RawURLEncoding.DecodeString(token)
	if err != nil {
		return false
	}

	if len(t) != TOKEN_RND_LEN+TOKEN_SIG_LEN {
		log.Infof("Invalid token length: %d", len(t))
		return false
	}
	data := t[0:TOKEN_RND_LEN]
	sig := t[TOKEN_RND_LEN : TOKEN_RND_LEN+TOKEN_SIG_LEN]
	mac := hmac.New(sha1.New, []byte(key))
	mac.Write(data)
	log.Debugf("HMAC: %v", mac.Sum(nil))
	log.Debugf("EXP : %v", sig)
	return hmac.Equal(mac.Sum(nil), sig)
}

func CardLogin(w http.ResponseWriter, r *http.Request) {
	vars, session, err := initSession(w, r)
	if err != nil {
		return
	}
	if isValidToken(vars["CardId"], *adminSecretKey) {
		log.Debugf("Valid Card Login: %v", vars["CardId"])
		session.Values["admin"] = "1"
		sessions.Save(r, w)
		err = templates["login-ok.html"].Execute(w, nil)
	} else {
		log.Infof("Bad Card Login: %v", vars["CardId"])
		session.Values["admin"] = "0"
		sessions.Save(r, w)
		err = templates["login-failed.html"].Execute(w, nil)
	}
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}

func Logout(w http.ResponseWriter, r *http.Request) {
	_, session, err := initSession(w, r)
	if err != nil {
		return
	}

	log.Debug("Logout")
	session.Values["admin"] = "0"
	sessions.Save(r, w)

	err = templates["logout.html"].Execute(w, nil)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}

func Index(w http.ResponseWriter, r *http.Request) {
	err := templates["index.html"].Execute(w, nil)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}

func Help(w http.ResponseWriter, r *http.Request) {
	err := templates["help.html"].Execute(w, nil)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}

func About(w http.ResponseWriter, r *http.Request) {
	err := templates["about.html"].Execute(w, nil)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}

func notFound(w http.ResponseWriter, r *http.Request) {
	err := templates["404.html"].Execute(w, nil)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}

func Debug(w http.ResponseWriter, r *http.Request) {
	_, session, err := initSession(w, r)
	if err != nil {
		return
	}

	log.Debug("Debug page")
	tmpl, err := template.ParseFiles(root + "/debug.html")
	if err != nil {
		log.Infof("Error 1 %s", err)
	}
	s := fmt.Sprintf("%v", session.Values)
	if err != nil {
		log.Infof("Error 2 %s", err)
	}
	err = tmpl.Execute(w, struct{ Session string }{string(s)})
}

func Start(w http.ResponseWriter, r *http.Request) {
	vars, session, err := initSession(w, r)
	if err != nil {
		return
	}

	if *startSecretKey == "" || isValidToken(vars["CardId"], *startSecretKey) {
		log.Debugf("Valid Start page: %v", vars["CardId"])
		session.Values["cardId"] = vars["CardId"]
		sessions.Save(r, w)

		if session.Values["admin"] == "1" {
			log.Debug("Sending Admin UI")
			http.ServeFile(w, r, root+"/admin.html")
		} else {
			log.Debug("Sending User UI")
			http.ServeFile(w, r, root+"/public.html")
		}
	} else {
		log.Infof("Bad Start page: %v", vars["CardId"])
		err = templates["bad-cards.html"].Execute(w, nil)
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
		}
	}
}

func main() {
	var port = flag.Int("port", 8080, "port")
	var debug = flag.Bool("debug", false, "run in debug mode")
	var domain = flag.String("domain", "thymio.tk", "Domain name (for the cookie)")
	var mongoServer = flag.String("mongo-server", "localhost", "MongoDB server URL")
	var cookieSecretKey = flag.String("cookie-secret-key", "not-so-secret", "Secret key (for secure cookies)")
	adminSecretKey = flag.String("admin-secret-key", "change-me", "Secret key (for admin card-login)")
	startSecretKey = flag.String("start-secret-key", "", "Secret key (for start ID)")

	flag.Parse()

	if *debug {
		log.SetLevel(log.DebugLevel)
		log.Debug("Debug mode")
	} else {
		log.SetLevel(log.InfoLevel)
	}

	if *startSecretKey == "" {
		log.Warn("Running without start id validation")
	} else {
		log.Info("Start id validation enabled")
	}

	var err error
	database, err = mgo.Dial(*mongoServer)
	if err != nil {
		log.Fatal(err)
	}
	store = mongostore.NewMongoStore(
		database.DB(dbName).C(sessionC),
		maxAge, true, []byte(*cookieSecretKey))

	store.Options.Domain = *domain

	tpls := template.Must(template.ParseGlob("internal_pages/templates/*"))
	nameList, err := filepath.Glob("internal_pages/*.html")
	if err != nil {
		panic(err)
	}
	for _, name := range nameList {
		log.Debugf("Reading %s", name)
		key := filepath.Base(name)
		t, _ := tpls.Clone()
		templates[key] = t
		_, err = templates[key].ParseFiles(name)
		if err != nil {
			panic(err)
		}
	}

	r := mux.NewRouter()
	r.HandleFunc("/start/{CardId}", Start)
	r.HandleFunc("/cardlogin/{CardId}", CardLogin)
	r.HandleFunc("/logout", Logout)
	r.HandleFunc("/debug", Debug)

	r.HandleFunc("/", Index)
	r.HandleFunc("/about", About)
	r.HandleFunc("/help", Help)

	r.NotFoundHandler = http.HandlerFunc(notFound)

	http.Handle("/img/", http.StripPrefix("/img/", http.FileServer(http.Dir("img"))))
	http.Handle("/css/", http.StripPrefix("/css/", http.FileServer(http.Dir("css"))))
	http.Handle("/vendor/", http.StripPrefix("/vendor/", http.FileServer(http.Dir("vendor"))))

	http.Handle("/", r)
	log.Infof("Ready, listening on port %d", *port)
	log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", *port), nil))
}
<commit_msg>replace ServeFile by ServeContent to control caching<commit_after>// Copyright 2016 Jacques Supcik <jacques.supcik@hefr.ch>
//
// 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.

//   _   _                     _                             _        _
//  | |_| |__  _   _ _ __ ___ (_) ___         ___ __ _ _ __ | |_ __ _(_)_ __
//  | __| '_ \| | | | '_ ` _ \| |/ _ \ _____ / __/ _` | '_ \| __/ _` | | '_ \
//  | |_| | | | |_| | | | | | | | (_) |_____| (_| (_| | |_) | || (_| | | | | |
//   \__|_| |_|\__, |_| |_| |_|_|\___/       \___\__,_| .__/ \__\__,_|_|_| |_|
//             |___/                                  |_|
//

// Frontend
package main

import (
	"crypto/hmac"
	"crypto/sha1"
	"encoding/base64"
	"flag"
	"fmt"
	log "github.com/Sirupsen/logrus"
	"github.com/gorilla/mux"
	"github.com/gorilla/sessions"
	"github.com/kidstuff/mongostore"
	"gopkg.in/mgo.v2"
	"html/template"
	"net/http"
	"os"
	"path/filepath"
)

const (
	dbName       = "thymio_captain"
	sessionC     = "sessions"
	maxAge       = 24 * 3600
	sessionKey   = "session-key"
	root         = "internal_pages"
	tokenRndLen  = 20
	tokenSignLen = 20
)

var (
	database       *mgo.Session
	store          *mongostore.MongoStore
	adminSecretKey *string
	startSecretKey *string
	templates      = make(map[string]*template.Template)
)

func initSession(w http.ResponseWriter, r *http.Request) (vars map[string]string, session *sessions.Session, err error) {
	database.Refresh()
	vars = mux.Vars(r)
	session, err = store.Get(r, sessionKey)
	log.Debugf("Session ID = %v", session.ID)
	if err != nil {
		log.Error(err.Error())
		http.Error(w, "Session Error", 500)
	} else {
		log.Debug("Session OK")
		w.Header().Set("Cache-Control", "max-age=0, no-cache, no-store")
		w.Header().Set("Pragma", "no-cache")
	}
	return
}

func isValidToken(token string, key string) bool {
	t, err := base64.RawURLEncoding.DecodeString(token)
	if err != nil {
		return false
	}

	if len(t) != tokenRndLen+tokenSignLen {
		log.Infof("Invalid token length: %d", len(t))
		return false
	}
	data := t[0:tokenRndLen]
	sig := t[tokenRndLen : tokenRndLen+tokenSignLen]
	mac := hmac.New(sha1.New, []byte(key))
	mac.Write(data)
	log.Debugf("HMAC: %v", mac.Sum(nil))
	log.Debugf("EXP : %v", sig)
	return hmac.Equal(mac.Sum(nil), sig)
}

func CardLogin(w http.ResponseWriter, r *http.Request) {
	vars, session, err := initSession(w, r)
	if err != nil {
		return
	}
	if isValidToken(vars["CardId"], *adminSecretKey) {
		log.Debugf("Valid Card Login: %v", vars["CardId"])
		session.Values["admin"] = "1"
		sessions.Save(r, w)
		err = templates["login-ok.html"].Execute(w, nil)
	} else {
		log.Infof("Bad Card Login: %v", vars["CardId"])
		session.Values["admin"] = "0"
		sessions.Save(r, w)
		err = templates["login-failed.html"].Execute(w, nil)
	}
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}

func Logout(w http.ResponseWriter, r *http.Request) {
	_, session, err := initSession(w, r)
	if err != nil {
		return
	}

	log.Debug("Logout")
	session.Values["admin"] = "0"
	sessions.Save(r, w)

	err = templates["logout.html"].Execute(w, nil)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}

func Index(w http.ResponseWriter, r *http.Request) {
	err := templates["index.html"].Execute(w, nil)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}

func Help(w http.ResponseWriter, r *http.Request) {
	err := templates["help.html"].Execute(w, nil)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}

func About(w http.ResponseWriter, r *http.Request) {
	err := templates["about.html"].Execute(w, nil)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}

func notFound(w http.ResponseWriter, r *http.Request) {
	err := templates["404.html"].Execute(w, nil)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}

func Debug(w http.ResponseWriter, r *http.Request) {
	_, session, err := initSession(w, r)
	if err != nil {
		return
	}

	log.Debug("Debug page")
	tmpl, err := template.ParseFiles(root + "/debug.html")
	if err != nil {
		log.Infof("Error 1 %s", err)
	}
	s := fmt.Sprintf("%v", session.Values)
	if err != nil {
		log.Infof("Error 2 %s", err)
	}
	err = tmpl.Execute(w, struct{ Session string }{string(s)})
}

func Start(w http.ResponseWriter, r *http.Request) {
	vars, session, err := initSession(w, r)
	if err != nil {
		return
	}

	if *startSecretKey == "" || isValidToken(vars["CardId"], *startSecretKey) {
		log.Debugf("Valid Start page: %v", vars["CardId"])
		session.Values["cardId"] = vars["CardId"]
		sessions.Save(r, w)

		var fileName string

		if session.Values["admin"] == "1" {
			log.Debug("Sending Admin UI")
			fileName = root + "/admin.html"
		} else {
			log.Debug("Sending User UI")
			fileName = root + "/public.html"
		}
		f, err := os.Open(fileName)
		if err == nil {
			http.ServeContent(w, r, fileName, 0, f)
		} else {
			log.Error(err.Error())
			http.Error(w, err.Error(), http.StatusInternalServerError)
		}
	} else {
		log.Infof("Bad Start page: %v", vars["CardId"])
		err = templates["bad-cards.html"].Execute(w, nil)
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
		}
	}
}

func main() {
	var port = flag.Int("port", 8080, "port")
	var debug = flag.Bool("debug", false, "run in debug mode")
	var domain = flag.String("domain", "thymio.tk", "Domain name (for the cookie)")
	var mongoServer = flag.String("mongo-server", "localhost", "MongoDB server URL")
	var cookieSecretKey = flag.String("cookie-secret-key", "not-so-secret", "Secret key (for secure cookies)")
	adminSecretKey = flag.String("admin-secret-key", "change-me", "Secret key (for admin card-login)")
	startSecretKey = flag.String("start-secret-key", "", "Secret key (for start ID)")

	flag.Parse()

	if *debug {
		log.SetLevel(log.DebugLevel)
		log.Debug("Debug mode")
	} else {
		log.SetLevel(log.InfoLevel)
	}

	if *startSecretKey == "" {
		log.Warn("Running without start id validation")
	} else {
		log.Info("Start id validation enabled")
	}

	var err error
	database, err = mgo.Dial(*mongoServer)
	if err != nil {
		log.Fatal(err)
	}
	store = mongostore.NewMongoStore(
		database.DB(dbName).C(sessionC),
		maxAge, true, []byte(*cookieSecretKey))

	store.Options.Domain = *domain

	tpls := template.Must(template.ParseGlob("internal_pages/templates/*"))
	nameList, err := filepath.Glob("internal_pages/*.html")
	if err != nil {
		panic(err)
	}
	for _, name := range nameList {
		log.Debugf("Reading %s", name)
		key := filepath.Base(name)
		t, _ := tpls.Clone()
		templates[key] = t
		_, err = templates[key].ParseFiles(name)
		if err != nil {
			panic(err)
		}
	}

	r := mux.NewRouter()
	r.HandleFunc("/start/{CardId}", Start)
	r.HandleFunc("/cardlogin/{CardId}", CardLogin)
	r.HandleFunc("/logout", Logout)
	r.HandleFunc("/debug", Debug)

	r.HandleFunc("/", Index)
	r.HandleFunc("/about", About)
	r.HandleFunc("/help", Help)

	r.NotFoundHandler = http.HandlerFunc(notFound)

	http.Handle("/img/", http.StripPrefix("/img/", http.FileServer(http.Dir("img"))))
	http.Handle("/css/", http.StripPrefix("/css/", http.FileServer(http.Dir("css"))))
	http.Handle("/vendor/", http.StripPrefix("/vendor/", http.FileServer(http.Dir("vendor"))))

	http.Handle("/", r)
	log.Infof("Ready, listening on port %d", *port)
	log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", *port), nil))
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"log"
	"testing"
)

func TestGetBoxMemory(t *testing.T) {
	const sysMemBytes = 2147483648
	conf, err := mockConfig()
	vacon := mockVagrantConnector(conf)
	mem, err := vacon.GetBoxMemory("windows")
	if err != nil || mem != sysMemBytes {
		t.Errorf("Fail: %s", err)
	}
}

func mockConfig() (*Configuration, error) {
	var c Configuration
	var configJson = []byte(`{
		  "jenkins_api_url":"http://localhost:8080",
		  "jenkins_api_secret":"",
		  "listener_port":"8888",
		  "max_vm_count":2,
		  "working_dir_path":"/tmp",
		  "boxes":
		  [{
			  "name": "win7-slave",
			  "labels": ["windows"],
			  "memory": "2048MB"
		  	}]
		}`)
	if err := json.Unmarshal(configJson, &c); err != nil {
		log.Fatalf("[TEST VAGRANTCONNECOR]: Error while parsing the test config json string. Reson: %s", err)
		return nil, err
	}
	return &c, nil
}

func mockVagrantConnector(conf *Configuration) *VagrantConnector {
	vagrantIndex := new(VagrantIndex)
	vagrantIndex.Version = 1
	vagrantIndex.Machines = make(map[string]Machine)
	var vagrantBoxes []Box
	vagrantBoxes = make([]Box, 1, 1)
	vagrantBoxes[0] = Box{123456, "Test-Box", "Test-Provider", 1.0}

	return &VagrantConnector{vagrantIndex, &vagrantBoxes, conf}
}
<commit_msg>Add test<commit_after>package main

import (
	"encoding/json"
	"log"
	"testing"
)

func TestGetBoxMemory(t *testing.T) {
	const sysMemBytes = 2147483648
	conf, err := mockConfig()
	vacon := mockVagrantConnector(conf)
	mem, err := vacon.GetBoxMemory("windows")
	if err != nil || mem != sysMemBytes {
		t.Errorf("Fail: %s", err)
	}
}

func TestGetBox(t *testing.T) {
	const boxLabel = "windows"
	conf, err := mockConfig()
	vacon := mockVagrantConnector(conf)
	box, err := vacon.getBox(boxLabel)
	if err != nil || box != "win7-slave" {
		t.Errorf("Fail: %s", err)
	}
}

func mockConfig() (*Configuration, error) {
	var c Configuration
	var configJson = []byte(`{
		  "jenkins_api_url":"http://localhost:8080",
		  "jenkins_api_secret":"",
		  "listener_port":"8888",
		  "max_vm_count":2,
		  "working_dir_path":"/tmp",
		  "boxes":
		  [{
			  "name": "win7-slave",
			  "labels": ["windows"],
			  "memory": "2048MB"
		  	}]
		}`)
	if err := json.Unmarshal(configJson, &c); err != nil {
		log.Fatalf("[TEST VAGRANTCONNECOR]: Error while parsing the test config json string. Reson: %s", err)
		return nil, err
	}
	return &c, nil
}

func mockVagrantConnector(conf *Configuration) *VagrantConnector {
	vagrantIndex := new(VagrantIndex)
	vagrantIndex.Version = 1
	vagrantIndex.Machines = make(map[string]Machine)
	var vagrantBoxes []Box
	vagrantBoxes = make([]Box, 1, 1)
	vagrantBoxes[0] = Box{123456, "Test-Box", "Test-Provider", 1.0}

	return &VagrantConnector{vagrantIndex, &vagrantBoxes, conf}
}
<|endoftext|>


<commit_before>//go:build library
// +build library

/*
Copyright 2018 The Knative 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 smoke

import (
	"fmt"
	"os"
	"syscall"
	"testing"
	"time"
)

// signal sends a UNIX signal to the test process.
func signal(s os.Signal) {
	p, _ := os.FindProcess(os.Getpid())
	_ = p.Signal(s)
	// Sleep so test won't finish and signal will be received.
	time.Sleep(999)
}

func TestSucceeds(t *testing.T) {
	// Always succeed.
}

func TestFails(t *testing.T) {
	t.Fail()
}

func TestFailsWithFatal(t *testing.T) {
	// Simulate a zap.Fatal() call.
	fmt.Println("fatal\tTestFailsWithFatal\tsimple_test.go:999\tFailed with logger.Fatal()")
	signal(os.Interrupt)
}

func TestFailsWithPanic(t *testing.T) {
	// Simulate a "panic" stack trace.
	fmt.Println("panic: test timed out after 5m0s")
	signal(os.Interrupt)
}

func TestFailsWithSigQuit(t *testing.T) {
	signal(syscall.SIGQUIT)
}
<commit_msg>Temporarily fix the unit tests for library.sh (#143)<commit_after>//go:build library
// +build library

/*
Copyright 2018 The Knative 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 smoke

import (
	"fmt"
	"os"
	"syscall"
	"testing"
	"time"
)

// signal sends a UNIX signal to the test process.
func signal(s os.Signal) {
	p, _ := os.FindProcess(os.Getpid())
	_ = p.Signal(s)
	// Sleep so test won't finish and signal will be received.
	time.Sleep(999)
}

func TestSucceeds(t *testing.T) {
	// Always succeed.
}

func TestFails(t *testing.T) {
	t.Fail()
}

func TestFailsWithFatal(t *testing.T) {
	// Simulate a zap.Fatal() call.
	fmt.Println("fatal\tTestFailsWithFatal\tsimple_test.go:999\tFailed with logger.Fatal()")
	signal(os.Kill)
}

func TestFailsWithPanic(t *testing.T) {
	// Simulate a "panic" stack trace.
	fmt.Println("panic: test timed out after 5m0s")
	signal(os.Kill)
}

func TestFailsWithSigQuit(t *testing.T) {
	signal(syscall.SIGQUIT)
}
<|endoftext|>


<commit_before>// Copyright 2016, RadiantBlue Technologies, 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 main

import (
	"encoding/base64"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
	"strconv"
	"time"

	"github.com/venicegeo/pzsvc-exec/pzse"
	"github.com/venicegeo/pzsvc-exec/pzsvc"
	"github.com/cloudfoundry-community/go-cfclient"
)

func main() {
	// Initialization Block
	s := pzsvc.Session{AppName: "Dispatcher", SessionID: "Startup", LogRootDir: "pzsvc-exec"}
	pzsvc.LogAudit(s, s.AppName, "startup", s.AppName, "", pzsvc.INFO)

	if len(os.Args) < 2 {
		pzsvc.LogSimpleErr(s, "error: Insufficient parameters.  You must specify a config file.", nil)
		os.Exit(1)
	}

	// First argument after the base call should be the path to the config file.
	// ReadFile returns the contents of the file as a byte buffer.
	configPath := os.Args[1]
	configBuf, err := ioutil.ReadFile(configPath)
	if err != nil {
		pzsvc.LogSimpleErr(s, "Dispatcher error in reading config: ", err)
		return
	}
	var configObj pzse.ConfigType
	err = json.Unmarshal(configBuf, &configObj)
	if err != nil {
		pzsvc.LogSimpleErr(s, "Dispatcher error in unmarshalling config: ", err)
		return
	}

	s.LogAudit = configObj.LogAudit
	if configObj.LogAudit {
		pzsvc.LogInfo(s, "Config: Audit logging enabled.")
	} else {
		pzsvc.LogInfo(s, "Config: Audit logging disabled.")
	}

	s.PzAddr = configObj.PzAddr
	if configObj.PzAddrEnVar != "" {
		newAddr := os.Getenv(configObj.PzAddrEnVar)
		if newAddr != "" {
			s.PzAddr = newAddr
		}
	}
	if s.PzAddr == "" {
		pzsvc.LogSimpleErr(s, "Config: Cannot work tasks.  Must have either a valid PzAddr, or a valid and populated PzAddrEnVar.", nil)
		return
	}

	if configObj.SvcName == "" {
		pzsvc.LogSimpleErr(s, "Config: Cannot work tasks without service name.", nil)
		return
	}

	if configObj.APIKeyEnVar == "" {
		pzsvc.LogSimpleErr(s, "Config: Cannot work tasks without valid APIKeyEnVar.", nil)
		return
	}
	apiKey := os.Getenv(configObj.APIKeyEnVar)
	if apiKey == "" {
		pzsvc.LogSimpleErr(s, "No API key at APIKeyEnVar.  Cannot work.", nil)
		return
	}
	s.PzAuth = "Basic " + base64.StdEncoding.EncodeToString([]byte(apiKey+":"))

	svcID := ""
	for i := 0; svcID == "" && i < 10; i++ {
		svcID, err = pzsvc.FindMySvc(s, configObj.SvcName)
		if err != nil {
			pzsvc.LogSimpleErr(s, "Dispatcher could not find Pz Service ID.  Initial Error: ", err)
			return
		}
		if svcID == "" && i < 9 {
			pzsvc.LogInfo(s, "Could not find service.  Will sleep and wait.")
			time.Sleep(15 * time.Second)
		}
	}
	if svcID == "" {
		pzsvc.LogSimpleErr(s, "Dispatcher could not find Pz Service ID.  Ensure the Service exists and is registered, and try again.", err)
		return
	}

	pzsvc.LogInfo(s, "Found target service.  ServiceID: "+ svcID + ".")

	// Initialize the CF Client
	clientConfig := &cfclient.Config{
		ApiAddress:   os.Getenv("CF_API"),
		Username:     os.Getenv("CF_USER"),
		Password:     os.Getenv("CF_PASS")
	}
	client, err := cfclient.NewClient(c)
	if err != nil {
		pzsvc.LogSimpleErr(s, "Error in Inflating Cloud Foundry API Client: ", err)
		return
	}

	pzsvc.LogInfo(s, "Cloud Foundry Client initialized. Beginning Polling.")

	pollForJobs(s, configObj, svcID, configPath, client)
}

// WorkBody exists as part of the response format of the Piazza job manager task request endpoint.
// specifically, it's one layer of the bit we care about.
type WorkBody struct {
	Content string `json:"content"`
}

// WorkDataInputs exists as part of the response format of the Piazza job manager task request endpoint.
// specifically, it's one layer of the bit we care about.
type WorkDataInputs struct {
	Body WorkBody `json:"body"`
}

// WorkInData exists as part of the response format of the Piazza job manager task request endpoint.
// specifically, it's one layer of the bit we care about.
type WorkInData struct {
	DataInputs WorkDataInputs `json:"dataInputs"`
}

// WorkSvcData exists as part of the response format of the Piazza job manager task request endpoint.
// specifically, it's one layer of the bit we care about.
type WorkSvcData struct {
	Data  WorkInData `json:"data"`
	JobID string     `json:"jobId"`
}

// WorkOutData exists as part of the response format of the Piazza job manager task request endpoint.
// specifically, it's one layer of the bit we care about.
type WorkOutData struct {
	SvcData WorkSvcData `json:"serviceData"`
}

func pollForJobs(s pzsvc.Session, configObj pzse.ConfigType, svcID string, configPath string, cfClient Client) {
	var (
		err       error
	)
	s.SessionID = "Polling"

	// Get the application name
	vcapJsonContainer := make(map[string]interface{})
	err := json.Unmarshal(os.Getenv("VCAP_APPLICATION"), &vcapJsonContainer)
	if pErr != nil {
		pzsvc.LogSimpleErr(s, "Cannot proceed: Error in reading VCAP Application properties: ", err)
		return
	}
	appName := // TODO

	for {
		var pzJobObj struct {
			Data WorkOutData `json:"data"`
		}
		pzJobObj.Data = WorkOutData{SvcData: WorkSvcData{JobID: "", Data: WorkInData{DataInputs: WorkDataInputs{Body: WorkBody{Content: ""}}}}}

		byts, pErr := pzsvc.RequestKnownJSON("POST", "", s.PzAddr+"/service/"+svcID+"/task", s.PzAuth, &pzJobObj)
		if pErr != nil {
			pErr.Log(s, "Dispatcher: error getting new task:")
			time.Sleep(time.Duration(5) * time.Second)
			continue
		}

		inpStr := pzJobObj.Data.SvcData.Data.DataInputs.Body.Content
		jobID := pzJobObj.Data.SvcData.JobID
		if inpStr != "" {
			pzsvc.LogInfo(s, "New Task Grabbed.  JobID: "+jobID)

			var outpByts []byte

			var respObj pzse.OutStruct
			var jobInputContent pzse.InpStruct
			var displayByt []byte
			err = json.Unmarshal([]byte(inpStr), &jobInputContent)
			if err == nil {
				if jobInputContent.ExtAuth != "" {
					jobInputContent.ExtAuth = "*****"
				}
				if jobInputContent.PzAuth != "" {
					jobInputContent.PzAuth = "*****"
				}
				displayByt, err = json.Marshal(jobInputContent)
				if err != nil {
					pzsvc.LogAudit(s, s.UserID, "Audit failure", s.AppName, "Could not Marshal.  Job Canceled.", pzsvc.ERROR)
					sendExecResult(s, s.PzAddr, s.PzAuth, svcID, jobID, "Fail", nil)
					time.Sleep(5 * time.Second)
					continue
				}
			}

			pzsvc.LogAudit(s, s.UserID, "Creating CF Task for Job" + jobID, s.AppName, string(displayByt), pzsvc.INFO)

			// Form the CLI for the Algorithm Task
			workerCommand := fmt.Sprintf("worker --cliCmd \"%s\" --userId \"%s\" --config \"%s\" --serviceId \"%s\"", jobInputContent.Command, jobInputContent.UserID, configPath, svcID)
			// For each input image, add that image ref as an argument to the CLI
			for i, imageFile := range jobInputContent.InExtFiles {
				workerCommand += fmt.Sprintf(" -i %s:%s", jobInputContent.InExtNames[i], jobInputContent.InExtFiles[i])
			}

			taskRequest := TaskRequest{
				Command: workerCommand
				Name: jobID
				MemoryInMegabyte: 0
				DiskInMegabyte: 0
				DropletGUID: jobID
			}

			// Send Run-Task request to CF
			task, error := cfClient.CreateTask(taskRequest)
			if err != nil {
				pzsvc.LogAudit(s, s.UserID, "Audit failure", s.AppName, "Could not Create PCF Task for Job. Job Failed.", pzsvc.ERROR)
				sendExecResult(s, s.PzAddr, s.PzAuth, svcID, jobID, "Fail", nil)
				time.Sleep(5 * time.Second)
				continue
			}

			pzsvc.LogAudit(s, s.UserID, "Task Created for CF Job" + , s.AppName, string(displayByt), pzsvc.INFO)

			time.Sleep(5 * time.Second)
		} else {
			pzsvc.LogInfo(s, "No Task.  Sleeping now.  input: "+string(byts))
			time.Sleep(5 * time.Second)
		}
	}

}

func sendExecResult(s pzsvc.Session, pzAddr, pzAuth, svcID, jobID, status string, resJSON []byte) {
	outAddr := pzAddr + `/service/` + svcID + `/task/` + jobID

	pzsvc.LogInfo(s, "Sending Exec Results.  Status: "+status+".")
	if resJSON != nil {
		dataID, err := pzsvc.Ingest(s, "Output", "text", "Dispatcher", "", resJSON, nil)
		if err == nil {
			outStr := `{ "status" : "` + status + `", "result" : { "type" : "data", "dataId" : "` + dataID + `" } }`
			pzsvc.SubmitSinglePart("POST", outStr, outAddr, s.PzAuth)
			return
		}
		pzsvc.LogInfo(s, "Send Exec Results: Ingest failed.")
		status = "Fail"
	}

	outStr := `{ "status" : "` + status + `" }`
	pzsvc.SubmitSinglePart("POST", outStr, outAddr, s.PzAuth)
}
<commit_msg>Fetching App Name<commit_after>// Copyright 2016, RadiantBlue Technologies, 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 main

import (
	"encoding/base64"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
	"strconv"
	"time"

	"github.com/venicegeo/pzsvc-exec/pzse"
	"github.com/venicegeo/pzsvc-exec/pzsvc"
	"github.com/cloudfoundry-community/go-cfclient"
)

func main() {
	// Initialization Block
	s := pzsvc.Session{AppName: "Dispatcher", SessionID: "Startup", LogRootDir: "pzsvc-exec"}
	pzsvc.LogAudit(s, s.AppName, "startup", s.AppName, "", pzsvc.INFO)

	if len(os.Args) < 2 {
		pzsvc.LogSimpleErr(s, "error: Insufficient parameters.  You must specify a config file.", nil)
		os.Exit(1)
	}

	// First argument after the base call should be the path to the config file.
	// ReadFile returns the contents of the file as a byte buffer.
	configPath := os.Args[1]
	configBuf, err := ioutil.ReadFile(configPath)
	if err != nil {
		pzsvc.LogSimpleErr(s, "Dispatcher error in reading config: ", err)
		return
	}
	var configObj pzse.ConfigType
	err = json.Unmarshal(configBuf, &configObj)
	if err != nil {
		pzsvc.LogSimpleErr(s, "Dispatcher error in unmarshalling config: ", err)
		return
	}

	s.LogAudit = configObj.LogAudit
	if configObj.LogAudit {
		pzsvc.LogInfo(s, "Config: Audit logging enabled.")
	} else {
		pzsvc.LogInfo(s, "Config: Audit logging disabled.")
	}

	s.PzAddr = configObj.PzAddr
	if configObj.PzAddrEnVar != "" {
		newAddr := os.Getenv(configObj.PzAddrEnVar)
		if newAddr != "" {
			s.PzAddr = newAddr
		}
	}
	if s.PzAddr == "" {
		pzsvc.LogSimpleErr(s, "Config: Cannot work tasks.  Must have either a valid PzAddr, or a valid and populated PzAddrEnVar.", nil)
		return
	}

	if configObj.SvcName == "" {
		pzsvc.LogSimpleErr(s, "Config: Cannot work tasks without service name.", nil)
		return
	}

	if configObj.APIKeyEnVar == "" {
		pzsvc.LogSimpleErr(s, "Config: Cannot work tasks without valid APIKeyEnVar.", nil)
		return
	}
	apiKey := os.Getenv(configObj.APIKeyEnVar)
	if apiKey == "" {
		pzsvc.LogSimpleErr(s, "No API key at APIKeyEnVar.  Cannot work.", nil)
		return
	}
	s.PzAuth = "Basic " + base64.StdEncoding.EncodeToString([]byte(apiKey+":"))

	svcID := ""
	for i := 0; svcID == "" && i < 10; i++ {
		svcID, err = pzsvc.FindMySvc(s, configObj.SvcName)
		if err != nil {
			pzsvc.LogSimpleErr(s, "Dispatcher could not find Pz Service ID.  Initial Error: ", err)
			return
		}
		if svcID == "" && i < 9 {
			pzsvc.LogInfo(s, "Could not find service.  Will sleep and wait.")
			time.Sleep(15 * time.Second)
		}
	}
	if svcID == "" {
		pzsvc.LogSimpleErr(s, "Dispatcher could not find Pz Service ID.  Ensure the Service exists and is registered, and try again.", err)
		return
	}

	pzsvc.LogInfo(s, "Found target service.  ServiceID: "+ svcID + ".")

	// Initialize the CF Client
	clientConfig := &cfclient.Config{
		ApiAddress:   os.Getenv("CF_API"),
		Username:     os.Getenv("CF_USER"),
		Password:     os.Getenv("CF_PASS")
	}
	client, err := cfclient.NewClient(c)
	if err != nil {
		pzsvc.LogSimpleErr(s, "Error in Inflating Cloud Foundry API Client: ", err)
		return
	}

	pzsvc.LogInfo(s, "Cloud Foundry Client initialized. Beginning Polling.")

	pollForJobs(s, configObj, svcID, configPath, client)
}

// WorkBody exists as part of the response format of the Piazza job manager task request endpoint.
// specifically, it's one layer of the bit we care about.
type WorkBody struct {
	Content string `json:"content"`
}

// WorkDataInputs exists as part of the response format of the Piazza job manager task request endpoint.
// specifically, it's one layer of the bit we care about.
type WorkDataInputs struct {
	Body WorkBody `json:"body"`
}

// WorkInData exists as part of the response format of the Piazza job manager task request endpoint.
// specifically, it's one layer of the bit we care about.
type WorkInData struct {
	DataInputs WorkDataInputs `json:"dataInputs"`
}

// WorkSvcData exists as part of the response format of the Piazza job manager task request endpoint.
// specifically, it's one layer of the bit we care about.
type WorkSvcData struct {
	Data  WorkInData `json:"data"`
	JobID string     `json:"jobId"`
}

// WorkOutData exists as part of the response format of the Piazza job manager task request endpoint.
// specifically, it's one layer of the bit we care about.
type WorkOutData struct {
	SvcData WorkSvcData `json:"serviceData"`
}

func pollForJobs(s pzsvc.Session, configObj pzse.ConfigType, svcID string, configPath string, cfClient Client) {
	var (
		err       error
	)
	s.SessionID = "Polling"

	// Get the application name
	vcapJsonContainer := make(map[string]interface{})
	err := json.Unmarshal(os.Getenv("VCAP_APPLICATION"), &vcapJsonContainer)
	if pErr != nil {
		pzsvc.LogSimpleErr(s, "Cannot proceed: Error in reading VCAP Application properties: ", err)
		return
	}
	appName, ok := vcapJsonContainer["application_name"].(string)

	for {
		var pzJobObj struct {
			Data WorkOutData `json:"data"`
		}
		pzJobObj.Data = WorkOutData{SvcData: WorkSvcData{JobID: "", Data: WorkInData{DataInputs: WorkDataInputs{Body: WorkBody{Content: ""}}}}}

		byts, pErr := pzsvc.RequestKnownJSON("POST", "", s.PzAddr+"/service/"+svcID+"/task", s.PzAuth, &pzJobObj)
		if pErr != nil {
			pErr.Log(s, "Dispatcher: error getting new task:")
			time.Sleep(time.Duration(5) * time.Second)
			continue
		}

		inpStr := pzJobObj.Data.SvcData.Data.DataInputs.Body.Content
		jobID := pzJobObj.Data.SvcData.JobID
		if inpStr != "" {
			pzsvc.LogInfo(s, "New Task Grabbed.  JobID: "+jobID)

			var outpByts []byte

			var respObj pzse.OutStruct
			var jobInputContent pzse.InpStruct
			var displayByt []byte
			err = json.Unmarshal([]byte(inpStr), &jobInputContent)
			if err == nil {
				if jobInputContent.ExtAuth != "" {
					jobInputContent.ExtAuth = "*****"
				}
				if jobInputContent.PzAuth != "" {
					jobInputContent.PzAuth = "*****"
				}
				displayByt, err = json.Marshal(jobInputContent)
				if err != nil {
					pzsvc.LogAudit(s, s.UserID, "Audit failure", s.AppName, "Could not Marshal.  Job Canceled.", pzsvc.ERROR)
					sendExecResult(s, s.PzAddr, s.PzAuth, svcID, jobID, "Fail", nil)
					time.Sleep(5 * time.Second)
					continue
				}
			}

			pzsvc.LogAudit(s, s.UserID, "Creating CF Task for Job" + jobID, s.AppName, string(displayByt), pzsvc.INFO)

			// Form the CLI for the Algorithm Task
			workerCommand := fmt.Sprintf("worker --cliCmd \"%s\" --userId \"%s\" --config \"%s\" --serviceId \"%s\"", jobInputContent.Command, jobInputContent.UserID, configPath, svcID)
			// For each input image, add that image ref as an argument to the CLI
			for i, imageFile := range jobInputContent.InExtFiles {
				workerCommand += fmt.Sprintf(" -i %s:%s", jobInputContent.InExtNames[i], jobInputContent.InExtFiles[i])
			}

			taskRequest := TaskRequest{
				Command: workerCommand
				Name: jobID
				MemoryInMegabyte: 0
				DiskInMegabyte: 0
				DropletGUID: jobID
			}

			// Send Run-Task request to CF
			task, error := cfClient.CreateTask(taskRequest)
			if err != nil {
				pzsvc.LogAudit(s, s.UserID, "Audit failure", s.AppName, "Could not Create PCF Task for Job. Job Failed.", pzsvc.ERROR)
				sendExecResult(s, s.PzAddr, s.PzAuth, svcID, jobID, "Fail", nil)
				time.Sleep(5 * time.Second)
				continue
			}

			pzsvc.LogAudit(s, s.UserID, "Task Created for CF Job" + , s.AppName, string(displayByt), pzsvc.INFO)

			time.Sleep(5 * time.Second)
		} else {
			pzsvc.LogInfo(s, "No Task.  Sleeping now.  input: "+string(byts))
			time.Sleep(5 * time.Second)
		}
	}

}

func sendExecResult(s pzsvc.Session, pzAddr, pzAuth, svcID, jobID, status string, resJSON []byte) {
	outAddr := pzAddr + `/service/` + svcID + `/task/` + jobID

	pzsvc.LogInfo(s, "Sending Exec Results.  Status: "+status+".")
	if resJSON != nil {
		dataID, err := pzsvc.Ingest(s, "Output", "text", "Dispatcher", "", resJSON, nil)
		if err == nil {
			outStr := `{ "status" : "` + status + `", "result" : { "type" : "data", "dataId" : "` + dataID + `" } }`
			pzsvc.SubmitSinglePart("POST", outStr, outAddr, s.PzAuth)
			return
		}
		pzsvc.LogInfo(s, "Send Exec Results: Ingest failed.")
		status = "Fail"
	}

	outStr := `{ "status" : "` + status + `" }`
	pzsvc.SubmitSinglePart("POST", outStr, outAddr, s.PzAuth)
}
<|endoftext|>


<commit_before>package connector

import (
	"context"
	"net"
	"os"
	"time"

	"github.com/datawire/ambassador/pkg/metriton"
	"github.com/datawire/dlib/dgroup"
	"github.com/datawire/dlib/dlog"
	"github.com/golang/protobuf/ptypes/empty"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
	"github.com/spf13/cobra"
	"golang.org/x/crypto/ssh/terminal"
	"google.golang.org/grpc"

	"github.com/datawire/telepresence2/pkg/client"
	manager "github.com/datawire/telepresence2/pkg/rpc"
	rpc "github.com/datawire/telepresence2/pkg/rpc/connector"
	"github.com/datawire/telepresence2/pkg/rpc/daemon"
	"github.com/datawire/telepresence2/pkg/rpc/version"
)

var help = `The Telepresence Connect is a background component that manages a connection. It
requires that a daemon is already running.

Launch the Telepresence Connector:
    telepresence connect

The Connector uses the Daemon's log so its output can be found in
    ` + client.Logfile + `
to troubleshoot problems.
`

// service represents the state of the Telepresence Connector
type service struct {
	rpc.UnimplementedConnectorServer
	daemon       daemon.DaemonClient
	daemonLogger daemonLogger
	cluster      *k8sCluster
	bridge       *bridge
	trafficMgr   *trafficManager
	grpc         *grpc.Server
	callCtx      context.Context
	cancel       func()
}

// Command returns the CLI sub-command for "connector-foreground"
func Command() *cobra.Command {
	var init bool
	c := &cobra.Command{
		Use:    "connector-foreground",
		Short:  "Launch Telepresence Connector in the foreground (debug)",
		Args:   cobra.ExactArgs(0),
		Hidden: true,
		Long:   help,
		RunE: func(_ *cobra.Command, args []string) error {
			return run(init)
		},
	}
	flags := c.Flags()
	flags.BoolVar(&init, "init", false, "initialize running connector (for debugging)")
	return c
}

func (s *service) callContext(_ context.Context) context.Context {
	return s.callCtx
}

func (s *service) Version(_ context.Context, _ *empty.Empty) (*version.VersionInfo, error) {
	return &version.VersionInfo{
		ApiVersion: client.APIVersion,
		Version:    client.Version(),
	}, nil
}

func (s *service) Status(c context.Context, _ *empty.Empty) (*rpc.ConnectorStatus, error) {
	return s.status(s.callContext(c)), nil
}

func (s *service) Connect(c context.Context, cr *rpc.ConnectRequest) (*rpc.ConnectInfo, error) {
	return s.connect(s.callContext(c), cr), nil
}

func (s *service) CreateIntercept(c context.Context, ir *manager.CreateInterceptRequest) (*rpc.InterceptResult, error) {
	ie, is := s.interceptStatus()
	if ie != rpc.InterceptError_UNSPECIFIED {
		return &rpc.InterceptResult{Error: ie, ErrorText: is}, nil
	}
	return s.trafficMgr.addIntercept(s.callContext(c), ir)
}

func (s *service) RemoveIntercept(c context.Context, rr *manager.RemoveInterceptRequest2) (*rpc.InterceptResult, error) {
	ie, is := s.interceptStatus()
	if ie != rpc.InterceptError_UNSPECIFIED {
		return &rpc.InterceptResult{Error: ie, ErrorText: is}, nil
	}
	_, err := s.trafficMgr.removeIntercept(s.callContext(c), rr.Name)
	if err != nil {
		return nil, err
	}
	return &rpc.InterceptResult{}, nil
}

func (s *service) AvailableIntercepts(_ context.Context, _ *empty.Empty) (*manager.AgentInfoSnapshot, error) {
	if !s.trafficMgr.IsOkay() {
		return &manager.AgentInfoSnapshot{}, nil
	}
	return s.trafficMgr.agentInfoSnapshot(), nil
}

func (s *service) ListIntercepts(_ context.Context, _ *empty.Empty) (*manager.InterceptInfoSnapshot, error) {
	if !s.trafficMgr.IsOkay() {
		return &manager.InterceptInfoSnapshot{}, nil
	}
	return s.trafficMgr.interceptInfoSnapshot(), nil
}

func (s *service) Quit(_ context.Context, _ *empty.Empty) (*empty.Empty, error) {
	s.cancel()
	return &empty.Empty{}, nil
}

// daemonLogger is an io.Writer implementation that sends data to the daemon logger
type daemonLogger struct {
	stream daemon.Daemon_LoggerClient
}

func (d *daemonLogger) Write(data []byte) (n int, err error) {
	err = d.stream.Send(&daemon.LogMessage{Text: data})
	return len(data), err
}

// connect the connector to a cluster
func (s *service) connect(c context.Context, cr *rpc.ConnectRequest) *rpc.ConnectInfo {
	reporter := &metriton.Reporter{
		Application:  "telepresence2",
		Version:      client.Version(),
		GetInstallID: func(_ *metriton.Reporter) (string, error) { return cr.InstallId, nil },
		BaseMetadata: map[string]interface{}{"mode": "daemon"},
	}

	if _, err := reporter.Report(c, map[string]interface{}{"action": "connect"}); err != nil {
		dlog.Errorf(c, "report failed: %+v", err)
	}

	// Sanity checks
	r := &rpc.ConnectInfo{}
	if s.cluster != nil {
		r.Error = rpc.ConnectInfo_ALREADY_CONNECTED
		return r
	}
	if s.bridge != nil {
		r.Error = rpc.ConnectInfo_DISCONNECTING
		return r
	}

	dlog.Info(c, "Connecting to traffic manager...")
	cluster, err := trackKCluster(c, cr.Context, cr.Namespace, cr.Args)
	if err != nil {
		dlog.Errorf(c, "unable to track k8s cluster: %+v", err)
		r.Error = rpc.ConnectInfo_CLUSTER_FAILED
		r.ErrorText = err.Error()
		s.cancel()
		return r
	}
	s.cluster = cluster

	/*
		previewHost, err := cluster.getClusterPreviewHostname(p)
		if err != nil {
			p.Logf("get preview URL hostname: %+v", err)
			previewHost = ""
		}
	*/

	dlog.Infof(c, "Connected to context %s (%s)", s.cluster.Context, s.cluster.server())

	r.ClusterContext = s.cluster.Context
	r.ClusterServer = s.cluster.server()

	tmgr, err := newTrafficManager(c, s.cluster, cr.InstallId, cr.IsCi)
	if err != nil {
		dlog.Errorf(c, "Unable to connect to TrafficManager: %s", err)
		r.Error = rpc.ConnectInfo_TRAFFIC_MANAGER_FAILED
		r.ErrorText = err.Error()
		if cr.InterceptEnabled {
			// No point in continuing without a traffic manager
			s.cancel()
		}
		return r
	}
	// tmgr.previewHost = previewHost
	s.trafficMgr = tmgr
	dlog.Infof(c, "Starting traffic-manager bridge in context %s, namespace %s", cluster.Context, cluster.Namespace)
	br := newBridge(cluster, s.daemon, tmgr.sshPort)
	err = br.start(c)
	if err != nil {
		dlog.Errorf(c, "Failed to start traffic-manager bridge: %s", err.Error())
		r.Error = rpc.ConnectInfo_BRIDGE_FAILED
		r.ErrorText = err.Error()
		// No point in continuing without a bridge
		s.cancel()
		return r
	}
	s.bridge = br
	s.cluster.setBridgeCheck(br.check)

	if !cr.InterceptEnabled {
		return r
	}

	// Wait for traffic manager to connect
	maxAttempts := 30 * 4 // 30 seconds max wait
	attempts := 0
	dlog.Info(c, "Waiting for TrafficManager to connect")
	for ; !tmgr.IsOkay() && attempts < maxAttempts; attempts++ {
		if s.trafficMgr.apiErr != nil {
			r.Error = rpc.ConnectInfo_TRAFFIC_MANAGER_FAILED
			r.ErrorText = s.trafficMgr.apiErr.Error()
			// No point in continuing without a traffic manager
			s.cancel()
			break
		}
		time.Sleep(time.Second / 4)
	}
	if attempts == maxAttempts {
		r.Error = rpc.ConnectInfo_TRAFFIC_MANAGER_FAILED
		r.ErrorText = "Timeout waiting for traffic manager"
		dlog.Error(c, r.ErrorText)
		s.cancel()
	}
	return r
}

// setUpLogging connects to the daemon logger
func (s *service) setUpLogging(c context.Context) (context.Context, error) {
	var err error
	s.daemonLogger.stream, err = s.daemon.Logger(c)
	if err != nil {
		return nil, err
	}

	logger := logrus.StandardLogger()
	logger.Out = &s.daemonLogger
	loggingToTerminal := terminal.IsTerminal(int(os.Stdout.Fd()))
	if loggingToTerminal {
		logger.Formatter = client.NewFormatter("15:04:05")
	} else {
		logger.Formatter = client.NewFormatter("2006/01/02 15:04:05")
	}
	logger.Level = logrus.DebugLevel
	return dlog.WithLogger(c, dlog.WrapLogrus(logger)), nil
}

// run is the main function when executing as the connector
func run(init bool) (err error) {
	var listener net.Listener
	defer func() {
		if listener != nil {
			_ = listener.Close()
		}
		_ = os.Remove(client.ConnectorSocketName)
	}()

	// Listen on unix domain socket
	listener, err = net.Listen("unix", client.ConnectorSocketName)
	if err != nil {
		return errors.Wrap(err, "listen")
	}

	g := dgroup.NewGroup(context.Background(), dgroup.GroupConfig{
		SoftShutdownTimeout:  2 * time.Second,
		EnableSignalHandling: true})

	g.Go("connector", func(c context.Context) error {
		// establish a connection to the daemon gRPC service
		conn, err := client.DialSocket(client.DaemonSocketName)
		if err != nil {
			return err
		}
		defer conn.Close()
		s := &service{daemon: daemon.NewDaemonClient(conn), grpc: grpc.NewServer()}
		rpc.RegisterConnectorServer(s.grpc, s)

		c, err = s.setUpLogging(c)
		if err != nil {
			return err
		}

		dlog.Info(c, "---")
		dlog.Infof(c, "Telepresence Connector %s starting...", client.DisplayVersion())
		dlog.Infof(c, "PID is %d", os.Getpid())
		dlog.Info(c, "")

		c, s.cancel = context.WithCancel(c)
		s.callCtx = c
		sg := dgroup.NewGroup(c, dgroup.GroupConfig{})
		sg.Go("teardown", s.handleShutdown)
		if init {
			sg.Go("debug-init", func(c context.Context) error {
				_, err = s.Connect(c, &rpc.ConnectRequest{InstallId: "dummy-id"})
				return err
			})
		}

		err = s.grpc.Serve(listener)
		listener = nil
		if err != nil {
			dlog.Error(c, err.Error())
		}
		return err
	})
	return g.Wait()
}

// handleShutdown ensures that the connector quits gracefully when receiving a signal
// or when the context is cancelled.
func (s *service) handleShutdown(c context.Context) error {
	defer s.grpc.GracefulStop()

	<-c.Done()
	dlog.Info(c, "Shutting down")

	cluster := s.cluster
	if cluster == nil {
		return nil
	}
	s.cluster = nil
	trafficMgr := s.trafficMgr

	s.trafficMgr = nil

	defer cluster.Close()

	if trafficMgr != nil {
		_ = trafficMgr.clearIntercepts(context.Background())
		_ = trafficMgr.Close()
	}
	s.bridge = nil
	return nil
}
<commit_msg>Prolong the time to wait for the traffic-manager to connect.<commit_after>package connector

import (
	"context"
	"net"
	"os"
	"time"

	"github.com/datawire/ambassador/pkg/metriton"
	"github.com/datawire/dlib/dgroup"
	"github.com/datawire/dlib/dlog"
	"github.com/golang/protobuf/ptypes/empty"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
	"github.com/spf13/cobra"
	"golang.org/x/crypto/ssh/terminal"
	"google.golang.org/grpc"

	"github.com/datawire/telepresence2/pkg/client"
	manager "github.com/datawire/telepresence2/pkg/rpc"
	rpc "github.com/datawire/telepresence2/pkg/rpc/connector"
	"github.com/datawire/telepresence2/pkg/rpc/daemon"
	"github.com/datawire/telepresence2/pkg/rpc/version"
)

var help = `The Telepresence Connect is a background component that manages a connection. It
requires that a daemon is already running.

Launch the Telepresence Connector:
    telepresence connect

The Connector uses the Daemon's log so its output can be found in
    ` + client.Logfile + `
to troubleshoot problems.
`

// service represents the state of the Telepresence Connector
type service struct {
	rpc.UnimplementedConnectorServer
	daemon       daemon.DaemonClient
	daemonLogger daemonLogger
	cluster      *k8sCluster
	bridge       *bridge
	trafficMgr   *trafficManager
	grpc         *grpc.Server
	callCtx      context.Context
	cancel       func()
}

// Command returns the CLI sub-command for "connector-foreground"
func Command() *cobra.Command {
	var init bool
	c := &cobra.Command{
		Use:    "connector-foreground",
		Short:  "Launch Telepresence Connector in the foreground (debug)",
		Args:   cobra.ExactArgs(0),
		Hidden: true,
		Long:   help,
		RunE: func(_ *cobra.Command, args []string) error {
			return run(init)
		},
	}
	flags := c.Flags()
	flags.BoolVar(&init, "init", false, "initialize running connector (for debugging)")
	return c
}

func (s *service) callContext(_ context.Context) context.Context {
	return s.callCtx
}

func (s *service) Version(_ context.Context, _ *empty.Empty) (*version.VersionInfo, error) {
	return &version.VersionInfo{
		ApiVersion: client.APIVersion,
		Version:    client.Version(),
	}, nil
}

func (s *service) Status(c context.Context, _ *empty.Empty) (*rpc.ConnectorStatus, error) {
	return s.status(s.callContext(c)), nil
}

func (s *service) Connect(c context.Context, cr *rpc.ConnectRequest) (*rpc.ConnectInfo, error) {
	return s.connect(s.callContext(c), cr), nil
}

func (s *service) CreateIntercept(c context.Context, ir *manager.CreateInterceptRequest) (*rpc.InterceptResult, error) {
	ie, is := s.interceptStatus()
	if ie != rpc.InterceptError_UNSPECIFIED {
		return &rpc.InterceptResult{Error: ie, ErrorText: is}, nil
	}
	return s.trafficMgr.addIntercept(s.callContext(c), ir)
}

func (s *service) RemoveIntercept(c context.Context, rr *manager.RemoveInterceptRequest2) (*rpc.InterceptResult, error) {
	ie, is := s.interceptStatus()
	if ie != rpc.InterceptError_UNSPECIFIED {
		return &rpc.InterceptResult{Error: ie, ErrorText: is}, nil
	}
	_, err := s.trafficMgr.removeIntercept(s.callContext(c), rr.Name)
	if err != nil {
		return nil, err
	}
	return &rpc.InterceptResult{}, nil
}

func (s *service) AvailableIntercepts(_ context.Context, _ *empty.Empty) (*manager.AgentInfoSnapshot, error) {
	if !s.trafficMgr.IsOkay() {
		return &manager.AgentInfoSnapshot{}, nil
	}
	return s.trafficMgr.agentInfoSnapshot(), nil
}

func (s *service) ListIntercepts(_ context.Context, _ *empty.Empty) (*manager.InterceptInfoSnapshot, error) {
	if !s.trafficMgr.IsOkay() {
		return &manager.InterceptInfoSnapshot{}, nil
	}
	return s.trafficMgr.interceptInfoSnapshot(), nil
}

func (s *service) Quit(_ context.Context, _ *empty.Empty) (*empty.Empty, error) {
	s.cancel()
	return &empty.Empty{}, nil
}

// daemonLogger is an io.Writer implementation that sends data to the daemon logger
type daemonLogger struct {
	stream daemon.Daemon_LoggerClient
}

func (d *daemonLogger) Write(data []byte) (n int, err error) {
	err = d.stream.Send(&daemon.LogMessage{Text: data})
	return len(data), err
}

// connect the connector to a cluster
func (s *service) connect(c context.Context, cr *rpc.ConnectRequest) *rpc.ConnectInfo {
	reporter := &metriton.Reporter{
		Application:  "telepresence2",
		Version:      client.Version(),
		GetInstallID: func(_ *metriton.Reporter) (string, error) { return cr.InstallId, nil },
		BaseMetadata: map[string]interface{}{"mode": "daemon"},
	}

	if _, err := reporter.Report(c, map[string]interface{}{"action": "connect"}); err != nil {
		dlog.Errorf(c, "report failed: %+v", err)
	}

	// Sanity checks
	r := &rpc.ConnectInfo{}
	if s.cluster != nil {
		r.Error = rpc.ConnectInfo_ALREADY_CONNECTED
		return r
	}
	if s.bridge != nil {
		r.Error = rpc.ConnectInfo_DISCONNECTING
		return r
	}

	dlog.Info(c, "Connecting to traffic manager...")
	cluster, err := trackKCluster(c, cr.Context, cr.Namespace, cr.Args)
	if err != nil {
		dlog.Errorf(c, "unable to track k8s cluster: %+v", err)
		r.Error = rpc.ConnectInfo_CLUSTER_FAILED
		r.ErrorText = err.Error()
		s.cancel()
		return r
	}
	s.cluster = cluster

	/*
		previewHost, err := cluster.getClusterPreviewHostname(p)
		if err != nil {
			p.Logf("get preview URL hostname: %+v", err)
			previewHost = ""
		}
	*/

	dlog.Infof(c, "Connected to context %s (%s)", s.cluster.Context, s.cluster.server())

	r.ClusterContext = s.cluster.Context
	r.ClusterServer = s.cluster.server()

	tmgr, err := newTrafficManager(c, s.cluster, cr.InstallId, cr.IsCi)
	if err != nil {
		dlog.Errorf(c, "Unable to connect to TrafficManager: %s", err)
		r.Error = rpc.ConnectInfo_TRAFFIC_MANAGER_FAILED
		r.ErrorText = err.Error()
		if cr.InterceptEnabled {
			// No point in continuing without a traffic manager
			s.cancel()
		}
		return r
	}
	// tmgr.previewHost = previewHost
	s.trafficMgr = tmgr
	dlog.Infof(c, "Starting traffic-manager bridge in context %s, namespace %s", cluster.Context, cluster.Namespace)
	br := newBridge(cluster, s.daemon, tmgr.sshPort)
	err = br.start(c)
	if err != nil {
		dlog.Errorf(c, "Failed to start traffic-manager bridge: %s", err.Error())
		r.Error = rpc.ConnectInfo_BRIDGE_FAILED
		r.ErrorText = err.Error()
		// No point in continuing without a bridge
		s.cancel()
		return r
	}
	s.bridge = br
	s.cluster.setBridgeCheck(br.check)

	if !cr.InterceptEnabled {
		return r
	}

	// Wait for traffic manager to connect
	maxAttempts := 60 * 4 // One minute max wait
	attempts := 0
	dlog.Info(c, "Waiting for TrafficManager to connect")
	for ; !tmgr.IsOkay() && attempts < maxAttempts; attempts++ {
		if s.trafficMgr.apiErr != nil {
			r.Error = rpc.ConnectInfo_TRAFFIC_MANAGER_FAILED
			r.ErrorText = s.trafficMgr.apiErr.Error()
			// No point in continuing without a traffic manager
			s.cancel()
			break
		}
		time.Sleep(time.Second / 4)
	}
	if attempts == maxAttempts {
		r.Error = rpc.ConnectInfo_TRAFFIC_MANAGER_FAILED
		r.ErrorText = "timeout waiting for traffic manager"
		dlog.Error(c, r.ErrorText)
		s.cancel()
	}
	return r
}

// setUpLogging connects to the daemon logger
func (s *service) setUpLogging(c context.Context) (context.Context, error) {
	var err error
	s.daemonLogger.stream, err = s.daemon.Logger(c)
	if err != nil {
		return nil, err
	}

	logger := logrus.StandardLogger()
	logger.Out = &s.daemonLogger
	loggingToTerminal := terminal.IsTerminal(int(os.Stdout.Fd()))
	if loggingToTerminal {
		logger.Formatter = client.NewFormatter("15:04:05")
	} else {
		logger.Formatter = client.NewFormatter("2006/01/02 15:04:05")
	}
	logger.Level = logrus.DebugLevel
	return dlog.WithLogger(c, dlog.WrapLogrus(logger)), nil
}

// run is the main function when executing as the connector
func run(init bool) (err error) {
	var listener net.Listener
	defer func() {
		if listener != nil {
			_ = listener.Close()
		}
		_ = os.Remove(client.ConnectorSocketName)
	}()

	// Listen on unix domain socket
	listener, err = net.Listen("unix", client.ConnectorSocketName)
	if err != nil {
		return errors.Wrap(err, "listen")
	}

	g := dgroup.NewGroup(context.Background(), dgroup.GroupConfig{
		SoftShutdownTimeout:  2 * time.Second,
		EnableSignalHandling: true})

	g.Go("connector", func(c context.Context) error {
		// establish a connection to the daemon gRPC service
		conn, err := client.DialSocket(client.DaemonSocketName)
		if err != nil {
			return err
		}
		defer conn.Close()
		s := &service{daemon: daemon.NewDaemonClient(conn), grpc: grpc.NewServer()}
		rpc.RegisterConnectorServer(s.grpc, s)

		c, err = s.setUpLogging(c)
		if err != nil {
			return err
		}

		dlog.Info(c, "---")
		dlog.Infof(c, "Telepresence Connector %s starting...", client.DisplayVersion())
		dlog.Infof(c, "PID is %d", os.Getpid())
		dlog.Info(c, "")

		c, s.cancel = context.WithCancel(c)
		s.callCtx = c
		sg := dgroup.NewGroup(c, dgroup.GroupConfig{})
		sg.Go("teardown", s.handleShutdown)
		if init {
			sg.Go("debug-init", func(c context.Context) error {
				_, err = s.Connect(c, &rpc.ConnectRequest{InstallId: "dummy-id"})
				return err
			})
		}

		err = s.grpc.Serve(listener)
		listener = nil
		if err != nil {
			dlog.Error(c, err.Error())
		}
		return err
	})
	return g.Wait()
}

// handleShutdown ensures that the connector quits gracefully when receiving a signal
// or when the context is cancelled.
func (s *service) handleShutdown(c context.Context) error {
	defer s.grpc.GracefulStop()

	<-c.Done()
	dlog.Info(c, "Shutting down")

	cluster := s.cluster
	if cluster == nil {
		return nil
	}
	s.cluster = nil
	trafficMgr := s.trafficMgr

	s.trafficMgr = nil

	defer cluster.Close()

	if trafficMgr != nil {
		_ = trafficMgr.clearIntercepts(context.Background())
		_ = trafficMgr.Close()
	}
	s.bridge = nil
	return nil
}
<|endoftext|>


<commit_before>/*
   Copyright The containerd 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 server

import (
	"context"
	"fmt"
	"os"
	"path"
	"path/filepath"
	"regexp"
	"sort"
	"strings"
	"syscall"
	"time"

	"github.com/containerd/containerd/log"
	"github.com/containerd/containerd/mount"
	"github.com/containerd/containerd/pkg/apparmor"
	"github.com/containerd/containerd/pkg/seccomp"
	"github.com/containerd/containerd/pkg/seutil"
	"github.com/moby/sys/mountinfo"
	"github.com/opencontainers/runtime-spec/specs-go"
	"github.com/opencontainers/selinux/go-selinux/label"
	"github.com/pkg/errors"
	"golang.org/x/sys/unix"
	runtime "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
)

const (
	// defaultSandboxOOMAdj is default omm adj for sandbox container. (kubernetes#47938).
	defaultSandboxOOMAdj = -998
	// defaultShmSize is the default size of the sandbox shm.
	defaultShmSize = int64(1024 * 1024 * 64)
	// relativeRootfsPath is the rootfs path relative to bundle path.
	relativeRootfsPath = "rootfs"
	// According to http://man7.org/linux/man-pages/man5/resolv.conf.5.html:
	// "The search list is currently limited to six domains with a total of 256 characters."
	maxDNSSearches = 6
	// devShm is the default path of /dev/shm.
	devShm = "/dev/shm"
	// etcHosts is the default path of /etc/hosts file.
	etcHosts = "/etc/hosts"
	// etcHostname is the default path of /etc/hostname file.
	etcHostname = "/etc/hostname"
	// resolvConfPath is the abs path of resolv.conf on host or container.
	resolvConfPath = "/etc/resolv.conf"
	// hostnameEnv is the key for HOSTNAME env.
	hostnameEnv = "HOSTNAME"
)

// getCgroupsPath generates container cgroups path.
func getCgroupsPath(cgroupsParent, id string) string {
	base := path.Base(cgroupsParent)
	if strings.HasSuffix(base, ".slice") {
		// For a.slice/b.slice/c.slice, base is c.slice.
		// runc systemd cgroup path format is "slice:prefix:name".
		return strings.Join([]string{base, "cri-containerd", id}, ":")
	}
	return filepath.Join(cgroupsParent, id)
}

// getSandboxHostname returns the hostname file path inside the sandbox root directory.
func (c *criService) getSandboxHostname(id string) string {
	return filepath.Join(c.getSandboxRootDir(id), "hostname")
}

// getSandboxHosts returns the hosts file path inside the sandbox root directory.
func (c *criService) getSandboxHosts(id string) string {
	return filepath.Join(c.getSandboxRootDir(id), "hosts")
}

// getResolvPath returns resolv.conf filepath for specified sandbox.
func (c *criService) getResolvPath(id string) string {
	return filepath.Join(c.getSandboxRootDir(id), "resolv.conf")
}

// getSandboxDevShm returns the shm file path inside the sandbox root directory.
func (c *criService) getSandboxDevShm(id string) string {
	return filepath.Join(c.getVolatileSandboxRootDir(id), "shm")
}

func toLabel(selinuxOptions *runtime.SELinuxOption) ([]string, error) {
	var labels []string

	if selinuxOptions == nil {
		return nil, nil
	}
	if err := checkSelinuxLevel(selinuxOptions.Level); err != nil {
		return nil, err
	}
	if selinuxOptions.User != "" {
		labels = append(labels, "user:"+selinuxOptions.User)
	}
	if selinuxOptions.Role != "" {
		labels = append(labels, "role:"+selinuxOptions.Role)
	}
	if selinuxOptions.Type != "" {
		labels = append(labels, "type:"+selinuxOptions.Type)
	}
	if selinuxOptions.Level != "" {
		labels = append(labels, "level:"+selinuxOptions.Level)
	}

	return labels, nil
}

func initLabelsFromOpt(selinuxOpts *runtime.SELinuxOption) (string, string, error) {
	labels, err := toLabel(selinuxOpts)
	if err != nil {
		return "", "", err
	}
	return label.InitLabels(labels)
}

func checkSelinuxLevel(level string) error {
	if len(level) == 0 {
		return nil
	}

	matched, err := regexp.MatchString(`^s\d(-s\d)??(:c\d{1,4}(\.c\d{1,4})?(,c\d{1,4}(\.c\d{1,4})?)*)?$`, level)
	if err != nil {
		return errors.Wrapf(err, "the format of 'level' %q is not correct", level)
	}
	if !matched {
		return fmt.Errorf("the format of 'level' %q is not correct", level)
	}
	return nil
}

// apparmorEnabled returns true if apparmor is enabled, supported by the host,
// if apparmor_parser is installed, and if we are not running docker-in-docker.
func (c *criService) apparmorEnabled() bool {
	if c.config.DisableApparmor {
		return false
	}
	return apparmor.HostSupports()
}

func (c *criService) seccompEnabled() bool {
	return seccomp.IsEnabled()
}

// openLogFile opens/creates a container log file.
func openLogFile(path string) (*os.File, error) {
	if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
		return nil, err
	}
	return os.OpenFile(path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0640)
}

// unmountRecursive unmounts the target and all mounts underneath, starting with
// the deepest mount first.
func unmountRecursive(ctx context.Context, target string) error {
	mounts, err := mountinfo.GetMounts(nil)
	if err != nil {
		return err
	}

	var toUnmount []string
	for _, m := range mounts {
		p, err := filepath.Rel(target, m.Mountpoint)
		if err != nil {
			return err
		}
		if !strings.HasPrefix(p, "..") {
			toUnmount = append(toUnmount, m.Mountpoint)
		}
	}

	// Make the deepest mount be first
	sort.Slice(toUnmount, func(i, j int) bool {
		return len(toUnmount[i]) > len(toUnmount[j])
	})

	for i, mountPath := range toUnmount {
		if err := mount.UnmountAll(mountPath, unix.MNT_DETACH); err != nil {
			if i == len(toUnmount)-1 { // last mount
				return err
			}
			// This is some submount, we can ignore this error for now, the final unmount will fail if this is a real problem
			log.G(ctx).WithError(err).Debugf("failed to unmount submount %s", mountPath)
		}
	}
	return nil
}

// ensureRemoveAll wraps `os.RemoveAll` to check for specific errors that can
// often be remedied.
// Only use `ensureRemoveAll` if you really want to make every effort to remove
// a directory.
//
// Because of the way `os.Remove` (and by extension `os.RemoveAll`) works, there
// can be a race between reading directory entries and then actually attempting
// to remove everything in the directory.
// These types of errors do not need to be returned since it's ok for the dir to
// be gone we can just retry the remove operation.
//
// This should not return a `os.ErrNotExist` kind of error under any circumstances
func ensureRemoveAll(ctx context.Context, dir string) error {
	notExistErr := make(map[string]bool)

	// track retries
	exitOnErr := make(map[string]int)
	maxRetry := 50

	// Attempt to unmount anything beneath this dir first.
	if err := unmountRecursive(ctx, dir); err != nil {
		log.G(ctx).WithError(err).Debugf("failed to do initial unmount of %s", dir)
	}

	for {
		err := os.RemoveAll(dir)
		if err == nil {
			return nil
		}

		pe, ok := err.(*os.PathError)
		if !ok {
			return err
		}

		if os.IsNotExist(err) {
			if notExistErr[pe.Path] {
				return err
			}
			notExistErr[pe.Path] = true

			// There is a race where some subdir can be removed but after the
			// parent dir entries have been read.
			// So the path could be from `os.Remove(subdir)`
			// If the reported non-existent path is not the passed in `dir` we
			// should just retry, but otherwise return with no error.
			if pe.Path == dir {
				return nil
			}
			continue
		}

		if pe.Err != syscall.EBUSY {
			return err
		}
		if e := mount.Unmount(pe.Path, unix.MNT_DETACH); e != nil {
			return errors.Wrapf(e, "error while removing %s", dir)
		}

		if exitOnErr[pe.Path] == maxRetry {
			return err
		}
		exitOnErr[pe.Path]++
		time.Sleep(100 * time.Millisecond)
	}
}

var vmbasedRuntimes = []string{
	"io.containerd.kata",
}

func isVMBasedRuntime(runtimeType string) bool {
	for _, rt := range vmbasedRuntimes {
		if strings.Contains(runtimeType, rt) {
			return true
		}
	}
	return false
}

func modifyProcessLabel(runtimeType string, spec *specs.Spec) error {
	if !isVMBasedRuntime(runtimeType) {
		return nil
	}
	l, err := getKVMLabel(spec.Process.SelinuxLabel)
	if err != nil {
		return errors.Wrap(err, "failed to get selinux kvm label")
	}
	spec.Process.SelinuxLabel = l
	return nil
}

func getKVMLabel(l string) (string, error) {
	if !seutil.HasType("container_kvm_t") {
		return "", nil
	}
	return seutil.ChangeToKVM(l)
}
<commit_msg>pkg/cri/server: optimizations in unmountRecursive()<commit_after>/*
   Copyright The containerd 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 server

import (
	"context"
	"fmt"
	"os"
	"path"
	"path/filepath"
	"regexp"
	"sort"
	"strings"
	"syscall"
	"time"

	"github.com/containerd/containerd/log"
	"github.com/containerd/containerd/mount"
	"github.com/containerd/containerd/pkg/apparmor"
	"github.com/containerd/containerd/pkg/seccomp"
	"github.com/containerd/containerd/pkg/seutil"
	"github.com/moby/sys/mountinfo"
	"github.com/opencontainers/runtime-spec/specs-go"
	"github.com/opencontainers/selinux/go-selinux/label"
	"github.com/pkg/errors"
	"golang.org/x/sys/unix"
	runtime "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
)

const (
	// defaultSandboxOOMAdj is default omm adj for sandbox container. (kubernetes#47938).
	defaultSandboxOOMAdj = -998
	// defaultShmSize is the default size of the sandbox shm.
	defaultShmSize = int64(1024 * 1024 * 64)
	// relativeRootfsPath is the rootfs path relative to bundle path.
	relativeRootfsPath = "rootfs"
	// According to http://man7.org/linux/man-pages/man5/resolv.conf.5.html:
	// "The search list is currently limited to six domains with a total of 256 characters."
	maxDNSSearches = 6
	// devShm is the default path of /dev/shm.
	devShm = "/dev/shm"
	// etcHosts is the default path of /etc/hosts file.
	etcHosts = "/etc/hosts"
	// etcHostname is the default path of /etc/hostname file.
	etcHostname = "/etc/hostname"
	// resolvConfPath is the abs path of resolv.conf on host or container.
	resolvConfPath = "/etc/resolv.conf"
	// hostnameEnv is the key for HOSTNAME env.
	hostnameEnv = "HOSTNAME"
)

// getCgroupsPath generates container cgroups path.
func getCgroupsPath(cgroupsParent, id string) string {
	base := path.Base(cgroupsParent)
	if strings.HasSuffix(base, ".slice") {
		// For a.slice/b.slice/c.slice, base is c.slice.
		// runc systemd cgroup path format is "slice:prefix:name".
		return strings.Join([]string{base, "cri-containerd", id}, ":")
	}
	return filepath.Join(cgroupsParent, id)
}

// getSandboxHostname returns the hostname file path inside the sandbox root directory.
func (c *criService) getSandboxHostname(id string) string {
	return filepath.Join(c.getSandboxRootDir(id), "hostname")
}

// getSandboxHosts returns the hosts file path inside the sandbox root directory.
func (c *criService) getSandboxHosts(id string) string {
	return filepath.Join(c.getSandboxRootDir(id), "hosts")
}

// getResolvPath returns resolv.conf filepath for specified sandbox.
func (c *criService) getResolvPath(id string) string {
	return filepath.Join(c.getSandboxRootDir(id), "resolv.conf")
}

// getSandboxDevShm returns the shm file path inside the sandbox root directory.
func (c *criService) getSandboxDevShm(id string) string {
	return filepath.Join(c.getVolatileSandboxRootDir(id), "shm")
}

func toLabel(selinuxOptions *runtime.SELinuxOption) ([]string, error) {
	var labels []string

	if selinuxOptions == nil {
		return nil, nil
	}
	if err := checkSelinuxLevel(selinuxOptions.Level); err != nil {
		return nil, err
	}
	if selinuxOptions.User != "" {
		labels = append(labels, "user:"+selinuxOptions.User)
	}
	if selinuxOptions.Role != "" {
		labels = append(labels, "role:"+selinuxOptions.Role)
	}
	if selinuxOptions.Type != "" {
		labels = append(labels, "type:"+selinuxOptions.Type)
	}
	if selinuxOptions.Level != "" {
		labels = append(labels, "level:"+selinuxOptions.Level)
	}

	return labels, nil
}

func initLabelsFromOpt(selinuxOpts *runtime.SELinuxOption) (string, string, error) {
	labels, err := toLabel(selinuxOpts)
	if err != nil {
		return "", "", err
	}
	return label.InitLabels(labels)
}

func checkSelinuxLevel(level string) error {
	if len(level) == 0 {
		return nil
	}

	matched, err := regexp.MatchString(`^s\d(-s\d)??(:c\d{1,4}(\.c\d{1,4})?(,c\d{1,4}(\.c\d{1,4})?)*)?$`, level)
	if err != nil {
		return errors.Wrapf(err, "the format of 'level' %q is not correct", level)
	}
	if !matched {
		return fmt.Errorf("the format of 'level' %q is not correct", level)
	}
	return nil
}

// apparmorEnabled returns true if apparmor is enabled, supported by the host,
// if apparmor_parser is installed, and if we are not running docker-in-docker.
func (c *criService) apparmorEnabled() bool {
	if c.config.DisableApparmor {
		return false
	}
	return apparmor.HostSupports()
}

func (c *criService) seccompEnabled() bool {
	return seccomp.IsEnabled()
}

// openLogFile opens/creates a container log file.
func openLogFile(path string) (*os.File, error) {
	if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
		return nil, err
	}
	return os.OpenFile(path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0640)
}

// unmountRecursive unmounts the target and all mounts underneath, starting with
// the deepest mount first.
func unmountRecursive(ctx context.Context, target string) error {
	toUnmount, err := mountinfo.GetMounts(mountinfo.PrefixFilter(target))
	if err != nil {
		return err
	}

	// Make the deepest mount be first
	sort.Slice(toUnmount, func(i, j int) bool {
		return len(toUnmount[i].Mountpoint) > len(toUnmount[j].Mountpoint)
	})

	for i, m := range toUnmount {
		if err := mount.UnmountAll(m.Mountpoint, unix.MNT_DETACH); err != nil {
			if i == len(toUnmount)-1 { // last mount
				return err
			}
			// This is some submount, we can ignore this error for now, the final unmount will fail if this is a real problem
			log.G(ctx).WithError(err).Debugf("failed to unmount submount %s", m.Mountpoint)
		}
	}
	return nil
}

// ensureRemoveAll wraps `os.RemoveAll` to check for specific errors that can
// often be remedied.
// Only use `ensureRemoveAll` if you really want to make every effort to remove
// a directory.
//
// Because of the way `os.Remove` (and by extension `os.RemoveAll`) works, there
// can be a race between reading directory entries and then actually attempting
// to remove everything in the directory.
// These types of errors do not need to be returned since it's ok for the dir to
// be gone we can just retry the remove operation.
//
// This should not return a `os.ErrNotExist` kind of error under any circumstances
func ensureRemoveAll(ctx context.Context, dir string) error {
	notExistErr := make(map[string]bool)

	// track retries
	exitOnErr := make(map[string]int)
	maxRetry := 50

	// Attempt to unmount anything beneath this dir first.
	if err := unmountRecursive(ctx, dir); err != nil {
		log.G(ctx).WithError(err).Debugf("failed to do initial unmount of %s", dir)
	}

	for {
		err := os.RemoveAll(dir)
		if err == nil {
			return nil
		}

		pe, ok := err.(*os.PathError)
		if !ok {
			return err
		}

		if os.IsNotExist(err) {
			if notExistErr[pe.Path] {
				return err
			}
			notExistErr[pe.Path] = true

			// There is a race where some subdir can be removed but after the
			// parent dir entries have been read.
			// So the path could be from `os.Remove(subdir)`
			// If the reported non-existent path is not the passed in `dir` we
			// should just retry, but otherwise return with no error.
			if pe.Path == dir {
				return nil
			}
			continue
		}

		if pe.Err != syscall.EBUSY {
			return err
		}
		if e := mount.Unmount(pe.Path, unix.MNT_DETACH); e != nil {
			return errors.Wrapf(e, "error while removing %s", dir)
		}

		if exitOnErr[pe.Path] == maxRetry {
			return err
		}
		exitOnErr[pe.Path]++
		time.Sleep(100 * time.Millisecond)
	}
}

var vmbasedRuntimes = []string{
	"io.containerd.kata",
}

func isVMBasedRuntime(runtimeType string) bool {
	for _, rt := range vmbasedRuntimes {
		if strings.Contains(runtimeType, rt) {
			return true
		}
	}
	return false
}

func modifyProcessLabel(runtimeType string, spec *specs.Spec) error {
	if !isVMBasedRuntime(runtimeType) {
		return nil
	}
	l, err := getKVMLabel(spec.Process.SelinuxLabel)
	if err != nil {
		return errors.Wrap(err, "failed to get selinux kvm label")
	}
	spec.Process.SelinuxLabel = l
	return nil
}

func getKVMLabel(l string) (string, error) {
	if !seutil.HasType("container_kvm_t") {
		return "", nil
	}
	return seutil.ChangeToKVM(l)
}
<|endoftext|>


<commit_before>package usagestats

import (
	"context"
	"fmt"
	"time"

	"github.com/grafana/grafana/pkg/bus"
	"github.com/grafana/grafana/pkg/login/social"
	"github.com/grafana/grafana/pkg/models"
	"github.com/grafana/grafana/pkg/services/alerting"
	"github.com/grafana/grafana/pkg/services/sqlstore"

	"github.com/grafana/grafana/pkg/infra/log"
	"github.com/grafana/grafana/pkg/registry"
	"github.com/grafana/grafana/pkg/setting"
)

var metricsLogger log.Logger = log.New("metrics")

func init() {
	registry.RegisterService(&UsageStatsService{})
}

type UsageStats interface {
	GetUsageReport(ctx context.Context) (UsageReport, error)

	RegisterMetric(name string, fn MetricFunc)
}

type MetricFunc func() (interface{}, error)

type UsageStatsService struct {
	Cfg                *setting.Cfg               `inject:""`
	Bus                bus.Bus                    `inject:""`
	SQLStore           *sqlstore.SQLStore         `inject:""`
	AlertingUsageStats alerting.UsageStatsQuerier `inject:""`
	License            models.Licensing           `inject:""`

	log log.Logger

	oauthProviders           map[string]bool
	externalMetrics          map[string]MetricFunc
	concurrentUserStatsCache memoConcurrentUserStats
}

func (uss *UsageStatsService) Init() error {
	uss.log = log.New("infra.usagestats")
	uss.oauthProviders = social.GetOAuthProviders(uss.Cfg)
	uss.externalMetrics = make(map[string]MetricFunc)
	return nil
}

func (uss *UsageStatsService) Run(ctx context.Context) error {
	uss.updateTotalStats()

	onceEveryDayTick := time.NewTicker(time.Hour * 24)
	everyMinuteTicker := time.NewTicker(time.Minute)
	defer onceEveryDayTick.Stop()
	defer everyMinuteTicker.Stop()

	for {
		select {
		case <-onceEveryDayTick.C:
			if err := uss.sendUsageStats(ctx); err != nil {
				metricsLogger.Warn("Failed to send usage stats", "err", err)
			}
		case <-everyMinuteTicker.C:
			uss.updateTotalStats()
		case <-ctx.Done():
			return ctx.Err()
		}
	}
}

type memoConcurrentUserStats struct {
	stats *concurrentUsersStats

	memoized time.Time
}

const concurrentUserStatsCacheLifetime = time.Hour

func (uss *UsageStatsService) GetConcurrentUsersStats(ctx context.Context) (*concurrentUsersStats, error) {
	memoizationPeriod := time.Now().Add(-concurrentUserStatsCacheLifetime)
	if !uss.concurrentUserStatsCache.memoized.Before(memoizationPeriod) {
		return uss.concurrentUserStatsCache.stats, nil
	}

	uss.concurrentUserStatsCache.stats = &concurrentUsersStats{}
	err := uss.SQLStore.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
		// Retrieves concurrent users stats as a histogram. Buckets are accumulative and upper bound is inclusive.
		rawSQL := `
SELECT
    COUNT(CASE WHEN tokens <= 3 THEN 1 END) AS bucket_le_3,
    COUNT(CASE WHEN tokens <= 6 THEN 1 END) AS bucket_le_6,
    COUNT(CASE WHEN tokens <= 9 THEN 1 END) AS bucket_le_9,
    COUNT(CASE WHEN tokens <= 12 THEN 1 END) AS bucket_le_12,
    COUNT(CASE WHEN tokens <= 15 THEN 1 END) AS bucket_le_15,
    COUNT(1) AS bucket_le_inf
FROM (select count(1) as tokens from user_auth_token group by user_id) uat;`
		_, err := sess.SQL(rawSQL).Get(uss.concurrentUserStatsCache.stats)
		if err != nil {
			return err
		}
		return nil
	})
	if err != nil {
		return nil, fmt.Errorf("failed to get concurrent users stats from database: %w", err)
	}

	uss.concurrentUserStatsCache.memoized = time.Now()
	return uss.concurrentUserStatsCache.stats, nil
}
<commit_msg>only update usagestats every 30min (#31131)<commit_after>package usagestats

import (
	"context"
	"fmt"
	"time"

	"github.com/grafana/grafana/pkg/bus"
	"github.com/grafana/grafana/pkg/login/social"
	"github.com/grafana/grafana/pkg/models"
	"github.com/grafana/grafana/pkg/services/alerting"
	"github.com/grafana/grafana/pkg/services/sqlstore"

	"github.com/grafana/grafana/pkg/infra/log"
	"github.com/grafana/grafana/pkg/registry"
	"github.com/grafana/grafana/pkg/setting"
)

var metricsLogger log.Logger = log.New("metrics")

func init() {
	registry.RegisterService(&UsageStatsService{})
}

type UsageStats interface {
	GetUsageReport(ctx context.Context) (UsageReport, error)

	RegisterMetric(name string, fn MetricFunc)
}

type MetricFunc func() (interface{}, error)

type UsageStatsService struct {
	Cfg                *setting.Cfg               `inject:""`
	Bus                bus.Bus                    `inject:""`
	SQLStore           *sqlstore.SQLStore         `inject:""`
	AlertingUsageStats alerting.UsageStatsQuerier `inject:""`
	License            models.Licensing           `inject:""`

	log log.Logger

	oauthProviders           map[string]bool
	externalMetrics          map[string]MetricFunc
	concurrentUserStatsCache memoConcurrentUserStats
}

func (uss *UsageStatsService) Init() error {
	uss.log = log.New("infra.usagestats")
	uss.oauthProviders = social.GetOAuthProviders(uss.Cfg)
	uss.externalMetrics = make(map[string]MetricFunc)
	return nil
}

func (uss *UsageStatsService) Run(ctx context.Context) error {
	uss.updateTotalStats()

	onceEveryDayTick := time.NewTicker(time.Hour * 24)
	everyMinuteTicker := time.NewTicker(time.Minute * 30)
	defer onceEveryDayTick.Stop()
	defer everyMinuteTicker.Stop()

	for {
		select {
		case <-onceEveryDayTick.C:
			if err := uss.sendUsageStats(ctx); err != nil {
				metricsLogger.Warn("Failed to send usage stats", "err", err)
			}
		case <-everyMinuteTicker.C:
			uss.updateTotalStats()
		case <-ctx.Done():
			return ctx.Err()
		}
	}
}

type memoConcurrentUserStats struct {
	stats *concurrentUsersStats

	memoized time.Time
}

const concurrentUserStatsCacheLifetime = time.Hour

func (uss *UsageStatsService) GetConcurrentUsersStats(ctx context.Context) (*concurrentUsersStats, error) {
	memoizationPeriod := time.Now().Add(-concurrentUserStatsCacheLifetime)
	if !uss.concurrentUserStatsCache.memoized.Before(memoizationPeriod) {
		return uss.concurrentUserStatsCache.stats, nil
	}

	uss.concurrentUserStatsCache.stats = &concurrentUsersStats{}
	err := uss.SQLStore.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
		// Retrieves concurrent users stats as a histogram. Buckets are accumulative and upper bound is inclusive.
		rawSQL := `
SELECT
    COUNT(CASE WHEN tokens <= 3 THEN 1 END) AS bucket_le_3,
    COUNT(CASE WHEN tokens <= 6 THEN 1 END) AS bucket_le_6,
    COUNT(CASE WHEN tokens <= 9 THEN 1 END) AS bucket_le_9,
    COUNT(CASE WHEN tokens <= 12 THEN 1 END) AS bucket_le_12,
    COUNT(CASE WHEN tokens <= 15 THEN 1 END) AS bucket_le_15,
    COUNT(1) AS bucket_le_inf
FROM (select count(1) as tokens from user_auth_token group by user_id) uat;`
		_, err := sess.SQL(rawSQL).Get(uss.concurrentUserStatsCache.stats)
		if err != nil {
			return err
		}
		return nil
	})
	if err != nil {
		return nil, fmt.Errorf("failed to get concurrent users stats from database: %w", err)
	}

	uss.concurrentUserStatsCache.memoized = time.Now()
	return uss.concurrentUserStatsCache.stats, nil
}
<|endoftext|>


<commit_before>package main

import (
	"testing"
)

func TestCharSequence_UngappedCoords(t *testing.T) {
	seq := "TTT---TTCTTATTG"
	s := CharSequence{"test", "", seq}
	exp := []int{0, 1, 2, 6, 7, 8, 9, 10, 11, 12, 13, 14}

	res := s.UngappedCoords("-")

	for i, expValue := range exp {
		if expValue != res[i] {
			t.Errorf("UngappedCoords(\"-\"): expected (%d) %d, actual %d",
				i, expValue, res[i],
			)
		}
	}
}

func TestCharSequence_UngappedPositionSlice(t *testing.T) {
	seq := "TTT---TTCTTATTG"
	s := CharSequence{"test", "", seq}
	exp := []int{0, 1, 2, -1, -1, -1, 3, 4, 5, 6, 7, 8, 9, 10, 11}

	res := s.UngappedPositionSlice("-")

	for i, expValue := range exp {
		if expValue != res[i] {
			t.Errorf("UngappedCoords(\"-\"): expected (%d) %d, actual %d",
				i, expValue, res[i],
			)
		}
	}
}

func TestCodonSequence_SetSequence_seq(t *testing.T) {
	s := CodonSequence{CharSequence{"test", "", ""}, "", []string{}}
	seq := "TTT---TTCTTATTG"
	s.SetSequence(seq)

	if s.seq != seq {
		t.Errorf("SetSequence(\"%s\"): expected %s, actual %s", seq, seq, s.seq)
	}
}

func TestCodonSequence_UngappedCoords(t *testing.T) {
	seq := "TTT---TTCTTATTG"
	s := NewCodonSequence("test", "", seq)
	gapChar := "---"
	exp := []int{0, 2, 3, 4}

	res := s.UngappedCoords(gapChar)

	for i, expValue := range exp {
		if expValue != res[i] {
			t.Errorf("UngappedCoords(\"%s\"): expected (%d) %d, actual %d",
				gapChar, i, expValue, res[i],
			)
		}
	}
}

func TestCodonSequence_UngappedPositionSlice(t *testing.T) {
	seq := "TTT---TTCTTATTG"
	s := NewCodonSequence("test", "", seq)
	gapChar := "---"
	exp := []int{0, -1, 1, 2, 3}

	res := s.UngappedPositionSlice(gapChar)

	for i, expValue := range exp {
		if expValue != res[i] {
			t.Errorf("UngappedCoords(\"%s\"): expected (%d) %d, actual %d",
				gapChar, i, expValue, res[i],
			)
		}
	}
}
<commit_msg>Tests prot field in SetSequence<commit_after>package main

import (
	"testing"
)

func TestCharSequence_UngappedCoords(t *testing.T) {
	seq := "TTT---TTCTTATTG"
	s := CharSequence{"test", "", seq}
	exp := []int{0, 1, 2, 6, 7, 8, 9, 10, 11, 12, 13, 14}

	res := s.UngappedCoords("-")

	for i, expValue := range exp {
		if expValue != res[i] {
			t.Errorf("UngappedCoords(\"-\"): expected (%d) %d, actual %d",
				i, expValue, res[i],
			)
		}
	}
}

func TestCharSequence_UngappedPositionSlice(t *testing.T) {
	seq := "TTT---TTCTTATTG"
	s := CharSequence{"test", "", seq}
	exp := []int{0, 1, 2, -1, -1, -1, 3, 4, 5, 6, 7, 8, 9, 10, 11}

	res := s.UngappedPositionSlice("-")

	for i, expValue := range exp {
		if expValue != res[i] {
			t.Errorf("UngappedCoords(\"-\"): expected (%d) %d, actual %d",
				i, expValue, res[i],
			)
		}
	}
}

func TestCodonSequence_SetSequence_seq(t *testing.T) {
	s := CodonSequence{CharSequence{"test", "", ""}, "", []string{}}
	seq := "TTT---TTCTTATTG"
	s.SetSequence(seq)

	if s.seq != seq {
		t.Errorf("SetSequence(\"%s\"): expected %s, actual %s", seq, seq, s.seq)
	}
}

func TestCodonSequence_SetSequence_prot(t *testing.T) {
	s := CodonSequence{CharSequence{"test", "", ""}, "", []string{}}
	seq := "TTTTTCTTATTGTCTTCCTCATCGTATTACTAATAGTGTTGCTGATGGCTTCTCCTACTGCCTCCCCCACCGCATCACCAACAGCGTCGCCGACGGATTATCATAATGACTACCACAACGAATAACAAAAAGAGTAGCAGAAGGGTTGTCGTAGTGGCTGCCGCAGCGGATGACGAAGAGGGTGGCGGAGGG---"
	s.SetSequence(seq)
	exp := "FFLLSSSSYY**CC*WLLLLPPPPHHQQRRRRIIIMTTTTNNKKSSRRVVVVAAAADDEEGGGG-"

	if s.prot != exp {
		t.Errorf("SetSequence(\"%s\"): expected %s, actual %s", seq, exp, s.prot)
	}
}

func TestCodonSequence_UngappedCoords(t *testing.T) {
	seq := "TTT---TTCTTATTG"
	s := NewCodonSequence("test", "", seq)
	gapChar := "---"
	exp := []int{0, 2, 3, 4}

	res := s.UngappedCoords(gapChar)

	for i, expValue := range exp {
		if expValue != res[i] {
			t.Errorf("UngappedCoords(\"%s\"): expected (%d) %d, actual %d",
				gapChar, i, expValue, res[i],
			)
		}
	}
}

func TestCodonSequence_UngappedPositionSlice(t *testing.T) {
	seq := "TTT---TTCTTATTG"
	s := NewCodonSequence("test", "", seq)
	gapChar := "---"
	exp := []int{0, -1, 1, 2, 3}

	res := s.UngappedPositionSlice(gapChar)

	for i, expValue := range exp {
		if expValue != res[i] {
			t.Errorf("UngappedCoords(\"%s\"): expected (%d) %d, actual %d",
				gapChar, i, expValue, res[i],
			)
		}
	}
}
<|endoftext|>