<commit_before>/*
Copyright 2013 The Camlistore 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 kvfile implements the Camlistore index storage abstraction
// on top of a single mutable database file on disk using
// github.com/cznic/kv.
package kvfile

import (
	"errors"
	"fmt"
	"io"
	"log"
	"os"
	"sync"

	"camlistore.org/pkg/blobserver"
	"camlistore.org/pkg/index"
	"camlistore.org/pkg/jsonconfig"
	"camlistore.org/pkg/sorted"
	"camlistore.org/third_party/github.com/camlistore/lock"
	"camlistore.org/third_party/github.com/cznic/kv"
)

func init() {
	blobserver.RegisterStorageConstructor("kvfileindexer",
		blobserver.StorageConstructor(newFromConfig))
}

func NewStorage(file string) (sorted.KeyValue, error) {
	createOpen := kv.Open
	if _, err := os.Stat(file); os.IsNotExist(err) {
		createOpen = kv.Create
	}
	db, err := createOpen(file, &kv.Options{
		Locker: func(dbname string) (io.Closer, error) {
			lkfile := dbname + ".lock"
			cl, err := lock.Lock(lkfile)
			if err != nil {
				return nil, fmt.Errorf("failed to acquire lock on %s: %v", lkfile, err)
			}
			return cl, nil
		},
	})
	if err != nil {
		return nil, err
	}
	is := &kvis{
		db: db,
	}
	return is, nil
}

type kvis struct {
	db   *kv.DB
	txmu sync.Mutex
}

// TODO: use bytepool package.
func getBuf(n int) []byte { return make([]byte, n) }
func putBuf([]byte)       {}

func (is *kvis) Get(key string) (string, error) {
	buf := getBuf(200)
	defer putBuf(buf)
	val, err := is.db.Get(buf, []byte(key))
	if err != nil {
		return "", err
	}
	if val == nil {
		return "", sorted.ErrNotFound
	}
	return string(val), nil
}

func (is *kvis) Set(key, value string) error {
	return is.db.Set([]byte(key), []byte(value))
}

func (is *kvis) Delete(key string) error {
	return is.db.Delete([]byte(key))
}

func (is *kvis) Find(key string) sorted.Iterator {
	return &iter{
		db:      is.db,
		initKey: key,
	}
}

func (is *kvis) BeginBatch() sorted.BatchMutation {
	return sorted.NewBatchMutation()
}

type batch interface {
	Mutations() []sorted.Mutation
}

func (is *kvis) CommitBatch(bm sorted.BatchMutation) error {
	b, ok := bm.(batch)
	if !ok {
		return errors.New("invalid batch type")
	}
	is.txmu.Lock()
	defer is.txmu.Unlock()

	good := false
	defer func() {
		if !good {
			is.db.Rollback()
		}
	}()

	if err := is.db.BeginTransaction(); err != nil {
		return err
	}
	for _, m := range b.Mutations() {
		if m.IsDelete() {
			if err := is.db.Delete([]byte(m.Key())); err != nil {
				return err
			}
		} else {
			if err := is.db.Set([]byte(m.Key()), []byte(m.Value())); err != nil {
				return err
			}
		}
	}

	good = true
	return is.db.Commit()
}

func (is *kvis) Close() error {
	log.Printf("Closing kvfile database")
	return is.db.Close()
}

type iter struct {
	db      *kv.DB
	initKey string

	enum *kv.Enumerator

	valid    bool
	key, val string

	err    error
	closed bool
}

func (it *iter) Close() error {
	it.closed = true
	return it.err
}

func (it *iter) Key() string {
	if !it.valid {
		panic("not valid")
	}
	return it.key
}

func (it *iter) Value() string {
	if !it.valid {
		panic("not valid")
	}
	return it.val
}

func (it *iter) Next() (ret bool) {
	if it.closed {
		panic("Next called after Next returned value")
	}
	defer func() {
		it.valid = ret
		if !ret {
			it.closed = true
		}
	}()
	if it.enum == nil {
		it.enum, _, it.err = it.db.Seek([]byte(it.initKey))
		if it.err != nil {
			return false
		}
	}
	key, val, err := it.enum.Next()
	if err == io.EOF {
		it.err = nil
		return false
	}
	it.key = string(key)
	it.val = string(val)
	return true
}

func init() {
	sorted.RegisterKeyValue("kv", newKeyValueFromConfig)
}

func newKeyValueFromConfig(cfg jsonconfig.Obj) (sorted.KeyValue, error) {
	file := cfg.RequiredString("file")
	if file == "" {
		return nil, errors.New("Missing 'file' parameter")
	}
	return NewStorage(file)
}

func newFromConfig(ld blobserver.Loader, config jsonconfig.Obj) (blobserver.Storage, error) {
	blobPrefix := config.RequiredString("blobSource")
	file := config.RequiredString("file")
	if err := config.Validate(); err != nil {
		return nil, err
	}

	is, err := NewStorage(file)
	if err != nil {
		return nil, err
	}

	sto, err := ld.GetStorage(blobPrefix)
	if err != nil {
		is.Close()
		return nil, err
	}

	ix := index.New(is)
	if err != nil {
		return nil, err
	}
	ix.BlobSource = sto

	// Good enough, for now:
	ix.KeyFetcher = ix.BlobSource

	return ix, err
}
<commit_msg>kvfile: log which db was closed<commit_after>/*
Copyright 2013 The Camlistore 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 kvfile implements the Camlistore index storage abstraction
// on top of a single mutable database file on disk using
// github.com/cznic/kv.
package kvfile

import (
	"errors"
	"fmt"
	"io"
	"log"
	"os"
	"sync"

	"camlistore.org/pkg/blobserver"
	"camlistore.org/pkg/index"
	"camlistore.org/pkg/jsonconfig"
	"camlistore.org/pkg/sorted"
	"camlistore.org/third_party/github.com/camlistore/lock"
	"camlistore.org/third_party/github.com/cznic/kv"
)

func init() {
	blobserver.RegisterStorageConstructor("kvfileindexer",
		blobserver.StorageConstructor(newFromConfig))
}

func NewStorage(file string) (sorted.KeyValue, error) {
	createOpen := kv.Open
	if _, err := os.Stat(file); os.IsNotExist(err) {
		createOpen = kv.Create
	}
	db, err := createOpen(file, &kv.Options{
		Locker: func(dbname string) (io.Closer, error) {
			lkfile := dbname + ".lock"
			cl, err := lock.Lock(lkfile)
			if err != nil {
				return nil, fmt.Errorf("failed to acquire lock on %s: %v", lkfile, err)
			}
			return cl, nil
		},
	})
	if err != nil {
		return nil, err
	}
	is := &kvis{
		db:   db,
		path: file,
	}
	return is, nil
}

type kvis struct {
	path string
	db   *kv.DB
	txmu sync.Mutex
}

// TODO: use bytepool package.
func getBuf(n int) []byte { return make([]byte, n) }
func putBuf([]byte)       {}

func (is *kvis) Get(key string) (string, error) {
	buf := getBuf(200)
	defer putBuf(buf)
	val, err := is.db.Get(buf, []byte(key))
	if err != nil {
		return "", err
	}
	if val == nil {
		return "", sorted.ErrNotFound
	}
	return string(val), nil
}

func (is *kvis) Set(key, value string) error {
	return is.db.Set([]byte(key), []byte(value))
}

func (is *kvis) Delete(key string) error {
	return is.db.Delete([]byte(key))
}

func (is *kvis) Find(key string) sorted.Iterator {
	return &iter{
		db:      is.db,
		initKey: key,
	}
}

func (is *kvis) BeginBatch() sorted.BatchMutation {
	return sorted.NewBatchMutation()
}

type batch interface {
	Mutations() []sorted.Mutation
}

func (is *kvis) CommitBatch(bm sorted.BatchMutation) error {
	b, ok := bm.(batch)
	if !ok {
		return errors.New("invalid batch type")
	}
	is.txmu.Lock()
	defer is.txmu.Unlock()

	good := false
	defer func() {
		if !good {
			is.db.Rollback()
		}
	}()

	if err := is.db.BeginTransaction(); err != nil {
		return err
	}
	for _, m := range b.Mutations() {
		if m.IsDelete() {
			if err := is.db.Delete([]byte(m.Key())); err != nil {
				return err
			}
		} else {
			if err := is.db.Set([]byte(m.Key()), []byte(m.Value())); err != nil {
				return err
			}
		}
	}

	good = true
	return is.db.Commit()
}

func (is *kvis) Close() error {
	log.Printf("Closing kvfile database %s", is.path)
	return is.db.Close()
}

type iter struct {
	db      *kv.DB
	initKey string

	enum *kv.Enumerator

	valid    bool
	key, val string

	err    error
	closed bool
}

func (it *iter) Close() error {
	it.closed = true
	return it.err
}

func (it *iter) Key() string {
	if !it.valid {
		panic("not valid")
	}
	return it.key
}

func (it *iter) Value() string {
	if !it.valid {
		panic("not valid")
	}
	return it.val
}

func (it *iter) Next() (ret bool) {
	if it.closed {
		panic("Next called after Next returned value")
	}
	defer func() {
		it.valid = ret
		if !ret {
			it.closed = true
		}
	}()
	if it.enum == nil {
		it.enum, _, it.err = it.db.Seek([]byte(it.initKey))
		if it.err != nil {
			return false
		}
	}
	key, val, err := it.enum.Next()
	if err == io.EOF {
		it.err = nil
		return false
	}
	it.key = string(key)
	it.val = string(val)
	return true
}

func init() {
	sorted.RegisterKeyValue("kv", newKeyValueFromConfig)
}

func newKeyValueFromConfig(cfg jsonconfig.Obj) (sorted.KeyValue, error) {
	file := cfg.RequiredString("file")
	if file == "" {
		return nil, errors.New("Missing 'file' parameter")
	}
	return NewStorage(file)
}

func newFromConfig(ld blobserver.Loader, config jsonconfig.Obj) (blobserver.Storage, error) {
	blobPrefix := config.RequiredString("blobSource")
	file := config.RequiredString("file")
	if err := config.Validate(); err != nil {
		return nil, err
	}

	is, err := NewStorage(file)
	if err != nil {
		return nil, err
	}

	sto, err := ld.GetStorage(blobPrefix)
	if err != nil {
		is.Close()
		return nil, err
	}

	ix := index.New(is)
	if err != nil {
		return nil, err
	}
	ix.BlobSource = sto

	// Good enough, for now:
	ix.KeyFetcher = ix.BlobSource

	return ix, err
}
<|endoftext|>


<commit_before>// Copyright 2017 Istio 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 queue

import (
	"errors"
	"sync"
	"testing"
	"time"
)

func TestOrdering(t *testing.T) {
	numValues := 1000

	q := NewQueue(1 * time.Microsecond)
	stop := make(chan struct{})
	defer close(stop)

	wg := sync.WaitGroup{}
	wg.Add(numValues)
	mu := sync.Mutex{}
	out := make([]int, 0)
	for i := 0; i < numValues; i++ {
		i := i

		q.Push(func() error {
			mu.Lock()
			out = append(out, i)
			defer mu.Unlock()
			wg.Done()
			return nil
		})

		// Start the queue at the halfway point.
		if i == numValues/2 {
			go q.Run(stop)
		}
	}

	// wait for all task processed
	wg.Wait()

	if len(out) != numValues {
		t.Fatalf("expected output array length %d to equal %d", len(out), numValues)
	}

	for i := 0; i < numValues; i++ {
		if i != out[i] {
			t.Fatalf("expected out[%d] %v to equal %v", i, out[i], i)
		}
	}
}

func TestRetry(t *testing.T) {
	q := NewQueue(1 * time.Microsecond)
	stop := make(chan struct{})
	defer close(stop)

	// Push a task that fails the first time and retries.
	wg := sync.WaitGroup{}
	wg.Add(2)
	failed := false
	q.Push(func() error {
		defer wg.Done()
		if failed {
			return nil
		}
		failed = true
		return errors.New("fake error")
	})

	go q.Run(stop)

	// wait for the task to run twice.
	wg.Wait()
}

func TestResourceFree(t *testing.T) {
	q := NewQueue(1 * time.Microsecond)
	stop := make(chan struct{})
	signal := make(chan struct{})
	go func() {
		q.Run(stop)
		signal <- struct{}{}
	}()

	q.Push(func() error {
		t.Log("mock exec")
		return nil
	})

	// mock queue wait cond signal
	time.Sleep(200 * time.Millisecond)

	close(stop)

	select {
	case <-time.After(500 * time.Microsecond):
		t.Error("close stop, method exit timeout.")
	case <-signal:
		t.Log("queue return.")
	}
}
<commit_msg>Fix queue unit tests  (#23408)<commit_after>// Copyright 2017 Istio 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 queue

import (
	"errors"
	"sync"
	"testing"
	"time"
)

func TestOrdering(t *testing.T) {
	numValues := 1000

	q := NewQueue(1 * time.Microsecond)
	stop := make(chan struct{})
	defer close(stop)

	wg := sync.WaitGroup{}
	wg.Add(numValues)
	mu := sync.Mutex{}
	out := make([]int, 0)
	for i := 0; i < numValues; i++ {
		i := i

		q.Push(func() error {
			mu.Lock()
			out = append(out, i)
			defer mu.Unlock()
			wg.Done()
			return nil
		})

		// Start the queue at the halfway point.
		if i == numValues/2 {
			go q.Run(stop)
		}
	}

	// wait for all task processed
	wg.Wait()

	if len(out) != numValues {
		t.Fatalf("expected output array length %d to equal %d", len(out), numValues)
	}

	for i := 0; i < numValues; i++ {
		if i != out[i] {
			t.Fatalf("expected out[%d] %v to equal %v", i, out[i], i)
		}
	}
}

func TestRetry(t *testing.T) {
	q := NewQueue(1 * time.Microsecond)
	stop := make(chan struct{})
	defer close(stop)

	// Push a task that fails the first time and retries.
	wg := sync.WaitGroup{}
	wg.Add(2)
	failed := false
	q.Push(func() error {
		defer wg.Done()
		if failed {
			return nil
		}
		failed = true
		return errors.New("fake error")
	})

	go q.Run(stop)

	// wait for the task to run twice.
	wg.Wait()
}

func TestResourceFree(t *testing.T) {
	q := NewQueue(1 * time.Microsecond)
	stop := make(chan struct{})
	signal := make(chan struct{})
	go func() {
		q.Run(stop)
		signal <- struct{}{}
	}()

	q.Push(func() error {
		t.Log("mock exec")
		return nil
	})

	// mock queue block wait cond signal
	time.AfterFunc(10*time.Millisecond, func() {
		close(stop)
	})

	select {
	case <-time.After(200 * time.Millisecond):
		t.Error("close stop, method exit timeout.")
	case <-signal:
		t.Log("queue return.")
	}
}
<|endoftext|>


<commit_before>package scheduler

import (
	"fmt"
	"sync"
	"sync/atomic"
	"time"

	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/tools"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
	log "github.com/golang/glog"
	mesos "github.com/mesos/mesos-go/mesosproto"
	mutil "github.com/mesos/mesos-go/mesosutil"
	bindings "github.com/mesos/mesos-go/scheduler"
	"github.com/mesosphere/kubernetes-mesos/pkg/executor/messages"
	"github.com/mesosphere/kubernetes-mesos/pkg/offers"
	"github.com/mesosphere/kubernetes-mesos/pkg/scheduler/meta"
	"github.com/mesosphere/kubernetes-mesos/pkg/scheduler/podtask"
)

const (
	defaultOfferTTL       = 5    // seconds that an offer is viable, prior to being expired
	defaultOfferLingerTTL = 120  // seconds that an expired offer lingers in history
	defaultListenerDelay  = 1    // number of seconds between offer listener notifications
	defaultUpdatesBacklog = 2048 // size of the pod updates channel
)

type Slave struct {
	HostName string
	Offers   map[string]empty
}

func newSlave(hostName string) *Slave {
	return &Slave{
		HostName: hostName,
		Offers:   make(map[string]empty),
	}
}

type PluginInterface interface {
	// the apiserver may have a different state for the pod than we do
	// so reconcile our records, but only for this one pod
	reconcilePod(api.Pod)

	// execute the Scheduling plugin, should start a go routine and return immediately
	Run()
}

// KubernetesScheduler implements:
// 1: A mesos scheduler.
// 2: A kubernetes scheduler plugin.
// 3: A kubernetes pod.Registry.
type KubernetesScheduler struct {
	// We use a lock here to avoid races
	// between invoking the mesos callback
	// and the invoking the pod registry interfaces.
	// In particular, changes to podtask.T objects are currently guarded by this lock.
	*sync.RWMutex

	// Mesos context.
	executor    *mesos.ExecutorInfo
	driver      bindings.SchedulerDriver
	frameworkId *mesos.FrameworkID
	masterInfo  *mesos.MasterInfo
	registered  bool

	offers       offers.Registry
	slaves       map[string]*Slave // SlaveID => slave.
	slaveIDs     map[string]string // Slave's hostname => slaveID
	taskRegistry podtask.Registry

	scheduleFunc PodScheduleFunc // The function that does scheduling.

	client     *client.Client
	plugin     PluginInterface
	etcdClient tools.EtcdClient
}

// New create a new KubernetesScheduler
func New(executor *mesos.ExecutorInfo, scheduleFunc PodScheduleFunc, client *client.Client, etcdClient tools.EtcdClient) *KubernetesScheduler {
	var k *KubernetesScheduler
	k = &KubernetesScheduler{
		RWMutex:  new(sync.RWMutex),
		executor: executor,
		offers: offers.CreateRegistry(offers.RegistryConfig{
			DeclineOffer: func(id string) error {
				offerId := mutil.NewOfferID(id)
				filters := &mesos.Filters{}
				_, err := k.driver.DeclineOffer(offerId, filters)
				return err
			},
			TTL:           defaultOfferTTL * time.Second,
			LingerTTL:     defaultOfferLingerTTL * time.Second, // remember expired offers so that we can tell if a previously scheduler offer relies on one
			ListenerDelay: defaultListenerDelay * time.Second,
		}),
		slaves:       make(map[string]*Slave),
		slaveIDs:     make(map[string]string),
		taskRegistry: podtask.NewInMemoryRegistry(),
		scheduleFunc: scheduleFunc,
		client:       client,
		etcdClient:   etcdClient,
	}
	return k
}

func (k *KubernetesScheduler) Init(d bindings.SchedulerDriver, pl PluginInterface) {
	k.driver = d
	k.plugin = pl
	k.offers.Init()
}

// Registered is called when the scheduler registered with the master successfully.
func (k *KubernetesScheduler) Registered(driver bindings.SchedulerDriver,
	frameworkId *mesos.FrameworkID, masterInfo *mesos.MasterInfo) {
	k.frameworkId = frameworkId
	k.masterInfo = masterInfo
	k.registered = true
	go k.storeFrameworkId() //TODO(jdef) only do this if we're checkpointing?
	log.Infof("Scheduler registered with the master: %v with frameworkId: %v\n", masterInfo, frameworkId)

	//TODO(jdef) partial reconciliation started... needs work
	r := &Reconciler{Action: k.ReconcileRunningTasks}
	go util.Forever(func() { r.Run(driver) }, 5*time.Minute) // TODO(jdef) parameterize reconciliation interval
}

func (k *KubernetesScheduler) storeFrameworkId() {
	_, err := k.etcdClient.Set(meta.FrameworkIDKey, k.frameworkId.GetValue(), 0)
	if err != nil {
		log.Error(err)
	}
}

// Reregistered is called when the scheduler re-registered with the master successfully.
// This happends when the master fails over.
func (k *KubernetesScheduler) Reregistered(driver bindings.SchedulerDriver, masterInfo *mesos.MasterInfo) {
	log.Infof("Scheduler reregistered with the master: %v\n", masterInfo)
	k.registered = true

	//TODO(jdef) partial reconciliation started... needs work
	r := &Reconciler{Action: k.ReconcileRunningTasks}
	go util.Forever(func() { r.Run(driver) }, 5*time.Minute) // TODO(jdef) parameterize reconciliation interval
}

// Disconnected is called when the scheduler loses connection to the master.
func (k *KubernetesScheduler) Disconnected(driver bindings.SchedulerDriver) {
	log.Infof("Master disconnected!\n")
	k.registered = false

	k.Lock()
	defer k.Unlock()

	// discard all cached offers to avoid unnecessary TASK_LOST updates
	k.offers.Invalidate("")
}

// ResourceOffers is called when the scheduler receives some offers from the master.
func (k *KubernetesScheduler) ResourceOffers(driver bindings.SchedulerDriver, offers []*mesos.Offer) {
	log.V(2).Infof("Received offers %+v", offers)

	k.Lock()
	defer k.Unlock()

	// Record the offers in the global offer map as well as each slave's offer map.
	k.offers.Add(offers)
	for _, offer := range offers {
		offerId := offer.GetId().GetValue()
		slaveId := offer.GetSlaveId().GetValue()

		slave, exists := k.slaves[slaveId]
		if !exists {
			k.slaves[slaveId] = newSlave(offer.GetHostname())
			slave = k.slaves[slaveId]
		}
		slave.Offers[offerId] = empty{}
		k.slaveIDs[slave.HostName] = slaveId
	}
}

// OfferRescinded is called when the resources are recinded from the scheduler.
func (k *KubernetesScheduler) OfferRescinded(driver bindings.SchedulerDriver, offerId *mesos.OfferID) {
	log.Infof("Offer rescinded %v\n", offerId)

	oid := offerId.GetValue()
	if offer, ok := k.offers.Get(oid); ok {
		k.offers.Delete(oid)
		if details := offer.Details(); details != nil {
			k.Lock()
			defer k.Unlock()

			slaveId := details.GetSlaveId().GetValue()

			if slave, found := k.slaves[slaveId]; !found {
				log.Infof("No slave for id %s associated with offer id %s", slaveId, oid)
			} else {
				delete(slave.Offers, oid)
			}
		} // else, offer already expired / lingering
	}
}

// StatusUpdate is called when a status update message is sent to the scheduler.
func (k *KubernetesScheduler) StatusUpdate(driver bindings.SchedulerDriver, taskStatus *mesos.TaskStatus) {
	//TODO(jdef) we're going to make changes to podtask.T objects in here and since the current TaskRegistry
	//implementation is in-memory we need a critical section for this.
	k.Lock()
	defer k.Unlock()

	log.Infof("Received status update %v\n", taskStatus)

	switch taskStatus.GetState() {
	case mesos.TaskState_TASK_STAGING:
		log.Errorf("Not implemented: task staging")
	case mesos.TaskState_TASK_RUNNING, mesos.TaskState_TASK_FINISHED, mesos.TaskState_TASK_STARTING:
		if !func() (exists bool) {
			slaveId := taskStatus.GetSlaveId().GetValue()
			_, exists = k.slaves[slaveId]
			return
		}() {
			log.Warningf("Ignore status %+v because the slave does not exist", taskStatus)
			return
		}
		fallthrough
	case mesos.TaskState_TASK_KILLED:
		k.taskRegistry.UpdateStatus(taskStatus)
	case mesos.TaskState_TASK_FAILED:
		if task, _ := k.taskRegistry.UpdateStatus(taskStatus); task != nil {
			if task.Has(podtask.Launched) && messages.CreateBindingFailure == taskStatus.GetMessage() {
				go k.plugin.reconcilePod(*task.Pod)
			}
		}
	case mesos.TaskState_TASK_LOST:
		task, state := k.taskRegistry.UpdateStatus(taskStatus)
		if state == podtask.StateRunning && taskStatus.ExecutorId != nil && taskStatus.SlaveId != nil {
			//TODO(jdef) this may not be meaningful once we have proper checkpointing and master detection
			//If we're reconciling and receive this then the executor may be
			//running a task that we need it to kill. It's possible that the framework
			//is unrecognized by the master at this point, so KillTask is not guaranteed
			//to do anything. The underlying driver transport may be able to send a
			//FrameworkMessage directly to the slave to terminate the task.
			log.V(2).Info("forwarding TASK_LOST message to executor %v on slave %v", taskStatus.ExecutorId, taskStatus.SlaveId)
			data := fmt.Sprintf("task-lost:%s", task.ID) //TODO(jdef) use a real message type
			if _, err := driver.SendFrameworkMessage(taskStatus.ExecutorId, taskStatus.SlaveId, data); err != nil {
				log.Error(err)
			}
		}
	}
}

// FrameworkMessage is called when the scheduler receives a message from the executor.
func (k *KubernetesScheduler) FrameworkMessage(driver bindings.SchedulerDriver,
	executorId *mesos.ExecutorID, slaveId *mesos.SlaveID, message string) {
	log.Infof("Received messages from executor %v of slave %v, %v\n", executorId, slaveId, message)
}

// SlaveLost is called when some slave is lost.
func (k *KubernetesScheduler) SlaveLost(driver bindings.SchedulerDriver, slaveId *mesos.SlaveID) {
	log.Infof("Slave %v is lost\n", slaveId)

	k.Lock()
	defer k.Unlock()

	// invalidate all offers mapped to that slave
	if slave, ok := k.slaves[slaveId.GetValue()]; ok {
		for offerId := range slave.Offers {
			k.offers.Invalidate(offerId)
		}
	}

	// TODO(jdef): delete slave from our internal list?

	// unfinished tasks/pods will be dropped. use a replication controller if you want pods to
	// be restarted when slaves die.
}

// ExecutorLost is called when some executor is lost.
func (k *KubernetesScheduler) ExecutorLost(driver bindings.SchedulerDriver,
	executorId *mesos.ExecutorID, slaveId *mesos.SlaveID, status int) {
	log.Infof("Executor %v of slave %v is lost, status: %v\n", executorId, slaveId, status)
	// TODO(yifan): Restart any unfinished tasks of the executor.
}

// Error is called when there is an unrecoverable error in the scheduler or scheduler driver.
// The driver should have been aborted before this is invoked.
func (k *KubernetesScheduler) Error(driver bindings.SchedulerDriver, message string) {
	log.Fatalf("fatal scheduler error: %v\n", message)
}

// intended to be invoked as a Reconciler.Action by Reconciler.Run
func (k *KubernetesScheduler) ReconcileRunningTasks(driver bindings.SchedulerDriver, canceled <-chan struct{}) error {
	log.Info("reconcile running tasks")
	remaining := util.NewStringSet()
	statusList := []*mesos.TaskStatus{}

	filter := podtask.StateRunning
	remaining.Insert(k.taskRegistry.List(&filter)...)
	if _, err := driver.ReconcileTasks(statusList); err != nil {
		return err
	}
	start := time.Now()
	const maxBackoff = 120 * time.Second
	first := true
	for backoff := 1 * time.Second; first || remaining.Len() > 0; backoff = backoff * 2 {
		first = false
		//TODO(jdef) reconcileTasks(remaining, canceled)
		if backoff > maxBackoff {
			backoff = maxBackoff
		}
		select {
		case <-canceled:
			return nil //TODO(jdef) should probably return a cancelation error
		case <-time.After(backoff):
			func() {
				k.RLock()
				defer k.RUnlock()
				for taskId := range remaining {
					if task, state := k.taskRegistry.Get(taskId); state == podtask.StateRunning && task.UpdatedTime.Before(start) {
						// keep this task in remaining list
						continue
					}
					remaining.Delete(taskId)
				}
			}()
		}
	}
	return nil
}

type Reconciler struct {
	Action  func(driver bindings.SchedulerDriver, canceled <-chan struct{}) error
	running int32 // 1 when Action is running, 0 otherwise
}

// execute task reconciliation, returns a cancelation channel or nil if reconciliation is already running.
// a client may signal that reconciliation should be canceled by closing the cancelation channel.
// no objects are ever read from, or written to, the cancelation channel.
func (r *Reconciler) Run(driver bindings.SchedulerDriver) <-chan struct{} {
	if atomic.CompareAndSwapInt32(&r.running, 0, 1) {
		canceled := make(chan struct{})
		go func() {
			defer atomic.StoreInt32(&r.running, 0)
			err := r.Action(driver, canceled)
			if err != nil {
				log.Errorf("reconciler action failed: %v", err)
			}
		}()
		return canceled
	}
	return nil
}
<commit_msg>reformatted for readability<commit_after>package scheduler

import (
	"fmt"
	"sync"
	"sync/atomic"
	"time"

	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/tools"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
	log "github.com/golang/glog"
	mesos "github.com/mesos/mesos-go/mesosproto"
	mutil "github.com/mesos/mesos-go/mesosutil"
	bindings "github.com/mesos/mesos-go/scheduler"
	"github.com/mesosphere/kubernetes-mesos/pkg/executor/messages"
	"github.com/mesosphere/kubernetes-mesos/pkg/offers"
	"github.com/mesosphere/kubernetes-mesos/pkg/scheduler/meta"
	"github.com/mesosphere/kubernetes-mesos/pkg/scheduler/podtask"
)

const (
	defaultOfferTTL       = 5    // seconds that an offer is viable, prior to being expired
	defaultOfferLingerTTL = 120  // seconds that an expired offer lingers in history
	defaultListenerDelay  = 1    // number of seconds between offer listener notifications
	defaultUpdatesBacklog = 2048 // size of the pod updates channel
)

type Slave struct {
	HostName string
	Offers   map[string]empty
}

func newSlave(hostName string) *Slave {
	return &Slave{
		HostName: hostName,
		Offers:   make(map[string]empty),
	}
}

type PluginInterface interface {
	// the apiserver may have a different state for the pod than we do
	// so reconcile our records, but only for this one pod
	reconcilePod(api.Pod)

	// execute the Scheduling plugin, should start a go routine and return immediately
	Run()
}

// KubernetesScheduler implements:
// 1: A mesos scheduler.
// 2: A kubernetes scheduler plugin.
// 3: A kubernetes pod.Registry.
type KubernetesScheduler struct {
	// We use a lock here to avoid races
	// between invoking the mesos callback
	// and the invoking the pod registry interfaces.
	// In particular, changes to podtask.T objects are currently guarded by this lock.
	*sync.RWMutex

	// Mesos context.
	executor    *mesos.ExecutorInfo
	driver      bindings.SchedulerDriver
	frameworkId *mesos.FrameworkID
	masterInfo  *mesos.MasterInfo
	registered  bool

	offers       offers.Registry
	slaves       map[string]*Slave // SlaveID => slave.
	slaveIDs     map[string]string // Slave's hostname => slaveID
	taskRegistry podtask.Registry

	scheduleFunc PodScheduleFunc // The function that does scheduling.

	client     *client.Client
	plugin     PluginInterface
	etcdClient tools.EtcdClient
}

// New create a new KubernetesScheduler
func New(executor *mesos.ExecutorInfo, scheduleFunc PodScheduleFunc, client *client.Client, etcdClient tools.EtcdClient) *KubernetesScheduler {
	var k *KubernetesScheduler
	k = &KubernetesScheduler{
		RWMutex:  new(sync.RWMutex),
		executor: executor,
		offers: offers.CreateRegistry(offers.RegistryConfig{
			DeclineOffer: func(id string) error {
				offerId := mutil.NewOfferID(id)
				filters := &mesos.Filters{}
				_, err := k.driver.DeclineOffer(offerId, filters)
				return err
			},
			TTL:           defaultOfferTTL * time.Second,
			LingerTTL:     defaultOfferLingerTTL * time.Second, // remember expired offers so that we can tell if a previously scheduler offer relies on one
			ListenerDelay: defaultListenerDelay * time.Second,
		}),
		slaves:       make(map[string]*Slave),
		slaveIDs:     make(map[string]string),
		taskRegistry: podtask.NewInMemoryRegistry(),
		scheduleFunc: scheduleFunc,
		client:       client,
		etcdClient:   etcdClient,
	}
	return k
}

func (k *KubernetesScheduler) Init(d bindings.SchedulerDriver, pl PluginInterface) {
	k.driver = d
	k.plugin = pl
	k.offers.Init()
}

// Registered is called when the scheduler registered with the master successfully.
func (k *KubernetesScheduler) Registered(driver bindings.SchedulerDriver,
	frameworkId *mesos.FrameworkID, masterInfo *mesos.MasterInfo) {
	k.frameworkId = frameworkId
	k.masterInfo = masterInfo
	k.registered = true
	go k.storeFrameworkId() //TODO(jdef) only do this if we're checkpointing?
	log.Infof("Scheduler registered with the master: %v with frameworkId: %v\n", masterInfo, frameworkId)

	//TODO(jdef) partial reconciliation started... needs work
	r := &Reconciler{Action: k.ReconcileRunningTasks}
	go util.Forever(func() { r.Run(driver) }, 5*time.Minute) // TODO(jdef) parameterize reconciliation interval
}

func (k *KubernetesScheduler) storeFrameworkId() {
	_, err := k.etcdClient.Set(meta.FrameworkIDKey, k.frameworkId.GetValue(), 0)
	if err != nil {
		log.Error(err)
	}
}

// Reregistered is called when the scheduler re-registered with the master successfully.
// This happends when the master fails over.
func (k *KubernetesScheduler) Reregistered(driver bindings.SchedulerDriver, masterInfo *mesos.MasterInfo) {
	log.Infof("Scheduler reregistered with the master: %v\n", masterInfo)
	k.registered = true

	//TODO(jdef) partial reconciliation started... needs work
	r := &Reconciler{Action: k.ReconcileRunningTasks}
	go util.Forever(func() { r.Run(driver) }, 5*time.Minute) // TODO(jdef) parameterize reconciliation interval
}

// Disconnected is called when the scheduler loses connection to the master.
func (k *KubernetesScheduler) Disconnected(driver bindings.SchedulerDriver) {
	log.Infof("Master disconnected!\n")
	k.registered = false

	k.Lock()
	defer k.Unlock()

	// discard all cached offers to avoid unnecessary TASK_LOST updates
	k.offers.Invalidate("")
}

// ResourceOffers is called when the scheduler receives some offers from the master.
func (k *KubernetesScheduler) ResourceOffers(driver bindings.SchedulerDriver, offers []*mesos.Offer) {
	log.V(2).Infof("Received offers %+v", offers)

	k.Lock()
	defer k.Unlock()

	// Record the offers in the global offer map as well as each slave's offer map.
	k.offers.Add(offers)
	for _, offer := range offers {
		offerId := offer.GetId().GetValue()
		slaveId := offer.GetSlaveId().GetValue()

		slave, exists := k.slaves[slaveId]
		if !exists {
			k.slaves[slaveId] = newSlave(offer.GetHostname())
			slave = k.slaves[slaveId]
		}
		slave.Offers[offerId] = empty{}
		k.slaveIDs[slave.HostName] = slaveId
	}
}

// OfferRescinded is called when the resources are recinded from the scheduler.
func (k *KubernetesScheduler) OfferRescinded(driver bindings.SchedulerDriver, offerId *mesos.OfferID) {
	log.Infof("Offer rescinded %v\n", offerId)

	oid := offerId.GetValue()
	if offer, ok := k.offers.Get(oid); ok {
		k.offers.Delete(oid)
		if details := offer.Details(); details != nil {
			k.Lock()
			defer k.Unlock()

			slaveId := details.GetSlaveId().GetValue()

			if slave, found := k.slaves[slaveId]; !found {
				log.Infof("No slave for id %s associated with offer id %s", slaveId, oid)
			} else {
				delete(slave.Offers, oid)
			}
		} // else, offer already expired / lingering
	}
}

// StatusUpdate is called when a status update message is sent to the scheduler.
func (k *KubernetesScheduler) StatusUpdate(driver bindings.SchedulerDriver, taskStatus *mesos.TaskStatus) {
	//TODO(jdef) we're going to make changes to podtask.T objects in here and since the current TaskRegistry
	//implementation is in-memory we need a critical section for this.
	k.Lock()
	defer k.Unlock()

	log.Infof("Received status update %v\n", taskStatus)

	switch taskStatus.GetState() {
	case mesos.TaskState_TASK_STAGING:
		log.Errorf("Not implemented: task staging")
	case mesos.TaskState_TASK_RUNNING, mesos.TaskState_TASK_FINISHED, mesos.TaskState_TASK_STARTING:
		if !func() (exists bool) {
			slaveId := taskStatus.GetSlaveId().GetValue()
			_, exists = k.slaves[slaveId]
			return
		}() {
			log.Warningf("Ignore status %+v because the slave does not exist", taskStatus)
			return
		}
		fallthrough
	case mesos.TaskState_TASK_KILLED:
		k.taskRegistry.UpdateStatus(taskStatus)
	case mesos.TaskState_TASK_FAILED:
		if task, _ := k.taskRegistry.UpdateStatus(taskStatus); task != nil {
			if task.Has(podtask.Launched) && messages.CreateBindingFailure == taskStatus.GetMessage() {
				go k.plugin.reconcilePod(*task.Pod)
			}
		}
	case mesos.TaskState_TASK_LOST:
		task, state := k.taskRegistry.UpdateStatus(taskStatus)
		if state == podtask.StateRunning && taskStatus.ExecutorId != nil && taskStatus.SlaveId != nil {
			//TODO(jdef) this may not be meaningful once we have proper checkpointing and master detection
			//If we're reconciling and receive this then the executor may be
			//running a task that we need it to kill. It's possible that the framework
			//is unrecognized by the master at this point, so KillTask is not guaranteed
			//to do anything. The underlying driver transport may be able to send a
			//FrameworkMessage directly to the slave to terminate the task.
			log.V(2).Info("forwarding TASK_LOST message to executor %v on slave %v", taskStatus.ExecutorId, taskStatus.SlaveId)
			data := fmt.Sprintf("task-lost:%s", task.ID) //TODO(jdef) use a real message type
			if _, err := driver.SendFrameworkMessage(taskStatus.ExecutorId, taskStatus.SlaveId, data); err != nil {
				log.Error(err)
			}
		}
	}
}

// FrameworkMessage is called when the scheduler receives a message from the executor.
func (k *KubernetesScheduler) FrameworkMessage(driver bindings.SchedulerDriver,
	executorId *mesos.ExecutorID, slaveId *mesos.SlaveID, message string) {
	log.Infof("Received messages from executor %v of slave %v, %v\n", executorId, slaveId, message)
}

// SlaveLost is called when some slave is lost.
func (k *KubernetesScheduler) SlaveLost(driver bindings.SchedulerDriver, slaveId *mesos.SlaveID) {
	log.Infof("Slave %v is lost\n", slaveId)

	k.Lock()
	defer k.Unlock()

	// invalidate all offers mapped to that slave
	if slave, ok := k.slaves[slaveId.GetValue()]; ok {
		for offerId := range slave.Offers {
			k.offers.Invalidate(offerId)
		}
	}

	// TODO(jdef): delete slave from our internal list?

	// unfinished tasks/pods will be dropped. use a replication controller if you want pods to
	// be restarted when slaves die.
}

// ExecutorLost is called when some executor is lost.
func (k *KubernetesScheduler) ExecutorLost(driver bindings.SchedulerDriver,
	executorId *mesos.ExecutorID, slaveId *mesos.SlaveID, status int) {
	log.Infof("Executor %v of slave %v is lost, status: %v\n", executorId, slaveId, status)
	// TODO(yifan): Restart any unfinished tasks of the executor.
}

// Error is called when there is an unrecoverable error in the scheduler or scheduler driver.
// The driver should have been aborted before this is invoked.
func (k *KubernetesScheduler) Error(driver bindings.SchedulerDriver, message string) {
	log.Fatalf("fatal scheduler error: %v\n", message)
}

// intended to be invoked as a Reconciler.Action by Reconciler.Run
func (k *KubernetesScheduler) ReconcileRunningTasks(driver bindings.SchedulerDriver, canceled <-chan struct{}) error {
	log.Info("reconcile running tasks")

	statusList := []*mesos.TaskStatus{}
	if _, err := driver.ReconcileTasks(statusList); err != nil {
		return err
	}

	filter := podtask.StateRunning
	remaining := util.NewStringSet()
	remaining.Insert(k.taskRegistry.List(&filter)...)
	start := time.Now()
	const maxBackoff = 120 * time.Second // TODO(jdef) extract constant
	first := true
	for backoff := 1 * time.Second; first || remaining.Len() > 0; backoff = backoff * 2 {
		first = false
		//TODO(jdef) reconcileTasks(remaining, canceled)
		if backoff > maxBackoff {
			backoff = maxBackoff
		}
		select {
		case <-canceled:
			return nil //TODO(jdef) should probably return a cancelation error
		case <-time.After(backoff):
			func() {
				k.RLock()
				defer k.RUnlock()
				for taskId := range remaining {
					if task, state := k.taskRegistry.Get(taskId); state == podtask.StateRunning && task.UpdatedTime.Before(start) {
						// keep this task in remaining list
						continue
					}
					remaining.Delete(taskId)
				}
			}()
		}
	}
	return nil
}

type Reconciler struct {
	Action  func(driver bindings.SchedulerDriver, canceled <-chan struct{}) error
	running int32 // 1 when Action is running, 0 otherwise
}

// execute task reconciliation, returns a cancelation channel or nil if reconciliation is already running.
// a client may signal that reconciliation should be canceled by closing the cancelation channel.
// no objects are ever read from, or written to, the cancelation channel.
func (r *Reconciler) Run(driver bindings.SchedulerDriver) <-chan struct{} {
	if atomic.CompareAndSwapInt32(&r.running, 0, 1) {
		canceled := make(chan struct{})
		go func() {
			defer atomic.StoreInt32(&r.running, 0)
			err := r.Action(driver, canceled)
			if err != nil {
				log.Errorf("reconciler action failed: %v", err)
			}
		}()
		return canceled
	}
	return nil
}
<|endoftext|>


<commit_before>package storage

import (
	"bytes"
	"io"
	"reflect"
	"testing"
	"time"

	"github.com/taku-k/polymerase/pkg/etcd"
	"github.com/taku-k/polymerase/pkg/keys"
	"github.com/taku-k/polymerase/pkg/polypb"
	"github.com/taku-k/polymerase/pkg/storage/storagepb"
	"github.com/taku-k/polymerase/pkg/utils/testutil"
)

func toPtr(s time.Time) *time.Time {
	return &s
}

type fakeEtcdCli struct {
	etcd.ClientAPI
	FakeGetBackupMeta func(key polypb.BackupMetaKey) (polypb.BackupMetaSlice, error)

	ts []time.Time
}

func (c *fakeEtcdCli) GetBackupMeta(key polypb.BackupMetaKey) (polypb.BackupMetaSlice, error) {
	return c.FakeGetBackupMeta(key)
}

func (c *fakeEtcdCli) tpAt(i int) polypb.TimePoint {
	return polypb.NewTimePoint(c.ts[i])
}

func (c *fakeEtcdCli) tAt(i int) time.Time {
	return c.ts[i]
}

func newFakeClient(t time.Time) *fakeEtcdCli {
	c := &fakeEtcdCli{}
	c.ts = make([]time.Time, 7)
	for i := 0; i < 6; i++ {
		c.ts[i] = t.Add(time.Duration(i-6) * time.Hour)
	}
	/*
	 Time order: t0 < t1 < t2 < t3 < t4 < t5
	 db
	 ├── t0
	 │   ├── t0 (FULL)
	 │   ├── t1 (INC)
	 │   ├── t2 (INC)
	 │   └── t4 (INC)
	 └── t3
	     ├── t3 (FULL)
	     └── t5 (INC)
	*/
	c.FakeGetBackupMeta = func(key polypb.BackupMetaKey) (polypb.BackupMetaSlice, error) {
		db, _, _, _ := keys.DecodeMetaKey(key)
		if !bytes.Equal(db, []byte("db")) {
			return make(polypb.BackupMetaSlice, 0), nil
		}
		return []*polypb.BackupMeta{
			{
				StoredTime:    toPtr(c.tAt(0)),
				BaseTimePoint: c.tpAt(0),
				ToLsn:         "10",
				BackupType:    polypb.BackupType_FULL,
				Key:           keys.MakeBackupKey(db, c.tpAt(0), c.tpAt(0)),
			},
			{
				StoredTime:    toPtr(c.tAt(1)),
				BaseTimePoint: c.tpAt(0),
				ToLsn:         "20",
				BackupType:    polypb.BackupType_INC,
				Key:           keys.MakeBackupKey(db, c.tpAt(0), c.tpAt(1)),
			},
			{
				StoredTime:    toPtr(c.tAt(2)),
				BaseTimePoint: c.tpAt(0),
				ToLsn:         "30",
				BackupType:    polypb.BackupType_INC,
				Key:           keys.MakeBackupKey(db, c.tpAt(0), c.tpAt(2)),
			},
			{
				StoredTime:    toPtr(c.tAt(4)),
				BaseTimePoint: c.tpAt(0),
				ToLsn:         "110",
				BackupType:    polypb.BackupType_INC,
				Key:           keys.MakeBackupKey(db, c.tpAt(0), c.tpAt(4)),
			},
			{
				StoredTime:    toPtr(c.tAt(3)),
				BaseTimePoint: c.tpAt(3),
				ToLsn:         "100",
				BackupType:    polypb.BackupType_FULL,
				Key:           keys.MakeBackupKey(db, c.tpAt(3), c.tpAt(3)),
			},
			{
				StoredTime:    toPtr(c.tAt(5)),
				BaseTimePoint: c.tpAt(3),
				ToLsn:         "110",
				BackupType:    polypb.BackupType_INC,
				Key:           keys.MakeBackupKey(db, c.tpAt(3), c.tpAt(5)),
			},
		}, nil
	}
	return c
}

type fakePhysicalStorage struct {
	PhysicalStorage
	FakeFullBackupStream func(key polypb.Key) (io.Reader, error)
}

func (s *fakePhysicalStorage) FullBackupStream(key polypb.Key) (io.Reader, error) {
	return s.FakeFullBackupStream(key)
}

func TestBackupManager_GetLatestToLSN(t *testing.T) {
	cli := newFakeClient(time.Now())
	mngr := &BackupManager{
		EtcdCli: cli,
	}

	testCases := []struct {
		db       polypb.DatabaseID
		expected string
		errStr   string
	}{
		{
			db:       polypb.DatabaseID("db"),
			expected: "110",
		},
		{
			db:     polypb.DatabaseID("db-nothing"),
			errStr: "not found any backups",
		},
	}

	for i, tc := range testCases {
		lsn, err := mngr.GetLatestToLSN(tc.db)
		if tc.errStr == "" {
			if err != nil {
				t.Errorf("#%d: GetLatestToLSN(%q): got error %q; want success",
					i, tc.db, err)
			}
			if lsn != tc.expected {
				t.Errorf("#%d: GetLatestToLSN(%q): got wrong lsn %s; want %s",
					i, tc.db, lsn, tc.expected)
			}
		} else {
			if !testutil.IsError(err, tc.errStr) {
				t.Errorf("#%d: GetLatestToLSN(%q): got wrong error %q; want %q",
					i, tc.db, err, tc.errStr)
			}
		}
	}

}

func TestBackupManager_SearchBaseTimePointByLSN(t *testing.T) {
	tn := time.Now()
	db := polypb.DatabaseID("db")
	fakeClient := newFakeClient(tn)

	testCases := []struct {
		db       polypb.DatabaseID
		lsn      string
		expected polypb.TimePoint
	}{
		{
			db:       db,
			lsn:      "30",
			expected: fakeClient.tpAt(0),
		},
		{
			db:       db,
			lsn:      "100",
			expected: fakeClient.tpAt(3),
		},
		{
			db:       db,
			lsn:      "110",
			expected: fakeClient.tpAt(3),
		},
	}

	mngr := &BackupManager{
		EtcdCli: fakeClient,
	}

	for i, tc := range testCases {
		tp, err := mngr.SearchBaseTimePointByLSN(tc.db, tc.lsn)
		if err != nil {
			t.Errorf("#%d: got error %q; want success", i, err)
		}
		if !tp.Equal(tc.expected) {
			t.Errorf("#%d: got wrong timepoint %q; want timepoint %q",
				i, tp, tc.expected)
		}
	}
}

func TestBackupManager_SearchConsecutiveIncBackups(t *testing.T) {
	tn := time.Now()
	db := polypb.DatabaseID("db")
	cli := newFakeClient(tn)

	testCases := []struct {
		from     time.Time
		expected []*storagepb.BackupFileInfo
	}{
		{
			from: time.Now(),
			expected: []*storagepb.BackupFileInfo{
				{
					StorageType: polypb.StorageType_LOCAL_MEM,
					BackupType:  polypb.BackupType_INC,
					Key:         keys.MakeBackupKey(db, cli.tpAt(3), cli.tpAt(5)),
				},
				{
					StorageType: polypb.StorageType_LOCAL_MEM,
					BackupType:  polypb.BackupType_FULL,
					Key:         keys.MakeBackupKey(db, cli.tpAt(3), cli.tpAt(3)),
				},
			},
		},
		{
			from: cli.tAt(2).Add(time.Minute),
			expected: []*storagepb.BackupFileInfo{
				{
					StorageType: polypb.StorageType_LOCAL_MEM,
					BackupType:  polypb.BackupType_INC,
					Key:         keys.MakeBackupKey(db, cli.tpAt(0), cli.tpAt(2)),
				},
				{
					StorageType: polypb.StorageType_LOCAL_MEM,
					BackupType:  polypb.BackupType_INC,
					Key:         keys.MakeBackupKey(db, cli.tpAt(0), cli.tpAt(1)),
				},
				{
					StorageType: polypb.StorageType_LOCAL_MEM,
					BackupType:  polypb.BackupType_FULL,
					Key:         keys.MakeBackupKey(db, cli.tpAt(0), cli.tpAt(0)),
				},
			},
		},
		{
			from: cli.tAt(0).Add(time.Minute),
			expected: []*storagepb.BackupFileInfo{
				{
					StorageType: polypb.StorageType_LOCAL_MEM,
					BackupType:  polypb.BackupType_FULL,
					Key:         keys.MakeBackupKey(db, cli.tpAt(0), cli.tpAt(0)),
				},
			},
		},
	}

	mngr := &BackupManager{
		EtcdCli: cli,
		storage: &MemStorage{},
	}
	for i, tc := range testCases {
		res, err := mngr.SearchConsecutiveIncBackups(db, tc.from)
		if err != nil {
			t.Errorf("#%d: got error %q; want success", i, err)
		}
		if !reflect.DeepEqual(res, tc.expected) {
			t.Errorf("#%d: got wrong BackupFileInfo %q; want %q",
				i, res, tc.expected)
		}
	}
}

func TestBackupManager_GetFileStream(t *testing.T) {
	tn := time.Now()
	db := polypb.DatabaseID("db")
	c := &fakeEtcdCli{
		FakeGetBackupMeta: func(key polypb.BackupMetaKey) (polypb.BackupMetaSlice, error) {
			return []*polypb.BackupMeta{
				{},
			}, nil
		},
	}
	st := &fakePhysicalStorage{
		FakeFullBackupStream: func(key polypb.Key) (io.Reader, error) {
			return bytes.NewBufferString("full"), nil
		},
	}

	mngr := &BackupManager{
		EtcdCli: c,
		storage: st,
	}

	stream, err := mngr.GetFileStream(keys.MakeBackupKey(db, polypb.NewTimePoint(tn), polypb.NewTimePoint(tn)))
	if err != nil {
		t.Errorf("Got error %q; want success", err)
	}
	buf := make([]byte, 4)
	stream.Read(buf)
	if string(buf) != "full" {
		t.Errorf("Got wrong stream %q; want full", buf)
	}
}
<commit_msg>add a case of inc backup<commit_after>package storage

import (
	"bytes"
	"io"
	"io/ioutil"
	"reflect"
	"testing"
	"time"

	"github.com/taku-k/polymerase/pkg/etcd"
	"github.com/taku-k/polymerase/pkg/keys"
	"github.com/taku-k/polymerase/pkg/polypb"
	"github.com/taku-k/polymerase/pkg/storage/storagepb"
	"github.com/taku-k/polymerase/pkg/utils/testutil"
)

func toPtr(s time.Time) *time.Time {
	return &s
}

type fakeEtcdCli struct {
	etcd.ClientAPI
	FakeGetBackupMeta func(key polypb.BackupMetaKey) (polypb.BackupMetaSlice, error)

	ts []time.Time
}

func (c *fakeEtcdCli) GetBackupMeta(key polypb.BackupMetaKey) (polypb.BackupMetaSlice, error) {
	return c.FakeGetBackupMeta(key)
}

func (c *fakeEtcdCli) tpAt(i int) polypb.TimePoint {
	return polypb.NewTimePoint(c.ts[i])
}

func (c *fakeEtcdCli) tAt(i int) time.Time {
	return c.ts[i]
}

func newFakeClient(t time.Time) *fakeEtcdCli {
	c := &fakeEtcdCli{}
	c.ts = make([]time.Time, 7)
	for i := 0; i < 6; i++ {
		c.ts[i] = t.Add(time.Duration(i-6) * time.Hour)
	}
	/*
	 Time order: t0 < t1 < t2 < t3 < t4 < t5
	 db
	 ├── t0
	 │   ├── t0 (FULL)
	 │   ├── t1 (INC)
	 │   ├── t2 (INC)
	 │   └── t4 (INC)
	 └── t3
	     ├── t3 (FULL)
	     └── t5 (INC)
	*/
	c.FakeGetBackupMeta = func(key polypb.BackupMetaKey) (polypb.BackupMetaSlice, error) {
		db, _, _, _ := keys.DecodeMetaKey(key)
		if !bytes.Equal(db, []byte("db")) {
			return make(polypb.BackupMetaSlice, 0), nil
		}
		return []*polypb.BackupMeta{
			{
				StoredTime:    toPtr(c.tAt(0)),
				BaseTimePoint: c.tpAt(0),
				ToLsn:         "10",
				BackupType:    polypb.BackupType_FULL,
				Key:           keys.MakeBackupKey(db, c.tpAt(0), c.tpAt(0)),
			},
			{
				StoredTime:    toPtr(c.tAt(1)),
				BaseTimePoint: c.tpAt(0),
				ToLsn:         "20",
				BackupType:    polypb.BackupType_INC,
				Key:           keys.MakeBackupKey(db, c.tpAt(0), c.tpAt(1)),
			},
			{
				StoredTime:    toPtr(c.tAt(2)),
				BaseTimePoint: c.tpAt(0),
				ToLsn:         "30",
				BackupType:    polypb.BackupType_INC,
				Key:           keys.MakeBackupKey(db, c.tpAt(0), c.tpAt(2)),
			},
			{
				StoredTime:    toPtr(c.tAt(4)),
				BaseTimePoint: c.tpAt(0),
				ToLsn:         "110",
				BackupType:    polypb.BackupType_INC,
				Key:           keys.MakeBackupKey(db, c.tpAt(0), c.tpAt(4)),
			},
			{
				StoredTime:    toPtr(c.tAt(3)),
				BaseTimePoint: c.tpAt(3),
				ToLsn:         "100",
				BackupType:    polypb.BackupType_FULL,
				Key:           keys.MakeBackupKey(db, c.tpAt(3), c.tpAt(3)),
			},
			{
				StoredTime:    toPtr(c.tAt(5)),
				BaseTimePoint: c.tpAt(3),
				ToLsn:         "110",
				BackupType:    polypb.BackupType_INC,
				Key:           keys.MakeBackupKey(db, c.tpAt(3), c.tpAt(5)),
			},
		}, nil
	}
	return c
}

type fakePhysicalStorage struct {
	PhysicalStorage
	FakeFullBackupStream func(key polypb.Key) (io.Reader, error)
	FakeIncBackupStream  func(key polypb.Key) (io.Reader, error)
}

func (s *fakePhysicalStorage) FullBackupStream(key polypb.Key) (io.Reader, error) {
	return s.FakeFullBackupStream(key)
}

func (s *fakePhysicalStorage) IncBackupStream(key polypb.Key) (io.Reader, error) {
	return s.FakeIncBackupStream(key)
}

func TestBackupManager_GetLatestToLSN(t *testing.T) {
	cli := newFakeClient(time.Now())
	mngr := &BackupManager{
		EtcdCli: cli,
	}

	testCases := []struct {
		db       polypb.DatabaseID
		expected string
		errStr   string
	}{
		{
			db:       polypb.DatabaseID("db"),
			expected: "110",
		},
		{
			db:     polypb.DatabaseID("db-nothing"),
			errStr: "not found any backups",
		},
	}

	for i, tc := range testCases {
		lsn, err := mngr.GetLatestToLSN(tc.db)
		if tc.errStr == "" {
			if err != nil {
				t.Errorf("#%d: GetLatestToLSN(%q): got error %q; want success",
					i, tc.db, err)
			}
			if lsn != tc.expected {
				t.Errorf("#%d: GetLatestToLSN(%q): got wrong lsn %s; want %s",
					i, tc.db, lsn, tc.expected)
			}
		} else {
			if !testutil.IsError(err, tc.errStr) {
				t.Errorf("#%d: GetLatestToLSN(%q): got wrong error %q; want %q",
					i, tc.db, err, tc.errStr)
			}
		}
	}

}

func TestBackupManager_SearchBaseTimePointByLSN(t *testing.T) {
	tn := time.Now()
	db := polypb.DatabaseID("db")
	fakeClient := newFakeClient(tn)

	testCases := []struct {
		db       polypb.DatabaseID
		lsn      string
		expected polypb.TimePoint
	}{
		{
			db:       db,
			lsn:      "30",
			expected: fakeClient.tpAt(0),
		},
		{
			db:       db,
			lsn:      "100",
			expected: fakeClient.tpAt(3),
		},
		{
			db:       db,
			lsn:      "110",
			expected: fakeClient.tpAt(3),
		},
	}

	mngr := &BackupManager{
		EtcdCli: fakeClient,
	}

	for i, tc := range testCases {
		tp, err := mngr.SearchBaseTimePointByLSN(tc.db, tc.lsn)
		if err != nil {
			t.Errorf("#%d: got error %q; want success", i, err)
		}
		if !tp.Equal(tc.expected) {
			t.Errorf("#%d: got wrong timepoint %q; want timepoint %q",
				i, tp, tc.expected)
		}
	}
}

func TestBackupManager_SearchConsecutiveIncBackups(t *testing.T) {
	tn := time.Now()
	db := polypb.DatabaseID("db")
	cli := newFakeClient(tn)

	testCases := []struct {
		from     time.Time
		expected []*storagepb.BackupFileInfo
	}{
		{
			from: time.Now(),
			expected: []*storagepb.BackupFileInfo{
				{
					StorageType: polypb.StorageType_LOCAL_MEM,
					BackupType:  polypb.BackupType_INC,
					Key:         keys.MakeBackupKey(db, cli.tpAt(3), cli.tpAt(5)),
				},
				{
					StorageType: polypb.StorageType_LOCAL_MEM,
					BackupType:  polypb.BackupType_FULL,
					Key:         keys.MakeBackupKey(db, cli.tpAt(3), cli.tpAt(3)),
				},
			},
		},
		{
			from: cli.tAt(2).Add(time.Minute),
			expected: []*storagepb.BackupFileInfo{
				{
					StorageType: polypb.StorageType_LOCAL_MEM,
					BackupType:  polypb.BackupType_INC,
					Key:         keys.MakeBackupKey(db, cli.tpAt(0), cli.tpAt(2)),
				},
				{
					StorageType: polypb.StorageType_LOCAL_MEM,
					BackupType:  polypb.BackupType_INC,
					Key:         keys.MakeBackupKey(db, cli.tpAt(0), cli.tpAt(1)),
				},
				{
					StorageType: polypb.StorageType_LOCAL_MEM,
					BackupType:  polypb.BackupType_FULL,
					Key:         keys.MakeBackupKey(db, cli.tpAt(0), cli.tpAt(0)),
				},
			},
		},
		{
			from: cli.tAt(0).Add(time.Minute),
			expected: []*storagepb.BackupFileInfo{
				{
					StorageType: polypb.StorageType_LOCAL_MEM,
					BackupType:  polypb.BackupType_FULL,
					Key:         keys.MakeBackupKey(db, cli.tpAt(0), cli.tpAt(0)),
				},
			},
		},
	}

	mngr := &BackupManager{
		EtcdCli: cli,
		storage: &MemStorage{},
	}
	for i, tc := range testCases {
		res, err := mngr.SearchConsecutiveIncBackups(db, tc.from)
		if err != nil {
			t.Errorf("#%d: got error %q; want success", i, err)
		}
		if !reflect.DeepEqual(res, tc.expected) {
			t.Errorf("#%d: got wrong BackupFileInfo %q; want %q",
				i, res, tc.expected)
		}
	}
}

func TestBackupManager_GetFileStream(t *testing.T) {
	tn := time.Now()
	db := polypb.DatabaseID("db")

	testCases := []struct {
		getBackupMeta func(key polypb.BackupMetaKey) (polypb.BackupMetaSlice, error)
		storage       *fakePhysicalStorage
		expected      string
	}{
		{
			getBackupMeta: func(key polypb.BackupMetaKey) (polypb.BackupMetaSlice, error) {
				return []*polypb.BackupMeta{
					{
						BackupType: polypb.BackupType_FULL,
					},
				}, nil
			},
			storage: &fakePhysicalStorage{
				FakeFullBackupStream: func(key polypb.Key) (io.Reader, error) {
					return bytes.NewBufferString("full"), nil
				},
			},
			expected: "full",
		},
		{
			getBackupMeta: func(key polypb.BackupMetaKey) (polypb.BackupMetaSlice, error) {
				return []*polypb.BackupMeta{
					{
						BackupType: polypb.BackupType_INC,
					},
				}, nil
			},
			storage: &fakePhysicalStorage{
				FakeIncBackupStream: func(key polypb.Key) (io.Reader, error) {
					return bytes.NewBufferString("inc"), nil
				},
			},
			expected: "inc",
		},
	}

	for i, tc := range testCases {
		mngr := &BackupManager{
			EtcdCli: &fakeEtcdCli{
				FakeGetBackupMeta: tc.getBackupMeta,
			},
			storage: tc.storage,
		}

		stream, err := mngr.GetFileStream(keys.MakeBackupKey(db, polypb.NewTimePoint(tn), polypb.NewTimePoint(tn)))
		if err != nil {
			t.Errorf("#%d: Got error %q; want success", i, err)
		}
		buf, err := ioutil.ReadAll(stream)
		if err != nil {
			t.Errorf("#%d: ioutil.ReadAll returns error %q; want success", i, err)
		}
		if string(buf) != tc.expected {
			t.Errorf("#%d: Got wrong stream %q; want %s", i, buf, tc.expected)
		}
	}
}
<|endoftext|>


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

package system

import (
	"unsafe"

	"golang.org/x/sys/unix"
)

func Lchflags(path string, flags uint32) error {
	p, err := unix.BytePtrFromString(path)
	if err != nil {
		return err
	}
	_, _, e1 := unix.Syscall(unix.SYS_LCHFLAGS, uintptr(unsafe.Pointer(p)), uintptr(flags), 0)
	if e1 != 0 {
		return e1
	}
	return nil
}
<commit_msg>pkg/system: Add file flag names for FreeBSD<commit_after>//go:build freebsd
// +build freebsd

package system

import (
	"unsafe"

	"golang.org/x/sys/unix"
)

// Flag values from <sys/stat.h>
const (
	/*
	 * Definitions of flags stored in file flags word.
	 *
	 * Super-user and owner changeable flags.
	 */
	UF_SETTABLE  uint32 = 0x0000ffff /* mask of owner changeable flags */
	UF_NODUMP    uint32 = 0x00000001 /* do not dump file */
	UF_IMMUTABLE uint32 = 0x00000002 /* file may not be changed */
	UF_APPEND    uint32 = 0x00000004 /* writes to file may only append */
	UF_OPAQUE    uint32 = 0x00000008 /* directory is opaque wrt. union */
	UF_NOUNLINK  uint32 = 0x00000010 /* file may not be removed or renamed */

	UF_SYSTEM   uint32 = 0x00000080 /* Windows system file bit */
	UF_SPARSE   uint32 = 0x00000100 /* sparse file */
	UF_OFFLINE  uint32 = 0x00000200 /* file is offline */
	UF_REPARSE  uint32 = 0x00000400 /* Windows reparse point file bit */
	UF_ARCHIVE  uint32 = 0x00000800 /* file needs to be archived */
	UF_READONLY uint32 = 0x00001000 /* Windows readonly file bit */
	/* This is the same as the MacOS X definition of UF_HIDDEN. */
	UF_HIDDEN uint32 = 0x00008000 /* file is hidden */

	/*
	 * Super-user changeable flags.
	 */
	SF_SETTABLE  uint32 = 0xffff0000 /* mask of superuser changeable flags */
	SF_ARCHIVED  uint32 = 0x00010000 /* file is archived */
	SF_IMMUTABLE uint32 = 0x00020000 /* file may not be changed */
	SF_APPEND    uint32 = 0x00040000 /* writes to file may only append */
	SF_NOUNLINK  uint32 = 0x00100000 /* file may not be removed or renamed */
	SF_SNAPSHOT  uint32 = 0x00200000 /* snapshot inode */
)

func Lchflags(path string, flags uint32) error {
	p, err := unix.BytePtrFromString(path)
	if err != nil {
		return err
	}
	_, _, e1 := unix.Syscall(unix.SYS_LCHFLAGS, uintptr(unsafe.Pointer(p)), uintptr(flags), 0)
	if e1 != 0 {
		return e1
	}
	return nil
}
<|endoftext|>


<commit_before>package terraform

import (
	"fmt"
	"path/filepath"

	"github.com/openshift/installer/data"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"

	"github.com/openshift/installer/pkg/lineprinter"
	texec "github.com/openshift/installer/pkg/terraform/exec"
)

const (
	// StateFileName is the default name for Terraform state files.
	StateFileName string = "terraform.tfstate"

	// VarFileName is the default name for Terraform var file.
	VarFileName string = "terraform.tfvars"
)

// Version gets the output of 'terrraform version'.
func Version() (version string, err error) {
	return texec.Version(), nil
}

// Apply unpacks the platform-specific Terraform modules into the
// given directory and then runs 'terraform init' and 'terraform
// apply'.  It returns the absolute path of the tfstate file, rooted
// in the specified directory, along with any errors from Terraform.
func Apply(dir string, platform string, extraArgs ...string) (path string, err error) {
	err = unpackAndInit(dir, platform)
	if err != nil {
		return "", err
	}

	defaultArgs := []string{
		"-auto-approve",
		"-input=false",
		fmt.Sprintf("-state=%s", filepath.Join(dir, StateFileName)),
		fmt.Sprintf("-state-out=%s", filepath.Join(dir, StateFileName)),
		fmt.Sprintf("-var-file=%s", filepath.Join(dir, VarFileName)),
	}
	args := append(defaultArgs, extraArgs...)
	args = append(args, dir)
	sf := filepath.Join(dir, StateFileName)

	tDebug := &lineprinter.Trimmer{WrappedPrint: logrus.Debug}
	tError := &lineprinter.Trimmer{WrappedPrint: logrus.Error}
	lpDebug := &lineprinter.LinePrinter{Print: tDebug.Print}
	lpError := &lineprinter.LinePrinter{Print: tError.Print}
	defer lpDebug.Close()
	defer lpError.Close()

	if exitCode := texec.Apply(dir, args, lpDebug, lpError); exitCode != 0 {
		return sf, errors.New("failed to apply using terraform")
	}
	return sf, nil
}

// Destroy unpacks the platform-specific Terraform modules into the
// given directory and then runs 'terraform init' and 'terraform
// destroy'.
func Destroy(dir string, platform string, extraArgs ...string) (err error) {
	err = unpackAndInit(dir, platform)
	if err != nil {
		return err
	}

	defaultArgs := []string{
		"-auto-approve",
		"-input=false",
		fmt.Sprintf("-state=%s", filepath.Join(dir, StateFileName)),
		fmt.Sprintf("-state-out=%s", filepath.Join(dir, StateFileName)),
		fmt.Sprintf("-var-file=%s", filepath.Join(dir, VarFileName)),
	}
	args := append(defaultArgs, extraArgs...)
	args = append(args, dir)

	tDebug := &lineprinter.Trimmer{WrappedPrint: logrus.Debug}
	tError := &lineprinter.Trimmer{WrappedPrint: logrus.Error}
	lpDebug := &lineprinter.LinePrinter{Print: tDebug.Print}
	lpError := &lineprinter.LinePrinter{Print: tError.Print}
	defer lpDebug.Close()
	defer lpError.Close()

	if exitCode := texec.Destroy(dir, args, lpDebug, lpError); exitCode != 0 {
		return errors.New("failed to destroy using terraform")
	}
	return nil
}

// unpack unpacks the platform-specific Terraform modules into the
// given directory.
func unpack(dir string, platform string) (err error) {
	err = data.Unpack(dir, platform)
	if err != nil {
		return err
	}

	err = data.Unpack(filepath.Join(dir, "config.tf"), "config.tf")
	if err != nil {
		return err
	}

	return nil
}

// unpackAndInit unpacks the platform-specific Terraform modules into
// the given directory and then runs 'terraform init'.
func unpackAndInit(dir string, platform string) (err error) {
	err = unpack(dir, platform)
	if err != nil {
		return errors.Wrap(err, "failed to unpack Terraform modules")
	}

	tDebug := &lineprinter.Trimmer{WrappedPrint: logrus.Debug}
	tError := &lineprinter.Trimmer{WrappedPrint: logrus.Error}
	lpDebug := &lineprinter.LinePrinter{Print: tDebug.Print}
	lpError := &lineprinter.LinePrinter{Print: tError.Print}
	defer lpDebug.Close()
	defer lpError.Close()

	if exitCode := texec.Init(dir, []string{dir}, lpDebug, lpError); exitCode != 0 {
		return errors.New("failed to initialize Terraform")
	}
	return nil
}
<commit_msg>terraform: create symlinks so that terraform can exec embedded plugins<commit_after>package terraform

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

	"github.com/openshift/installer/data"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"

	"github.com/openshift/installer/pkg/lineprinter"
	texec "github.com/openshift/installer/pkg/terraform/exec"
	"github.com/openshift/installer/pkg/terraform/exec/plugins"
)

const (
	// StateFileName is the default name for Terraform state files.
	StateFileName string = "terraform.tfstate"

	// VarFileName is the default name for Terraform var file.
	VarFileName string = "terraform.tfvars"
)

// Version gets the output of 'terrraform version'.
func Version() (version string, err error) {
	return texec.Version(), nil
}

// Apply unpacks the platform-specific Terraform modules into the
// given directory and then runs 'terraform init' and 'terraform
// apply'.  It returns the absolute path of the tfstate file, rooted
// in the specified directory, along with any errors from Terraform.
func Apply(dir string, platform string, extraArgs ...string) (path string, err error) {
	err = unpackAndInit(dir, platform)
	if err != nil {
		return "", err
	}

	defaultArgs := []string{
		"-auto-approve",
		"-input=false",
		fmt.Sprintf("-state=%s", filepath.Join(dir, StateFileName)),
		fmt.Sprintf("-state-out=%s", filepath.Join(dir, StateFileName)),
		fmt.Sprintf("-var-file=%s", filepath.Join(dir, VarFileName)),
	}
	args := append(defaultArgs, extraArgs...)
	args = append(args, dir)
	sf := filepath.Join(dir, StateFileName)

	tDebug := &lineprinter.Trimmer{WrappedPrint: logrus.Debug}
	tError := &lineprinter.Trimmer{WrappedPrint: logrus.Error}
	lpDebug := &lineprinter.LinePrinter{Print: tDebug.Print}
	lpError := &lineprinter.LinePrinter{Print: tError.Print}
	defer lpDebug.Close()
	defer lpError.Close()

	if exitCode := texec.Apply(dir, args, lpDebug, lpError); exitCode != 0 {
		return sf, errors.New("failed to apply using terraform")
	}
	return sf, nil
}

// Destroy unpacks the platform-specific Terraform modules into the
// given directory and then runs 'terraform init' and 'terraform
// destroy'.
func Destroy(dir string, platform string, extraArgs ...string) (err error) {
	err = unpackAndInit(dir, platform)
	if err != nil {
		return err
	}

	defaultArgs := []string{
		"-auto-approve",
		"-input=false",
		fmt.Sprintf("-state=%s", filepath.Join(dir, StateFileName)),
		fmt.Sprintf("-state-out=%s", filepath.Join(dir, StateFileName)),
		fmt.Sprintf("-var-file=%s", filepath.Join(dir, VarFileName)),
	}
	args := append(defaultArgs, extraArgs...)
	args = append(args, dir)

	tDebug := &lineprinter.Trimmer{WrappedPrint: logrus.Debug}
	tError := &lineprinter.Trimmer{WrappedPrint: logrus.Error}
	lpDebug := &lineprinter.LinePrinter{Print: tDebug.Print}
	lpError := &lineprinter.LinePrinter{Print: tError.Print}
	defer lpDebug.Close()
	defer lpError.Close()

	if exitCode := texec.Destroy(dir, args, lpDebug, lpError); exitCode != 0 {
		return errors.New("failed to destroy using terraform")
	}
	return nil
}

// unpack unpacks the platform-specific Terraform modules into the
// given directory.
func unpack(dir string, platform string) (err error) {
	err = data.Unpack(dir, platform)
	if err != nil {
		return err
	}

	err = data.Unpack(filepath.Join(dir, "config.tf"), "config.tf")
	if err != nil {
		return err
	}

	return nil
}

// unpackAndInit unpacks the platform-specific Terraform modules into
// the given directory and then runs 'terraform init'.
func unpackAndInit(dir string, platform string) (err error) {
	err = unpack(dir, platform)
	if err != nil {
		return errors.Wrap(err, "failed to unpack Terraform modules")
	}

	if err := setupEmbeddedPlugins(dir); err != nil {
		return errors.Wrap(err, "failed to setup embedded terraform plugins")
	}

	tDebug := &lineprinter.Trimmer{WrappedPrint: logrus.Debug}
	tError := &lineprinter.Trimmer{WrappedPrint: logrus.Error}
	lpDebug := &lineprinter.LinePrinter{Print: tDebug.Print}
	lpError := &lineprinter.LinePrinter{Print: tError.Print}
	defer lpDebug.Close()
	defer lpError.Close()

	if exitCode := texec.Init(dir, []string{dir}, lpDebug, lpError); exitCode != 0 {
		return errors.New("failed to initialize Terraform")
	}
	return nil
}

func setupEmbeddedPlugins(dir string) error {
	execPath, err := os.Executable()
	if err != nil {
		return errors.Wrap(err, "failed to find path for the executable")
	}

	pdir := filepath.Join(dir, "plugins")
	if err := os.MkdirAll(pdir, 0777); err != nil {
		return err
	}
	for name := range plugins.KnownPlugins {
		dst := filepath.Join(pdir, name)
		if runtime.GOOS == "windows" {
			dst = fmt.Sprintf("%s.exe", dst)
		}
		if _, err := os.Stat(dst); err == nil {
			// stat succeeded, the plugin already exists.
			continue
		}
		logrus.Debugf("Symlinking plugin %s src: %q dst: %q", name, execPath, dst)
		if err := os.Symlink(execPath, dst); err != nil {
			return err
		}
	}
	return nil
}
<|endoftext|>


<commit_before>// Copyright 2015 CoreOS, 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 updateengine

import (
	"fmt"
	"os"
	"strconv"

	"github.com/godbus/dbus"
)

const (
	dbusPath            = "/com/coreos/update1"
	dbusInterface       = "com.coreos.update1.Manager"
	dbusMember          = "StatusUpdate"
	dbusMemberInterface = dbusInterface + "." + dbusMember
	signalBuffer        = 32 // TODO(bp): What is a reasonable value here?
)

type Client struct {
	conn   *dbus.Conn
	object dbus.BusObject
	ch     chan *dbus.Signal
}

func New() (c *Client, err error) {
	c = new(Client)

	c.conn, err = dbus.SystemBusPrivate()
	if err != nil {
		return nil, err
	}

	methods := []dbus.Auth{dbus.AuthExternal(strconv.Itoa(os.Getuid()))}
	err = c.conn.Auth(methods)
	if err != nil {
		c.conn.Close()
		return nil, err
	}

	err = c.conn.Hello()
	if err != nil {
		c.conn.Close()
		return nil, err
	}

	c.object = c.conn.Object("com.coreos.update1", dbus.ObjectPath(dbusPath))

	// Setup the filter for the StatusUpdate signals
	match := fmt.Sprintf("type='signal',interface='%s',member='%s'", dbusInterface, dbusMember)

	call := c.conn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0, match)
	if call.Err != nil {
		return nil, err
	}

	c.ch = make(chan *dbus.Signal, signalBuffer)
	c.conn.Signal(c.ch)

	return c, nil
}

func (c *Client) Close() error {
	if c.conn != nil {
		return c.conn.Close()
	}
	return nil
}

// ReceiveStatuses receives signal messages from dbus and sends them as Statues
// on the rcvr channel, until the stop channel is closed. An attempt is made to
// get the initial status and send it on the rcvr channel before receiving
// starts.
func (c *Client) ReceiveStatuses(rcvr chan Status, stop <-chan struct{}) {
	// if there is an error getting the current status, ignore it and just
	// move onto the main loop.
	st, _ := c.GetStatus()
	rcvr <- st

	for {
		select {
		case <-stop:
			return
		case signal := <-c.ch:
			rcvr <- NewStatus(signal.Body)
		}
	}
}

func (c *Client) RebootNeededSignal(rcvr chan Status, stop <-chan struct{}) {
	for {
		select {
		case <-stop:
			return
		case signal := <-c.ch:
			s := NewStatus(signal.Body)
			if s.CurrentOperation == UpdateStatusUpdatedNeedReboot {
				rcvr <- s
			}
		}
	}
}

// GetStatus gets the current status from update_engine
func (c *Client) GetStatus() (result Status, err error) {
	call := c.object.Call(dbusInterface+".GetStatus", 0)
	err = call.Err
	if err != nil {
		return
	}

	result = NewStatus(call.Body)

	return
}

// AttemptUpdate will trigger an update if available. This is an asynchronous
// call - it returns immediately.
func (c *Client) AttemptUpdate() error {
	call := c.object.Call(dbusInterface+".AttemptUpdate", 0)
	return call.Err
}
<commit_msg>pkg/updateengine: Fix call error return<commit_after>// Copyright 2015 CoreOS, 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 updateengine

import (
	"fmt"
	"os"
	"strconv"

	"github.com/godbus/dbus"
)

const (
	dbusPath            = "/com/coreos/update1"
	dbusInterface       = "com.coreos.update1.Manager"
	dbusMember          = "StatusUpdate"
	dbusMemberInterface = dbusInterface + "." + dbusMember
	signalBuffer        = 32 // TODO(bp): What is a reasonable value here?
)

type Client struct {
	conn   *dbus.Conn
	object dbus.BusObject
	ch     chan *dbus.Signal
}

func New() (c *Client, err error) {
	c = new(Client)

	c.conn, err = dbus.SystemBusPrivate()
	if err != nil {
		return nil, err
	}

	methods := []dbus.Auth{dbus.AuthExternal(strconv.Itoa(os.Getuid()))}
	err = c.conn.Auth(methods)
	if err != nil {
		c.conn.Close()
		return nil, err
	}

	err = c.conn.Hello()
	if err != nil {
		c.conn.Close()
		return nil, err
	}

	c.object = c.conn.Object("com.coreos.update1", dbus.ObjectPath(dbusPath))

	// Setup the filter for the StatusUpdate signals
	match := fmt.Sprintf("type='signal',interface='%s',member='%s'", dbusInterface, dbusMember)

	call := c.conn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0, match)
	if call.Err != nil {
		return nil, call.Err
	}

	c.ch = make(chan *dbus.Signal, signalBuffer)
	c.conn.Signal(c.ch)

	return c, nil
}

func (c *Client) Close() error {
	if c.conn != nil {
		return c.conn.Close()
	}
	return nil
}

// ReceiveStatuses receives signal messages from dbus and sends them as Statues
// on the rcvr channel, until the stop channel is closed. An attempt is made to
// get the initial status and send it on the rcvr channel before receiving
// starts.
func (c *Client) ReceiveStatuses(rcvr chan Status, stop <-chan struct{}) {
	// if there is an error getting the current status, ignore it and just
	// move onto the main loop.
	st, _ := c.GetStatus()
	rcvr <- st

	for {
		select {
		case <-stop:
			return
		case signal := <-c.ch:
			rcvr <- NewStatus(signal.Body)
		}
	}
}

func (c *Client) RebootNeededSignal(rcvr chan Status, stop <-chan struct{}) {
	for {
		select {
		case <-stop:
			return
		case signal := <-c.ch:
			s := NewStatus(signal.Body)
			if s.CurrentOperation == UpdateStatusUpdatedNeedReboot {
				rcvr <- s
			}
		}
	}
}

// GetStatus gets the current status from update_engine
func (c *Client) GetStatus() (result Status, err error) {
	call := c.object.Call(dbusInterface+".GetStatus", 0)
	err = call.Err
	if err != nil {
		return
	}

	result = NewStatus(call.Body)

	return
}

// AttemptUpdate will trigger an update if available. This is an asynchronous
// call - it returns immediately.
func (c *Client) AttemptUpdate() error {
	call := c.object.Call(dbusInterface+".AttemptUpdate", 0)
	return call.Err
}
<|endoftext|>


<commit_before>/*
Copyright 2017 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package validate

import (
	"net/http"
	"strconv"
	"time"

	"github.com/kubernetes-incubator/cri-tools/pkg/framework"
	internalapi "k8s.io/kubernetes/pkg/kubelet/api"
	runtimeapi "k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/runtime"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

const (
	defaultDNSServer   string = "10.10.10.10"
	defaultDNSSearch   string = "google.com"
	defaultDNSOption   string = "ndots:8"
	resolvConfigPath   string = "/etc/resolv.conf"
	nginxImage         string = "nginx"
	nginxContainerPort int32  = 80
	nginxHostPort      int32  = 8000
)

var _ = framework.KubeDescribe("Networking", func() {
	f := framework.NewDefaultCRIFramework()

	var rc internalapi.RuntimeService
	var ic internalapi.ImageManagerService

	BeforeEach(func() {
		rc = f.CRIClient.CRIRuntimeClient
		ic = f.CRIClient.CRIImageClient
	})

	Context("runtime should support networking", func() {
		var podID string

		AfterEach(func() {
			By("stop PodSandbox")
			rc.StopPodSandbox(podID)
			By("delete PodSandbox")
			rc.RemovePodSandbox(podID)
		})

		It("runtime should support DNS config [Conformance]", func() {
			By("create a PodSandbxo with DNS config")
			var podConfig *runtimeapi.PodSandboxConfig
			podID, podConfig = createPodSandWithDNSConfig(rc)

			By("create container")
			containerID := createDefaultContainer(rc, ic, podID, podConfig, "container-for-DNS-config-test-")

			By("start container")
			startContainer(rc, containerID)

			By("check DNS config")
			expectedContent := []string{
				"nameserver " + defaultDNSServer,
				"search " + defaultDNSSearch,
				"options " + defaultDNSOption,
			}
			checkDNSConfig(rc, containerID, expectedContent)
		})

		It("runtime should support port mapping with only container port [Conformance]", func() {
			By("create a PodSandbxo with container port port mapping")
			var podConfig *runtimeapi.PodSandboxConfig
			portMappings := []*runtimeapi.PortMapping{
				{
					ContainerPort: nginxContainerPort,
				},
			}
			podID, podConfig = createPodSandboxWithPortMapping(rc, portMappings)

			By("create a nginx container")
			containerID := createNginxContainer(rc, ic, podID, podConfig, "container-for-container-port")

			By("start the nginx container")
			startContainer(rc, containerID)

			By("check the port mapping with only container port")
			checkPortMapping(rc, podID, true)
		})

		It("runtime should support port mapping with host port and container port [Conformance]", func() {
			By("create a PodSandbxo with host port and container port port mapping")
			var podConfig *runtimeapi.PodSandboxConfig
			portMappings := []*runtimeapi.PortMapping{
				{
					ContainerPort: nginxContainerPort,
					HostPort:      nginxHostPort,
				},
			}
			podID, podConfig = createPodSandboxWithPortMapping(rc, portMappings)

			By("create a nginx container")
			containerID := createNginxContainer(rc, ic, podID, podConfig, "container-for-host-port")

			By("start the nginx container")
			startContainer(rc, containerID)

			By("check the port mapping with host port and container port")
			checkPortMapping(rc, podID, false)
		})
	})
})

// createPodSandWithDNSConfig create a PodSandbxo with DNS config.
func createPodSandWithDNSConfig(c internalapi.RuntimeService) (string, *runtimeapi.PodSandboxConfig) {
	podSandboxName := "create-PodSandbox-with-DNS-config" + framework.NewUUID()
	uid := defaultUIDPrefix + framework.NewUUID()
	namespace := defaultNamespacePrefix + framework.NewUUID()
	config := &runtimeapi.PodSandboxConfig{
		Metadata: buildPodSandboxMetadata(podSandboxName, uid, namespace, defaultAttempt),
		DnsConfig: &runtimeapi.DNSConfig{
			Servers:  []string{defaultDNSServer},
			Searches: []string{defaultDNSSearch},
			Options:  []string{defaultDNSOption},
		},
		Linux: &runtimeapi.LinuxPodSandboxConfig{},
	}

	podID := runPodSandbox(c, config)
	return podID, config
}

// createPodSandboxWithPortMapping create a PodSandbxo with port mapping.
func createPodSandboxWithPortMapping(c internalapi.RuntimeService, portMappings []*runtimeapi.PortMapping) (string, *runtimeapi.PodSandboxConfig) {
	podSandboxName := "create-PodSandbox-with-port-mapping" + framework.NewUUID()
	uid := defaultUIDPrefix + framework.NewUUID()
	namespace := defaultNamespacePrefix + framework.NewUUID()
	config := &runtimeapi.PodSandboxConfig{
		Metadata:     buildPodSandboxMetadata(podSandboxName, uid, namespace, defaultAttempt),
		PortMappings: portMappings,
		Linux:        &runtimeapi.LinuxPodSandboxConfig{},
	}

	podID := runPodSandbox(c, config)
	return podID, config
}

// checkDNSConfig checks the content of /etc/resolv.conf.
func checkDNSConfig(c internalapi.RuntimeService, containerID string, expectedContent []string) {
	By("get the content of /etc/resolv.conf via execSync")
	cmd := []string{"cat", resolvConfigPath}
	stdout, stderr, err := c.ExecSync(containerID, cmd, time.Duration(defaultExecSyncTimeout)*time.Second)
	framework.ExpectNoError(err, "failed to execSync in container %q", containerID)
	for _, content := range expectedContent {
		Expect(string(stdout)).To(ContainSubstring(content), "The stdout output of execSync should contain %q", content)
	}
	Expect(stderr).To(BeNil(), "The stderr should be nil.")
	framework.Logf("check DNS config succeed")
}

// createNginxContainer creates a  nginx container.
func createNginxContainer(rc internalapi.RuntimeService, ic internalapi.ImageManagerService, podID string, podConfig *runtimeapi.PodSandboxConfig, prefix string) string {
	containerName := prefix + framework.NewUUID()
	containerConfig := &runtimeapi.ContainerConfig{
		Metadata: buildContainerMetadata(containerName, defaultAttempt),
		Image:    &runtimeapi.ImageSpec{Image: nginxImage},
		Linux:    &runtimeapi.LinuxContainerConfig{},
	}
	return createContainer(rc, ic, containerConfig, podID, podConfig)
}

// checkPortMapping check if the given IP:port works fine.
func checkPortMapping(c internalapi.RuntimeService, podID string, containerPortOnly bool) {
	By("get the IP:port needed to be checked")
	url := "http://"
	if containerPortOnly {
		status := getPodSandboxStatus(c, podID)
		Expect(status.GetNetwork()).NotTo(BeNil(), "The network in status should not be nil.")
		Expect(status.GetNetwork().Ip).NotTo(BeNil(), "The IP should not be nil.")
		url += status.GetNetwork().Ip + ":" + strconv.Itoa(int(nginxContainerPort))
	} else {
		url += "localhost:" + strconv.Itoa(int(nginxHostPort))
	}
	framework.Logf("the IP:port is " + url)

	By("check the content of " + url)
	resp, err := http.Get(url)
	framework.ExpectNoError(err, "failed to get the content of %q", url)
	Expect(resp.StatusCode).To(Equal(200), "The status code of response should be 200.")
	framework.Logf("check port mapping succeed")
}
<commit_msg>fix misspell "podSandbox"<commit_after>/*
Copyright 2017 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package validate

import (
	"net/http"
	"strconv"
	"time"

	"github.com/kubernetes-incubator/cri-tools/pkg/framework"
	internalapi "k8s.io/kubernetes/pkg/kubelet/api"
	runtimeapi "k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/runtime"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

const (
	defaultDNSServer   string = "10.10.10.10"
	defaultDNSSearch   string = "google.com"
	defaultDNSOption   string = "ndots:8"
	resolvConfigPath   string = "/etc/resolv.conf"
	nginxImage         string = "nginx"
	nginxContainerPort int32  = 80
	nginxHostPort      int32  = 8000
)

var _ = framework.KubeDescribe("Networking", func() {
	f := framework.NewDefaultCRIFramework()

	var rc internalapi.RuntimeService
	var ic internalapi.ImageManagerService

	BeforeEach(func() {
		rc = f.CRIClient.CRIRuntimeClient
		ic = f.CRIClient.CRIImageClient
	})

	Context("runtime should support networking", func() {
		var podID string

		AfterEach(func() {
			By("stop PodSandbox")
			rc.StopPodSandbox(podID)
			By("delete PodSandbox")
			rc.RemovePodSandbox(podID)
		})

		It("runtime should support DNS config [Conformance]", func() {
			By("create a PodSandbox with DNS config")
			var podConfig *runtimeapi.PodSandboxConfig
			podID, podConfig = createPodSandWithDNSConfig(rc)

			By("create container")
			containerID := createDefaultContainer(rc, ic, podID, podConfig, "container-for-DNS-config-test-")

			By("start container")
			startContainer(rc, containerID)

			By("check DNS config")
			expectedContent := []string{
				"nameserver " + defaultDNSServer,
				"search " + defaultDNSSearch,
				"options " + defaultDNSOption,
			}
			checkDNSConfig(rc, containerID, expectedContent)
		})

		It("runtime should support port mapping with only container port [Conformance]", func() {
			By("create a PodSandbox with container port port mapping")
			var podConfig *runtimeapi.PodSandboxConfig
			portMappings := []*runtimeapi.PortMapping{
				{
					ContainerPort: nginxContainerPort,
				},
			}
			podID, podConfig = createPodSandboxWithPortMapping(rc, portMappings)

			By("create a nginx container")
			containerID := createNginxContainer(rc, ic, podID, podConfig, "container-for-container-port")

			By("start the nginx container")
			startContainer(rc, containerID)

			By("check the port mapping with only container port")
			checkPortMapping(rc, podID, true)
		})

		It("runtime should support port mapping with host port and container port [Conformance]", func() {
			By("create a PodSandbox with host port and container port port mapping")
			var podConfig *runtimeapi.PodSandboxConfig
			portMappings := []*runtimeapi.PortMapping{
				{
					ContainerPort: nginxContainerPort,
					HostPort:      nginxHostPort,
				},
			}
			podID, podConfig = createPodSandboxWithPortMapping(rc, portMappings)

			By("create a nginx container")
			containerID := createNginxContainer(rc, ic, podID, podConfig, "container-for-host-port")

			By("start the nginx container")
			startContainer(rc, containerID)

			By("check the port mapping with host port and container port")
			checkPortMapping(rc, podID, false)
		})
	})
})

// createPodSandWithDNSConfig create a PodSandbox with DNS config.
func createPodSandWithDNSConfig(c internalapi.RuntimeService) (string, *runtimeapi.PodSandboxConfig) {
	podSandboxName := "create-PodSandbox-with-DNS-config" + framework.NewUUID()
	uid := defaultUIDPrefix + framework.NewUUID()
	namespace := defaultNamespacePrefix + framework.NewUUID()
	config := &runtimeapi.PodSandboxConfig{
		Metadata: buildPodSandboxMetadata(podSandboxName, uid, namespace, defaultAttempt),
		DnsConfig: &runtimeapi.DNSConfig{
			Servers:  []string{defaultDNSServer},
			Searches: []string{defaultDNSSearch},
			Options:  []string{defaultDNSOption},
		},
		Linux: &runtimeapi.LinuxPodSandboxConfig{},
	}

	podID := runPodSandbox(c, config)
	return podID, config
}

// createPodSandboxWithPortMapping create a PodSandbox with port mapping.
func createPodSandboxWithPortMapping(c internalapi.RuntimeService, portMappings []*runtimeapi.PortMapping) (string, *runtimeapi.PodSandboxConfig) {
	podSandboxName := "create-PodSandbox-with-port-mapping" + framework.NewUUID()
	uid := defaultUIDPrefix + framework.NewUUID()
	namespace := defaultNamespacePrefix + framework.NewUUID()
	config := &runtimeapi.PodSandboxConfig{
		Metadata:     buildPodSandboxMetadata(podSandboxName, uid, namespace, defaultAttempt),
		PortMappings: portMappings,
		Linux:        &runtimeapi.LinuxPodSandboxConfig{},
	}

	podID := runPodSandbox(c, config)
	return podID, config
}

// checkDNSConfig checks the content of /etc/resolv.conf.
func checkDNSConfig(c internalapi.RuntimeService, containerID string, expectedContent []string) {
	By("get the content of /etc/resolv.conf via execSync")
	cmd := []string{"cat", resolvConfigPath}
	stdout, stderr, err := c.ExecSync(containerID, cmd, time.Duration(defaultExecSyncTimeout)*time.Second)
	framework.ExpectNoError(err, "failed to execSync in container %q", containerID)
	for _, content := range expectedContent {
		Expect(string(stdout)).To(ContainSubstring(content), "The stdout output of execSync should contain %q", content)
	}
	Expect(stderr).To(BeNil(), "The stderr should be nil.")
	framework.Logf("check DNS config succeed")
}

// createNginxContainer creates a  nginx container.
func createNginxContainer(rc internalapi.RuntimeService, ic internalapi.ImageManagerService, podID string, podConfig *runtimeapi.PodSandboxConfig, prefix string) string {
	containerName := prefix + framework.NewUUID()
	containerConfig := &runtimeapi.ContainerConfig{
		Metadata: buildContainerMetadata(containerName, defaultAttempt),
		Image:    &runtimeapi.ImageSpec{Image: nginxImage},
		Linux:    &runtimeapi.LinuxContainerConfig{},
	}
	return createContainer(rc, ic, containerConfig, podID, podConfig)
}

// checkPortMapping check if the given IP:port works fine.
func checkPortMapping(c internalapi.RuntimeService, podID string, containerPortOnly bool) {
	By("get the IP:port needed to be checked")
	url := "http://"
	if containerPortOnly {
		status := getPodSandboxStatus(c, podID)
		Expect(status.GetNetwork()).NotTo(BeNil(), "The network in status should not be nil.")
		Expect(status.GetNetwork().Ip).NotTo(BeNil(), "The IP should not be nil.")
		url += status.GetNetwork().Ip + ":" + strconv.Itoa(int(nginxContainerPort))
	} else {
		url += "localhost:" + strconv.Itoa(int(nginxHostPort))
	}
	framework.Logf("the IP:port is " + url)

	By("check the content of " + url)
	resp, err := http.Get(url)
	framework.ExpectNoError(err, "failed to get the content of %q", url)
	Expect(resp.StatusCode).To(Equal(200), "The status code of response should be 200.")
	framework.Logf("check port mapping succeed")
}
<|endoftext|>