<commit_before>package main

import (
	"bufio"
	"fmt"
	"net"
	"os"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/miekg/dns"
)

type ResolvError struct {
	qname, net  string
	nameservers []string
}

func (e ResolvError) Error() string {
	errmsg := fmt.Sprintf("%s resolv failed on %s (%s)", e.qname, strings.Join(e.nameservers, "; "), e.net)
	return errmsg
}

type Resolver struct {
	servers       []string
	domain_server *suffixTreeNode
	config        *ResolvSettings
}

func NewResolver(c ResolvSettings) *Resolver {
	r := &Resolver{
		servers:       []string{},
		domain_server: newSuffixTreeRoot(),
		config:        &c,
	}

	if len(c.ServerListFile) > 0 {
		r.ReadServerListFile(c.ServerListFile)
	}

	if len(c.ResolvFile) > 0 {
		clientConfig, err := dns.ClientConfigFromFile(c.ResolvFile)
		if err != nil {
			logger.Error(":%s is not a valid resolv.conf file\n", c.ResolvFile)
			logger.Error("%s", err)
			panic(err)
		}
		for _, server := range clientConfig.Servers {
			nameserver := net.JoinHostPort(server, clientConfig.Port)
			r.servers = append(r.servers, nameserver)
		}
	}

	return r
}

func (r *Resolver) ReadServerListFile(file string) {
	buf, err := os.Open(file)
	if err != nil {
		panic("Can't open " + file)
	}
	scanner := bufio.NewScanner(buf)
	for scanner.Scan() {
		line := scanner.Text()
		line = strings.TrimSpace(line)

		if !strings.HasPrefix(line, "server") {
			continue
		}

		sli := strings.Split(line, "=")
		if len(sli) != 2 {
			continue
		}

		line = strings.TrimSpace(sli[1])

		tokens := strings.Split(line, "/")
		switch len(tokens) {
		case 3:
			domain := tokens[1]
			ip := tokens[2]
			if !isDomain(domain) || !isIP(ip) {
				continue
			}
			r.domain_server.sinsert(strings.Split(domain, "."), ip)
		case 1:
			srv_port := strings.Split(line, "#")
			if len(srv_port) > 2 {
				continue
			}

			ip := ""
			if ip = srv_port[0]; !isIP(ip) {
				continue
			}

			port := "53"
			if len(srv_port) == 2 {
				if _, err := strconv.Atoi(srv_port[1]); err != nil {
					continue
				}
				port = srv_port[1]
			}
			r.servers = append(r.servers, net.JoinHostPort(ip, port))
		}
	}

}

// Lookup will ask each nameserver in top-to-bottom fashion, starting a new request
// in every second, and return as early as possbile (have an answer).
// It returns an error if no request has succeeded.
func (r *Resolver) Lookup(net string, req *dns.Msg) (message *dns.Msg, err error) {
	c := &dns.Client{
		Net:          net,
		ReadTimeout:  r.Timeout(),
		WriteTimeout: r.Timeout(),
	}

	if net == "udp" && settings.ResolvConfig.SetEDNS0 {
		req = req.SetEdns0(65535, true)
	}

	qname := req.Question[0].Name

	res := make(chan *dns.Msg, 1)
	var wg sync.WaitGroup
	L := func(nameserver string) {
		defer wg.Done()
		r, rtt, err := c.Exchange(req, nameserver)
		if err != nil {
			logger.Warn("%s socket error on %s", qname, nameserver)
			logger.Warn("error:%s", err.Error())
			return
		}
		// If SERVFAIL happen, should return immediately and try another upstream resolver.
		// However, other Error code like NXDOMAIN is an clear response stating
		// that it has been verified no such domain existas and ask other resolvers
		// would make no sense. See more about #20
		if r != nil && r.Rcode != dns.RcodeSuccess {
			logger.Warn("%s failed to get an valid answer on %s", qname, nameserver)
			if r.Rcode == dns.RcodeServerFailure {
				return
			}
		} else {
			logger.Debug("%s resolv on %s (%s) ttl: %v", UnFqdn(qname), nameserver, net, rtt)
		}
		select {
		case res <- r:
		default:
		}
	}

	ticker := time.NewTicker(time.Duration(settings.ResolvConfig.Interval) * time.Millisecond)
	defer ticker.Stop()
	// Start lookup on each nameserver top-down, in every second
	nameservers := r.Nameservers(qname)
	for _, nameserver := range nameservers {
		wg.Add(1)
		go L(nameserver)
		// but exit early, if we have an answer
		select {
		case r := <-res:
			// logger.Debug("%s resolv on %s rtt: %v", UnFqdn(qname), nameserver, rtt)
			return r, nil
		case <-ticker.C:
			continue
		}
	}
	// wait for all the namservers to finish
	wg.Wait()
	select {
	case r := <-res:
		// logger.Debug("%s resolv on %s rtt: %v", UnFqdn(qname), nameserver, rtt)
		return r, nil
	default:
		return nil, ResolvError{qname, net, nameservers}
	}
}

// Namservers return the array of nameservers, with port number appended.
// '#' in the name is treated as port separator, as with dnsmasq.

func (r *Resolver) Nameservers(qname string) []string {
	queryKeys := strings.Split(qname, ".")
	queryKeys = queryKeys[:len(queryKeys)-1] // ignore last '.'

	ns := []string{}
	if v, found := r.domain_server.search(queryKeys); found {
		logger.Debug("found upstream: %v", v)
		server := v
		nameserver := server + ":53"
		ns = append(ns, nameserver)
	}

	for _, nameserver := range r.servers {
		ns = append(ns, nameserver)
	}
	return ns
}

func (r *Resolver) Timeout() time.Duration {
	return time.Duration(r.config.Timeout) * time.Second
}
<commit_msg>more pretty logging and ensure query the specific upstream nameserver in async Lookup() function.<commit_after>package main

import (
	"bufio"
	"fmt"
	"net"
	"os"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/miekg/dns"
)

type ResolvError struct {
	qname, net  string
	nameservers []string
}

func (e ResolvError) Error() string {
	errmsg := fmt.Sprintf("%s resolv failed on %s (%s)", e.qname, strings.Join(e.nameservers, "; "), e.net)
	return errmsg
}

type RResp struct {
	msg        *dns.Msg
	nameserver string
	rtt        time.Duration
}

type Resolver struct {
	servers       []string
	domain_server *suffixTreeNode
	config        *ResolvSettings
}

func NewResolver(c ResolvSettings) *Resolver {
	r := &Resolver{
		servers:       []string{},
		domain_server: newSuffixTreeRoot(),
		config:        &c,
	}

	if len(c.ServerListFile) > 0 {
		r.ReadServerListFile(c.ServerListFile)
	}

	if len(c.ResolvFile) > 0 {
		clientConfig, err := dns.ClientConfigFromFile(c.ResolvFile)
		if err != nil {
			logger.Error(":%s is not a valid resolv.conf file\n", c.ResolvFile)
			logger.Error("%s", err)
			panic(err)
		}
		for _, server := range clientConfig.Servers {
			nameserver := net.JoinHostPort(server, clientConfig.Port)
			r.servers = append(r.servers, nameserver)
		}
	}

	return r
}

func (r *Resolver) ReadServerListFile(file string) {
	buf, err := os.Open(file)
	if err != nil {
		panic("Can't open " + file)
	}
	scanner := bufio.NewScanner(buf)
	for scanner.Scan() {
		line := scanner.Text()
		line = strings.TrimSpace(line)

		if !strings.HasPrefix(line, "server") {
			continue
		}

		sli := strings.Split(line, "=")
		if len(sli) != 2 {
			continue
		}

		line = strings.TrimSpace(sli[1])

		tokens := strings.Split(line, "/")
		switch len(tokens) {
		case 3:
			domain := tokens[1]
			ip := tokens[2]
			if !isDomain(domain) || !isIP(ip) {
				continue
			}
			r.domain_server.sinsert(strings.Split(domain, "."), ip)
		case 1:
			srv_port := strings.Split(line, "#")
			if len(srv_port) > 2 {
				continue
			}

			ip := ""
			if ip = srv_port[0]; !isIP(ip) {
				continue
			}

			port := "53"
			if len(srv_port) == 2 {
				if _, err := strconv.Atoi(srv_port[1]); err != nil {
					continue
				}
				port = srv_port[1]
			}
			r.servers = append(r.servers, net.JoinHostPort(ip, port))
		}
	}

}

// Lookup will ask each nameserver in top-to-bottom fashion, starting a new request
// in every second, and return as early as possbile (have an answer).
// It returns an error if no request has succeeded.
func (r *Resolver) Lookup(net string, req *dns.Msg) (message *dns.Msg, err error) {
	c := &dns.Client{
		Net:          net,
		ReadTimeout:  r.Timeout(),
		WriteTimeout: r.Timeout(),
	}

	if net == "udp" && settings.ResolvConfig.SetEDNS0 {
		req = req.SetEdns0(65535, true)
	}

	qname := req.Question[0].Name

	res := make(chan *RResp, 1)
	var wg sync.WaitGroup
	L := func(nameserver string) {
		defer wg.Done()
		r, rtt, err := c.Exchange(req, nameserver)
		if err != nil {
			logger.Warn("%s socket error on %s", qname, nameserver)
			logger.Warn("error:%s", err.Error())
			return
		}
		// If SERVFAIL happen, should return immediately and try another upstream resolver.
		// However, other Error code like NXDOMAIN is an clear response stating
		// that it has been verified no such domain existas and ask other resolvers
		// would make no sense. See more about #20
		if r != nil && r.Rcode != dns.RcodeSuccess {
			logger.Warn("%s failed to get an valid answer on %s", qname, nameserver)
			if r.Rcode == dns.RcodeServerFailure {
				return
			}
		}
		re := &RResp{r, nameserver, rtt}
		select {
		case res <- re:
		default:
		}
	}

	ticker := time.NewTicker(time.Duration(settings.ResolvConfig.Interval) * time.Millisecond)
	defer ticker.Stop()
	// Start lookup on each nameserver top-down, in every second
	nameservers := r.Nameservers(qname)
	for _, nameserver := range nameservers {
		wg.Add(1)
		go L(nameserver)
		// but exit early, if we have an answer
		select {
		case re := <-res:
			logger.Debug("%s resolv on %s rtt: %v", UnFqdn(qname), re.nameserver, re.rtt)
			return re.msg, nil
		case <-ticker.C:
			continue
		}
	}
	// wait for all the namservers to finish
	wg.Wait()
	select {
	case re := <-res:
		logger.Debug("%s resolv on %s rtt: %v", UnFqdn(qname), re.nameserver, re.rtt)
		return re.msg, nil
	default:
		return nil, ResolvError{qname, net, nameservers}
	}
}

// Namservers return the array of nameservers, with port number appended.
// '#' in the name is treated as port separator, as with dnsmasq.

func (r *Resolver) Nameservers(qname string) []string {
	queryKeys := strings.Split(qname, ".")
	queryKeys = queryKeys[:len(queryKeys)-1] // ignore last '.'

	ns := []string{}
	if v, found := r.domain_server.search(queryKeys); found {
		logger.Debug("%s be found in domain server list, upstream: %v", qname, v)
		server := v
		nameserver := net.JoinHostPort(server, "53")
		ns = append(ns, nameserver)
		//Ensure query the specific upstream nameserver in async Lookup() function.
		return ns
	}

	for _, nameserver := range r.servers {
		ns = append(ns, nameserver)
	}
	return ns
}

func (r *Resolver) Timeout() time.Duration {
	return time.Duration(r.config.Timeout) * time.Second
}
<|endoftext|>


<commit_before>package utils
import (
 "gopkg.in/cookieo9/resources-go.v2"
 "io/ioutil"
 "strings"
 "log"
 "mime"
 "time"
 "path/filepath"
 "net/http"
)
type Resource struct{}

var DefaultResource *Resource=&Resource{}

func (re *Resource)Load(path string) []byte{
     res,err:=re.Get(path)
     if(err!=nil){
        return []byte{}
      }
     r,_:=res.Open()
     bf,err:=ioutil.ReadAll(r)
     if(err!=nil){
        log.Println("read res[",path,"] failed",err.Error())
      }
     return bf
}

func (re *Resource)Get(path string)(resources.Resource,error){
    path=strings.TrimLeft(path,"/")
    res,err:=resources.Find(path)
    if(err!=nil){
      log.Println("load res[",path,"] failed",err.Error())
      return nil,err
     }
     return res,nil
}

func (re *Resource)HandleStatic(w http.ResponseWriter,r *http.Request,path string){
    res,err:=re.Get(path)
    if(err!=nil){
        http.NotFound(w,r)
        return;
     }
    finfo,_:=res.Stat()
    modtime:=finfo.ModTime()
    if t, err := time.Parse(http.TimeFormat, r.Header.Get("If-Modified-Since")); err == nil && modtime.Before(t.Add(1*time.Second)) {
           h := w.Header()
           delete(h, "Content-Type")
           delete(h, "Content-Length")
           w.WriteHeader(http.StatusNotModified)
           return
           }
   mimeType:= mime.TypeByExtension(filepath.Ext(path))
   if(mimeType!=""){
       w.Header().Set("Content-Type",mimeType)
     }
    w.Header().Set("Last-Modified",modtime.UTC().Format(http.TimeFormat))
    w.Write(re.Load(path))
}

func ResetDefaultBundle(execDir bool){
   resources.DefaultBundle=make(resources.BundleSequence,1,10)
   var exe_dir, exe resources.Bundle
   if exe_path, err := resources.ExecutablePath(); err == nil {
		exe_dir = resources.OpenFS(filepath.Dir(exe_path))
		if exe, err = resources.OpenZip(exe_path); err == nil {
			resources.DefaultBundle = append(resources.DefaultBundle, exe)
		}
		if(execDir){
			resources.DefaultBundle = append(resources.DefaultBundle, exe_dir)
		}
	}
}

//func init() {
//	var cwd, cur_pkg, exe_dir, exe Bundle
//	cwd = OpenFS(".")
//	cur_pkg = OpenAutoBundle(OpenCurrentPackage)
//
//	if exe_path, err := ExecutablePath(); err == nil {
//		exe_dir = OpenFS(filepath.Dir(exe_path))
//		if exe, err = OpenZip(exe_path); err == nil {
//			DefaultBundle = append(DefaultBundle, exe)
//		}
//	}
//
//	DefaultBundle = append(DefaultBundle, cwd, exe_dir, cur_pkg, exe)
//}

<commit_msg>auto bundle zip<commit_after>package utils
import (
 "gopkg.in/cookieo9/resources-go.v2"
 "io/ioutil"
 "strings"
 "log"
 "mime"
 "time"
 "path/filepath"
 "net/http"
)
type Resource struct{}

var DefaultResource *Resource=&Resource{}

func (re *Resource)Load(path string) []byte{
     res,err:=re.Get(path)
     if(err!=nil){
        return []byte{}
      }
     r,_:=res.Open()
     bf,err:=ioutil.ReadAll(r)
     if(err!=nil){
        log.Println("read res[",path,"] failed",err.Error())
      }
     return bf
}

func (re *Resource)Get(path string)(resources.Resource,error){
    path=strings.TrimLeft(path,"/")
    res,err:=resources.Find(path)
    if(err!=nil){
      log.Println("load res[",path,"] failed",err.Error())
      return nil,err
     }
     return res,nil
}

func (re *Resource)HandleStatic(w http.ResponseWriter,r *http.Request,path string){
    res,err:=re.Get(path)
    if(err!=nil){
        http.NotFound(w,r)
        return;
     }
    finfo,_:=res.Stat()
    modtime:=finfo.ModTime()
    if t, err := time.Parse(http.TimeFormat, r.Header.Get("If-Modified-Since")); err == nil && modtime.Before(t.Add(1*time.Second)) {
           h := w.Header()
           delete(h, "Content-Type")
           delete(h, "Content-Length")
            w.Header().Set("Last-Modified",modtime.UTC().Format(http.TimeFormat))
           w.WriteHeader(http.StatusNotModified)
           return
           }
   mimeType:= mime.TypeByExtension(filepath.Ext(path))
   if(mimeType!=""){
       w.Header().Set("Content-Type",mimeType)
     }
    w.Header().Set("Last-Modified",modtime.UTC().Format(http.TimeFormat))
    w.Write(re.Load(path))
}

func ResetDefaultBundle(){
   resources.DefaultBundle=make(resources.BundleSequence,1,10)

   var cwd ,exe_dir, exe ,cur_pkg resources.Bundle
    hasZip:=false
   if exe_path, err := resources.ExecutablePath(); err == nil {
		if exe, err = resources.OpenZip(exe_path); err == nil {
            log.Println("bundle resource zip",exe_path)
            hasZip=true
			resources.DefaultBundle = append(resources.DefaultBundle, exe)
		}
		if(err!=nil){
		   log.Println("bundle resource  zip failed")
		}
		if(!hasZip){
			exe_dir = resources.OpenFS(filepath.Dir(exe_path))
			resources.DefaultBundle = append(resources.DefaultBundle, exe_dir)
		}
	}
	if(!hasZip){
	   cwd = resources.OpenFS(".")
	   cur_pkg = resources.OpenAutoBundle(resources.OpenCurrentPackage)
	   resources.DefaultBundle = append(resources.DefaultBundle, cwd,cur_pkg)
	}
}

//func init() {
//	var cwd, cur_pkg, exe_dir, exe Bundle
//	cwd = OpenFS(".")
//	cur_pkg = OpenAutoBundle(OpenCurrentPackage)
//
//	if exe_path, err := ExecutablePath(); err == nil {
//		exe_dir = OpenFS(filepath.Dir(exe_path))
//		if exe, err = OpenZip(exe_path); err == nil {
//			DefaultBundle = append(DefaultBundle, exe)
//		}
//	}
//
//	DefaultBundle = append(DefaultBundle, cwd, exe_dir, cur_pkg, exe)
//}

<|endoftext|>


<commit_before>package executor

import (
	"context"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"testing"
	"time"

	"github.com/hashicorp/nomad/client/allocdir"
	"github.com/hashicorp/nomad/client/taskenv"
	"github.com/hashicorp/nomad/client/testutil"
	"github.com/hashicorp/nomad/helper/testlog"
	"github.com/hashicorp/nomad/nomad/mock"
	"github.com/hashicorp/nomad/plugins/drivers"
	tu "github.com/hashicorp/nomad/testutil"
	lconfigs "github.com/opencontainers/runc/libcontainer/configs"
	"github.com/stretchr/testify/require"
	"golang.org/x/sys/unix"
)

func init() {
	executorFactories["LibcontainerExecutor"] = libcontainerFactory
}

var libcontainerFactory = executorFactory{
	new: NewExecutorWithIsolation,
	configureExecCmd: func(t *testing.T, cmd *ExecCommand) {
		cmd.ResourceLimits = true
		setupRootfs(t, cmd.TaskDir)
	},
}

// testExecutorContextWithChroot returns an ExecutorContext and AllocDir with
// chroot. Use testExecutorContext if you don't need a chroot.
//
// The caller is responsible for calling AllocDir.Destroy() to cleanup.
func testExecutorCommandWithChroot(t *testing.T) *testExecCmd {
	chrootEnv := map[string]string{
		"/etc/ld.so.cache":  "/etc/ld.so.cache",
		"/etc/ld.so.conf":   "/etc/ld.so.conf",
		"/etc/ld.so.conf.d": "/etc/ld.so.conf.d",
		"/lib":              "/lib",
		"/lib64":            "/lib64",
		"/usr/lib":          "/usr/lib",
		"/bin/ls":           "/bin/ls",
		"/bin/cat":          "/bin/cat",
		"/bin/echo":         "/bin/echo",
		"/bin/bash":         "/bin/bash",
		"/bin/sleep":        "/bin/sleep",
		"/foobar":           "/does/not/exist",
	}

	alloc := mock.Alloc()
	task := alloc.Job.TaskGroups[0].Tasks[0]
	taskEnv := taskenv.NewBuilder(mock.Node(), alloc, task, "global").Build()

	allocDir := allocdir.NewAllocDir(testlog.HCLogger(t), filepath.Join(os.TempDir(), alloc.ID))
	if err := allocDir.Build(); err != nil {
		t.Fatalf("AllocDir.Build() failed: %v", err)
	}
	if err := allocDir.NewTaskDir(task.Name).Build(true, chrootEnv); err != nil {
		allocDir.Destroy()
		t.Fatalf("allocDir.NewTaskDir(%q) failed: %v", task.Name, err)
	}
	td := allocDir.TaskDirs[task.Name]
	cmd := &ExecCommand{
		Env:     taskEnv.List(),
		TaskDir: td.Dir,
		Resources: &drivers.Resources{
			NomadResources: alloc.AllocatedResources.Tasks[task.Name],
		},
	}

	testCmd := &testExecCmd{
		command:  cmd,
		allocDir: allocDir,
	}
	configureTLogging(t, testCmd)
	return testCmd
}

func TestExecutor_IsolationAndConstraints(t *testing.T) {
	t.Parallel()
	require := require.New(t)
	testutil.ExecCompatible(t)

	testExecCmd := testExecutorCommandWithChroot(t)
	execCmd, allocDir := testExecCmd.command, testExecCmd.allocDir
	execCmd.Cmd = "/bin/ls"
	execCmd.Args = []string{"-F", "/", "/etc/"}
	defer allocDir.Destroy()

	execCmd.ResourceLimits = true

	executor := NewExecutorWithIsolation(testlog.HCLogger(t))
	defer executor.Shutdown("SIGKILL", 0)

	ps, err := executor.Launch(execCmd)
	require.NoError(err)
	require.NotZero(ps.Pid)

	state, err := executor.Wait(context.Background())
	require.NoError(err)
	require.Zero(state.ExitCode)

	// Check if the resource constraints were applied
	if lexec, ok := executor.(*LibcontainerExecutor); ok {
		state, err := lexec.container.State()
		require.NoError(err)

		memLimits := filepath.Join(state.CgroupPaths["memory"], "memory.limit_in_bytes")
		data, err := ioutil.ReadFile(memLimits)
		require.NoError(err)

		expectedMemLim := strconv.Itoa(int(execCmd.Resources.NomadResources.Memory.MemoryMB * 1024 * 1024))
		actualMemLim := strings.TrimSpace(string(data))
		require.Equal(actualMemLim, expectedMemLim)
		require.NoError(executor.Shutdown("", 0))
		executor.Wait(context.Background())

		// Check if Nomad has actually removed the cgroups
		tu.WaitForResult(func() (bool, error) {
			_, err = os.Stat(memLimits)
			if err == nil {
				return false, fmt.Errorf("expected an error from os.Stat %s", memLimits)
			}
			return true, nil
		}, func(err error) { t.Error(err) })

	}
	expected := `/:
alloc/
bin/
dev/
etc/
lib/
lib64/
local/
proc/
secrets/
sys/
tmp/
usr/

/etc/:
ld.so.cache
ld.so.conf
ld.so.conf.d/`
	tu.WaitForResult(func() (bool, error) {
		output := testExecCmd.stdout.String()
		act := strings.TrimSpace(string(output))
		if act != expected {
			return false, fmt.Errorf("Command output incorrectly: want %v; got %v", expected, act)
		}
		return true, nil
	}, func(err error) { t.Error(err) })
}

func TestUniversalExecutor_LookupTaskBin(t *testing.T) {
	t.Parallel()
	require := require.New(t)

	// Create a temp dir
	tmpDir, err := ioutil.TempDir("", "")
	require.Nil(err)
	defer os.Remove(tmpDir)

	// Create the command
	cmd := &ExecCommand{Env: []string{"PATH=/bin"}, TaskDir: tmpDir}

	// Make a foo subdir
	os.MkdirAll(filepath.Join(tmpDir, "foo"), 0700)

	// Write a file under foo
	filePath := filepath.Join(tmpDir, "foo", "tmp.txt")
	err = ioutil.WriteFile(filePath, []byte{1, 2}, os.ModeAppend)
	require.NoError(err)

	// Lookout with an absolute path to the binary
	cmd.Cmd = "/foo/tmp.txt"
	_, err = lookupTaskBin(cmd)
	require.NoError(err)

	// Write a file under local subdir
	os.MkdirAll(filepath.Join(tmpDir, "local"), 0700)
	filePath2 := filepath.Join(tmpDir, "local", "tmp.txt")
	ioutil.WriteFile(filePath2, []byte{1, 2}, os.ModeAppend)

	// Lookup with file name, should find the one we wrote above
	cmd.Cmd = "tmp.txt"
	_, err = lookupTaskBin(cmd)
	require.NoError(err)

	// Lookup a host absolute path
	cmd.Cmd = "/bin/sh"
	_, err = lookupTaskBin(cmd)
	require.Error(err)
}

// Exec Launch looks for the binary only inside the chroot
func TestExecutor_EscapeContainer(t *testing.T) {
	t.Parallel()
	require := require.New(t)
	testutil.ExecCompatible(t)

	testExecCmd := testExecutorCommandWithChroot(t)
	execCmd, allocDir := testExecCmd.command, testExecCmd.allocDir
	execCmd.Cmd = "/bin/kill" // missing from the chroot container
	defer allocDir.Destroy()

	execCmd.ResourceLimits = true

	executor := NewExecutorWithIsolation(testlog.HCLogger(t))
	defer executor.Shutdown("SIGKILL", 0)

	_, err := executor.Launch(execCmd)
	require.Error(err)
	require.Regexp("^file /bin/kill not found under path", err)

	// Bare files are looked up using the system path, inside the container
	allocDir.Destroy()
	testExecCmd = testExecutorCommandWithChroot(t)
	execCmd, allocDir = testExecCmd.command, testExecCmd.allocDir
	execCmd.Cmd = "kill"
	_, err = executor.Launch(execCmd)
	require.Error(err)
	require.Regexp("^file kill not found under path", err)

	allocDir.Destroy()
	testExecCmd = testExecutorCommandWithChroot(t)
	execCmd, allocDir = testExecCmd.command, testExecCmd.allocDir
	execCmd.Cmd = "echo"
	_, err = executor.Launch(execCmd)
	require.NoError(err)
}

func TestExecutor_Capabilities(t *testing.T) {
	t.Parallel()
	require := require.New(t)
	testutil.ExecCompatible(t)

	testExecCmd := testExecutorCommandWithChroot(t)
	execCmd, allocDir := testExecCmd.command, testExecCmd.allocDir
	defer allocDir.Destroy()

	execCmd.ResourceLimits = true
	execCmd.Cmd = "/bin/sh"
	execCmd.Args = []string{"-c", "cat /proc/$$/cmdline"}

	executor := NewExecutorWithIsolation(testlog.HCLogger(t))
	defer executor.Shutdown("SIGKILL", 0)

	_, err := executor.Launch(execCmd)
	require.NoError(err)

	ch := make(chan interface{})
	go func() {
		executor.Wait(context.Background())
		close(ch)
	}()

	select {
	case <-ch:
		// all good
	case <-time.After(5 * time.Second):
		require.Fail("timeout waiting for exec to shutdown")
	}

	output := testExecCmd.stdout.String()
	require.Empty(output)

}

func TestExecutor_ClientCleanup(t *testing.T) {
	t.Parallel()
	testutil.ExecCompatible(t)
	require := require.New(t)

	testExecCmd := testExecutorCommandWithChroot(t)
	execCmd, allocDir := testExecCmd.command, testExecCmd.allocDir
	defer allocDir.Destroy()

	executor := NewExecutorWithIsolation(testlog.HCLogger(t))
	defer executor.Shutdown("", 0)

	// Need to run a command which will produce continuous output but not
	// too quickly to ensure executor.Exit() stops the process.
	execCmd.Cmd = "/bin/bash"
	execCmd.Args = []string{"-c", "while true; do /bin/echo X; /bin/sleep 1; done"}
	execCmd.ResourceLimits = true

	ps, err := executor.Launch(execCmd)

	require.NoError(err)
	require.NotZero(ps.Pid)
	time.Sleep(500 * time.Millisecond)
	require.NoError(executor.Shutdown("SIGINT", 100*time.Millisecond))

	ch := make(chan interface{})
	go func() {
		executor.Wait(context.Background())
		close(ch)
	}()

	select {
	case <-ch:
		// all good
	case <-time.After(5 * time.Second):
		require.Fail("timeout waiting for exec to shutdown")
	}

	output := testExecCmd.stdout.String()
	require.NotZero(len(output))
	time.Sleep(2 * time.Second)
	output1 := testExecCmd.stdout.String()
	require.Equal(len(output), len(output1))
}

func TestExecutor_cmdDevices(t *testing.T) {
	input := []*drivers.DeviceConfig{
		{
			HostPath:    "/dev/null",
			TaskPath:    "/task/dev/null",
			Permissions: "rwm",
		},
	}

	expected := &lconfigs.Device{
		Path:        "/task/dev/null",
		Type:        99,
		Major:       1,
		Minor:       3,
		Permissions: "rwm",
	}

	found, err := cmdDevices(input)
	require.NoError(t, err)
	require.Len(t, found, 1)

	// ignore file permission and ownership
	// as they are host specific potentially
	d := found[0]
	d.FileMode = 0
	d.Uid = 0
	d.Gid = 0

	require.EqualValues(t, expected, d)
}

func TestExecutor_cmdMounts(t *testing.T) {
	input := []*drivers.MountConfig{
		{
			HostPath: "/host/path-ro",
			TaskPath: "/task/path-ro",
			Readonly: true,
		},
		{
			HostPath: "/host/path-rw",
			TaskPath: "/task/path-rw",
			Readonly: false,
		},
	}

	expected := []*lconfigs.Mount{
		{
			Source:      "/host/path-ro",
			Destination: "/task/path-ro",
			Flags:       unix.MS_BIND | unix.MS_RDONLY,
			Device:      "bind",
		},
		{
			Source:      "/host/path-rw",
			Destination: "/task/path-rw",
			Flags:       unix.MS_BIND,
			Device:      "bind",
		},
	}

	require.EqualValues(t, expected, cmdMounts(input))
}
<commit_msg>use /bin/bash<commit_after>package executor

import (
	"context"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"testing"
	"time"

	"github.com/hashicorp/nomad/client/allocdir"
	"github.com/hashicorp/nomad/client/taskenv"
	"github.com/hashicorp/nomad/client/testutil"
	"github.com/hashicorp/nomad/helper/testlog"
	"github.com/hashicorp/nomad/nomad/mock"
	"github.com/hashicorp/nomad/plugins/drivers"
	tu "github.com/hashicorp/nomad/testutil"
	lconfigs "github.com/opencontainers/runc/libcontainer/configs"
	"github.com/stretchr/testify/require"
	"golang.org/x/sys/unix"
)

func init() {
	executorFactories["LibcontainerExecutor"] = libcontainerFactory
}

var libcontainerFactory = executorFactory{
	new: NewExecutorWithIsolation,
	configureExecCmd: func(t *testing.T, cmd *ExecCommand) {
		cmd.ResourceLimits = true
		setupRootfs(t, cmd.TaskDir)
	},
}

// testExecutorContextWithChroot returns an ExecutorContext and AllocDir with
// chroot. Use testExecutorContext if you don't need a chroot.
//
// The caller is responsible for calling AllocDir.Destroy() to cleanup.
func testExecutorCommandWithChroot(t *testing.T) *testExecCmd {
	chrootEnv := map[string]string{
		"/etc/ld.so.cache":  "/etc/ld.so.cache",
		"/etc/ld.so.conf":   "/etc/ld.so.conf",
		"/etc/ld.so.conf.d": "/etc/ld.so.conf.d",
		"/lib":              "/lib",
		"/lib64":            "/lib64",
		"/usr/lib":          "/usr/lib",
		"/bin/ls":           "/bin/ls",
		"/bin/cat":          "/bin/cat",
		"/bin/echo":         "/bin/echo",
		"/bin/bash":         "/bin/bash",
		"/bin/sleep":        "/bin/sleep",
		"/foobar":           "/does/not/exist",
	}

	alloc := mock.Alloc()
	task := alloc.Job.TaskGroups[0].Tasks[0]
	taskEnv := taskenv.NewBuilder(mock.Node(), alloc, task, "global").Build()

	allocDir := allocdir.NewAllocDir(testlog.HCLogger(t), filepath.Join(os.TempDir(), alloc.ID))
	if err := allocDir.Build(); err != nil {
		t.Fatalf("AllocDir.Build() failed: %v", err)
	}
	if err := allocDir.NewTaskDir(task.Name).Build(true, chrootEnv); err != nil {
		allocDir.Destroy()
		t.Fatalf("allocDir.NewTaskDir(%q) failed: %v", task.Name, err)
	}
	td := allocDir.TaskDirs[task.Name]
	cmd := &ExecCommand{
		Env:     taskEnv.List(),
		TaskDir: td.Dir,
		Resources: &drivers.Resources{
			NomadResources: alloc.AllocatedResources.Tasks[task.Name],
		},
	}

	testCmd := &testExecCmd{
		command:  cmd,
		allocDir: allocDir,
	}
	configureTLogging(t, testCmd)
	return testCmd
}

func TestExecutor_IsolationAndConstraints(t *testing.T) {
	t.Parallel()
	require := require.New(t)
	testutil.ExecCompatible(t)

	testExecCmd := testExecutorCommandWithChroot(t)
	execCmd, allocDir := testExecCmd.command, testExecCmd.allocDir
	execCmd.Cmd = "/bin/ls"
	execCmd.Args = []string{"-F", "/", "/etc/"}
	defer allocDir.Destroy()

	execCmd.ResourceLimits = true

	executor := NewExecutorWithIsolation(testlog.HCLogger(t))
	defer executor.Shutdown("SIGKILL", 0)

	ps, err := executor.Launch(execCmd)
	require.NoError(err)
	require.NotZero(ps.Pid)

	state, err := executor.Wait(context.Background())
	require.NoError(err)
	require.Zero(state.ExitCode)

	// Check if the resource constraints were applied
	if lexec, ok := executor.(*LibcontainerExecutor); ok {
		state, err := lexec.container.State()
		require.NoError(err)

		memLimits := filepath.Join(state.CgroupPaths["memory"], "memory.limit_in_bytes")
		data, err := ioutil.ReadFile(memLimits)
		require.NoError(err)

		expectedMemLim := strconv.Itoa(int(execCmd.Resources.NomadResources.Memory.MemoryMB * 1024 * 1024))
		actualMemLim := strings.TrimSpace(string(data))
		require.Equal(actualMemLim, expectedMemLim)
		require.NoError(executor.Shutdown("", 0))
		executor.Wait(context.Background())

		// Check if Nomad has actually removed the cgroups
		tu.WaitForResult(func() (bool, error) {
			_, err = os.Stat(memLimits)
			if err == nil {
				return false, fmt.Errorf("expected an error from os.Stat %s", memLimits)
			}
			return true, nil
		}, func(err error) { t.Error(err) })

	}
	expected := `/:
alloc/
bin/
dev/
etc/
lib/
lib64/
local/
proc/
secrets/
sys/
tmp/
usr/

/etc/:
ld.so.cache
ld.so.conf
ld.so.conf.d/`
	tu.WaitForResult(func() (bool, error) {
		output := testExecCmd.stdout.String()
		act := strings.TrimSpace(string(output))
		if act != expected {
			return false, fmt.Errorf("Command output incorrectly: want %v; got %v", expected, act)
		}
		return true, nil
	}, func(err error) { t.Error(err) })
}

func TestUniversalExecutor_LookupTaskBin(t *testing.T) {
	t.Parallel()
	require := require.New(t)

	// Create a temp dir
	tmpDir, err := ioutil.TempDir("", "")
	require.Nil(err)
	defer os.Remove(tmpDir)

	// Create the command
	cmd := &ExecCommand{Env: []string{"PATH=/bin"}, TaskDir: tmpDir}

	// Make a foo subdir
	os.MkdirAll(filepath.Join(tmpDir, "foo"), 0700)

	// Write a file under foo
	filePath := filepath.Join(tmpDir, "foo", "tmp.txt")
	err = ioutil.WriteFile(filePath, []byte{1, 2}, os.ModeAppend)
	require.NoError(err)

	// Lookout with an absolute path to the binary
	cmd.Cmd = "/foo/tmp.txt"
	_, err = lookupTaskBin(cmd)
	require.NoError(err)

	// Write a file under local subdir
	os.MkdirAll(filepath.Join(tmpDir, "local"), 0700)
	filePath2 := filepath.Join(tmpDir, "local", "tmp.txt")
	ioutil.WriteFile(filePath2, []byte{1, 2}, os.ModeAppend)

	// Lookup with file name, should find the one we wrote above
	cmd.Cmd = "tmp.txt"
	_, err = lookupTaskBin(cmd)
	require.NoError(err)

	// Lookup a host absolute path
	cmd.Cmd = "/bin/sh"
	_, err = lookupTaskBin(cmd)
	require.Error(err)
}

// Exec Launch looks for the binary only inside the chroot
func TestExecutor_EscapeContainer(t *testing.T) {
	t.Parallel()
	require := require.New(t)
	testutil.ExecCompatible(t)

	testExecCmd := testExecutorCommandWithChroot(t)
	execCmd, allocDir := testExecCmd.command, testExecCmd.allocDir
	execCmd.Cmd = "/bin/kill" // missing from the chroot container
	defer allocDir.Destroy()

	execCmd.ResourceLimits = true

	executor := NewExecutorWithIsolation(testlog.HCLogger(t))
	defer executor.Shutdown("SIGKILL", 0)

	_, err := executor.Launch(execCmd)
	require.Error(err)
	require.Regexp("^file /bin/kill not found under path", err)

	// Bare files are looked up using the system path, inside the container
	allocDir.Destroy()
	testExecCmd = testExecutorCommandWithChroot(t)
	execCmd, allocDir = testExecCmd.command, testExecCmd.allocDir
	execCmd.Cmd = "kill"
	_, err = executor.Launch(execCmd)
	require.Error(err)
	require.Regexp("^file kill not found under path", err)

	allocDir.Destroy()
	testExecCmd = testExecutorCommandWithChroot(t)
	execCmd, allocDir = testExecCmd.command, testExecCmd.allocDir
	execCmd.Cmd = "echo"
	_, err = executor.Launch(execCmd)
	require.NoError(err)
}

func TestExecutor_Capabilities(t *testing.T) {
	t.Parallel()
	require := require.New(t)
	testutil.ExecCompatible(t)

	testExecCmd := testExecutorCommandWithChroot(t)
	execCmd, allocDir := testExecCmd.command, testExecCmd.allocDir
	defer allocDir.Destroy()

	execCmd.ResourceLimits = true
	execCmd.Cmd = "/bin/bash"
	execCmd.Args = []string{"-c", "cat /proc/$$/cmdline"}

	executor := NewExecutorWithIsolation(testlog.HCLogger(t))
	defer executor.Shutdown("SIGKILL", 0)

	_, err := executor.Launch(execCmd)
	require.NoError(err)

	ch := make(chan interface{})
	go func() {
		executor.Wait(context.Background())
		close(ch)
	}()

	select {
	case <-ch:
		// all good
	case <-time.After(5 * time.Second):
		require.Fail("timeout waiting for exec to shutdown")
	}

	output := testExecCmd.stdout.String()
	require.Empty(output)

}

func TestExecutor_ClientCleanup(t *testing.T) {
	t.Parallel()
	testutil.ExecCompatible(t)
	require := require.New(t)

	testExecCmd := testExecutorCommandWithChroot(t)
	execCmd, allocDir := testExecCmd.command, testExecCmd.allocDir
	defer allocDir.Destroy()

	executor := NewExecutorWithIsolation(testlog.HCLogger(t))
	defer executor.Shutdown("", 0)

	// Need to run a command which will produce continuous output but not
	// too quickly to ensure executor.Exit() stops the process.
	execCmd.Cmd = "/bin/bash"
	execCmd.Args = []string{"-c", "while true; do /bin/echo X; /bin/sleep 1; done"}
	execCmd.ResourceLimits = true

	ps, err := executor.Launch(execCmd)

	require.NoError(err)
	require.NotZero(ps.Pid)
	time.Sleep(500 * time.Millisecond)
	require.NoError(executor.Shutdown("SIGINT", 100*time.Millisecond))

	ch := make(chan interface{})
	go func() {
		executor.Wait(context.Background())
		close(ch)
	}()

	select {
	case <-ch:
		// all good
	case <-time.After(5 * time.Second):
		require.Fail("timeout waiting for exec to shutdown")
	}

	output := testExecCmd.stdout.String()
	require.NotZero(len(output))
	time.Sleep(2 * time.Second)
	output1 := testExecCmd.stdout.String()
	require.Equal(len(output), len(output1))
}

func TestExecutor_cmdDevices(t *testing.T) {
	input := []*drivers.DeviceConfig{
		{
			HostPath:    "/dev/null",
			TaskPath:    "/task/dev/null",
			Permissions: "rwm",
		},
	}

	expected := &lconfigs.Device{
		Path:        "/task/dev/null",
		Type:        99,
		Major:       1,
		Minor:       3,
		Permissions: "rwm",
	}

	found, err := cmdDevices(input)
	require.NoError(t, err)
	require.Len(t, found, 1)

	// ignore file permission and ownership
	// as they are host specific potentially
	d := found[0]
	d.FileMode = 0
	d.Uid = 0
	d.Gid = 0

	require.EqualValues(t, expected, d)
}

func TestExecutor_cmdMounts(t *testing.T) {
	input := []*drivers.MountConfig{
		{
			HostPath: "/host/path-ro",
			TaskPath: "/task/path-ro",
			Readonly: true,
		},
		{
			HostPath: "/host/path-rw",
			TaskPath: "/task/path-rw",
			Readonly: false,
		},
	}

	expected := []*lconfigs.Mount{
		{
			Source:      "/host/path-ro",
			Destination: "/task/path-ro",
			Flags:       unix.MS_BIND | unix.MS_RDONLY,
			Device:      "bind",
		},
		{
			Source:      "/host/path-rw",
			Destination: "/task/path-rw",
			Flags:       unix.MS_BIND,
			Device:      "bind",
		},
	}

	require.EqualValues(t, expected, cmdMounts(input))
}
<|endoftext|>


<commit_before>/*
Copyright 2016 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 csr

import (
	"crypto"
	"crypto/sha512"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/base64"
	"encoding/pem"
	"fmt"
	"reflect"
	"time"

	"k8s.io/klog"

	certificates "k8s.io/api/certificates/v1beta1"
	"k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/fields"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/apimachinery/pkg/util/wait"
	"k8s.io/apimachinery/pkg/watch"
	certificatesclient "k8s.io/client-go/kubernetes/typed/certificates/v1beta1"
	"k8s.io/client-go/tools/cache"
	watchtools "k8s.io/client-go/tools/watch"
	certutil "k8s.io/client-go/util/cert"
)

// RequestNodeCertificate will create a certificate signing request for a node
// (Organization and CommonName for the CSR will be set as expected for node
// certificates) and send it to API server, then it will watch the object's
// status, once approved by API server, it will return the API server's issued
// certificate (pem-encoded). If there is any errors, or the watch timeouts, it
// will return an error. This is intended for use on nodes (kubelet and
// kubeadm).
func RequestNodeCertificate(client certificatesclient.CertificateSigningRequestInterface, privateKeyData []byte, nodeName types.NodeName) (certData []byte, err error) {
	subject := &pkix.Name{
		Organization: []string{"system:nodes"},
		CommonName:   "system:node:" + string(nodeName),
	}

	privateKey, err := certutil.ParsePrivateKeyPEM(privateKeyData)
	if err != nil {
		return nil, fmt.Errorf("invalid private key for certificate request: %v", err)
	}
	csrData, err := certutil.MakeCSR(privateKey, subject, nil, nil)
	if err != nil {
		return nil, fmt.Errorf("unable to generate certificate request: %v", err)
	}

	usages := []certificates.KeyUsage{
		certificates.UsageDigitalSignature,
		certificates.UsageKeyEncipherment,
		certificates.UsageClientAuth,
	}
	name := digestedName(privateKeyData, subject, usages)
	req, err := RequestCertificate(client, csrData, name, usages, privateKey)
	if err != nil {
		return nil, err
	}
	return WaitForCertificate(client, req, 3600*time.Second)
}

// RequestCertificate will either use an existing (if this process has run
// before but not to completion) or create a certificate signing request using the
// PEM encoded CSR and send it to API server, then it will watch the object's
// status, once approved by API server, it will return the API server's issued
// certificate (pem-encoded). If there is any errors, or the watch timeouts, it
// will return an error.
func RequestCertificate(client certificatesclient.CertificateSigningRequestInterface, csrData []byte, name string, usages []certificates.KeyUsage, privateKey interface{}) (req *certificates.CertificateSigningRequest, err error) {
	csr := &certificates.CertificateSigningRequest{
		// Username, UID, Groups will be injected by API server.
		TypeMeta: metav1.TypeMeta{Kind: "CertificateSigningRequest"},
		ObjectMeta: metav1.ObjectMeta{
			Name: name,
		},
		Spec: certificates.CertificateSigningRequestSpec{
			Request: csrData,
			Usages:  usages,
		},
	}
	if len(csr.Name) == 0 {
		csr.GenerateName = "csr-"
	}

	req, err = client.Create(csr)
	switch {
	case err == nil:
	case errors.IsAlreadyExists(err) && len(name) > 0:
		klog.Infof("csr for this node already exists, reusing")
		req, err = client.Get(name, metav1.GetOptions{})
		if err != nil {
			return nil, formatError("cannot retrieve certificate signing request: %v", err)
		}
		if err := ensureCompatible(req, csr, privateKey); err != nil {
			return nil, fmt.Errorf("retrieved csr is not compatible: %v", err)
		}
		klog.Infof("csr for this node is still valid")
	default:
		return nil, formatError("cannot create certificate signing request: %v", err)
	}
	return req, nil
}

// WaitForCertificate waits for a certificate to be issued until timeout, or returns an error.
func WaitForCertificate(client certificatesclient.CertificateSigningRequestInterface, req *certificates.CertificateSigningRequest, timeout time.Duration) (certData []byte, err error) {
	fieldSelector := fields.OneTermEqualSelector("metadata.name", req.Name).String()

	event, err := watchtools.ListWatchUntil(
		timeout,
		&cache.ListWatch{
			ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
				options.FieldSelector = fieldSelector
				return client.List(options)
			},
			WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
				options.FieldSelector = fieldSelector
				return client.Watch(options)
			},
		},
		func(event watch.Event) (bool, error) {
			switch event.Type {
			case watch.Modified, watch.Added:
			case watch.Deleted:
				return false, fmt.Errorf("csr %q was deleted", req.Name)
			default:
				return false, nil
			}
			csr := event.Object.(*certificates.CertificateSigningRequest)
			if csr.UID != req.UID {
				return false, fmt.Errorf("csr %q changed UIDs", csr.Name)
			}
			for _, c := range csr.Status.Conditions {
				if c.Type == certificates.CertificateDenied {
					return false, fmt.Errorf("certificate signing request is not approved, reason: %v, message: %v", c.Reason, c.Message)
				}
				if c.Type == certificates.CertificateApproved && csr.Status.Certificate != nil {
					return true, nil
				}
			}
			return false, nil
		},
	)
	if err == wait.ErrWaitTimeout {
		return nil, wait.ErrWaitTimeout
	}
	if err != nil {
		return nil, formatError("cannot watch on the certificate signing request: %v", err)
	}

	return event.Object.(*certificates.CertificateSigningRequest).Status.Certificate, nil
}

// This digest should include all the relevant pieces of the CSR we care about.
// We can't direcly hash the serialized CSR because of random padding that we
// regenerate every loop and we include usages which are not contained in the
// CSR. This needs to be kept up to date as we add new fields to the node
// certificates and with ensureCompatible.
func digestedName(privateKeyData []byte, subject *pkix.Name, usages []certificates.KeyUsage) string {
	hash := sha512.New512_256()

	// Here we make sure two different inputs can't write the same stream
	// to the hash. This delimiter is not in the base64.URLEncoding
	// alphabet so there is no way to have spill over collisions. Without
	// it 'CN:foo,ORG:bar' hashes to the same value as 'CN:foob,ORG:ar'
	const delimiter = '|'
	encode := base64.RawURLEncoding.EncodeToString

	write := func(data []byte) {
		hash.Write([]byte(encode(data)))
		hash.Write([]byte{delimiter})
	}

	write(privateKeyData)
	write([]byte(subject.CommonName))
	for _, v := range subject.Organization {
		write([]byte(v))
	}
	for _, v := range usages {
		write([]byte(v))
	}

	return "node-csr-" + encode(hash.Sum(nil))
}

// ensureCompatible ensures that a CSR object is compatible with an original CSR
func ensureCompatible(new, orig *certificates.CertificateSigningRequest, privateKey interface{}) error {
	newCsr, err := ParseCSR(new)
	if err != nil {
		return fmt.Errorf("unable to parse new csr: %v", err)
	}
	origCsr, err := ParseCSR(orig)
	if err != nil {
		return fmt.Errorf("unable to parse original csr: %v", err)
	}
	if !reflect.DeepEqual(newCsr.Subject, origCsr.Subject) {
		return fmt.Errorf("csr subjects differ: new: %#v, orig: %#v", newCsr.Subject, origCsr.Subject)
	}
	signer, ok := privateKey.(crypto.Signer)
	if !ok {
		return fmt.Errorf("privateKey is not a signer")
	}
	newCsr.PublicKey = signer.Public()
	if err := newCsr.CheckSignature(); err != nil {
		return fmt.Errorf("error validating signature new CSR against old key: %v", err)
	}
	if len(new.Status.Certificate) > 0 {
		certs, err := certutil.ParseCertsPEM(new.Status.Certificate)
		if err != nil {
			return fmt.Errorf("error parsing signed certificate for CSR: %v", err)
		}
		now := time.Now()
		for _, cert := range certs {
			if now.After(cert.NotAfter) {
				return fmt.Errorf("one of the certificates for the CSR has expired: %s", cert.NotAfter)
			}
		}
	}
	return nil
}

// formatError preserves the type of an API message but alters the message. Expects
// a single argument format string, and returns the wrapped error.
func formatError(format string, err error) error {
	if s, ok := err.(errors.APIStatus); ok {
		se := &errors.StatusError{ErrStatus: s.Status()}
		se.ErrStatus.Message = fmt.Sprintf(format, se.ErrStatus.Message)
		return se
	}
	return fmt.Errorf(format, err)
}

// ParseCSR extracts the CSR from the API object and decodes it.
func ParseCSR(obj *certificates.CertificateSigningRequest) (*x509.CertificateRequest, error) {
	// extract PEM from request object
	pemBytes := obj.Spec.Request
	block, _ := pem.Decode(pemBytes)
	if block == nil || block.Type != "CERTIFICATE REQUEST" {
		return nil, fmt.Errorf("PEM block type must be CERTIFICATE REQUEST")
	}
	csr, err := x509.ParseCertificateRequest(block.Bytes)
	if err != nil {
		return nil, err
	}
	return csr, nil
}
<commit_msg>Unexport csr.ParseCSR<commit_after>/*
Copyright 2016 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 csr

import (
	"crypto"
	"crypto/sha512"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/base64"
	"encoding/pem"
	"fmt"
	"reflect"
	"time"

	"k8s.io/klog"

	certificates "k8s.io/api/certificates/v1beta1"
	"k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/fields"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/apimachinery/pkg/util/wait"
	"k8s.io/apimachinery/pkg/watch"
	certificatesclient "k8s.io/client-go/kubernetes/typed/certificates/v1beta1"
	"k8s.io/client-go/tools/cache"
	watchtools "k8s.io/client-go/tools/watch"
	certutil "k8s.io/client-go/util/cert"
)

// RequestNodeCertificate will create a certificate signing request for a node
// (Organization and CommonName for the CSR will be set as expected for node
// certificates) and send it to API server, then it will watch the object's
// status, once approved by API server, it will return the API server's issued
// certificate (pem-encoded). If there is any errors, or the watch timeouts, it
// will return an error. This is intended for use on nodes (kubelet and
// kubeadm).
func RequestNodeCertificate(client certificatesclient.CertificateSigningRequestInterface, privateKeyData []byte, nodeName types.NodeName) (certData []byte, err error) {
	subject := &pkix.Name{
		Organization: []string{"system:nodes"},
		CommonName:   "system:node:" + string(nodeName),
	}

	privateKey, err := certutil.ParsePrivateKeyPEM(privateKeyData)
	if err != nil {
		return nil, fmt.Errorf("invalid private key for certificate request: %v", err)
	}
	csrData, err := certutil.MakeCSR(privateKey, subject, nil, nil)
	if err != nil {
		return nil, fmt.Errorf("unable to generate certificate request: %v", err)
	}

	usages := []certificates.KeyUsage{
		certificates.UsageDigitalSignature,
		certificates.UsageKeyEncipherment,
		certificates.UsageClientAuth,
	}
	name := digestedName(privateKeyData, subject, usages)
	req, err := RequestCertificate(client, csrData, name, usages, privateKey)
	if err != nil {
		return nil, err
	}
	return WaitForCertificate(client, req, 3600*time.Second)
}

// RequestCertificate will either use an existing (if this process has run
// before but not to completion) or create a certificate signing request using the
// PEM encoded CSR and send it to API server, then it will watch the object's
// status, once approved by API server, it will return the API server's issued
// certificate (pem-encoded). If there is any errors, or the watch timeouts, it
// will return an error.
func RequestCertificate(client certificatesclient.CertificateSigningRequestInterface, csrData []byte, name string, usages []certificates.KeyUsage, privateKey interface{}) (req *certificates.CertificateSigningRequest, err error) {
	csr := &certificates.CertificateSigningRequest{
		// Username, UID, Groups will be injected by API server.
		TypeMeta: metav1.TypeMeta{Kind: "CertificateSigningRequest"},
		ObjectMeta: metav1.ObjectMeta{
			Name: name,
		},
		Spec: certificates.CertificateSigningRequestSpec{
			Request: csrData,
			Usages:  usages,
		},
	}
	if len(csr.Name) == 0 {
		csr.GenerateName = "csr-"
	}

	req, err = client.Create(csr)
	switch {
	case err == nil:
	case errors.IsAlreadyExists(err) && len(name) > 0:
		klog.Infof("csr for this node already exists, reusing")
		req, err = client.Get(name, metav1.GetOptions{})
		if err != nil {
			return nil, formatError("cannot retrieve certificate signing request: %v", err)
		}
		if err := ensureCompatible(req, csr, privateKey); err != nil {
			return nil, fmt.Errorf("retrieved csr is not compatible: %v", err)
		}
		klog.Infof("csr for this node is still valid")
	default:
		return nil, formatError("cannot create certificate signing request: %v", err)
	}
	return req, nil
}

// WaitForCertificate waits for a certificate to be issued until timeout, or returns an error.
func WaitForCertificate(client certificatesclient.CertificateSigningRequestInterface, req *certificates.CertificateSigningRequest, timeout time.Duration) (certData []byte, err error) {
	fieldSelector := fields.OneTermEqualSelector("metadata.name", req.Name).String()

	event, err := watchtools.ListWatchUntil(
		timeout,
		&cache.ListWatch{
			ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
				options.FieldSelector = fieldSelector
				return client.List(options)
			},
			WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
				options.FieldSelector = fieldSelector
				return client.Watch(options)
			},
		},
		func(event watch.Event) (bool, error) {
			switch event.Type {
			case watch.Modified, watch.Added:
			case watch.Deleted:
				return false, fmt.Errorf("csr %q was deleted", req.Name)
			default:
				return false, nil
			}
			csr := event.Object.(*certificates.CertificateSigningRequest)
			if csr.UID != req.UID {
				return false, fmt.Errorf("csr %q changed UIDs", csr.Name)
			}
			for _, c := range csr.Status.Conditions {
				if c.Type == certificates.CertificateDenied {
					return false, fmt.Errorf("certificate signing request is not approved, reason: %v, message: %v", c.Reason, c.Message)
				}
				if c.Type == certificates.CertificateApproved && csr.Status.Certificate != nil {
					return true, nil
				}
			}
			return false, nil
		},
	)
	if err == wait.ErrWaitTimeout {
		return nil, wait.ErrWaitTimeout
	}
	if err != nil {
		return nil, formatError("cannot watch on the certificate signing request: %v", err)
	}

	return event.Object.(*certificates.CertificateSigningRequest).Status.Certificate, nil
}

// This digest should include all the relevant pieces of the CSR we care about.
// We can't direcly hash the serialized CSR because of random padding that we
// regenerate every loop and we include usages which are not contained in the
// CSR. This needs to be kept up to date as we add new fields to the node
// certificates and with ensureCompatible.
func digestedName(privateKeyData []byte, subject *pkix.Name, usages []certificates.KeyUsage) string {
	hash := sha512.New512_256()

	// Here we make sure two different inputs can't write the same stream
	// to the hash. This delimiter is not in the base64.URLEncoding
	// alphabet so there is no way to have spill over collisions. Without
	// it 'CN:foo,ORG:bar' hashes to the same value as 'CN:foob,ORG:ar'
	const delimiter = '|'
	encode := base64.RawURLEncoding.EncodeToString

	write := func(data []byte) {
		hash.Write([]byte(encode(data)))
		hash.Write([]byte{delimiter})
	}

	write(privateKeyData)
	write([]byte(subject.CommonName))
	for _, v := range subject.Organization {
		write([]byte(v))
	}
	for _, v := range usages {
		write([]byte(v))
	}

	return "node-csr-" + encode(hash.Sum(nil))
}

// ensureCompatible ensures that a CSR object is compatible with an original CSR
func ensureCompatible(new, orig *certificates.CertificateSigningRequest, privateKey interface{}) error {
	newCSR, err := parseCSR(new)
	if err != nil {
		return fmt.Errorf("unable to parse new csr: %v", err)
	}
	origCSR, err := parseCSR(orig)
	if err != nil {
		return fmt.Errorf("unable to parse original csr: %v", err)
	}
	if !reflect.DeepEqual(newCSR.Subject, origCSR.Subject) {
		return fmt.Errorf("csr subjects differ: new: %#v, orig: %#v", newCSR.Subject, origCSR.Subject)
	}
	signer, ok := privateKey.(crypto.Signer)
	if !ok {
		return fmt.Errorf("privateKey is not a signer")
	}
	newCSR.PublicKey = signer.Public()
	if err := newCSR.CheckSignature(); err != nil {
		return fmt.Errorf("error validating signature new CSR against old key: %v", err)
	}
	if len(new.Status.Certificate) > 0 {
		certs, err := certutil.ParseCertsPEM(new.Status.Certificate)
		if err != nil {
			return fmt.Errorf("error parsing signed certificate for CSR: %v", err)
		}
		now := time.Now()
		for _, cert := range certs {
			if now.After(cert.NotAfter) {
				return fmt.Errorf("one of the certificates for the CSR has expired: %s", cert.NotAfter)
			}
		}
	}
	return nil
}

// formatError preserves the type of an API message but alters the message. Expects
// a single argument format string, and returns the wrapped error.
func formatError(format string, err error) error {
	if s, ok := err.(errors.APIStatus); ok {
		se := &errors.StatusError{ErrStatus: s.Status()}
		se.ErrStatus.Message = fmt.Sprintf(format, se.ErrStatus.Message)
		return se
	}
	return fmt.Errorf(format, err)
}

// parseCSR extracts the CSR from the API object and decodes it.
func parseCSR(obj *certificates.CertificateSigningRequest) (*x509.CertificateRequest, error) {
	// extract PEM from request object
	block, _ := pem.Decode(obj.Spec.Request)
	if block == nil || block.Type != "CERTIFICATE REQUEST" {
		return nil, fmt.Errorf("PEM block type must be CERTIFICATE REQUEST")
	}
	return x509.ParseCertificateRequest(block.Bytes)
}
<|endoftext|>


<commit_before>// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package test

import (
	"fmt"
	"os"
	"path/filepath"

	"v.io/x/devtools/internal/collect"
	"v.io/x/devtools/internal/test"
	"v.io/x/devtools/internal/tool"
	"v.io/x/devtools/internal/util"
	"v.io/x/devtools/internal/xunit"
)

var (
	mirrors = []Mirror{
		Mirror{
			name:         "blue",
			googlesource: "https://vanadium.googlesource.com/roadmap.blue",
			github:       "git@github.com:veyron/blue.git",
		},
		Mirror{
			name:         "browser",
			googlesource: "https://vanadium.googlesource.com/release.projects.browser",
			github:       "git@github.com:vanadium/browser.git",
		},
		Mirror{
			name:         "go.v23",
			googlesource: "https://vanadium.googlesource.com/release.go.v23",
			github:       "git@github.com:vanadium/go.v23.git",
		},
		Mirror{
			name:         "go.devtools",
			googlesource: "https://vanadium.googlesource.com/release.go.x.devtools",
			github:       "git@github.com:vanadium/go.devtools.git",
		},
		Mirror{
			name:         "go.lib",
			googlesource: "https://vanadium.googlesource.com/release.go.x.lib",
			github:       "git@github.com:vanadium/go.lib.git",
		},
		Mirror{
			name:         "go.ref",
			googlesource: "https://vanadium.googlesource.com/release.go.x.ref",
			github:       "git@github.com:vanadium/go.ref.git",
		},
		Mirror{
			name:         "js",
			googlesource: "https://vanadium.googlesource.com/release.js.core",
			github:       "git@github.com:vanadium/js.git",
		},
		Mirror{
			name:         "chat",
			googlesource: "https://vanadium.googlesource.com/release.projects.chat",
			github:       "git@github.com:vanadium/chat.git",
		},
		Mirror{
			name:         "media-sharing",
			googlesource: "https://vanadium.googlesource.com/release.projects.media-sharing",
			github:       "git@github.com:vanadium/media-sharing.git",
		},
		Mirror{
			name:         "pipe2browser",
			googlesource: "https://vanadium.googlesource.com/release.projects.pipe2browser",
			github:       "git@github.com:vanadium/pipe2browser.git",
		},
		Mirror{
			name:         "playground",
			googlesource: "https://vanadium.googlesource.com/release.projects.playground",
			github:       "git@github.com:vanadium/playground.git",
		},
		Mirror{
			name:         "physical-lock",
			googlesource: "https://vanadium.googlesource.com/release.projects.physical-lock",
			github:       "git@github.com:vanadium/physical-lock.git",
		},
		Mirror{
			name:         "reader",
			googlesource: "https://vanadium.googlesource.com/release.projects.reader",
			github:       "git@github.com:vanadium/reader.git",
		},
		Mirror{
			name:         "third_party",
			googlesource: "https://vanadium.googlesource.com/third_party",
			github:       "git@github.com:vanadium/third_party.git",
		},
		Mirror{
			name:         "www",
			googlesource: "https://vanadium.googlesource.com/www",
			github:       "git@github.com:vanadium/www.git",
		},
	}
)

type Mirror struct {
	name, googlesource, github string
}

// vanadiumGitHubMirror mirrors googlesource.com vanadium projects to
// github.com.
func vanadiumGitHubMirror(ctx *tool.Context, testName string, _ ...Opt) (_ *test.Result, e error) {
	// Initialize the test/task.
	cleanup, err := initTest(ctx, testName, nil)
	if err != nil {
		return nil, internalTestError{err, "Init"}
	}
	defer collect.Error(func() error { return cleanup() }, &e)

	root, err := util.V23Root()
	if err != nil {
		return nil, internalTestError{err, "V23Root"}
	}

	projects := filepath.Join(root, "projects")
	mode := os.FileMode(0755)
	if err := ctx.Run().MkdirAll(projects, mode); err != nil {
		return nil, internalTestError{err, "MkdirAll"}
	}

	allPassed := true
	suites := []xunit.TestSuite{}
	for _, mirror := range mirrors {
		suite, err := sync(ctx, mirror, projects)
		if err != nil {
			return nil, internalTestError{err, "sync"}
		}

		allPassed = allPassed && (suite.Failures == 0)
		suites = append(suites, *suite)
	}

	if err := xunit.CreateReport(ctx, testName, suites); err != nil {
		return nil, err
	}

	if !allPassed {
		return &test.Result{Status: test.Failed}, nil
	}

	return &test.Result{Status: test.Passed}, nil
}

func sync(ctx *tool.Context, mirror Mirror, projects string) (*xunit.TestSuite, error) {
	suite := xunit.TestSuite{Name: mirror.name}
	dirname := filepath.Join(projects, mirror.name)

	// If dirname does not exist `git clone` otherwise `git pull`.
	if _, err := os.Stat(dirname); err != nil {
		if !os.IsNotExist(err) {
			return nil, internalTestError{err, "Stat"}
		}

		err := clone(ctx, mirror, projects)
		testCase := makeTestCase("clone", err)
		if err != nil {
			suite.Failures++
		}
		suite.Cases = append(suite.Cases, *testCase)
	} else {
		err := pull(ctx, mirror, projects)
		testCase := makeTestCase("pull", err)
		if err != nil {
			suite.Failures++
		}
		suite.Cases = append(suite.Cases, *testCase)
	}

	err := push(ctx, mirror, projects)
	testCase := makeTestCase("push", err)
	if err != nil {
		suite.Failures++
	}
	suite.Cases = append(suite.Cases, *testCase)

	return &suite, nil
}

func makeTestCase(action string, err error) *xunit.TestCase {
	c := xunit.TestCase{
		Classname: "git",
		Name:      action,
	}

	if err != nil {
		f := xunit.Failure{
			Message: "git error",
			Data:    fmt.Sprintf("%v", err),
		}
		c.Failures = append(c.Failures, f)
	}

	return &c
}

func clone(ctx *tool.Context, mirror Mirror, projects string) error {
	dirname := filepath.Join(projects, mirror.name)
	return ctx.Git().Clone(mirror.googlesource, dirname)
}

func pull(ctx *tool.Context, mirror Mirror, projects string) error {
	dirname := filepath.Join(projects, mirror.name)
	opts := tool.RootDirOpt(dirname)
	return ctx.Git(opts).Pull("origin", "master")
}

func push(ctx *tool.Context, mirror Mirror, projects string) error {
	dirname := filepath.Join(projects, mirror.name)
	opts := tool.RootDirOpt(dirname)
	return ctx.Git(opts).Push(mirror.github, "master")
}
<commit_msg>Revert "TBR: Add mirroring from roadmap.blue to github.com/veyron/blue"<commit_after>// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package test

import (
	"fmt"
	"os"
	"path/filepath"

	"v.io/x/devtools/internal/collect"
	"v.io/x/devtools/internal/test"
	"v.io/x/devtools/internal/tool"
	"v.io/x/devtools/internal/util"
	"v.io/x/devtools/internal/xunit"
)

var (
	mirrors = []Mirror{
		Mirror{
			name:         "browser",
			googlesource: "https://vanadium.googlesource.com/release.projects.browser",
			github:       "git@github.com:vanadium/browser.git",
		},
		Mirror{
			name:         "go.v23",
			googlesource: "https://vanadium.googlesource.com/release.go.v23",
			github:       "git@github.com:vanadium/go.v23.git",
		},
		Mirror{
			name:         "go.devtools",
			googlesource: "https://vanadium.googlesource.com/release.go.x.devtools",
			github:       "git@github.com:vanadium/go.devtools.git",
		},
		Mirror{
			name:         "go.lib",
			googlesource: "https://vanadium.googlesource.com/release.go.x.lib",
			github:       "git@github.com:vanadium/go.lib.git",
		},
		Mirror{
			name:         "go.ref",
			googlesource: "https://vanadium.googlesource.com/release.go.x.ref",
			github:       "git@github.com:vanadium/go.ref.git",
		},
		Mirror{
			name:         "js",
			googlesource: "https://vanadium.googlesource.com/release.js.core",
			github:       "git@github.com:vanadium/js.git",
		},
		Mirror{
			name:         "chat",
			googlesource: "https://vanadium.googlesource.com/release.projects.chat",
			github:       "git@github.com:vanadium/chat.git",
		},
		Mirror{
			name:         "media-sharing",
			googlesource: "https://vanadium.googlesource.com/release.projects.media-sharing",
			github:       "git@github.com:vanadium/media-sharing.git",
		},
		Mirror{
			name:         "pipe2browser",
			googlesource: "https://vanadium.googlesource.com/release.projects.pipe2browser",
			github:       "git@github.com:vanadium/pipe2browser.git",
		},
		Mirror{
			name:         "playground",
			googlesource: "https://vanadium.googlesource.com/release.projects.playground",
			github:       "git@github.com:vanadium/playground.git",
		},
		Mirror{
			name:         "physical-lock",
			googlesource: "https://vanadium.googlesource.com/release.projects.physical-lock",
			github:       "git@github.com:vanadium/physical-lock.git",
		},
		Mirror{
			name:         "reader",
			googlesource: "https://vanadium.googlesource.com/release.projects.reader",
			github:       "git@github.com:vanadium/reader.git",
		},
		Mirror{
			name:         "third_party",
			googlesource: "https://vanadium.googlesource.com/third_party",
			github:       "git@github.com:vanadium/third_party.git",
		},
		Mirror{
			name:         "www",
			googlesource: "https://vanadium.googlesource.com/www",
			github:       "git@github.com:vanadium/www.git",
		},
	}
)

type Mirror struct {
	name, googlesource, github string
}

// vanadiumGitHubMirror mirrors googlesource.com vanadium projects to
// github.com.
func vanadiumGitHubMirror(ctx *tool.Context, testName string, _ ...Opt) (_ *test.Result, e error) {
	// Initialize the test/task.
	cleanup, err := initTest(ctx, testName, nil)
	if err != nil {
		return nil, internalTestError{err, "Init"}
	}
	defer collect.Error(func() error { return cleanup() }, &e)

	root, err := util.V23Root()
	if err != nil {
		return nil, internalTestError{err, "V23Root"}
	}

	projects := filepath.Join(root, "projects")
	mode := os.FileMode(0755)
	if err := ctx.Run().MkdirAll(projects, mode); err != nil {
		return nil, internalTestError{err, "MkdirAll"}
	}

	allPassed := true
	suites := []xunit.TestSuite{}
	for _, mirror := range mirrors {
		suite, err := sync(ctx, mirror, projects)
		if err != nil {
			return nil, internalTestError{err, "sync"}
		}

		allPassed = allPassed && (suite.Failures == 0)
		suites = append(suites, *suite)
	}

	if err := xunit.CreateReport(ctx, testName, suites); err != nil {
		return nil, err
	}

	if !allPassed {
		return &test.Result{Status: test.Failed}, nil
	}

	return &test.Result{Status: test.Passed}, nil
}

func sync(ctx *tool.Context, mirror Mirror, projects string) (*xunit.TestSuite, error) {
	suite := xunit.TestSuite{Name: mirror.name}
	dirname := filepath.Join(projects, mirror.name)

	// If dirname does not exist `git clone` otherwise `git pull`.
	if _, err := os.Stat(dirname); err != nil {
		if !os.IsNotExist(err) {
			return nil, internalTestError{err, "Stat"}
		}

		err := clone(ctx, mirror, projects)
		testCase := makeTestCase("clone", err)
		if err != nil {
			suite.Failures++
		}
		suite.Cases = append(suite.Cases, *testCase)
	} else {
		err := pull(ctx, mirror, projects)
		testCase := makeTestCase("pull", err)
		if err != nil {
			suite.Failures++
		}
		suite.Cases = append(suite.Cases, *testCase)
	}

	err := push(ctx, mirror, projects)
	testCase := makeTestCase("push", err)
	if err != nil {
		suite.Failures++
	}
	suite.Cases = append(suite.Cases, *testCase)

	return &suite, nil
}

func makeTestCase(action string, err error) *xunit.TestCase {
	c := xunit.TestCase{
		Classname: "git",
		Name:      action,
	}

	if err != nil {
		f := xunit.Failure{
			Message: "git error",
			Data:    fmt.Sprintf("%v", err),
		}
		c.Failures = append(c.Failures, f)
	}

	return &c
}

func clone(ctx *tool.Context, mirror Mirror, projects string) error {
	dirname := filepath.Join(projects, mirror.name)
	return ctx.Git().Clone(mirror.googlesource, dirname)
}

func pull(ctx *tool.Context, mirror Mirror, projects string) error {
	dirname := filepath.Join(projects, mirror.name)
	opts := tool.RootDirOpt(dirname)
	return ctx.Git(opts).Pull("origin", "master")
}

func push(ctx *tool.Context, mirror Mirror, projects string) error {
	dirname := filepath.Join(projects, mirror.name)
	opts := tool.RootDirOpt(dirname)
	return ctx.Git(opts).Push(mirror.github, "master")
}
<|endoftext|>


<commit_before>/*
Copyright 2021 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 apimachinery

import (
	"context"
	"strings"

	"github.com/onsi/ginkgo"
	"k8s.io/apimachinery/pkg/runtime/schema"

	v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
	"k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
	"k8s.io/apiextensions-apiserver/test/integration/fixtures"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"k8s.io/apimachinery/pkg/util/json"
	"k8s.io/apiserver/pkg/storage/names"
	"k8s.io/client-go/dynamic"
	"k8s.io/kubernetes/test/e2e/framework"
)

var _ = SIGDescribe("CustomResourceValidationRules [Privileged:ClusterAdmin][Alpha][Feature:CustomResourceValidationExpressions]", func() {
	f := framework.NewDefaultFramework("crd-validation-expressions")

	var apiExtensionClient *clientset.Clientset
	ginkgo.BeforeEach(func() {
		var err error
		apiExtensionClient, err = clientset.NewForConfig(f.ClientConfig())
		framework.ExpectNoError(err, "initializing apiExtensionClient")
	})

	customResourceClient := func(crd *v1.CustomResourceDefinition) (dynamic.NamespaceableResourceInterface, schema.GroupVersionResource) {
		gvrs := fixtures.GetGroupVersionResourcesOfCustomResource(crd)
		if len(gvrs) != 1 {
			ginkgo.Fail("Expected one version in custom resource definition")
		}
		gvr := gvrs[0]
		return f.DynamicClient.Resource(gvr), gvr
	}
	unmarshallSchema := func(schemaJson []byte) *v1.JSONSchemaProps {
		var c v1.JSONSchemaProps
		err := json.Unmarshal(schemaJson, &c)
		framework.ExpectNoError(err, "unmarshalling OpenAPIv3 schema")
		return &c
	}

	var schemaWithValidationExpression = unmarshallSchema([]byte(`{
	   "type":"object",
	   "properties":{
		  "spec":{
			 "type":"object",
			 "x-kubernetes-validations":[
		       { "rule":"self.x + self.y > 0" }
	         ],
			 "properties":{
				"x":{ "type":"integer" },
				"y":{ "type":"integer" }
			 }
		  },
		  "status":{
			 "type":"object",
			 "x-kubernetes-validations":[
				{ "rule":"self.health == 'ok' || self.health == 'unhealthy'" }
			 ],
			 "properties":{
				"health":{ "type":"string" }
			 }
		  }
	   }
	}`))
	ginkgo.It("MUST NOT fail validation for create of a custom resource that satisfies the x-kubernetes-validator rules", func() {
		ginkgo.By("Creating a custom resource definition with validation rules")
		crd := fixtures.NewRandomNameV1CustomResourceDefinitionWithSchema(v1.NamespaceScoped, schemaWithValidationExpression, false)
		crd, err := fixtures.CreateNewV1CustomResourceDefinitionWatchUnsafe(crd, apiExtensionClient)
		framework.ExpectNoError(err, "creating CustomResourceDefinition")
		defer func() {
			err = fixtures.DeleteV1CustomResourceDefinition(crd, apiExtensionClient)
			framework.ExpectNoError(err, "deleting CustomResourceDefinition")
		}()

		ginkgo.By("Creating a custom resource with values that are allowed by the validation rules set on the custom resource definition")
		crClient, gvr := customResourceClient(crd)
		name1 := names.SimpleNameGenerator.GenerateName("cr-1")
		_, err = crClient.Namespace(f.Namespace.Name).Create(context.TODO(), &unstructured.Unstructured{Object: map[string]interface{}{
			"apiVersion": gvr.Group + "/" + gvr.Version,
			"kind":       crd.Spec.Names.Kind,
			"metadata": map[string]interface{}{
				"name":      name1,
				"namespace": f.Namespace.Name,
			},
			"spec": map[string]interface{}{
				"x": int64(1),
				"y": int64(0),
			},
		}}, metav1.CreateOptions{})
		framework.ExpectNoError(err, "validation rules satisfied")
	})
	ginkgo.It("MUST fail validation for create of a custom resource that does not satisfy the x-kubernetes-validator rules", func() {
		ginkgo.By("Creating a custom resource definition with validation rules")
		crd := fixtures.NewRandomNameV1CustomResourceDefinitionWithSchema(v1.NamespaceScoped, schemaWithValidationExpression, false)
		crd, err := fixtures.CreateNewV1CustomResourceDefinitionWatchUnsafe(crd, apiExtensionClient)
		framework.ExpectNoError(err, "creating CustomResourceDefinition")
		defer func() {
			err = fixtures.DeleteV1CustomResourceDefinition(crd, apiExtensionClient)
			framework.ExpectNoError(err, "deleting CustomResourceDefinition")
		}()

		ginkgo.By("Creating a custom resource with values that fail the validation rules set on the custom resource definition")
		crClient, gvr := customResourceClient(crd)
		name1 := names.SimpleNameGenerator.GenerateName("cr-1")
		_, err = crClient.Namespace(f.Namespace.Name).Create(context.TODO(), &unstructured.Unstructured{Object: map[string]interface{}{
			"apiVersion": gvr.Group + "/" + gvr.Version,
			"kind":       crd.Spec.Names.Kind,
			"metadata": map[string]interface{}{
				"name":      name1,
				"namespace": f.Namespace.Name,
			},
			"spec": map[string]interface{}{
				"x": int64(0),
				"y": int64(0),
			},
		}}, metav1.CreateOptions{})
		framework.ExpectError(err, "validation rules not satisfied")
		expectedErrMsg := "failed rule"
		if !strings.Contains(err.Error(), expectedErrMsg) {
			framework.Failf("expect error contains %q, got %q", expectedErrMsg, err.Error())
		}
	})

	ginkgo.It("MUST fail create of a custom resource definition that contains a x-kubernetes-validator rule that refers to a property that do not exist", func() {
		ginkgo.By("Defining a custom resource definition with a validation rule that refers to a property that do not exist")
		var schemaWithInvalidValidationRule = unmarshallSchema([]byte(`{
		   "type":"object",
		   "properties":{
			  "spec":{
				 "type":"object",
				 "x-kubernetes-validations":[
				   { "rule":"self.z == 100" }
				 ],
				 "properties":{
					"x":{ "type":"integer" }
				 }
			  }
		   }
		}`))
		crd := fixtures.NewRandomNameV1CustomResourceDefinitionWithSchema(v1.NamespaceScoped, schemaWithInvalidValidationRule, false)
		_, err := fixtures.CreateNewV1CustomResourceDefinitionWatchUnsafe(crd, apiExtensionClient)
		framework.ExpectError(err, "creating CustomResourceDefinition with a validation rule that refers to a property that do not exist")
		expectedErrMsg := "undefined field 'z'"
		if !strings.Contains(err.Error(), expectedErrMsg) {
			framework.Failf("expect error contains %q, got %q", expectedErrMsg, err.Error())
		}
	})
})
<commit_msg>UPSTREAM: <drop>: Fix e2e test to solve ginkgo panic with make update<commit_after>/*
Copyright 2021 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 apimachinery

import (
	"context"
	"strings"

	"github.com/onsi/ginkgo"
	"k8s.io/apimachinery/pkg/runtime/schema"

	v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
	"k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
	"k8s.io/apiextensions-apiserver/test/integration/fixtures"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"k8s.io/apimachinery/pkg/util/json"
	"k8s.io/apiserver/pkg/storage/names"
	"k8s.io/client-go/dynamic"
	"k8s.io/kubernetes/test/e2e/framework"
)

var _ = SIGDescribe("CustomResourceValidationRules [Privileged:ClusterAdmin][Alpha][Feature:CustomResourceValidationExpressions]", func() {
	f := framework.NewDefaultFramework("crd-validation-expressions")

	var apiExtensionClient *clientset.Clientset
	ginkgo.BeforeEach(func() {
		var err error
		apiExtensionClient, err = clientset.NewForConfig(f.ClientConfig())
		framework.ExpectNoError(err, "initializing apiExtensionClient")
	})

	customResourceClient := func(crd *v1.CustomResourceDefinition) (dynamic.NamespaceableResourceInterface, schema.GroupVersionResource) {
		gvrs := fixtures.GetGroupVersionResourcesOfCustomResource(crd)
		if len(gvrs) != 1 {
			ginkgo.Fail("Expected one version in custom resource definition")
		}
		gvr := gvrs[0]
		return f.DynamicClient.Resource(gvr), gvr
	}
	unmarshallSchema := func(schemaJson []byte) *v1.JSONSchemaProps {
		var c v1.JSONSchemaProps
		err := json.Unmarshal(schemaJson, &c)
		framework.ExpectNoError(err, "unmarshalling OpenAPIv3 schema")
		return &c
	}

	ginkgo.It("MUST NOT fail validation for create of a custom resource that satisfies the x-kubernetes-validator rules", func() {
		ginkgo.By("Creating a custom resource definition with validation rules")
		var schemaWithValidationExpression = unmarshallSchema([]byte(`{
			"type":"object",
			"properties":{
			   "spec":{
				  "type":"object",
				  "x-kubernetes-validations":[
					{ "rule":"self.x + self.y > 0" }
				  ],
				  "properties":{
					 "x":{ "type":"integer" },
					 "y":{ "type":"integer" }
				  }
			   },
			   "status":{
				  "type":"object",
				  "x-kubernetes-validations":[
					 { "rule":"self.health == 'ok' || self.health == 'unhealthy'" }
				  ],
				  "properties":{
					 "health":{ "type":"string" }
				  }
			   }
			}
		 }`))
		crd := fixtures.NewRandomNameV1CustomResourceDefinitionWithSchema(v1.NamespaceScoped, schemaWithValidationExpression, false)
		crd, err := fixtures.CreateNewV1CustomResourceDefinitionWatchUnsafe(crd, apiExtensionClient)
		framework.ExpectNoError(err, "creating CustomResourceDefinition")
		defer func() {
			err = fixtures.DeleteV1CustomResourceDefinition(crd, apiExtensionClient)
			framework.ExpectNoError(err, "deleting CustomResourceDefinition")
		}()

		ginkgo.By("Creating a custom resource with values that are allowed by the validation rules set on the custom resource definition")
		crClient, gvr := customResourceClient(crd)
		name1 := names.SimpleNameGenerator.GenerateName("cr-1")
		_, err = crClient.Namespace(f.Namespace.Name).Create(context.TODO(), &unstructured.Unstructured{Object: map[string]interface{}{
			"apiVersion": gvr.Group + "/" + gvr.Version,
			"kind":       crd.Spec.Names.Kind,
			"metadata": map[string]interface{}{
				"name":      name1,
				"namespace": f.Namespace.Name,
			},
			"spec": map[string]interface{}{
				"x": int64(1),
				"y": int64(0),
			},
		}}, metav1.CreateOptions{})
		framework.ExpectNoError(err, "validation rules satisfied")
	})
	ginkgo.It("MUST fail validation for create of a custom resource that does not satisfy the x-kubernetes-validator rules", func() {
		ginkgo.By("Creating a custom resource definition with validation rules")
		var schemaWithValidationExpression = unmarshallSchema([]byte(`{
			"type":"object",
			"properties":{
			   "spec":{
				  "type":"object",
				  "x-kubernetes-validations":[
					{ "rule":"self.x + self.y > 0" }
				  ],
				  "properties":{
					 "x":{ "type":"integer" },
					 "y":{ "type":"integer" }
				  }
			   },
			   "status":{
				  "type":"object",
				  "x-kubernetes-validations":[
					 { "rule":"self.health == 'ok' || self.health == 'unhealthy'" }
				  ],
				  "properties":{
					 "health":{ "type":"string" }
				  }
			   }
			}
		 }`))
		crd := fixtures.NewRandomNameV1CustomResourceDefinitionWithSchema(v1.NamespaceScoped, schemaWithValidationExpression, false)
		crd, err := fixtures.CreateNewV1CustomResourceDefinitionWatchUnsafe(crd, apiExtensionClient)
		framework.ExpectNoError(err, "creating CustomResourceDefinition")
		defer func() {
			err = fixtures.DeleteV1CustomResourceDefinition(crd, apiExtensionClient)
			framework.ExpectNoError(err, "deleting CustomResourceDefinition")
		}()

		ginkgo.By("Creating a custom resource with values that fail the validation rules set on the custom resource definition")
		crClient, gvr := customResourceClient(crd)
		name1 := names.SimpleNameGenerator.GenerateName("cr-1")
		_, err = crClient.Namespace(f.Namespace.Name).Create(context.TODO(), &unstructured.Unstructured{Object: map[string]interface{}{
			"apiVersion": gvr.Group + "/" + gvr.Version,
			"kind":       crd.Spec.Names.Kind,
			"metadata": map[string]interface{}{
				"name":      name1,
				"namespace": f.Namespace.Name,
			},
			"spec": map[string]interface{}{
				"x": int64(0),
				"y": int64(0),
			},
		}}, metav1.CreateOptions{})
		framework.ExpectError(err, "validation rules not satisfied")
		expectedErrMsg := "failed rule"
		if !strings.Contains(err.Error(), expectedErrMsg) {
			framework.Failf("expect error contains %q, got %q", expectedErrMsg, err.Error())
		}
	})

	ginkgo.It("MUST fail create of a custom resource definition that contains a x-kubernetes-validator rule that refers to a property that do not exist", func() {
		ginkgo.By("Defining a custom resource definition with a validation rule that refers to a property that do not exist")
		var schemaWithInvalidValidationRule = unmarshallSchema([]byte(`{
		   "type":"object",
		   "properties":{
			  "spec":{
				 "type":"object",
				 "x-kubernetes-validations":[
				   { "rule":"self.z == 100" }
				 ],
				 "properties":{
					"x":{ "type":"integer" }
				 }
			  }
		   }
		}`))
		crd := fixtures.NewRandomNameV1CustomResourceDefinitionWithSchema(v1.NamespaceScoped, schemaWithInvalidValidationRule, false)
		_, err := fixtures.CreateNewV1CustomResourceDefinitionWatchUnsafe(crd, apiExtensionClient)
		framework.ExpectError(err, "creating CustomResourceDefinition with a validation rule that refers to a property that do not exist")
		expectedErrMsg := "undefined field 'z'"
		if !strings.Contains(err.Error(), expectedErrMsg) {
			framework.Failf("expect error contains %q, got %q", expectedErrMsg, err.Error())
		}
	})
})
<|endoftext|>


<commit_before>package api

import (
	"encoding/json"
	"fmt"
	"net/http"
	"time"

	"strconv"

	"../miniserver"
	"../utils"
)

const KA_TAG = "kerneladiutor"

type KernelAdiutorApi struct {
	client     *miniserver.Client
	path       string
	version    string
	devicedata *DeviceData
}

func (kaAPi KernelAdiutorApi) GetResponse() *miniserver.Response {
	switch kaAPi.version {
	case "v1":
		return kaAPi.kernelAdiutorApiv1()
	default:
		return nil
	}
}

func NewKernelAdiutorApi(client *miniserver.Client,
	path, version string,
	dData *DeviceData) KernelAdiutorApi {
	return KernelAdiutorApi{
		client:     client,
		path:       path,
		version:    version,
		devicedata: dData,
	}
}

func (kaAPi KernelAdiutorApi) kernelAdiutorApiv1() *miniserver.Response {
	var response *miniserver.Response

	switch kaAPi.path {
	case "device/create":
		if kaAPi.client.Method == http.MethodPost &&
			len(kaAPi.client.Request) > 0 {

			var data map[string]interface{}
			json.Unmarshal(kaAPi.client.Request, &data)

			var dInfo *DeviceInfo = NewDeviceInfo(data)
			if dInfo.valid() {

				var updated bool = kaAPi.putDatabase(dInfo)
				if b, err := kaAPi.createStatus(true); err == nil {
					response = kaAPi.client.ResponseBody(string(b))
				}

				if updated {
					utils.LogI(KA_TAG, fmt.Sprintf("Updating device %s", dInfo.Model))
				} else {
					utils.LogI(KA_TAG, fmt.Sprintf("Inserting device %s", dInfo.Model))
				}
			}
		}
	case "device/get":
		if kaAPi.client.Method == http.MethodGet {

			// Get all
			if page, pageok := kaAPi.client.Queries["page"]; (pageok && len(kaAPi.client.Queries) == 1) ||
				len(kaAPi.client.Queries) == 0 {

				var pageNumber int = 1
				if pageok {
					if num, err := strconv.Atoi(page[0]); err == nil {
						pageNumber = num
					}
				}

				responses := make([]DeviceInfo, 0)
				for i := (pageNumber - 1) * 10; i < pageNumber*10; i++ {
					if i < len(kaAPi.devicedata.sortedScores) {
						if value, ok := kaAPi.devicedata.infos[kaAPi.devicedata.sortedScores[i]]; ok {
							var info DeviceInfo = *value
							info.AndroidID = ""
							responses = append(responses, info)
						}
					}
				}
				if len(responses) > 0 {
					b, err := json.Marshal(responses)
					if err == nil {
						response = kaAPi.client.ResponseBody(string(b))
					}
				}
			}
		}

	}

	if response == nil {
		if b, err := kaAPi.createStatus(false); err == nil {
			response = kaAPi.client.ResponseBody(string(b))
		}
	}
	response.SetContentType(miniserver.ContentJson)

	return response
}

func (kaApi KernelAdiutorApi) createStatus(success bool) ([]byte, error) {
	var statusCode int = http.StatusOK
	if !success {
		statusCode = http.StatusNotFound
	}
	return json.Marshal(struct {
		Success bool   `json:"success"`
		Method  string `json:"method"`
		Request string `json:"request"`
		Version string `json:"version"`
		Status  int64  `json:"status"`
	}{success, kaApi.client.Method, kaApi.path,
		kaApi.version, int64(statusCode)})
}

type DeviceInfo struct {
	ID             string    `json:"id"`
	AndroidID      string    `json:"android_id,omitempty"`
	AndroidVersion string    `json:"android_version"`
	KernelVersion  string    `json:"kernel_version"`
	AppVersion     string    `json:"app_version"`
	Board          string    `json:"board"`
	Model          string    `json:"model"`
	Vendor         string    `json:"vendor"`
	Commands       []string  `json:"commands"`
	Times          []float64 `json:"times"`
	Cpu            float64   `json:"cpu"`
	Date           string    `json:"date"`
	Score          float64   `json:score`
}

func NewDeviceInfo(data map[string]interface{}) *DeviceInfo {
	var j utils.Json = utils.Json{data}

	var dInfo *DeviceInfo = &DeviceInfo{
		ID:             j.GetString("id"),
		AndroidID:      j.GetString("android_id"),
		AndroidVersion: j.GetString("android_version"),
		KernelVersion:  j.GetString("kernel_version"),
		AppVersion:     j.GetString("app_version"),
		Board:          j.GetString("board"),
		Model:          j.GetString("model"),
		Vendor:         j.GetString("vendor"),
		Commands:       j.GetStringArray("commands"),
		Times:          j.GetFloatArray("times"),
		Cpu:            j.GetFloat("cpu"),
		Date:           j.GetString("date"),
		Score:          j.GetFloat("score"),
	}

	if dInfo.valid() {
		if utils.StringEmpty(dInfo.ID) {
			dInfo.ID = utils.Encode(dInfo.AndroidID)
		}
		if utils.StringEmpty(dInfo.Date) {
			dInfo.Date = time.Now().Format(time.RFC3339)
		}
		if dInfo.Score == 0 {
			dInfo.Score = utils.GetAverage(dInfo.Times)*1e9 - dInfo.Cpu
		}
	}

	return dInfo
}

func (dInfo DeviceInfo) valid() bool {
	return !utils.StringEmpty(dInfo.AndroidID) &&
		!utils.StringEmpty(dInfo.AndroidVersion) &&
		!utils.StringEmpty(dInfo.KernelVersion) &&
		!utils.StringEmpty(dInfo.AppVersion) &&
		!utils.StringEmpty(dInfo.Board) &&
		!utils.StringEmpty(dInfo.Model) && dInfo.Model != "unknown" &&
		!utils.StringEmpty(dInfo.Vendor) &&
		dInfo.Commands != nil && len(dInfo.Commands) >= 10 &&
		dInfo.Times != nil && len(dInfo.Times) >= 20 &&
		dInfo.Cpu != 0
}

func (dInfo DeviceInfo) Json() ([]byte, error) {
	return json.Marshal(dInfo)
}

func (kaApi KernelAdiutorApi) putDatabase(dInfo *DeviceInfo) bool {
	return kaApi.devicedata.Update(dInfo)
}
<commit_msg>server: kerneladiutor: Accept all length of commands<commit_after>package api

import (
	"encoding/json"
	"fmt"
	"net/http"
	"time"

	"strconv"

	"../miniserver"
	"../utils"
)

const KA_TAG = "kerneladiutor"

type KernelAdiutorApi struct {
	client     *miniserver.Client
	path       string
	version    string
	devicedata *DeviceData
}

func (kaAPi KernelAdiutorApi) GetResponse() *miniserver.Response {
	switch kaAPi.version {
	case "v1":
		return kaAPi.kernelAdiutorApiv1()
	default:
		return nil
	}
}

func NewKernelAdiutorApi(client *miniserver.Client,
	path, version string,
	dData *DeviceData) KernelAdiutorApi {
	return KernelAdiutorApi{
		client:     client,
		path:       path,
		version:    version,
		devicedata: dData,
	}
}

func (kaAPi KernelAdiutorApi) kernelAdiutorApiv1() *miniserver.Response {
	var response *miniserver.Response

	switch kaAPi.path {
	case "device/create":
		if kaAPi.client.Method == http.MethodPost &&
			len(kaAPi.client.Request) > 0 {

			var data map[string]interface{}
			json.Unmarshal(kaAPi.client.Request, &data)

			var dInfo *DeviceInfo = NewDeviceInfo(data)
			if dInfo.valid() {

				var updated bool = kaAPi.putDatabase(dInfo)
				if b, err := kaAPi.createStatus(true); err == nil {
					response = kaAPi.client.ResponseBody(string(b))
				}

				if updated {
					utils.LogI(KA_TAG, fmt.Sprintf("Updating device %s", dInfo.Model))
				} else {
					utils.LogI(KA_TAG, fmt.Sprintf("Inserting device %s", dInfo.Model))
				}
			}
		}
	case "device/get":
		if kaAPi.client.Method == http.MethodGet {

			// Get all
			if page, pageok := kaAPi.client.Queries["page"]; (pageok && len(kaAPi.client.Queries) == 1) ||
				len(kaAPi.client.Queries) == 0 {

				var pageNumber int = 1
				if pageok {
					if num, err := strconv.Atoi(page[0]); err == nil {
						pageNumber = num
					}
				}

				responses := make([]DeviceInfo, 0)
				for i := (pageNumber - 1) * 10; i < pageNumber*10; i++ {
					if i < len(kaAPi.devicedata.sortedScores) {
						if value, ok := kaAPi.devicedata.infos[kaAPi.devicedata.sortedScores[i]]; ok {
							var info DeviceInfo = *value
							info.AndroidID = ""
							responses = append(responses, info)
						}
					}
				}
				if len(responses) > 0 {
					b, err := json.Marshal(responses)
					if err == nil {
						response = kaAPi.client.ResponseBody(string(b))
					}
				}
			}
		}

	}

	if response == nil {
		if b, err := kaAPi.createStatus(false); err == nil {
			response = kaAPi.client.ResponseBody(string(b))
		}
	}
	response.SetContentType(miniserver.ContentJson)

	return response
}

func (kaApi KernelAdiutorApi) createStatus(success bool) ([]byte, error) {
	var statusCode int = http.StatusOK
	if !success {
		statusCode = http.StatusNotFound
	}
	return json.Marshal(struct {
		Success bool   `json:"success"`
		Method  string `json:"method"`
		Request string `json:"request"`
		Version string `json:"version"`
		Status  int64  `json:"status"`
	}{success, kaApi.client.Method, kaApi.path,
		kaApi.version, int64(statusCode)})
}

type DeviceInfo struct {
	ID             string    `json:"id"`
	AndroidID      string    `json:"android_id,omitempty"`
	AndroidVersion string    `json:"android_version"`
	KernelVersion  string    `json:"kernel_version"`
	AppVersion     string    `json:"app_version"`
	Board          string    `json:"board"`
	Model          string    `json:"model"`
	Vendor         string    `json:"vendor"`
	Commands       []string  `json:"commands"`
	Times          []float64 `json:"times"`
	Cpu            float64   `json:"cpu"`
	Date           string    `json:"date"`
	Score          float64   `json:score`
}

func NewDeviceInfo(data map[string]interface{}) *DeviceInfo {
	var j utils.Json = utils.Json{data}

	var dInfo *DeviceInfo = &DeviceInfo{
		ID:             j.GetString("id"),
		AndroidID:      j.GetString("android_id"),
		AndroidVersion: j.GetString("android_version"),
		KernelVersion:  j.GetString("kernel_version"),
		AppVersion:     j.GetString("app_version"),
		Board:          j.GetString("board"),
		Model:          j.GetString("model"),
		Vendor:         j.GetString("vendor"),
		Commands:       j.GetStringArray("commands"),
		Times:          j.GetFloatArray("times"),
		Cpu:            j.GetFloat("cpu"),
		Date:           j.GetString("date"),
		Score:          j.GetFloat("score"),
	}

	if dInfo.valid() {
		if utils.StringEmpty(dInfo.ID) {
			dInfo.ID = utils.Encode(dInfo.AndroidID)
		}
		if utils.StringEmpty(dInfo.Date) {
			dInfo.Date = time.Now().Format(time.RFC3339)
		}
		if dInfo.Score == 0 {
			dInfo.Score = utils.GetAverage(dInfo.Times)*1e9 - dInfo.Cpu
		}
	}

	return dInfo
}

func (dInfo DeviceInfo) valid() bool {
	return !utils.StringEmpty(dInfo.AndroidID) &&
		!utils.StringEmpty(dInfo.AndroidVersion) &&
		!utils.StringEmpty(dInfo.KernelVersion) &&
		!utils.StringEmpty(dInfo.AppVersion) &&
		!utils.StringEmpty(dInfo.Board) &&
		!utils.StringEmpty(dInfo.Model) && dInfo.Model != "unknown" &&
		!utils.StringEmpty(dInfo.Vendor) &&
		dInfo.Commands != nil &&
		dInfo.Times != nil && len(dInfo.Times) >= 20 &&
		dInfo.Cpu != 0
}

func (dInfo DeviceInfo) Json() ([]byte, error) {
	return json.Marshal(dInfo)
}

func (kaApi KernelAdiutorApi) putDatabase(dInfo *DeviceInfo) bool {
	return kaApi.devicedata.Update(dInfo)
}
<|endoftext|>