<commit_before>package btelegram

import (
	"regexp"
	"strconv"

	"github.com/42wim/matterbridge/bridge/config"
	"github.com/42wim/matterbridge/bridge/helper"
	log "github.com/Sirupsen/logrus"
	"github.com/go-telegram-bot-api/telegram-bot-api"
)

type Btelegram struct {
	c       *tgbotapi.BotAPI
	Config  *config.Protocol
	Remote  chan config.Message
	Account string
}

var flog *log.Entry
var protocol = "telegram"

func init() {
	flog = log.WithFields(log.Fields{"module": protocol})
}

func New(cfg config.Protocol, account string, c chan config.Message) *Btelegram {
	b := &Btelegram{}
	b.Config = &cfg
	b.Remote = c
	b.Account = account
	return b
}

func (b *Btelegram) Connect() error {
	var err error
	flog.Info("Connecting")
	b.c, err = tgbotapi.NewBotAPI(b.Config.Token)
	if err != nil {
		flog.Debugf("%#v", err)
		return err
	}
	updates, err := b.c.GetUpdatesChan(tgbotapi.NewUpdate(0))
	if err != nil {
		flog.Debugf("%#v", err)
		return err
	}
	flog.Info("Connection succeeded")
	go b.handleRecv(updates)
	return nil
}

func (b *Btelegram) Disconnect() error {
	return nil

}

func (b *Btelegram) JoinChannel(channel config.ChannelInfo) error {
	return nil
}

func (b *Btelegram) Send(msg config.Message) (string, error) {
	flog.Debugf("Receiving %#v", msg)
	chatid, err := strconv.ParseInt(msg.Channel, 10, 64)
	if err != nil {
		return "", err
	}

	if b.Config.MessageFormat == "HTML" {
		msg.Text = makeHTML(msg.Text)
	}

	if msg.Event == config.EVENT_MSG_DELETE {
		if msg.ID == "" {
			return "", nil
		}
		msgid, err := strconv.Atoi(msg.ID)
		if err != nil {
			return "", err
		}
		_, err = b.c.DeleteMessage(tgbotapi.DeleteMessageConfig{ChatID: chatid, MessageID: msgid})
		return "", err
	}

	// edit the message if we have a msg ID
	if msg.ID != "" {
		msgid, err := strconv.Atoi(msg.ID)
		if err != nil {
			return "", err
		}
		m := tgbotapi.NewEditMessageText(chatid, msgid, msg.Username+msg.Text)
		_, err = b.c.Send(m)
		if err != nil {
			return "", err
		}
		return "", nil
	}

	if msg.Extra != nil {
		// check if we have files to upload (from slack, telegram or mattermost)
		if len(msg.Extra["file"]) > 0 {
			var c tgbotapi.Chattable
			for _, f := range msg.Extra["file"] {
				fi := f.(config.FileInfo)
				file := tgbotapi.FileBytes{fi.Name, *fi.Data}
				re := regexp.MustCompile(".(jpg|png)$")
				if re.MatchString(fi.Name) {
					c = tgbotapi.NewPhotoUpload(chatid, file)
				} else {
					c = tgbotapi.NewDocumentUpload(chatid, file)
				}
				_, err := b.c.Send(c)
				if err != nil {
					log.Errorf("file upload failed: %#v", err)
				}
			}
		}
	}

	m := tgbotapi.NewMessage(chatid, msg.Username+msg.Text)
	if b.Config.MessageFormat == "HTML" {
		m.ParseMode = tgbotapi.ModeHTML
	}
	res, err := b.c.Send(m)
	if err != nil {
		return "", err
	}
	return strconv.Itoa(res.MessageID), nil

}

func (b *Btelegram) handleRecv(updates <-chan tgbotapi.Update) {
	for update := range updates {
		flog.Debugf("Receiving from telegram: %#v", update.Message)
		var message *tgbotapi.Message
		username := ""
		channel := ""
		text := ""

		fmsg := config.Message{Extra: make(map[string][]interface{})}

		// handle channels
		if update.ChannelPost != nil {
			message = update.ChannelPost
		}
		if update.EditedChannelPost != nil && !b.Config.EditDisable {
			message = update.EditedChannelPost
			message.Text = message.Text + b.Config.EditSuffix
		}
		// handle groups
		if update.Message != nil {
			message = update.Message
		}
		if update.EditedMessage != nil && !b.Config.EditDisable {
			message = update.EditedMessage
			message.Text = message.Text + b.Config.EditSuffix
		}
		if message.From != nil {
			if b.Config.UseFirstName {
				username = message.From.FirstName
			}
			if username == "" {
				username = message.From.UserName
				if username == "" {
					username = message.From.FirstName
				}
			}
			text = message.Text
			channel = strconv.FormatInt(message.Chat.ID, 10)
		}

		if username == "" {
			username = "unknown"
		}
		if message.Sticker != nil {
			b.handleDownload(message.Sticker, &fmsg)
		}
		if message.Video != nil {
			b.handleDownload(message.Video, &fmsg)
		}
		if message.Photo != nil {
			b.handleDownload(message.Photo, &fmsg)
		}
		if message.Document != nil {
			b.handleDownload(message.Document, &fmsg)
		}

		// quote the previous message
		if message.ReplyToMessage != nil {
			usernameReply := ""
			if message.ReplyToMessage.From != nil {
				if b.Config.UseFirstName {
					usernameReply = message.ReplyToMessage.From.FirstName
				}
				if usernameReply == "" {
					usernameReply = message.ReplyToMessage.From.UserName
					if usernameReply == "" {
						usernameReply = message.ReplyToMessage.From.FirstName
					}
				}
			}
			if usernameReply == "" {
				usernameReply = "unknown"
			}
			text = text + " (re @" + usernameReply + ":" + message.ReplyToMessage.Text + ")"
		}

		if text != "" || len(fmsg.Extra) > 0 {
			flog.Debugf("Sending message from %s on %s to gateway", username, b.Account)
			msg := config.Message{Username: username, Text: text, Channel: channel, Account: b.Account, UserID: strconv.Itoa(message.From.ID), ID: strconv.Itoa(message.MessageID), Extra: fmsg.Extra}
			flog.Debugf("Message is %#v", msg)
			b.Remote <- msg
		}
	}
}

func (b *Btelegram) getFileDirectURL(id string) string {
	res, err := b.c.GetFileDirectURL(id)
	if err != nil {
		return ""
	}
	return res
}

func (b *Btelegram) handleDownload(file interface{}, msg *config.Message) {
	size := 0
	url := ""
	name := ""
	text := ""
	fileid := ""
	switch v := file.(type) {
	case *tgbotapi.Sticker:
		size = v.FileSize
		url = b.getFileDirectURL(v.FileID)
		name = "sticker"
		text = " " + url
		fileid = v.FileID
	case *tgbotapi.Video:
		size = v.FileSize
		url = b.getFileDirectURL(v.FileID)
		name = "video"
		text = " " + url
		fileid = v.FileID
	case *[]tgbotapi.PhotoSize:
		photos := *v
		size = photos[len(photos)-1].FileSize
		url = b.getFileDirectURL(photos[len(photos)-1].FileID)
		name = "photo"
		text = " " + url
	case *tgbotapi.Document:
		size = v.FileSize
		url = b.getFileDirectURL(v.FileID)
		name = v.FileName
		text = " " + v.FileName + " : " + url
		fileid = v.FileID
	}
	if b.Config.UseInsecureURL {
		msg.Text = text
		return
	}
	// if we have a file attached, download it (in memory) and put a pointer to it in msg.Extra
	// limit to 1MB for now
	flog.Debugf("trying to download %#v fileid %#v with size %#v", name, fileid, size)
	if size <= 1000000 {
		data, err := helper.DownloadFile(url)
		if err != nil {
			flog.Errorf("download %s failed %#v", url, err)
		} else {
			flog.Debugf("download OK %#v %#v %#v", name, len(*data), len(url))
			msg.Extra["file"] = append(msg.Extra["file"], config.FileInfo{Name: name, Data: data})
		}
	}
}
<commit_msg>Add extension to sticker/video/photo (telegram)<commit_after>package btelegram

import (
	"regexp"
	"strconv"
	"strings"

	"github.com/42wim/matterbridge/bridge/config"
	"github.com/42wim/matterbridge/bridge/helper"
	log "github.com/Sirupsen/logrus"
	"github.com/go-telegram-bot-api/telegram-bot-api"
)

type Btelegram struct {
	c       *tgbotapi.BotAPI
	Config  *config.Protocol
	Remote  chan config.Message
	Account string
}

var flog *log.Entry
var protocol = "telegram"

func init() {
	flog = log.WithFields(log.Fields{"module": protocol})
}

func New(cfg config.Protocol, account string, c chan config.Message) *Btelegram {
	b := &Btelegram{}
	b.Config = &cfg
	b.Remote = c
	b.Account = account
	return b
}

func (b *Btelegram) Connect() error {
	var err error
	flog.Info("Connecting")
	b.c, err = tgbotapi.NewBotAPI(b.Config.Token)
	if err != nil {
		flog.Debugf("%#v", err)
		return err
	}
	updates, err := b.c.GetUpdatesChan(tgbotapi.NewUpdate(0))
	if err != nil {
		flog.Debugf("%#v", err)
		return err
	}
	flog.Info("Connection succeeded")
	go b.handleRecv(updates)
	return nil
}

func (b *Btelegram) Disconnect() error {
	return nil

}

func (b *Btelegram) JoinChannel(channel config.ChannelInfo) error {
	return nil
}

func (b *Btelegram) Send(msg config.Message) (string, error) {
	flog.Debugf("Receiving %#v", msg)
	chatid, err := strconv.ParseInt(msg.Channel, 10, 64)
	if err != nil {
		return "", err
	}

	if b.Config.MessageFormat == "HTML" {
		msg.Text = makeHTML(msg.Text)
	}

	if msg.Event == config.EVENT_MSG_DELETE {
		if msg.ID == "" {
			return "", nil
		}
		msgid, err := strconv.Atoi(msg.ID)
		if err != nil {
			return "", err
		}
		_, err = b.c.DeleteMessage(tgbotapi.DeleteMessageConfig{ChatID: chatid, MessageID: msgid})
		return "", err
	}

	// edit the message if we have a msg ID
	if msg.ID != "" {
		msgid, err := strconv.Atoi(msg.ID)
		if err != nil {
			return "", err
		}
		m := tgbotapi.NewEditMessageText(chatid, msgid, msg.Username+msg.Text)
		_, err = b.c.Send(m)
		if err != nil {
			return "", err
		}
		return "", nil
	}

	if msg.Extra != nil {
		// check if we have files to upload (from slack, telegram or mattermost)
		if len(msg.Extra["file"]) > 0 {
			var c tgbotapi.Chattable
			for _, f := range msg.Extra["file"] {
				fi := f.(config.FileInfo)
				file := tgbotapi.FileBytes{fi.Name, *fi.Data}
				re := regexp.MustCompile(".(jpg|png)$")
				if re.MatchString(fi.Name) {
					c = tgbotapi.NewPhotoUpload(chatid, file)
				} else {
					c = tgbotapi.NewDocumentUpload(chatid, file)
				}
				_, err := b.c.Send(c)
				if err != nil {
					log.Errorf("file upload failed: %#v", err)
				}
			}
		}
	}

	m := tgbotapi.NewMessage(chatid, msg.Username+msg.Text)
	if b.Config.MessageFormat == "HTML" {
		m.ParseMode = tgbotapi.ModeHTML
	}
	res, err := b.c.Send(m)
	if err != nil {
		return "", err
	}
	return strconv.Itoa(res.MessageID), nil

}

func (b *Btelegram) handleRecv(updates <-chan tgbotapi.Update) {
	for update := range updates {
		flog.Debugf("Receiving from telegram: %#v", update.Message)
		var message *tgbotapi.Message
		username := ""
		channel := ""
		text := ""

		fmsg := config.Message{Extra: make(map[string][]interface{})}

		// handle channels
		if update.ChannelPost != nil {
			message = update.ChannelPost
		}
		if update.EditedChannelPost != nil && !b.Config.EditDisable {
			message = update.EditedChannelPost
			message.Text = message.Text + b.Config.EditSuffix
		}
		// handle groups
		if update.Message != nil {
			message = update.Message
		}
		if update.EditedMessage != nil && !b.Config.EditDisable {
			message = update.EditedMessage
			message.Text = message.Text + b.Config.EditSuffix
		}
		if message.From != nil {
			if b.Config.UseFirstName {
				username = message.From.FirstName
			}
			if username == "" {
				username = message.From.UserName
				if username == "" {
					username = message.From.FirstName
				}
			}
			text = message.Text
			channel = strconv.FormatInt(message.Chat.ID, 10)
		}

		if username == "" {
			username = "unknown"
		}
		if message.Sticker != nil {
			b.handleDownload(message.Sticker, &fmsg)
		}
		if message.Video != nil {
			b.handleDownload(message.Video, &fmsg)
		}
		if message.Photo != nil {
			b.handleDownload(message.Photo, &fmsg)
		}
		if message.Document != nil {
			b.handleDownload(message.Document, &fmsg)
		}

		// quote the previous message
		if message.ReplyToMessage != nil {
			usernameReply := ""
			if message.ReplyToMessage.From != nil {
				if b.Config.UseFirstName {
					usernameReply = message.ReplyToMessage.From.FirstName
				}
				if usernameReply == "" {
					usernameReply = message.ReplyToMessage.From.UserName
					if usernameReply == "" {
						usernameReply = message.ReplyToMessage.From.FirstName
					}
				}
			}
			if usernameReply == "" {
				usernameReply = "unknown"
			}
			text = text + " (re @" + usernameReply + ":" + message.ReplyToMessage.Text + ")"
		}

		if text != "" || len(fmsg.Extra) > 0 {
			flog.Debugf("Sending message from %s on %s to gateway", username, b.Account)
			msg := config.Message{Username: username, Text: text, Channel: channel, Account: b.Account, UserID: strconv.Itoa(message.From.ID), ID: strconv.Itoa(message.MessageID), Extra: fmsg.Extra}
			flog.Debugf("Message is %#v", msg)
			b.Remote <- msg
		}
	}
}

func (b *Btelegram) getFileDirectURL(id string) string {
	res, err := b.c.GetFileDirectURL(id)
	if err != nil {
		return ""
	}
	return res
}

func (b *Btelegram) handleDownload(file interface{}, msg *config.Message) {
	size := 0
	url := ""
	name := ""
	text := ""
	fileid := ""
	switch v := file.(type) {
	case *tgbotapi.Sticker:
		size = v.FileSize
		url = b.getFileDirectURL(v.FileID)
		urlPart := strings.Split(url, "/")
		name = urlPart[len(urlPart)-1]
		text = " " + url
		fileid = v.FileID
	case *tgbotapi.Video:
		size = v.FileSize
		url = b.getFileDirectURL(v.FileID)
		urlPart := strings.Split(url, "/")
		name = urlPart[len(urlPart)-1]
		text = " " + url
		fileid = v.FileID
	case *[]tgbotapi.PhotoSize:
		photos := *v
		size = photos[len(photos)-1].FileSize
		url = b.getFileDirectURL(photos[len(photos)-1].FileID)
		urlPart := strings.Split(url, "/")
		name = urlPart[len(urlPart)-1]
		text = " " + url
	case *tgbotapi.Document:
		size = v.FileSize
		url = b.getFileDirectURL(v.FileID)
		name = v.FileName
		text = " " + v.FileName + " : " + url
		fileid = v.FileID
	}
	if b.Config.UseInsecureURL {
		msg.Text = text
		return
	}
	// if we have a file attached, download it (in memory) and put a pointer to it in msg.Extra
	// limit to 1MB for now
	flog.Debugf("trying to download %#v fileid %#v with size %#v", name, fileid, size)
	if size <= 1000000 {
		data, err := helper.DownloadFile(url)
		if err != nil {
			flog.Errorf("download %s failed %#v", url, err)
		} else {
			flog.Debugf("download OK %#v %#v %#v", name, len(*data), len(url))
			msg.Extra["file"] = append(msg.Extra["file"], config.FileInfo{Name: name, Data: data})
		}
	}
}
<|endoftext|>


<commit_before>package handyfile

import (
	"hash/crc32"
	"io"
	"io/ioutil"
	"os"
)

func Checksum32(path string) uint32 {
	file, _ := os.Open(path)
	defer file.Close()
	c := crc32.NewIEEE()
	bytes, _ := ioutil.ReadAll(file)
	withcrc := c.Sum(bytes)
	return crc32.ChecksumIEEE(withcrc)
}

func Exists(path string) bool {
	_, err := os.Stat(path)
	return !os.IsNotExist(err)
}

func Copy(src, dest string) error {
	fsrc, err := os.Open(src)
	if err != nil {
		return err
	}
	defer fsrc.Close()

	fdest, err := os.Create(dest)
	if err != nil {
		return err
	}
	defer fdest.Close()

	if _, err := io.Copy(fdest, fsrc); err != nil {
		return err
	}

	return nil
}
<commit_msg>Add function for hash a file using anything hasher that returns []bytes. Make function so that it works on any size of file, rather than reading the file into memory.<commit_after>package handyfile

import (
	"hash"
	"hash/crc32"
	"io"
	"io/ioutil"
	"os"
)

func Checksum32(path string) uint32 {
	file, _ := os.Open(path)
	defer file.Close()
	c := crc32.NewIEEE()
	bytes, _ := ioutil.ReadAll(file)
	withcrc := c.Sum(bytes)
	return crc32.ChecksumIEEE(withcrc)
}

func Hash(hasher hash.Hash, path string) ([]byte, error) {
	f, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	defer f.Close()

	if _, err := io.Copy(hasher, f); err != nil {
		return nil, err
	}
	return hasher.Sum(nil), nil
}

func Exists(path string) bool {
	_, err := os.Stat(path)
	return !os.IsNotExist(err)
}

func Copy(src, dest string) error {
	fsrc, err := os.Open(src)
	if err != nil {
		return err
	}
	defer fsrc.Close()

	fdest, err := os.Create(dest)
	if err != nil {
		return err
	}
	defer fdest.Close()

	if _, err := io.Copy(fdest, fsrc); err != nil {
		return err
	}

	return nil
}
<|endoftext|>


<commit_before><commit_msg>fix(policy): remove commit header length from conventional commit policy (#102)<commit_after><|endoftext|>


<commit_before>// Copyright 2014 Google Inc. All Rights Reserved.
//
// 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 docker

import (
	"bufio"
	"encoding/json"
	"fmt"
	"math"
	"os"
	"path"
	"strings"
	"time"

	"github.com/docker/libcontainer"
	"github.com/docker/libcontainer/cgroups"
	"github.com/docker/libcontainer/cgroups/fs"
	"github.com/fsouza/go-dockerclient"
	"github.com/google/cadvisor/container"
	"github.com/google/cadvisor/info"
)

type dockerContainerHandler struct {
	client             *docker.Client
	name               string
	aliases            []string
	machineInfoFactory info.MachineInfoFactory
}

func newDockerContainerHandler(
	client *docker.Client,
	name string,
	machineInfoFactory info.MachineInfoFactory,
) (container.ContainerHandler, error) {
	handler := &dockerContainerHandler{
		client:             client,
		name:               name,
		machineInfoFactory: machineInfoFactory,
	}
	if !handler.isDockerContainer() {
		return handler, nil
	}
	_, id, err := handler.splitName()
	if err != nil {
		return nil, fmt.Errorf("invalid docker container %v: %v", name, err)
	}
	ctnr, err := client.InspectContainer(id)
	if err != nil {
		return nil, fmt.Errorf("unable to inspect container %v: %v", name, err)
	}
	handler.aliases = append(handler.aliases, path.Join("/docker", ctnr.Name))
	return handler, nil
}

func (self *dockerContainerHandler) ContainerReference() (info.ContainerReference, error) {
	return info.ContainerReference{
		Name:    self.name,
		Aliases: self.aliases,
	}, nil
}

func (self *dockerContainerHandler) splitName() (string, string, error) {
	parent, id := path.Split(self.name)
	cgroupSelf, err := os.Open("/proc/self/cgroup")
	if err != nil {
		return "", "", err
	}
	scanner := bufio.NewScanner(cgroupSelf)

	subsys := []string{"memory", "cpu"}
	nestedLevels := 0
	for scanner.Scan() {
		line := scanner.Text()
		elems := strings.Split(line, ":")
		if len(elems) < 3 {
			continue
		}
		for _, s := range subsys {
			if elems[1] == s {
				// count how many nested docker containers are there.
				nestedLevels = strings.Count(elems[2], "/docker")
				break
			}
		}
	}
	if nestedLevels > 0 {
		// we are running inside a docker container
		upperLevel := strings.Repeat("../../", nestedLevels)
		//parent = strings.Join([]string{parent, upperLevel}, "/")
		parent = fmt.Sprintf("%v%v", upperLevel, parent)
	}
	return parent, id, nil
}

func (self *dockerContainerHandler) isDockerRoot() bool {
	// TODO(dengnan): Should we consider other cases?
	return self.name == "/docker"
}

func (self *dockerContainerHandler) isRootContainer() bool {
	return self.name == "/"
}

func (self *dockerContainerHandler) isDockerContainer() bool {
	return (!self.isDockerRoot()) && (!self.isRootContainer())
}

// TODO(vmarmol): Switch to getting this from libcontainer once we have a solid API.
func readLibcontainerSpec(id string) (spec *libcontainer.Config, err error) {
	dir := "/var/lib/docker/execdriver/native"
	configPath := path.Join(dir, id, "container.json")
	f, err := os.Open(configPath)
	if err != nil {
		return
	}
	defer f.Close()
	d := json.NewDecoder(f)
	ret := new(libcontainer.Config)
	err = d.Decode(ret)
	if err != nil {
		return
	}
	spec = ret
	return
}

func libcontainerConfigToContainerSpec(config *libcontainer.Config, mi *info.MachineInfo) *info.ContainerSpec {
	spec := new(info.ContainerSpec)
	spec.Memory = new(info.MemorySpec)
	spec.Memory.Limit = math.MaxUint64
	spec.Memory.SwapLimit = math.MaxUint64
	if config.Cgroups.Memory > 0 {
		spec.Memory.Limit = uint64(config.Cgroups.Memory)
	}
	if config.Cgroups.MemorySwap > 0 {
		spec.Memory.SwapLimit = uint64(config.Cgroups.MemorySwap)
	}

	// Get CPU info
	spec.Cpu = new(info.CpuSpec)
	spec.Cpu.Limit = 1024
	if config.Cgroups.CpuShares != 0 {
		spec.Cpu.Limit = uint64(config.Cgroups.CpuShares)
	}
	n := (mi.NumCores + 63) / 64
	spec.Cpu.Mask.Data = make([]uint64, n)
	for i := 0; i < n; i++ {
		spec.Cpu.Mask.Data[i] = math.MaxUint64
	}
	// TODO(vmarmol): Get CPUs from config.Cgroups.CpusetCpus
	return spec
}

func (self *dockerContainerHandler) GetSpec() (spec *info.ContainerSpec, err error) {
	if !self.isDockerContainer() {
		spec = new(info.ContainerSpec)
		return
	}
	mi, err := self.machineInfoFactory.GetMachineInfo()
	if err != nil {
		return
	}
	_, id, err := self.splitName()
	if err != nil {
		return
	}
	libcontainerSpec, err := readLibcontainerSpec(id)
	if err != nil {
		return
	}

	spec = libcontainerConfigToContainerSpec(libcontainerSpec, mi)
	return
}

func libcontainerToContainerStats(s *cgroups.Stats, mi *info.MachineInfo) *info.ContainerStats {
	ret := new(info.ContainerStats)
	ret.Timestamp = time.Now()
	ret.Cpu = new(info.CpuStats)
	ret.Cpu.Usage.User = s.CpuStats.CpuUsage.UsageInUsermode
	ret.Cpu.Usage.System = s.CpuStats.CpuUsage.UsageInKernelmode
	n := len(s.CpuStats.CpuUsage.PercpuUsage)
	ret.Cpu.Usage.PerCpu = make([]uint64, n)

	ret.Cpu.Usage.Total = 0
	for i := 0; i < n; i++ {
		ret.Cpu.Usage.PerCpu[i] = s.CpuStats.CpuUsage.PercpuUsage[i]
		ret.Cpu.Usage.Total += s.CpuStats.CpuUsage.PercpuUsage[i]
	}
	ret.Memory = new(info.MemoryStats)
	ret.Memory.Usage = s.MemoryStats.Usage
	if v, ok := s.MemoryStats.Stats["pgfault"]; ok {
		ret.Memory.ContainerData.Pgfault = v
		ret.Memory.HierarchicalData.Pgfault = v
	}
	if v, ok := s.MemoryStats.Stats["pgmajfault"]; ok {
		ret.Memory.ContainerData.Pgmajfault = v
		ret.Memory.HierarchicalData.Pgmajfault = v
	}
	return ret
}

func (self *dockerContainerHandler) GetStats() (stats *info.ContainerStats, err error) {
	if !self.isDockerContainer() {
		// Return empty stats for root containers.
		stats = new(info.ContainerStats)
		stats.Timestamp = time.Now()
		return
	}
	mi, err := self.machineInfoFactory.GetMachineInfo()
	if err != nil {
		return
	}
	parent, id, err := self.splitName()
	if err != nil {
		return
	}
	cg := &cgroups.Cgroup{
		Parent: parent,
		Name:   id,
	}
	s, err := fs.GetStats(cg)
	if err != nil {
		return
	}
	stats = libcontainerToContainerStats(s, mi)
	return
}

func (self *dockerContainerHandler) ListContainers(listType container.ListType) ([]info.ContainerReference, error) {
	if self.isDockerContainer() {
		return nil, nil
	}
	if self.isRootContainer() && listType == container.LIST_SELF {
		return []info.ContainerReference{info.ContainerReference{Name: "/docker"}}, nil
	}
	opt := docker.ListContainersOptions{
		All: true,
	}
	containers, err := self.client.ListContainers(opt)
	if err != nil {
		return nil, err
	}
	ret := make([]info.ContainerReference, 0, len(containers)+1)
	for _, c := range containers {
		if !strings.HasPrefix(c.Status, "Up ") {
			continue
		}
		path := fmt.Sprintf("/docker/%v", c.ID)
		aliases := c.Names
		ref := info.ContainerReference{
			Name:    path,
			Aliases: aliases,
		}
		ret = append(ret, ref)
	}
	if self.isRootContainer() {
		ret = append(ret, info.ContainerReference{Name: "/docker"})
	}
	return ret, nil
}

func (self *dockerContainerHandler) ListThreads(listType container.ListType) ([]int, error) {
	return nil, nil
}

func (self *dockerContainerHandler) ListProcesses(listType container.ListType) ([]int, error) {
	return nil, nil
}
<commit_msg>calculate working set based on #. pages in active LRU.<commit_after>// Copyright 2014 Google Inc. All Rights Reserved.
//
// 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 docker

import (
	"bufio"
	"encoding/json"
	"fmt"
	"math"
	"os"
	"path"
	"strings"
	"time"

	"github.com/docker/libcontainer"
	"github.com/docker/libcontainer/cgroups"
	"github.com/docker/libcontainer/cgroups/fs"
	"github.com/fsouza/go-dockerclient"
	"github.com/google/cadvisor/container"
	"github.com/google/cadvisor/info"
)

type dockerContainerHandler struct {
	client             *docker.Client
	name               string
	aliases            []string
	machineInfoFactory info.MachineInfoFactory
}

func newDockerContainerHandler(
	client *docker.Client,
	name string,
	machineInfoFactory info.MachineInfoFactory,
) (container.ContainerHandler, error) {
	handler := &dockerContainerHandler{
		client:             client,
		name:               name,
		machineInfoFactory: machineInfoFactory,
	}
	if !handler.isDockerContainer() {
		return handler, nil
	}
	_, id, err := handler.splitName()
	if err != nil {
		return nil, fmt.Errorf("invalid docker container %v: %v", name, err)
	}
	ctnr, err := client.InspectContainer(id)
	if err != nil {
		return nil, fmt.Errorf("unable to inspect container %v: %v", name, err)
	}
	handler.aliases = append(handler.aliases, path.Join("/docker", ctnr.Name))
	return handler, nil
}

func (self *dockerContainerHandler) ContainerReference() (info.ContainerReference, error) {
	return info.ContainerReference{
		Name:    self.name,
		Aliases: self.aliases,
	}, nil
}

func (self *dockerContainerHandler) splitName() (string, string, error) {
	parent, id := path.Split(self.name)
	cgroupSelf, err := os.Open("/proc/self/cgroup")
	if err != nil {
		return "", "", err
	}
	scanner := bufio.NewScanner(cgroupSelf)

	subsys := []string{"memory", "cpu"}
	nestedLevels := 0
	for scanner.Scan() {
		line := scanner.Text()
		elems := strings.Split(line, ":")
		if len(elems) < 3 {
			continue
		}
		for _, s := range subsys {
			if elems[1] == s {
				// count how many nested docker containers are there.
				nestedLevels = strings.Count(elems[2], "/docker")
				break
			}
		}
	}
	if nestedLevels > 0 {
		// we are running inside a docker container
		upperLevel := strings.Repeat("../../", nestedLevels)
		//parent = strings.Join([]string{parent, upperLevel}, "/")
		parent = fmt.Sprintf("%v%v", upperLevel, parent)
	}
	return parent, id, nil
}

func (self *dockerContainerHandler) isDockerRoot() bool {
	// TODO(dengnan): Should we consider other cases?
	return self.name == "/docker"
}

func (self *dockerContainerHandler) isRootContainer() bool {
	return self.name == "/"
}

func (self *dockerContainerHandler) isDockerContainer() bool {
	return (!self.isDockerRoot()) && (!self.isRootContainer())
}

// TODO(vmarmol): Switch to getting this from libcontainer once we have a solid API.
func readLibcontainerSpec(id string) (spec *libcontainer.Config, err error) {
	dir := "/var/lib/docker/execdriver/native"
	configPath := path.Join(dir, id, "container.json")
	f, err := os.Open(configPath)
	if err != nil {
		return
	}
	defer f.Close()
	d := json.NewDecoder(f)
	ret := new(libcontainer.Config)
	err = d.Decode(ret)
	if err != nil {
		return
	}
	spec = ret
	return
}

func libcontainerConfigToContainerSpec(config *libcontainer.Config, mi *info.MachineInfo) *info.ContainerSpec {
	spec := new(info.ContainerSpec)
	spec.Memory = new(info.MemorySpec)
	spec.Memory.Limit = math.MaxUint64
	spec.Memory.SwapLimit = math.MaxUint64
	if config.Cgroups.Memory > 0 {
		spec.Memory.Limit = uint64(config.Cgroups.Memory)
	}
	if config.Cgroups.MemorySwap > 0 {
		spec.Memory.SwapLimit = uint64(config.Cgroups.MemorySwap)
	}

	// Get CPU info
	spec.Cpu = new(info.CpuSpec)
	spec.Cpu.Limit = 1024
	if config.Cgroups.CpuShares != 0 {
		spec.Cpu.Limit = uint64(config.Cgroups.CpuShares)
	}
	n := (mi.NumCores + 63) / 64
	spec.Cpu.Mask.Data = make([]uint64, n)
	for i := 0; i < n; i++ {
		spec.Cpu.Mask.Data[i] = math.MaxUint64
	}
	// TODO(vmarmol): Get CPUs from config.Cgroups.CpusetCpus
	return spec
}

func (self *dockerContainerHandler) GetSpec() (spec *info.ContainerSpec, err error) {
	if !self.isDockerContainer() {
		spec = new(info.ContainerSpec)
		return
	}
	mi, err := self.machineInfoFactory.GetMachineInfo()
	if err != nil {
		return
	}
	_, id, err := self.splitName()
	if err != nil {
		return
	}
	libcontainerSpec, err := readLibcontainerSpec(id)
	if err != nil {
		return
	}

	spec = libcontainerConfigToContainerSpec(libcontainerSpec, mi)
	return
}

func libcontainerToContainerStats(s *cgroups.Stats, mi *info.MachineInfo) *info.ContainerStats {
	ret := new(info.ContainerStats)
	ret.Timestamp = time.Now()
	ret.Cpu = new(info.CpuStats)
	ret.Cpu.Usage.User = s.CpuStats.CpuUsage.UsageInUsermode
	ret.Cpu.Usage.System = s.CpuStats.CpuUsage.UsageInKernelmode
	n := len(s.CpuStats.CpuUsage.PercpuUsage)
	ret.Cpu.Usage.PerCpu = make([]uint64, n)

	ret.Cpu.Usage.Total = 0
	for i := 0; i < n; i++ {
		ret.Cpu.Usage.PerCpu[i] = s.CpuStats.CpuUsage.PercpuUsage[i]
		ret.Cpu.Usage.Total += s.CpuStats.CpuUsage.PercpuUsage[i]
	}
	ret.Memory = new(info.MemoryStats)
	ret.Memory.Usage = s.MemoryStats.Usage
	if v, ok := s.MemoryStats.Stats["pgfault"]; ok {
		ret.Memory.ContainerData.Pgfault = v
		ret.Memory.HierarchicalData.Pgfault = v
	}
	if v, ok := s.MemoryStats.Stats["pgmajfault"]; ok {
		ret.Memory.ContainerData.Pgmajfault = v
		ret.Memory.HierarchicalData.Pgmajfault = v
	}
	if v, ok := s.MemoryStats.Stats["active_anon"]; ok {
		ret.Memory.WorkingSet = v
		if v, ok := s.MemoryStats.Stats["active_file"]; ok {
			ret.Memory.WorkingSet += v
		}
	}
	return ret
}

func (self *dockerContainerHandler) GetStats() (stats *info.ContainerStats, err error) {
	if !self.isDockerContainer() {
		// Return empty stats for root containers.
		stats = new(info.ContainerStats)
		stats.Timestamp = time.Now()
		return
	}
	mi, err := self.machineInfoFactory.GetMachineInfo()
	if err != nil {
		return
	}
	parent, id, err := self.splitName()
	if err != nil {
		return
	}
	cg := &cgroups.Cgroup{
		Parent: parent,
		Name:   id,
	}
	s, err := fs.GetStats(cg)
	if err != nil {
		return
	}
	stats = libcontainerToContainerStats(s, mi)
	return
}

func (self *dockerContainerHandler) ListContainers(listType container.ListType) ([]info.ContainerReference, error) {
	if self.isDockerContainer() {
		return nil, nil
	}
	if self.isRootContainer() && listType == container.LIST_SELF {
		return []info.ContainerReference{info.ContainerReference{Name: "/docker"}}, nil
	}
	opt := docker.ListContainersOptions{
		All: true,
	}
	containers, err := self.client.ListContainers(opt)
	if err != nil {
		return nil, err
	}
	ret := make([]info.ContainerReference, 0, len(containers)+1)
	for _, c := range containers {
		if !strings.HasPrefix(c.Status, "Up ") {
			continue
		}
		path := fmt.Sprintf("/docker/%v", c.ID)
		aliases := c.Names
		ref := info.ContainerReference{
			Name:    path,
			Aliases: aliases,
		}
		ret = append(ret, ref)
	}
	if self.isRootContainer() {
		ret = append(ret, info.ContainerReference{Name: "/docker"})
	}
	return ret, nil
}

func (self *dockerContainerHandler) ListThreads(listType container.ListType) ([]int, error) {
	return nil, nil
}

func (self *dockerContainerHandler) ListProcesses(listType container.ListType) ([]int, error) {
	return nil, nil
}
<|endoftext|>


<commit_before>/*
Copyright 2019 The Vitess 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 main

import (
	"context"
	"fmt"
	"io"
	"io/ioutil"
	"math/rand"
	"os"
	"path"
	"strings"
	"testing"
	"time"

	"google.golang.org/protobuf/encoding/protojson"

	"vitess.io/vitess/go/sqltypes"
	"vitess.io/vitess/go/vt/tlstest"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"

	"vitess.io/vitess/go/mysql"

	"vitess.io/vitess/go/vt/vttest"

	"vitess.io/vitess/go/vt/proto/logutil"
	"vitess.io/vitess/go/vt/proto/vschema"
	"vitess.io/vitess/go/vt/vtctl/vtctlclient"
)

type columnVindex struct {
	keyspace   string
	table      string
	vindex     string
	vindexType string
	column     string
}

func TestRunsVschemaMigrations(t *testing.T) {
	args := os.Args
	conf := config
	defer resetFlags(args, conf)

	cluster, err := startCluster()
	defer cluster.TearDown()

	assert.NoError(t, err)
	assertColumnVindex(t, cluster, columnVindex{keyspace: "test_keyspace", table: "test_table", vindex: "my_vdx", vindexType: "hash", column: "id"})
	assertColumnVindex(t, cluster, columnVindex{keyspace: "app_customer", table: "customers", vindex: "hash", vindexType: "hash", column: "id"})

	// Add Hash vindex via vtgate execution on table
	err = addColumnVindex(cluster, "test_keyspace", "alter vschema on test_table1 add vindex my_vdx (id)")
	assert.NoError(t, err)
	assertColumnVindex(t, cluster, columnVindex{keyspace: "test_keyspace", table: "test_table1", vindex: "my_vdx", vindexType: "hash", column: "id"})
}

func TestPersistentMode(t *testing.T) {
	args := os.Args
	conf := config
	defer resetFlags(args, conf)

	dir, err := ioutil.TempDir("/tmp", "vttestserver_persistent_mode_")
	assert.NoError(t, err)
	defer os.RemoveAll(dir)

	cluster, err := startPersistentCluster(dir)
	assert.NoError(t, err)

	// basic sanity checks similar to TestRunsVschemaMigrations
	assertColumnVindex(t, cluster, columnVindex{keyspace: "test_keyspace", table: "test_table", vindex: "my_vdx", vindexType: "hash", column: "id"})
	assertColumnVindex(t, cluster, columnVindex{keyspace: "app_customer", table: "customers", vindex: "hash", vindexType: "hash", column: "id"})

	// insert some data to ensure persistence across teardowns
	err = execOnCluster(cluster, "app_customer", func(conn *mysql.Conn) error {
		_, err := conn.ExecuteFetch("insert into customers (id, name) values (1, 'gopherson')", 1, false)
		return err
	})
	assert.NoError(t, err)

	expectedRows := [][]sqltypes.Value{
		{sqltypes.NewInt64(1), sqltypes.NewVarChar("gopherson"), sqltypes.NULL},
	}

	// ensure data was actually inserted
	var res *sqltypes.Result
	err = execOnCluster(cluster, "app_customer", func(conn *mysql.Conn) (err error) {
		res, err = conn.ExecuteFetch("SELECT * FROM customers", 1, false)
		return err
	})
	assert.NoError(t, err)
	assert.Equal(t, expectedRows, res.Rows)

	// reboot the persistent cluster
	cluster.TearDown()
	cluster, err = startPersistentCluster(dir)
	defer cluster.TearDown()
	assert.NoError(t, err)

	// rerun our sanity checks to make sure vschema migrations are run during every startup
	assertColumnVindex(t, cluster, columnVindex{keyspace: "test_keyspace", table: "test_table", vindex: "my_vdx", vindexType: "hash", column: "id"})
	assertColumnVindex(t, cluster, columnVindex{keyspace: "app_customer", table: "customers", vindex: "hash", vindexType: "hash", column: "id"})

	// ensure previous data was successfully persisted
	err = execOnCluster(cluster, "app_customer", func(conn *mysql.Conn) (err error) {
		res, err = conn.ExecuteFetch("SELECT * FROM customers", 1, false)
		return err
	})
	assert.NoError(t, err)
	assert.Equal(t, expectedRows, res.Rows)
}

func TestCanVtGateExecute(t *testing.T) {
	args := os.Args
	conf := config
	defer resetFlags(args, conf)

	cluster, err := startCluster()
	assert.NoError(t, err)
	defer cluster.TearDown()

	client, err := vtctlclient.New(fmt.Sprintf("localhost:%v", cluster.GrpcPort()))
	assert.NoError(t, err)
	defer client.Close()
	stream, err := client.ExecuteVtctlCommand(
		context.Background(),
		[]string{
			"VtGateExecute",
			"-server",
			fmt.Sprintf("localhost:%v", cluster.GrpcPort()),
			"select 'success';",
		},
		30*time.Second,
	)
	assert.NoError(t, err)

	var b strings.Builder
	b.Grow(1024)

Out:
	for {
		e, err := stream.Recv()
		switch err {
		case nil:
			b.WriteString(e.Value)
		case io.EOF:
			break Out
		default:
			assert.FailNow(t, err.Error())
		}
	}

	assert.Contains(t, b.String(), "success")
}

func TestMtlsAuth(t *testing.T) {
	args := os.Args
	conf := config
	defer resetFlags(args, conf)

	// Our test root.
	root, err := ioutil.TempDir("", "tlstest")
	if err != nil {
		t.Fatalf("TempDir failed: %v", err)
	}
	defer os.RemoveAll(root)

	// Create the certs and configs.
	tlstest.CreateCA(root)
	caCert := path.Join(root, "ca-cert.pem")

	tlstest.CreateSignedCert(root, tlstest.CA, "01", "vtctld", "vtctld.example.com")
	cert := path.Join(root, "vtctld-cert.pem")
	key := path.Join(root, "vtctld-key.pem")

	tlstest.CreateSignedCert(root, tlstest.CA, "02", "client", "ClientApp")
	clientCert := path.Join(root, "client-cert.pem")
	clientKey := path.Join(root, "client-key.pem")

	// When cluster starts it will apply SQL and VSchema migrations in the configured schema_dir folder
	// With mtls authorization enabled, the authorized CN must match the certificate's CN
	cluster, err := startCluster(
		"-grpc_auth_mode=mtls",
		fmt.Sprintf("-grpc_key=%s", key),
		fmt.Sprintf("-grpc_cert=%s", cert),
		fmt.Sprintf("-grpc_ca=%s", caCert),
		fmt.Sprintf("-vtctld_grpc_key=%s", clientKey),
		fmt.Sprintf("-vtctld_grpc_cert=%s", clientCert),
		fmt.Sprintf("-vtctld_grpc_ca=%s", caCert),
		fmt.Sprintf("-grpc_auth_mtls_allowed_substrings=%s", "CN=ClientApp"))
	assert.NoError(t, err)
	defer cluster.TearDown()

	// startCluster will apply vschema migrations using vtctl grpc and the clientCert.
	assertColumnVindex(t, cluster, columnVindex{keyspace: "test_keyspace", table: "test_table", vindex: "my_vdx", vindexType: "hash", column: "id"})
	assertColumnVindex(t, cluster, columnVindex{keyspace: "app_customer", table: "customers", vindex: "hash", vindexType: "hash", column: "id"})
}

func TestMtlsAuthUnauthorizedFails(t *testing.T) {
	args := os.Args
	conf := config
	defer resetFlags(args, conf)

	// Our test root.
	root, err := ioutil.TempDir("", "tlstest")
	if err != nil {
		t.Fatalf("TempDir failed: %v", err)
	}
	defer os.RemoveAll(root)

	// Create the certs and configs.
	tlstest.CreateCA(root)
	caCert := path.Join(root, "ca-cert.pem")

	tlstest.CreateSignedCert(root, tlstest.CA, "01", "vtctld", "vtctld.example.com")
	cert := path.Join(root, "vtctld-cert.pem")
	key := path.Join(root, "vtctld-key.pem")

	tlstest.CreateSignedCert(root, tlstest.CA, "02", "client", "AnotherApp")
	clientCert := path.Join(root, "client-cert.pem")
	clientKey := path.Join(root, "client-key.pem")

	// When cluster starts it will apply SQL and VSchema migrations in the configured schema_dir folder
	// For mtls authorization failure by providing a client certificate with different CN thant the
	// authorized in the configuration
	cluster, err := startCluster(
		"-grpc_auth_mode=mtls",
		fmt.Sprintf("-grpc_key=%s", key),
		fmt.Sprintf("-grpc_cert=%s", cert),
		fmt.Sprintf("-grpc_ca=%s", caCert),
		fmt.Sprintf("-vtctld_grpc_key=%s", clientKey),
		fmt.Sprintf("-vtctld_grpc_cert=%s", clientCert),
		fmt.Sprintf("-vtctld_grpc_ca=%s", caCert),
		fmt.Sprintf("-grpc_auth_mtls_allowed_substrings=%s", "CN=ClientApp"))
	defer cluster.TearDown()

	assert.Error(t, err)
	assert.Contains(t, err.Error(), "code = Unauthenticated desc = client certificate not authorized")
}

func startPersistentCluster(dir string, flags ...string) (vttest.LocalCluster, error) {
	flags = append(flags, []string{
		"-persistent_mode",
		// FIXME: if port is not provided, data_dir is not respected
		fmt.Sprintf("-port=%d", randomPort()),
		fmt.Sprintf("-data_dir=%s", dir),
	}...)
	return startCluster(flags...)
}

func startCluster(flags ...string) (vttest.LocalCluster, error) {
	schemaDirArg := "-schema_dir=data/schema"
	tabletHostname := "-tablet_hostname=localhost"
	keyspaceArg := "-keyspaces=test_keyspace,app_customer"
	numShardsArg := "-num_shards=2,2"
	vschemaDDLAuthorizedUsers := "-vschema_ddl_authorized_users=%"
	os.Args = append(os.Args, []string{schemaDirArg, keyspaceArg, numShardsArg, tabletHostname, vschemaDDLAuthorizedUsers}...)
	os.Args = append(os.Args, flags...)
	return runCluster()
}

func addColumnVindex(cluster vttest.LocalCluster, keyspace string, vschemaMigration string) error {
	return execOnCluster(cluster, keyspace, func(conn *mysql.Conn) error {
		_, err := conn.ExecuteFetch(vschemaMigration, 1, false)
		return err
	})
}

func execOnCluster(cluster vttest.LocalCluster, keyspace string, f func(*mysql.Conn) error) error {
	ctx := context.Background()
	vtParams := mysql.ConnParams{
		Host:   "localhost",
		DbName: keyspace,
		Port:   cluster.Env.PortForProtocol("vtcombo_mysql_port", ""),
	}

	conn, err := mysql.Connect(ctx, &vtParams)
	if err != nil {
		return err
	}
	defer conn.Close()
	return f(conn)
}

func assertColumnVindex(t *testing.T, cluster vttest.LocalCluster, expected columnVindex) {
	server := fmt.Sprintf("localhost:%v", cluster.GrpcPort())
	args := []string{"GetVSchema", expected.keyspace}
	ctx := context.Background()

	err := vtctlclient.RunCommandAndWait(ctx, server, args, func(e *logutil.Event) {
		var keyspace vschema.Keyspace
		if err := protojson.Unmarshal([]byte(e.Value), &keyspace); err != nil {
			t.Error(err)
		}

		columnVindex := keyspace.Tables[expected.table].ColumnVindexes[0]
		actualVindex := keyspace.Vindexes[expected.vindex]
		assertEqual(t, actualVindex.Type, expected.vindexType, "Actual vindex type different from expected")
		assertEqual(t, columnVindex.Name, expected.vindex, "Actual vindex name different from expected")
		assertEqual(t, columnVindex.Columns[0], expected.column, "Actual vindex column different from expected")
	})
	require.NoError(t, err)
}

func assertEqual(t *testing.T, actual string, expected string, message string) {
	if actual != expected {
		t.Errorf("%s: actual %s, expected %s", message, actual, expected)
	}
}

func resetFlags(args []string, conf vttest.Config) {
	os.Args = args
	config = conf
}

func randomPort() int {
	v := rand.Int31n(20000)
	return int(v + 10000)
}
<commit_msg>go/cmd/vttestserver/vttestserver_test.go:  + TestForeignKeys()<commit_after>/*
Copyright 2019 The Vitess 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 main

import (
	"context"
	"fmt"
	"io"
	"io/ioutil"
	"math/rand"
	"os"
	"path"
	"strings"
	"testing"
	"time"

	"google.golang.org/protobuf/encoding/protojson"

	"vitess.io/vitess/go/sqltypes"
	"vitess.io/vitess/go/vt/tlstest"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"

	"vitess.io/vitess/go/mysql"

	"vitess.io/vitess/go/vt/vttest"

	"vitess.io/vitess/go/vt/proto/logutil"
	"vitess.io/vitess/go/vt/proto/vschema"
	"vitess.io/vitess/go/vt/vtctl/vtctlclient"
)

type columnVindex struct {
	keyspace   string
	table      string
	vindex     string
	vindexType string
	column     string
}

func TestRunsVschemaMigrations(t *testing.T) {
	args := os.Args
	conf := config
	defer resetFlags(args, conf)

	cluster, err := startCluster()
	defer cluster.TearDown()

	assert.NoError(t, err)
	assertColumnVindex(t, cluster, columnVindex{keyspace: "test_keyspace", table: "test_table", vindex: "my_vdx", vindexType: "hash", column: "id"})
	assertColumnVindex(t, cluster, columnVindex{keyspace: "app_customer", table: "customers", vindex: "hash", vindexType: "hash", column: "id"})

	// Add Hash vindex via vtgate execution on table
	err = addColumnVindex(cluster, "test_keyspace", "alter vschema on test_table1 add vindex my_vdx (id)")
	assert.NoError(t, err)
	assertColumnVindex(t, cluster, columnVindex{keyspace: "test_keyspace", table: "test_table1", vindex: "my_vdx", vindexType: "hash", column: "id"})
}

func TestPersistentMode(t *testing.T) {
	args := os.Args
	conf := config
	defer resetFlags(args, conf)

	dir, err := ioutil.TempDir("/tmp", "vttestserver_persistent_mode_")
	assert.NoError(t, err)
	defer os.RemoveAll(dir)

	cluster, err := startPersistentCluster(dir)
	assert.NoError(t, err)

	// basic sanity checks similar to TestRunsVschemaMigrations
	assertColumnVindex(t, cluster, columnVindex{keyspace: "test_keyspace", table: "test_table", vindex: "my_vdx", vindexType: "hash", column: "id"})
	assertColumnVindex(t, cluster, columnVindex{keyspace: "app_customer", table: "customers", vindex: "hash", vindexType: "hash", column: "id"})

	// insert some data to ensure persistence across teardowns
	err = execOnCluster(cluster, "app_customer", func(conn *mysql.Conn) error {
		_, err := conn.ExecuteFetch("insert into customers (id, name) values (1, 'gopherson')", 1, false)
		return err
	})
	assert.NoError(t, err)

	expectedRows := [][]sqltypes.Value{
		{sqltypes.NewInt64(1), sqltypes.NewVarChar("gopherson"), sqltypes.NULL},
	}

	// ensure data was actually inserted
	var res *sqltypes.Result
	err = execOnCluster(cluster, "app_customer", func(conn *mysql.Conn) (err error) {
		res, err = conn.ExecuteFetch("SELECT * FROM customers", 1, false)
		return err
	})
	assert.NoError(t, err)
	assert.Equal(t, expectedRows, res.Rows)

	// reboot the persistent cluster
	cluster.TearDown()
	cluster, err = startPersistentCluster(dir)
	defer cluster.TearDown()
	assert.NoError(t, err)

	// rerun our sanity checks to make sure vschema migrations are run during every startup
	assertColumnVindex(t, cluster, columnVindex{keyspace: "test_keyspace", table: "test_table", vindex: "my_vdx", vindexType: "hash", column: "id"})
	assertColumnVindex(t, cluster, columnVindex{keyspace: "app_customer", table: "customers", vindex: "hash", vindexType: "hash", column: "id"})

	// ensure previous data was successfully persisted
	err = execOnCluster(cluster, "app_customer", func(conn *mysql.Conn) (err error) {
		res, err = conn.ExecuteFetch("SELECT * FROM customers", 1, false)
		return err
	})
	assert.NoError(t, err)
	assert.Equal(t, expectedRows, res.Rows)
}

func TestForeignKeys(t *testing.T) {
	args := os.Args
	conf := config
	defer resetFlags(args, conf)

	cluster, err := startCluster("-foreign_key_mode=allow")
	assert.NoError(t, err)
	defer cluster.TearDown()

	err = execOnCluster(cluster, "test_keyspace", func(conn *mysql.Conn) error {
		_, err := conn.ExecuteFetch(`CREATE TABLE test_table_2 (
			id BIGINT,
			test_table_id BIGINT,
			FOREIGN KEY (test_table_id) REFERENCES test_table(id)
		)`, 1, false)
		return err
	})
	assert.NoError(t, err)

	cluster.TearDown()
	cluster, err = startCluster("-foreign_key_mode=disallow")
	assert.NoError(t, err)
	defer cluster.TearDown()

	err = execOnCluster(cluster, "test_keyspace", func(conn *mysql.Conn) error {
		_, err := conn.ExecuteFetch(`CREATE TABLE test_table_2 (
			id BIGINT,
			test_table_id BIGINT,
			FOREIGN KEY (test_table_id) REFERENCES test_table(id)
		)`, 1, false)
		return err
	})
	assert.Error(t, err)
}

func TestCanVtGateExecute(t *testing.T) {
	args := os.Args
	conf := config
	defer resetFlags(args, conf)

	cluster, err := startCluster()
	assert.NoError(t, err)
	defer cluster.TearDown()

	client, err := vtctlclient.New(fmt.Sprintf("localhost:%v", cluster.GrpcPort()))
	assert.NoError(t, err)
	defer client.Close()
	stream, err := client.ExecuteVtctlCommand(
		context.Background(),
		[]string{
			"VtGateExecute",
			"-server",
			fmt.Sprintf("localhost:%v", cluster.GrpcPort()),
			"select 'success';",
		},
		30*time.Second,
	)
	assert.NoError(t, err)

	var b strings.Builder
	b.Grow(1024)

Out:
	for {
		e, err := stream.Recv()
		switch err {
		case nil:
			b.WriteString(e.Value)
		case io.EOF:
			break Out
		default:
			assert.FailNow(t, err.Error())
		}
	}

	assert.Contains(t, b.String(), "success")
}

func TestMtlsAuth(t *testing.T) {
	args := os.Args
	conf := config
	defer resetFlags(args, conf)

	// Our test root.
	root, err := ioutil.TempDir("", "tlstest")
	if err != nil {
		t.Fatalf("TempDir failed: %v", err)
	}
	defer os.RemoveAll(root)

	// Create the certs and configs.
	tlstest.CreateCA(root)
	caCert := path.Join(root, "ca-cert.pem")

	tlstest.CreateSignedCert(root, tlstest.CA, "01", "vtctld", "vtctld.example.com")
	cert := path.Join(root, "vtctld-cert.pem")
	key := path.Join(root, "vtctld-key.pem")

	tlstest.CreateSignedCert(root, tlstest.CA, "02", "client", "ClientApp")
	clientCert := path.Join(root, "client-cert.pem")
	clientKey := path.Join(root, "client-key.pem")

	// When cluster starts it will apply SQL and VSchema migrations in the configured schema_dir folder
	// With mtls authorization enabled, the authorized CN must match the certificate's CN
	cluster, err := startCluster(
		"-grpc_auth_mode=mtls",
		fmt.Sprintf("-grpc_key=%s", key),
		fmt.Sprintf("-grpc_cert=%s", cert),
		fmt.Sprintf("-grpc_ca=%s", caCert),
		fmt.Sprintf("-vtctld_grpc_key=%s", clientKey),
		fmt.Sprintf("-vtctld_grpc_cert=%s", clientCert),
		fmt.Sprintf("-vtctld_grpc_ca=%s", caCert),
		fmt.Sprintf("-grpc_auth_mtls_allowed_substrings=%s", "CN=ClientApp"))
	assert.NoError(t, err)
	defer cluster.TearDown()

	// startCluster will apply vschema migrations using vtctl grpc and the clientCert.
	assertColumnVindex(t, cluster, columnVindex{keyspace: "test_keyspace", table: "test_table", vindex: "my_vdx", vindexType: "hash", column: "id"})
	assertColumnVindex(t, cluster, columnVindex{keyspace: "app_customer", table: "customers", vindex: "hash", vindexType: "hash", column: "id"})
}

func TestMtlsAuthUnauthorizedFails(t *testing.T) {
	args := os.Args
	conf := config
	defer resetFlags(args, conf)

	// Our test root.
	root, err := ioutil.TempDir("", "tlstest")
	if err != nil {
		t.Fatalf("TempDir failed: %v", err)
	}
	defer os.RemoveAll(root)

	// Create the certs and configs.
	tlstest.CreateCA(root)
	caCert := path.Join(root, "ca-cert.pem")

	tlstest.CreateSignedCert(root, tlstest.CA, "01", "vtctld", "vtctld.example.com")
	cert := path.Join(root, "vtctld-cert.pem")
	key := path.Join(root, "vtctld-key.pem")

	tlstest.CreateSignedCert(root, tlstest.CA, "02", "client", "AnotherApp")
	clientCert := path.Join(root, "client-cert.pem")
	clientKey := path.Join(root, "client-key.pem")

	// When cluster starts it will apply SQL and VSchema migrations in the configured schema_dir folder
	// For mtls authorization failure by providing a client certificate with different CN thant the
	// authorized in the configuration
	cluster, err := startCluster(
		"-grpc_auth_mode=mtls",
		fmt.Sprintf("-grpc_key=%s", key),
		fmt.Sprintf("-grpc_cert=%s", cert),
		fmt.Sprintf("-grpc_ca=%s", caCert),
		fmt.Sprintf("-vtctld_grpc_key=%s", clientKey),
		fmt.Sprintf("-vtctld_grpc_cert=%s", clientCert),
		fmt.Sprintf("-vtctld_grpc_ca=%s", caCert),
		fmt.Sprintf("-grpc_auth_mtls_allowed_substrings=%s", "CN=ClientApp"))
	defer cluster.TearDown()

	assert.Error(t, err)
	assert.Contains(t, err.Error(), "code = Unauthenticated desc = client certificate not authorized")
}

func startPersistentCluster(dir string, flags ...string) (vttest.LocalCluster, error) {
	flags = append(flags, []string{
		"-persistent_mode",
		// FIXME: if port is not provided, data_dir is not respected
		fmt.Sprintf("-port=%d", randomPort()),
		fmt.Sprintf("-data_dir=%s", dir),
	}...)
	return startCluster(flags...)
}

func startCluster(flags ...string) (vttest.LocalCluster, error) {
	schemaDirArg := "-schema_dir=data/schema"
	tabletHostname := "-tablet_hostname=localhost"
	keyspaceArg := "-keyspaces=test_keyspace,app_customer"
	numShardsArg := "-num_shards=2,2"
	vschemaDDLAuthorizedUsers := "-vschema_ddl_authorized_users=%"
	os.Args = append(os.Args, []string{schemaDirArg, keyspaceArg, numShardsArg, tabletHostname, vschemaDDLAuthorizedUsers}...)
	os.Args = append(os.Args, flags...)
	return runCluster()
}

func addColumnVindex(cluster vttest.LocalCluster, keyspace string, vschemaMigration string) error {
	return execOnCluster(cluster, keyspace, func(conn *mysql.Conn) error {
		_, err := conn.ExecuteFetch(vschemaMigration, 1, false)
		return err
	})
}

func execOnCluster(cluster vttest.LocalCluster, keyspace string, f func(*mysql.Conn) error) error {
	ctx := context.Background()
	vtParams := mysql.ConnParams{
		Host:   "localhost",
		DbName: keyspace,
		Port:   cluster.Env.PortForProtocol("vtcombo_mysql_port", ""),
	}

	conn, err := mysql.Connect(ctx, &vtParams)
	if err != nil {
		return err
	}
	defer conn.Close()
	return f(conn)
}

func assertColumnVindex(t *testing.T, cluster vttest.LocalCluster, expected columnVindex) {
	server := fmt.Sprintf("localhost:%v", cluster.GrpcPort())
	args := []string{"GetVSchema", expected.keyspace}
	ctx := context.Background()

	err := vtctlclient.RunCommandAndWait(ctx, server, args, func(e *logutil.Event) {
		var keyspace vschema.Keyspace
		if err := protojson.Unmarshal([]byte(e.Value), &keyspace); err != nil {
			t.Error(err)
		}

		columnVindex := keyspace.Tables[expected.table].ColumnVindexes[0]
		actualVindex := keyspace.Vindexes[expected.vindex]
		assertEqual(t, actualVindex.Type, expected.vindexType, "Actual vindex type different from expected")
		assertEqual(t, columnVindex.Name, expected.vindex, "Actual vindex name different from expected")
		assertEqual(t, columnVindex.Columns[0], expected.column, "Actual vindex column different from expected")
	})
	require.NoError(t, err)
}

func assertEqual(t *testing.T, actual string, expected string, message string) {
	if actual != expected {
		t.Errorf("%s: actual %s, expected %s", message, actual, expected)
	}
}

func resetFlags(args []string, conf vttest.Config) {
	os.Args = args
	config = conf
}

func randomPort() int {
	v := rand.Int31n(20000)
	return int(v + 10000)
}
<|endoftext|>


<commit_before>package kloud

import (
	"errors"
	"fmt"
	"koding/db/mongodb/modelhelper"
	"koding/kites/kloud/contexthelper/request"
	"koding/kites/kloud/contexthelper/session"
	"koding/kites/kloud/klient"
	"time"

	"golang.org/x/net/context"

	"github.com/koding/kite"
)

type AdminRequest struct {
	MachineId string `json:"machineId"`
	GroupName string `json:"groupName"`
}

func (k *Kloud) AdminAdd(r *kite.Request) (interface{}, error) {
	kl, err := k.authorizedKlient(r)
	if err != nil {
		return nil, err
	}

	if err := kl.AddUser(r.Username); err != nil {
		return nil, err
	}

	return true, nil
}

func (k *Kloud) AdminRemove(r *kite.Request) (interface{}, error) {
	kl, err := k.authorizedKlient(r)
	if err != nil {
		return nil, err
	}

	if err := kl.RemoveUser(r.Username); err != nil {
		return nil, err
	}

	return true, nil
}

func (k *Kloud) authorizedKlient(r *kite.Request) (*klient.Klient, error) {
	if r.Args == nil {
		return nil, NewError(ErrNoArguments)
	}

	var args *AdminRequest
	if err := r.Args.One().Unmarshal(&args); err != nil {
		return nil, err
	}

	if args.MachineId == "" {
		return nil, errors.New("machineId is not passed")
	}

	if args.GroupName == "" {
		return nil, errors.New("groupName is not passed")
	}

	k.Log.Debug("Got arguments %+v for method: %s", args, r.Method)

	isAdmin, err := modelhelper.IsAdmin(args.GroupName, r.Username)
	if err != nil {
		return nil, err
	}

	if !isAdmin {
		return nil, fmt.Errorf("User '%s' is not an admin of group '%s'", r.Username, args.GroupName)
	}

	machine, err := modelhelper.GetMachine(args.MachineId)
	if err != nil {
		return nil, fmt.Errorf("getMachine err: %s", err)
	}

	g, err := modelhelper.GetGroup(args.GroupName)
	if err != nil {
		return nil, err
	}

	isGroupMember := false
	for _, group := range machine.Groups {
		if group.Id.Hex() == g.Id.Hex() {
			isGroupMember = true
		}
	}
	if !isGroupMember {
		return nil, fmt.Errorf("'%s' machine does not belong to '%s' group",
			args.MachineId, args.GroupName)
	}

	// Now we are ready to go.
	ctx := request.NewContext(context.Background(), r)
	ctx = k.ContextCreator(ctx)
	sess, ok := session.FromContext(ctx)
	if !ok {
		return nil, errors.New("internal server error (err: session context is not available)")
	}

	return klient.NewWithTimeout(sess.Kite, machine.QueryString, time.Second*10)
}
<commit_msg>kloud: add more debug<commit_after>package kloud

import (
	"errors"
	"fmt"
	"koding/db/mongodb/modelhelper"
	"koding/kites/kloud/contexthelper/request"
	"koding/kites/kloud/contexthelper/session"
	"koding/kites/kloud/klient"
	"time"

	"golang.org/x/net/context"

	"github.com/koding/kite"
)

type AdminRequest struct {
	MachineId string `json:"machineId"`
	GroupName string `json:"groupName"`
}

func (k *Kloud) AdminAdd(r *kite.Request) (interface{}, error) {
	kl, err := k.authorizedKlient(r)
	if err != nil {
		return nil, err
	}

	if err := kl.AddUser(r.Username); err != nil {
		return nil, err
	}

	return true, nil
}

func (k *Kloud) AdminRemove(r *kite.Request) (interface{}, error) {
	kl, err := k.authorizedKlient(r)
	if err != nil {
		return nil, err
	}

	if err := kl.RemoveUser(r.Username); err != nil {
		return nil, err
	}

	return true, nil
}

func (k *Kloud) authorizedKlient(r *kite.Request) (*klient.Klient, error) {
	if r.Args == nil {
		return nil, NewError(ErrNoArguments)
	}

	var args *AdminRequest
	if err := r.Args.One().Unmarshal(&args); err != nil {
		return nil, err
	}

	if args.MachineId == "" {
		return nil, errors.New("machineId is not passed")
	}

	if args.GroupName == "" {
		return nil, errors.New("groupName is not passed")
	}

	k.Log.Debug("Got arguments %+v for method: %s", args, r.Method)

	isAdmin, err := modelhelper.IsAdmin(args.GroupName, r.Username)
	if err != nil {
		return nil, err
	}

	if !isAdmin {
		return nil, fmt.Errorf("User '%s' is not an admin of group '%s'", r.Username, args.GroupName)
	}

	k.Log.Debug("User '%s' is an admin. Checking for machine permission", r.Username)

	machine, err := modelhelper.GetMachine(args.MachineId)
	if err != nil {
		return nil, fmt.Errorf("getMachine err: %s", err)
	}

	g, err := modelhelper.GetGroup(args.GroupName)
	if err != nil {
		return nil, err
	}

	isGroupMember := false
	for _, group := range machine.Groups {
		if group.Id.Hex() == g.Id.Hex() {
			isGroupMember = true
		}
	}
	if !isGroupMember {
		return nil, fmt.Errorf("'%s' machine does not belong to '%s' group",
			args.MachineId, args.GroupName)
	}

	k.Log.Debug("Incoming user is authorized, setting up DB and Klient connection")

	// Now we are ready to go.
	ctx := request.NewContext(context.Background(), r)
	ctx = k.ContextCreator(ctx)
	sess, ok := session.FromContext(ctx)
	if !ok {
		return nil, errors.New("internal server error (err: session context is not available)")
	}

	k.Log.Debug("Calling Klient method: %s", r.Method)
	return klient.NewWithTimeout(sess.Kite, machine.QueryString, time.Second*10)
}
<|endoftext|>


<commit_before>// A library for easing the interaction with klient.
package klient

import (
	"errors"
	"io/ioutil"
	"strings"
	"time"

	"koding/klient/client"
	"koding/klient/command"
	"koding/klient/fs"
	"koding/klient/remote/req"
	"koding/klientctl/config"
	"koding/klientctl/list"

	"github.com/koding/kite"
	"github.com/koding/kite/dnode"
)

// defaultKlientTimeout is a general timeout for klient communications.
const defaultKlientTimeout = 5 * time.Second

// Klient implements methods that klientctl uses when calling Klient, unmarshalling
// automatically into the proper methods.
type Klient struct {
	// The Teller is Klient's main "transport" for communication with the internal
	// Client.
	//
	// Why an interface here? Rather than Requiring a kite.Client specifically, the
	// Tell() method is the only thing required. As such, the Klient struct itself,
	// some older Transport structs, and pretty much any struct that talks to Kites
	// has this Tell method and Satisfies the interface.
	Teller interface {
		Tell(string, ...interface{}) (*dnode.Partial, error)
	}

	// Client is exposed (and via GetClient() mainly to allow the Klient struct
	// to be backwards compatible with non-Klient using methods.
	//
	// All actual communication is done via the Teller - this field purely exists
	// for the GetClient() method.
	Client *kite.Client
}

// NewKlient creates a Klient instance from the given kite.Client.
func NewKlient(c *kite.Client) *Klient {
	return &Klient{
		Client: c,
		Teller: c,
	}
}

// KlientOptions contains various fields for connecting to a klient.
type KlientOptions struct {
	// Address is the path to the Klient.
	Address string

	// KiteKeyPath is the full path to kite.key, which will be loaded and used
	// to authorize kdbin requests to Klient.
	KiteKeyPath string

	// Name, as passed to the first argument in `kite.New()`.
	Name string

	// Version, as passed to the second argument to `kite.New()`.
	Version string

	// Environment for the kite.Config.Environemnt.
	Environment string
}

// NewKlientOptions returns KlientOptions initialized to default values.
func NewKlientOptions() KlientOptions {
	return KlientOptions{
		Address:     config.Konfig.KlientURL,
		KiteKeyPath: config.Konfig.KiteKeyFile,
		Name:        config.Name,
		Version:     config.KiteVersion,
		Environment: config.Environment,
	}
}

// CreateKlientClient creates a kite with default KlientOptions and returns a
// Kite Client to talk to that Klient.
func CreateKlientWithDefaultOpts() (*kite.Client, error) {
	return CreateKlientClient(NewKlientOptions())
}

// CreateKlientClient creates a kite to the klient specified by KlientOptions.
// In most cases CreateKlientWithDefaultOpts should be used instead of this, ie
// this should be used only if you want to override KlientOptions.
func CreateKlientClient(opts KlientOptions) (*kite.Client, error) {
	if opts.Version == "" {
		return nil, errors.New("CreateKlientClient: Version is required")
	}

	if opts.Address == "" {
		return nil, errors.New("CreateKlientClient: Address is required")
	}

	k := kite.New(opts.Name, opts.Version)
	k.Config.Environment = opts.Environment
	c := k.NewClient(opts.Address)

	// If a key path is declared, load it and setup auth.
	if opts.KiteKeyPath != "" {
		data, err := ioutil.ReadFile(opts.KiteKeyPath)
		if err != nil {
			return nil, err
		}

		c.Auth = &kite.Auth{
			Type: "kiteKey",
			Key:  strings.TrimSpace(string(data)),
		}
	}

	return c, nil
}

// NewDefaultDialedKlient creates a pre-dialed Klient instance using default
// klient options.
func NewDefaultDialedKlient() (*Klient, error) {
	return NewDialedKlient(NewKlientOptions())
}

// NewDialedKlient creates a pre-dialed Klient instance. In most cases
// NewDefaultDialedKlient should be used instead of this, ie this should be used
// only if you want to override KlientOptions.
func NewDialedKlient(opts KlientOptions) (*Klient, error) {
	c, err := CreateKlientClient(opts)
	if err != nil {
		return nil, err
	}

	if err := c.Dial(); err != nil {
		return nil, err
	}

	return NewKlient(c), nil
}

func (k *Klient) Tell(methodName string, reqs ...interface{}) (*dnode.Partial, error) {
	if k.Teller == nil {
		return nil, errors.New("Missing Teller on Klient struct")
	}

	return k.Teller.Tell(methodName, reqs...)
}

// GetClient is a utility function for getting the underlying kite Client
// back from a Klient struct hidden behind an interface. Used mainly to
// interact with legacy code.
func (k *Klient) GetClient() *kite.Client {
	return k.Client
}

// RemoteList the current machines.
func (k *Klient) RemoteList() (list.KiteInfos, error) {
	res, err := k.Client.TellWithTimeout("remote.list", defaultKlientTimeout)
	if err != nil {
		return nil, err
	}

	var infos []list.KiteInfo
	if err := res.Unmarshal(&infos); err != nil {
		return nil, err
	}

	return infos, nil
}

// RemoteCache calls klient's remote.cache method.
//
// Note that due to how the remote/req library is setup, this function needs to
// take the callback as a separate argument for now. This will be improved
// in the future, in one way or another.
func (k *Klient) RemoteCache(r req.Cache, cb func(par *dnode.Partial)) error {
	cacheReq := struct {
		req.Cache
		Progress dnode.Function `json:"progress"`
	}{
		Cache: r,
	}

	if cb != nil {
		cacheReq.Progress = dnode.Callback(cb)
	}

	// No response from cacheFolder currently.
	_, err := k.Tell("remote.cacheFolder", cacheReq)
	return err
}

// RemoteMountFolder calls klient's remote.mountFolder method. If there are
// any warnings, those are returned here.
func (k *Klient) RemoteMountFolder(r req.MountFolder) (string, error) {
	resp, err := k.Tell("remote.mountFolder", r)
	if err != nil {
		return "", err
	}

	var warning string
	// TODO: Ignore the nil unmarshal error, but return others.
	resp.Unmarshal(&warning)

	return warning, nil
}

// RemoteStatus calls klients remote.status method.
func (k *Klient) RemoteStatus(r req.Status) error {
	_, err := k.Tell("remote.status", r)
	return err
}

// RemoteMountInfo calls klients remote.mountInfo method.
func (k *Klient) RemoteMountInfo(mountName string) (req.MountInfoResponse, error) {
	r := req.MountInfo{MountName: mountName}
	resp, err := k.Tell("remote.mountInfo", r)
	if err != nil {
		return req.MountInfoResponse{}, err
	}

	var mountInfo req.MountInfoResponse
	// TODO: Ignore the nil unmarshal error, but return others.
	resp.Unmarshal(&mountInfo)

	return mountInfo, nil
}

// RemoteRemount calls klient's remote.remount method.
func (k *Klient) RemoteRemount(mountName string) error {
	r := req.Remount{MountName: mountName}
	_, err := k.Tell("remote.remount", r)
	return err
}

// RemoteExec calls the `remote.exec` method.
func (k *Klient) RemoteExec(machineName, c string) (command.Output, error) {
	var res command.Output
	req := req.Exec{Machine: machineName, Command: c}
	kRes, err := k.Tell("remote.exec", req)
	if err != nil {
		return res, err
	}

	return res, kRes.Unmarshal(&res)
}

func (k *Klient) RemoteReadDirectory(mountName, remotePath string) ([]fs.FileEntry, error) {
	r := req.ReadDirectoryOptions{
		Machine: mountName,
		ReadDirectoryOptions: fs.ReadDirectoryOptions{
			Path: remotePath,
		},
	}

	res, err := k.Tell("remote.readDirectory", r)
	if err != nil {
		return nil, err
	}

	resMap, err := res.Map()
	if err != nil {
		return nil, err
	}

	partial, ok := resMap["files"]
	if !ok {
		return nil, nil
	}

	filePartials, err := partial.Slice()
	if err != nil {
		return nil, err
	}

	files := make([]fs.FileEntry, len(filePartials))
	for i, p := range filePartials {
		var fe fs.FileEntry
		if err := p.Unmarshal(&fe); err != nil {
			return nil, err
		}
		files[i] = fe
	}

	return files, nil
}

// RemoteCurrentUsername calls klient's `remote.currentUsername` method
func (k *Klient) RemoteCurrentUsername(opts req.CurrentUsernameOptions) (string, error) {
	var username string

	kRes, err := k.Tell("remote.currentUsername", opts)
	if err != nil {
		return username, err
	}

	return username, kRes.Unmarshal(&username)
}

// RemoteGetPathSize calls the klient's `remote.getPathSize` method
func (k *Klient) RemoteGetPathSize(opts req.GetPathSizeOptions) (uint64, error) {
	var size uint64

	kRes, err := k.Tell("remote.getPathSize", opts)
	if err != nil {
		return size, err
	}

	return size, kRes.Unmarshal(&size)
}

func (k *Klient) LocalOpenFiles(files ...string) error {
	_, err := k.Tell("client.Publish", FilesEvent{
		PublishRequest: client.PublishRequest{EventName: "openFiles"},
		Files:          files,
	})

	return err
}
<commit_msg>kd: fix reading kite.key<commit_after>// A library for easing the interaction with klient.
package klient

import (
	"errors"
	"io/ioutil"
	"strings"
	"time"

	"koding/klient/client"
	"koding/klient/command"
	"koding/klient/fs"
	"koding/klient/remote/req"
	"koding/klientctl/config"
	"koding/klientctl/list"

	"github.com/koding/kite"
	"github.com/koding/kite/dnode"
)

// defaultKlientTimeout is a general timeout for klient communications.
const defaultKlientTimeout = 5 * time.Second

// Klient implements methods that klientctl uses when calling Klient, unmarshalling
// automatically into the proper methods.
type Klient struct {
	// The Teller is Klient's main "transport" for communication with the internal
	// Client.
	//
	// Why an interface here? Rather than Requiring a kite.Client specifically, the
	// Tell() method is the only thing required. As such, the Klient struct itself,
	// some older Transport structs, and pretty much any struct that talks to Kites
	// has this Tell method and Satisfies the interface.
	Teller interface {
		Tell(string, ...interface{}) (*dnode.Partial, error)
	}

	// Client is exposed (and via GetClient() mainly to allow the Klient struct
	// to be backwards compatible with non-Klient using methods.
	//
	// All actual communication is done via the Teller - this field purely exists
	// for the GetClient() method.
	Client *kite.Client
}

// NewKlient creates a Klient instance from the given kite.Client.
func NewKlient(c *kite.Client) *Klient {
	return &Klient{
		Client: c,
		Teller: c,
	}
}

// KlientOptions contains various fields for connecting to a klient.
type KlientOptions struct {
	// Address is the path to the Klient.
	Address string

	// KiteKeyPath is the full path to kite.key, which will be loaded and used
	// to authorize kdbin requests to Klient.
	KiteKeyPath string

	// KiteKey is a content of kite.key, which is used for
	// authenticating kd with other klients.
	//
	// If not empty, this fields is used instead of KiteKeyPath.
	KiteKey string

	// Name, as passed to the first argument in `kite.New()`.
	Name string

	// Version, as passed to the second argument to `kite.New()`.
	Version string

	// Environment for the kite.Config.Environemnt.
	Environment string
}

// NewKlientOptions returns KlientOptions initialized to default values.
func NewKlientOptions() KlientOptions {
	return KlientOptions{
		Address:     config.Konfig.KlientURL,
		KiteKeyPath: config.Konfig.KiteKeyFile,
		KiteKey:     config.Konfig.KiteKey,
		Name:        config.Name,
		Version:     config.KiteVersion,
		Environment: config.Environment,
	}
}

// CreateKlientClient creates a kite with default KlientOptions and returns a
// Kite Client to talk to that Klient.
func CreateKlientWithDefaultOpts() (*kite.Client, error) {
	return CreateKlientClient(NewKlientOptions())
}

// CreateKlientClient creates a kite to the klient specified by KlientOptions.
// In most cases CreateKlientWithDefaultOpts should be used instead of this, ie
// this should be used only if you want to override KlientOptions.
func CreateKlientClient(opts KlientOptions) (*kite.Client, error) {
	if opts.Version == "" {
		return nil, errors.New("CreateKlientClient: Version is required")
	}

	if opts.Address == "" {
		return nil, errors.New("CreateKlientClient: Address is required")
	}

	k := kite.New(opts.Name, opts.Version)
	k.Config.Environment = opts.Environment
	c := k.NewClient(opts.Address)

	if opts.KiteKey != "" {
		c.Auth = &kite.Auth{
			Type: "kiteKey",
			Key:  opts.KiteKey,
		}
	} else if opts.KiteKeyPath != "" {
		// If a key path is declared, load it and setup auth.
		data, err := ioutil.ReadFile(opts.KiteKeyPath)
		if err != nil {
			return nil, err
		}

		c.Auth = &kite.Auth{
			Type: "kiteKey",
			Key:  strings.TrimSpace(string(data)),
		}
	}

	return c, nil
}

// NewDefaultDialedKlient creates a pre-dialed Klient instance using default
// klient options.
func NewDefaultDialedKlient() (*Klient, error) {
	return NewDialedKlient(NewKlientOptions())
}

// NewDialedKlient creates a pre-dialed Klient instance. In most cases
// NewDefaultDialedKlient should be used instead of this, ie this should be used
// only if you want to override KlientOptions.
func NewDialedKlient(opts KlientOptions) (*Klient, error) {
	c, err := CreateKlientClient(opts)
	if err != nil {
		return nil, err
	}

	if err := c.Dial(); err != nil {
		return nil, err
	}

	return NewKlient(c), nil
}

func (k *Klient) Tell(methodName string, reqs ...interface{}) (*dnode.Partial, error) {
	if k.Teller == nil {
		return nil, errors.New("Missing Teller on Klient struct")
	}

	return k.Teller.Tell(methodName, reqs...)
}

// GetClient is a utility function for getting the underlying kite Client
// back from a Klient struct hidden behind an interface. Used mainly to
// interact with legacy code.
func (k *Klient) GetClient() *kite.Client {
	return k.Client
}

// RemoteList the current machines.
func (k *Klient) RemoteList() (list.KiteInfos, error) {
	res, err := k.Client.TellWithTimeout("remote.list", defaultKlientTimeout)
	if err != nil {
		return nil, err
	}

	var infos []list.KiteInfo
	if err := res.Unmarshal(&infos); err != nil {
		return nil, err
	}

	return infos, nil
}

// RemoteCache calls klient's remote.cache method.
//
// Note that due to how the remote/req library is setup, this function needs to
// take the callback as a separate argument for now. This will be improved
// in the future, in one way or another.
func (k *Klient) RemoteCache(r req.Cache, cb func(par *dnode.Partial)) error {
	cacheReq := struct {
		req.Cache
		Progress dnode.Function `json:"progress"`
	}{
		Cache: r,
	}

	if cb != nil {
		cacheReq.Progress = dnode.Callback(cb)
	}

	// No response from cacheFolder currently.
	_, err := k.Tell("remote.cacheFolder", cacheReq)
	return err
}

// RemoteMountFolder calls klient's remote.mountFolder method. If there are
// any warnings, those are returned here.
func (k *Klient) RemoteMountFolder(r req.MountFolder) (string, error) {
	resp, err := k.Tell("remote.mountFolder", r)
	if err != nil {
		return "", err
	}

	var warning string
	// TODO: Ignore the nil unmarshal error, but return others.
	resp.Unmarshal(&warning)

	return warning, nil
}

// RemoteStatus calls klients remote.status method.
func (k *Klient) RemoteStatus(r req.Status) error {
	_, err := k.Tell("remote.status", r)
	return err
}

// RemoteMountInfo calls klients remote.mountInfo method.
func (k *Klient) RemoteMountInfo(mountName string) (req.MountInfoResponse, error) {
	r := req.MountInfo{MountName: mountName}
	resp, err := k.Tell("remote.mountInfo", r)
	if err != nil {
		return req.MountInfoResponse{}, err
	}

	var mountInfo req.MountInfoResponse
	// TODO: Ignore the nil unmarshal error, but return others.
	resp.Unmarshal(&mountInfo)

	return mountInfo, nil
}

// RemoteRemount calls klient's remote.remount method.
func (k *Klient) RemoteRemount(mountName string) error {
	r := req.Remount{MountName: mountName}
	_, err := k.Tell("remote.remount", r)
	return err
}

// RemoteExec calls the `remote.exec` method.
func (k *Klient) RemoteExec(machineName, c string) (command.Output, error) {
	var res command.Output
	req := req.Exec{Machine: machineName, Command: c}
	kRes, err := k.Tell("remote.exec", req)
	if err != nil {
		return res, err
	}

	return res, kRes.Unmarshal(&res)
}

func (k *Klient) RemoteReadDirectory(mountName, remotePath string) ([]fs.FileEntry, error) {
	r := req.ReadDirectoryOptions{
		Machine: mountName,
		ReadDirectoryOptions: fs.ReadDirectoryOptions{
			Path: remotePath,
		},
	}

	res, err := k.Tell("remote.readDirectory", r)
	if err != nil {
		return nil, err
	}

	resMap, err := res.Map()
	if err != nil {
		return nil, err
	}

	partial, ok := resMap["files"]
	if !ok {
		return nil, nil
	}

	filePartials, err := partial.Slice()
	if err != nil {
		return nil, err
	}

	files := make([]fs.FileEntry, len(filePartials))
	for i, p := range filePartials {
		var fe fs.FileEntry
		if err := p.Unmarshal(&fe); err != nil {
			return nil, err
		}
		files[i] = fe
	}

	return files, nil
}

// RemoteCurrentUsername calls klient's `remote.currentUsername` method
func (k *Klient) RemoteCurrentUsername(opts req.CurrentUsernameOptions) (string, error) {
	var username string

	kRes, err := k.Tell("remote.currentUsername", opts)
	if err != nil {
		return username, err
	}

	return username, kRes.Unmarshal(&username)
}

// RemoteGetPathSize calls the klient's `remote.getPathSize` method
func (k *Klient) RemoteGetPathSize(opts req.GetPathSizeOptions) (uint64, error) {
	var size uint64

	kRes, err := k.Tell("remote.getPathSize", opts)
	if err != nil {
		return size, err
	}

	return size, kRes.Unmarshal(&size)
}

func (k *Klient) LocalOpenFiles(files ...string) error {
	_, err := k.Tell("client.Publish", FilesEvent{
		PublishRequest: client.PublishRequest{EventName: "openFiles"},
		Files:          files,
	})

	return err
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	kodingmodels "koding/db/models"
	"koding/db/mongodb/modelhelper"
	"socialapi/models"
	"strconv"

	"labix.org/v2/mgo/bson"

	"github.com/koding/bongo"
	"github.com/koding/logging"
)

func setDefaults(log logging.Logger) {
	group, err := modelhelper.GetGroup(models.Channel_KODING_NAME)
	if err != nil {
		log.Error("err while fetching koding group: %s", err.Error())
		return
	}

	log.Debug("mongo group found")

	setPublicChannel(log, group)
	setSetChangeLogChannel(log, group)
	log.Info("socialApi defaults are created")
}

func setPublicChannel(log logging.Logger, group *kodingmodels.Group) {
	c := models.NewChannel()
	selector := map[string]interface{}{
		"type_constant": models.Channel_TYPE_GROUP,
		"group_name":    models.Channel_KODING_NAME,
	}

	err := c.One(bongo.NewQS(selector))
	if err != nil && err != bongo.RecordNotFound {
		log.Error("err while fetching koding channel:", err.Error())
		return
	}

	if err == bongo.RecordNotFound {
		log.Debug("postgres group couldn't found, creating it")

		acc, err := createChannelOwner(group)
		if err != nil {
			log.Error(err.Error())
			return
		}

		c.Name = "public"
		c.CreatorId = acc.Id
		c.GroupName = models.Channel_KODING_NAME
		c.TypeConstant = models.Channel_TYPE_GROUP
		c.PrivacyConstant = models.Channel_PRIVACY_PUBLIC
		if err := c.Create(); err != nil {
			log.Error("err while creating the koding channel: %s", err.Error())
			return
		}
	}

	socialApiId := strconv.FormatInt(c.Id, 10)
	if group.SocialApiChannelId == socialApiId {
		log.Error("mongo and postgres socialApiChannelId ids are same")
		return
	}

	log.Debug("mongo and postgres socialApiChannelId ids are different, fixing it")
	if err := updateGroupPartially(group.Id, "socialApiChannelId", socialApiId); err != nil {
		log.Error("err while udpating socialApiChannelId: %s", err.Error())
		return
	}
}

func setSetChangeLogChannel(log logging.Logger, group *kodingmodels.Group) {

	c := models.NewChannel()
	selector := map[string]interface{}{
		"type_constant": models.Channel_TYPE_ANNOUNCEMENT,
		"group_name":    models.Channel_KODING_NAME,
	}

	// if err is nil
	// it means we already have that channel
	err := c.One(bongo.NewQS(selector))
	if err != nil && err != bongo.RecordNotFound {
		log.Error("err while fetching changelog channel:", err.Error())
		return
	}

	if err == bongo.RecordNotFound {
		log.Error("postgres changelog couldn't found, creating it")

		acc, err := createChannelOwner(group)
		if err != nil {
			log.Error(err.Error())
			return
		}

		c.Name = "changelog"
		c.CreatorId = acc.Id
		c.GroupName = models.Channel_KODING_NAME
		c.TypeConstant = models.Channel_TYPE_ANNOUNCEMENT
		c.PrivacyConstant = models.Channel_PRIVACY_PRIVATE
		if err := c.Create(); err != nil {
			log.Error("err while creating the koding channel:", err.Error())
			return
		}
	}

	socialApiAnnouncementChannelId := strconv.FormatInt(c.Id, 10)
	if group.SocialApiAnnouncementChannelId == socialApiAnnouncementChannelId {
		log.Info("mongo and postgres socialApiAnnouncementChannel ids are same")
		return
	}

	log.Debug("mongo and postgres socialApiAnnouncementChannel ids are different, fixing it")
	if err := updateGroupPartially(group.Id, "socialApiAnnouncementChannelId", strconv.FormatInt(c.Id, 10)); err != nil {
		log.Error("err while udpating socialApiAnnouncementChannelId:", err.Error())
		return
	}
}

func createChannelOwner(group *kodingmodels.Group) (*models.Account, error) {
	owner, err := modelhelper.GetGroupOwner(group)
	if err != nil {
		return nil, fmt.Errorf("err while fetching koding owner: %s", err.Error())
	}

	acc := models.NewAccount()
	acc.OldId = owner.Id.Hex()
	acc.Nick = owner.Profile.Nickname
	if err := acc.FetchOrCreate(); err != nil {
		return nil, fmt.Errorf("err while fetching owner from postgres: %s", err.Error())
	}

	return acc, nil
}

func updateGroupPartially(groupId bson.ObjectId, property string, value string) error {
	return modelhelper.UpdateGroupPartial(
		modelhelper.Selector{"_id": groupId},
		modelhelper.Selector{
			"$set": modelhelper.Selector{
				property: value,
			},
		},
	)
}
<commit_msg>Social: error to debug log<commit_after>package main

import (
	"fmt"
	kodingmodels "koding/db/models"
	"koding/db/mongodb/modelhelper"
	"socialapi/models"
	"strconv"

	"labix.org/v2/mgo/bson"

	"github.com/koding/bongo"
	"github.com/koding/logging"
)

func setDefaults(log logging.Logger) {
	group, err := modelhelper.GetGroup(models.Channel_KODING_NAME)
	if err != nil {
		log.Error("err while fetching koding group: %s", err.Error())
		return
	}

	log.Debug("mongo group found")

	setPublicChannel(log, group)
	setSetChangeLogChannel(log, group)
	log.Info("socialApi defaults are created")
}

func setPublicChannel(log logging.Logger, group *kodingmodels.Group) {
	c := models.NewChannel()
	selector := map[string]interface{}{
		"type_constant": models.Channel_TYPE_GROUP,
		"group_name":    models.Channel_KODING_NAME,
	}

	err := c.One(bongo.NewQS(selector))
	if err != nil && err != bongo.RecordNotFound {
		log.Error("err while fetching koding channel:", err.Error())
		return
	}

	if err == bongo.RecordNotFound {
		log.Debug("postgres group couldn't found, creating it")

		acc, err := createChannelOwner(group)
		if err != nil {
			log.Error(err.Error())
			return
		}

		c.Name = "public"
		c.CreatorId = acc.Id
		c.GroupName = models.Channel_KODING_NAME
		c.TypeConstant = models.Channel_TYPE_GROUP
		c.PrivacyConstant = models.Channel_PRIVACY_PUBLIC
		if err := c.Create(); err != nil {
			log.Error("err while creating the koding channel: %s", err.Error())
			return
		}
	}

	socialApiId := strconv.FormatInt(c.Id, 10)
	if group.SocialApiChannelId == socialApiId {
		log.Debug("mongo and postgres socialApiChannelId ids are same")
		return
	}

	log.Debug("mongo and postgres socialApiChannelId ids are different, fixing it")
	if err := updateGroupPartially(group.Id, "socialApiChannelId", socialApiId); err != nil {
		log.Error("err while udpating socialApiChannelId: %s", err.Error())
		return
	}
}

func setSetChangeLogChannel(log logging.Logger, group *kodingmodels.Group) {

	c := models.NewChannel()
	selector := map[string]interface{}{
		"type_constant": models.Channel_TYPE_ANNOUNCEMENT,
		"group_name":    models.Channel_KODING_NAME,
	}

	// if err is nil
	// it means we already have that channel
	err := c.One(bongo.NewQS(selector))
	if err != nil && err != bongo.RecordNotFound {
		log.Error("err while fetching changelog channel:", err.Error())
		return
	}

	if err == bongo.RecordNotFound {
		log.Error("postgres changelog couldn't found, creating it")

		acc, err := createChannelOwner(group)
		if err != nil {
			log.Error(err.Error())
			return
		}

		c.Name = "changelog"
		c.CreatorId = acc.Id
		c.GroupName = models.Channel_KODING_NAME
		c.TypeConstant = models.Channel_TYPE_ANNOUNCEMENT
		c.PrivacyConstant = models.Channel_PRIVACY_PRIVATE
		if err := c.Create(); err != nil {
			log.Error("err while creating the koding channel:", err.Error())
			return
		}
	}

	socialApiAnnouncementChannelId := strconv.FormatInt(c.Id, 10)
	if group.SocialApiAnnouncementChannelId == socialApiAnnouncementChannelId {
		log.Info("mongo and postgres socialApiAnnouncementChannel ids are same")
		return
	}

	log.Debug("mongo and postgres socialApiAnnouncementChannel ids are different, fixing it")
	if err := updateGroupPartially(group.Id, "socialApiAnnouncementChannelId", strconv.FormatInt(c.Id, 10)); err != nil {
		log.Error("err while udpating socialApiAnnouncementChannelId:", err.Error())
		return
	}
}

func createChannelOwner(group *kodingmodels.Group) (*models.Account, error) {
	owner, err := modelhelper.GetGroupOwner(group)
	if err != nil {
		return nil, fmt.Errorf("err while fetching koding owner: %s", err.Error())
	}

	acc := models.NewAccount()
	acc.OldId = owner.Id.Hex()
	acc.Nick = owner.Profile.Nickname
	if err := acc.FetchOrCreate(); err != nil {
		return nil, fmt.Errorf("err while fetching owner from postgres: %s", err.Error())
	}

	return acc, nil
}

func updateGroupPartially(groupId bson.ObjectId, property string, value string) error {
	return modelhelper.UpdateGroupPartial(
		modelhelper.Selector{"_id": groupId},
		modelhelper.Selector{
			"$set": modelhelper.Selector{
				property: value,
			},
		},
	)
}
<|endoftext|>


<commit_before>package opensds

import (
	"fmt"
	"log"
	"os"
	"runtime"

	"google.golang.org/grpc/codes"

	"github.com/container-storage-interface/spec/lib/go/csi"
	"github.com/opensds/nbp/client/iscsi"
	sdscontroller "github.com/opensds/nbp/client/opensds"
	"github.com/opensds/opensds/pkg/model"
	"golang.org/x/net/context"
	"google.golang.org/grpc/status"
	"strings"
)

////////////////////////////////////////////////////////////////////////////////
//                            Node Service                                    //
////////////////////////////////////////////////////////////////////////////////

// NodePublishVolume implementation
func (p *Plugin) NodePublishVolume(
	ctx context.Context,
	req *csi.NodePublishVolumeRequest) (
	*csi.NodePublishVolumeResponse, error) {

	log.Println("start to NodePublishVolume")
	defer log.Println("end to NodePublishVolume")

	if errCode := p.CheckVersionSupport(req.Version); errCode != codes.OK {
		msg := "the version specified in the request is not supported by the Plugin."
		return nil, status.Error(errCode, msg)
	}

	client := sdscontroller.GetClient("")

	//check volume is exist
	volSpec, errVol := client.GetVolume(req.VolumeId)
	if errVol != nil || volSpec == nil {
		msg := fmt.Sprintf("the volume %s is not exist", req.VolumeId)
		return nil, status.Error(codes.NotFound, msg)
	}

	atc, atcErr := client.GetVolumeAttachment(req.PublishVolumeInfo["atcid"])
	if atcErr != nil || atc == nil {
		return nil, status.Error(codes.FailedPrecondition, "Failed to publish node.")
	}

	var targetPaths []string
	if tps, exist := atc.Metadata["target_path"]; exist {
		targetPaths = strings.Split(tps, ";")
		for _, tp := range targetPaths {
			if req.TargetPath == tp {
				return &csi.NodePublishVolumeResponse{}, nil
			}
		}

		// if volume don't have MULTI_NODE capability, just termination.
		mode := req.VolumeCapability.AccessMode.Mode
		if mode != csi.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER &&
			mode != csi.VolumeCapability_AccessMode_MULTI_NODE_READER_ONLY &&
			mode != csi.VolumeCapability_AccessMode_MULTI_NODE_SINGLE_WRITER {
			msg := fmt.Sprintf("the volume %s has been published to this node.", req.VolumeId)
			return nil, status.Error(codes.Aborted, msg)
		}
	}

	portal := req.PublishVolumeInfo["portal"]
	targetiqn := req.PublishVolumeInfo["targetiqn"]
	targetlun := req.PublishVolumeInfo["targetlun"]

	// Connect Target
	log.Printf("[NodePublishVolume] portal:%s targetiqn:%s targetlun:%s volumeid:%s",
		portal, targetiqn, targetlun, req.VolumeId)
	device, err := iscsi.Connect(portal, targetiqn, targetlun)
	if err != nil {
		return nil, err
	}

	// obtain attachments to decide if can format.
	atcs, err := client.ListVolumeAttachments()
	if err != nil {
		return nil, status.Error(codes.FailedPrecondition, "Failed to publish node.")
	}
	format := true
	for _, attachSpec := range atcs {
		if attachSpec.VolumeId == req.VolumeId {
			if _, exist := attachSpec.Metadata["target_path"]; exist {
				// The device is formatted, can't be reformat for shared storage.
				format = false
				break
			}
		}
	}

	// Format and Mount
	log.Printf("[NodePublishVolume] device:%s TargetPath:%s", device, req.TargetPath)
	if format {
		err = iscsi.FormatandMount(device, "", req.TargetPath)
	} else {
		err = iscsi.Mount(device, req.TargetPath)
	}
	if err != nil {
		return nil, err
	}

	targetPaths = append(targetPaths, req.TargetPath)
	atc.Metadata["target_path"] = strings.Join(targetPaths, ";")
	_, err = client.UpdateVolumeAttachment(atc.Id, atc)
	if err != nil {
		return nil, status.Error(codes.FailedPrecondition, "Failed to publish node.")
	}

	return &csi.NodePublishVolumeResponse{}, nil
}

// NodeUnpublishVolume implementation
func (p *Plugin) NodeUnpublishVolume(
	ctx context.Context,
	req *csi.NodeUnpublishVolumeRequest) (
	*csi.NodeUnpublishVolumeResponse, error) {

	log.Println("start to NodeUnpublishVolume")
	defer log.Println("end to NodeUnpublishVolume")

	if errCode := p.CheckVersionSupport(req.Version); errCode != codes.OK {
		msg := "the version specified in the request is not supported by the Plugin."
		return nil, status.Error(errCode, msg)
	}

	client := sdscontroller.GetClient("")

	//check volume is exist
	volSpec, errVol := client.GetVolume(req.VolumeId)
	if errVol != nil || volSpec == nil {
		msg := fmt.Sprintf("the volume %s is not exist", req.VolumeId)
		return nil, status.Error(codes.NotFound, msg)
	}

	attachments, err := client.ListVolumeAttachments()
	if err != nil {
		return nil, status.Error(codes.FailedPrecondition, "Failed to NodeUnpublish volume.")
	}

	var atc *model.VolumeAttachmentSpec
	hostname, _ := os.Hostname()
	for _, attachSpec := range attachments {
		if attachSpec.VolumeId == req.VolumeId && attachSpec.Host == hostname {
			atc = attachSpec
			break
		}
	}

	if atc == nil {
		return &csi.NodeUnpublishVolumeResponse{}, nil
	}

	if _, exist := atc.Metadata["target_path"]; !exist {
		return &csi.NodeUnpublishVolumeResponse{}, nil
	}

	var modifyTargetPaths []string
	tpExist := false
	targetPaths := strings.Split(atc.Metadata["target_path"], ";")
	for index, path := range targetPaths {
		if path == req.TargetPath {
			modifyTargetPaths = append(targetPaths[:index], targetPaths[index+1:]...)
			tpExist = true
			break
		}
	}
	if !tpExist {
		return &csi.NodeUnpublishVolumeResponse{}, nil
	}

	// Umount
	log.Printf("[NodeUnpublishVolume] TargetPath:%s", req.TargetPath)
	err = iscsi.Umount(req.TargetPath)
	if err != nil {
		return nil, err
	}

	if len(modifyTargetPaths) == 0 {
		iscsiCon := iscsi.ParseIscsiConnectInfo(atc.ConnectionData)
		// Disconnect
		if iscsiCon != nil {
			err = iscsi.Disconnect(iscsiCon.TgtPortal, iscsiCon.TgtIQN)
			if err != nil {
				return nil, err
			}
		}
	}

	atc.Metadata["target_path"] = strings.Join(modifyTargetPaths, ";")
	_, err = client.UpdateVolumeAttachment(atc.Id, atc)
	if err != nil {
		return nil, status.Error(codes.FailedPrecondition, "Failed to NodeUnpublish volume.")
	}

	return &csi.NodeUnpublishVolumeResponse{}, nil
}

// GetNodeID implementation
func (p *Plugin) GetNodeID(
	ctx context.Context,
	req *csi.GetNodeIDRequest) (
	*csi.GetNodeIDResponse, error) {

	log.Println("start to GetNodeID")
	defer log.Println("end to GetNodeID")

	// Get host name from os
	hostname, err := os.Hostname()
	if err != nil {
		return nil, err
	}

	return &csi.GetNodeIDResponse{
		NodeId: hostname,
	}, nil
}

// NodeProbe implementation
func (p *Plugin) NodeProbe(
	ctx context.Context,
	req *csi.NodeProbeRequest) (
	*csi.NodeProbeResponse, error) {

	log.Println("start to NodeProbe")
	defer log.Println("end to NodeProbe")

	switch runtime.GOOS {
	case "linux":
		return &csi.NodeProbeResponse{}, nil
	default:
		msg := "unsupported operating system:" + runtime.GOOS
		log.Fatalf(msg)
		// csi.Error_NodeProbeError_MISSING_REQUIRED_HOST_DEPENDENCY
		return nil, status.Error(codes.FailedPrecondition, msg)
	}
}

// NodeGetCapabilities implementation
func (p *Plugin) NodeGetCapabilities(
	ctx context.Context,
	req *csi.NodeGetCapabilitiesRequest) (
	*csi.NodeGetCapabilitiesResponse, error) {

	log.Println("start to NodeGetCapabilities")
	defer log.Println("end to NodeGetCapabilities")

	return &csi.NodeGetCapabilitiesResponse{
		Capabilities: []*csi.NodeServiceCapability{
			&csi.NodeServiceCapability{
				Type: &csi.NodeServiceCapability_Rpc{
					Rpc: &csi.NodeServiceCapability_RPC{
						Type: csi.NodeServiceCapability_RPC_UNKNOWN,
					},
				},
			},
		},
	}, nil
}
<commit_msg>add judgement of target_path field<commit_after>package opensds

import (
	"fmt"
	"log"
	"os"
	"runtime"

	"google.golang.org/grpc/codes"

	"github.com/container-storage-interface/spec/lib/go/csi"
	"github.com/opensds/nbp/client/iscsi"
	sdscontroller "github.com/opensds/nbp/client/opensds"
	"github.com/opensds/opensds/pkg/model"
	"golang.org/x/net/context"
	"google.golang.org/grpc/status"
	"strings"
)

////////////////////////////////////////////////////////////////////////////////
//                            Node Service                                    //
////////////////////////////////////////////////////////////////////////////////

// NodePublishVolume implementation
func (p *Plugin) NodePublishVolume(
	ctx context.Context,
	req *csi.NodePublishVolumeRequest) (
	*csi.NodePublishVolumeResponse, error) {

	log.Println("start to NodePublishVolume")
	defer log.Println("end to NodePublishVolume")

	if errCode := p.CheckVersionSupport(req.Version); errCode != codes.OK {
		msg := "the version specified in the request is not supported by the Plugin."
		return nil, status.Error(errCode, msg)
	}

	client := sdscontroller.GetClient("")

	//check volume is exist
	volSpec, errVol := client.GetVolume(req.VolumeId)
	if errVol != nil || volSpec == nil {
		msg := fmt.Sprintf("the volume %s is not exist", req.VolumeId)
		return nil, status.Error(codes.NotFound, msg)
	}

	atc, atcErr := client.GetVolumeAttachment(req.PublishVolumeInfo["atcid"])
	if atcErr != nil || atc == nil {
		return nil, status.Error(codes.FailedPrecondition, "Failed to publish node.")
	}

	var targetPaths []string
	if tps, exist := atc.Metadata["target_path"]; exist && len(tps) != 0 {
		targetPaths = strings.Split(tps, ";")
		for _, tp := range targetPaths {
			if req.TargetPath == tp {
				return &csi.NodePublishVolumeResponse{}, nil
			}
		}

		// if volume don't have MULTI_NODE capability, just termination.
		mode := req.VolumeCapability.AccessMode.Mode
		if mode != csi.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER &&
			mode != csi.VolumeCapability_AccessMode_MULTI_NODE_READER_ONLY &&
			mode != csi.VolumeCapability_AccessMode_MULTI_NODE_SINGLE_WRITER {
			msg := fmt.Sprintf("the volume %s has been published to this node.", req.VolumeId)
			return nil, status.Error(codes.Aborted, msg)
		}
	}

	portal := req.PublishVolumeInfo["portal"]
	targetiqn := req.PublishVolumeInfo["targetiqn"]
	targetlun := req.PublishVolumeInfo["targetlun"]

	// Connect Target
	log.Printf("[NodePublishVolume] portal:%s targetiqn:%s targetlun:%s volumeid:%s",
		portal, targetiqn, targetlun, req.VolumeId)
	device, err := iscsi.Connect(portal, targetiqn, targetlun)
	if err != nil {
		return nil, err
	}

	// obtain attachments to decide if can format.
	atcs, err := client.ListVolumeAttachments()
	if err != nil {
		return nil, status.Error(codes.FailedPrecondition, "Failed to publish node.")
	}
	format := true
	for _, attachSpec := range atcs {
		if attachSpec.VolumeId == req.VolumeId {
			if _, exist := attachSpec.Metadata["target_path"]; exist {
				// The device is formatted, can't be reformat for shared storage.
				format = false
				break
			}
		}
	}

	// Format and Mount
	log.Printf("[NodePublishVolume] device:%s TargetPath:%s", device, req.TargetPath)
	if format {
		err = iscsi.FormatandMount(device, "", req.TargetPath)
	} else {
		err = iscsi.Mount(device, req.TargetPath)
	}
	if err != nil {
		return nil, err
	}

	targetPaths = append(targetPaths, req.TargetPath)
	atc.Metadata["target_path"] = strings.Join(targetPaths, ";")
	_, err = client.UpdateVolumeAttachment(atc.Id, atc)
	if err != nil {
		return nil, status.Error(codes.FailedPrecondition, "Failed to publish node.")
	}

	return &csi.NodePublishVolumeResponse{}, nil
}

// NodeUnpublishVolume implementation
func (p *Plugin) NodeUnpublishVolume(
	ctx context.Context,
	req *csi.NodeUnpublishVolumeRequest) (
	*csi.NodeUnpublishVolumeResponse, error) {

	log.Println("start to NodeUnpublishVolume")
	defer log.Println("end to NodeUnpublishVolume")

	if errCode := p.CheckVersionSupport(req.Version); errCode != codes.OK {
		msg := "the version specified in the request is not supported by the Plugin."
		return nil, status.Error(errCode, msg)
	}

	client := sdscontroller.GetClient("")

	//check volume is exist
	volSpec, errVol := client.GetVolume(req.VolumeId)
	if errVol != nil || volSpec == nil {
		msg := fmt.Sprintf("the volume %s is not exist", req.VolumeId)
		return nil, status.Error(codes.NotFound, msg)
	}

	attachments, err := client.ListVolumeAttachments()
	if err != nil {
		return nil, status.Error(codes.FailedPrecondition, "Failed to NodeUnpublish volume.")
	}

	var atc *model.VolumeAttachmentSpec
	hostname, _ := os.Hostname()
	for _, attachSpec := range attachments {
		if attachSpec.VolumeId == req.VolumeId && attachSpec.Host == hostname {
			atc = attachSpec
			break
		}
	}

	if atc == nil {
		return &csi.NodeUnpublishVolumeResponse{}, nil
	}

	if _, exist := atc.Metadata["target_path"]; !exist {
		return &csi.NodeUnpublishVolumeResponse{}, nil
	}

	var modifyTargetPaths []string
	tpExist := false
	targetPaths := strings.Split(atc.Metadata["target_path"], ";")
	for index, path := range targetPaths {
		if path == req.TargetPath {
			modifyTargetPaths = append(targetPaths[:index], targetPaths[index+1:]...)
			tpExist = true
			break
		}
	}
	if !tpExist {
		return &csi.NodeUnpublishVolumeResponse{}, nil
	}

	// Umount
	log.Printf("[NodeUnpublishVolume] TargetPath:%s", req.TargetPath)
	err = iscsi.Umount(req.TargetPath)
	if err != nil {
		return nil, err
	}

	if len(modifyTargetPaths) == 0 {
		iscsiCon := iscsi.ParseIscsiConnectInfo(atc.ConnectionData)
		// Disconnect
		if iscsiCon != nil {
			err = iscsi.Disconnect(iscsiCon.TgtPortal, iscsiCon.TgtIQN)
			if err != nil {
				return nil, err
			}
		}
	}

	atc.Metadata["target_path"] = strings.Join(modifyTargetPaths, ";")
	_, err = client.UpdateVolumeAttachment(atc.Id, atc)
	if err != nil {
		return nil, status.Error(codes.FailedPrecondition, "Failed to NodeUnpublish volume.")
	}

	return &csi.NodeUnpublishVolumeResponse{}, nil
}

// GetNodeID implementation
func (p *Plugin) GetNodeID(
	ctx context.Context,
	req *csi.GetNodeIDRequest) (
	*csi.GetNodeIDResponse, error) {

	log.Println("start to GetNodeID")
	defer log.Println("end to GetNodeID")

	// Get host name from os
	hostname, err := os.Hostname()
	if err != nil {
		return nil, err
	}

	return &csi.GetNodeIDResponse{
		NodeId: hostname,
	}, nil
}

// NodeProbe implementation
func (p *Plugin) NodeProbe(
	ctx context.Context,
	req *csi.NodeProbeRequest) (
	*csi.NodeProbeResponse, error) {

	log.Println("start to NodeProbe")
	defer log.Println("end to NodeProbe")

	switch runtime.GOOS {
	case "linux":
		return &csi.NodeProbeResponse{}, nil
	default:
		msg := "unsupported operating system:" + runtime.GOOS
		log.Fatalf(msg)
		// csi.Error_NodeProbeError_MISSING_REQUIRED_HOST_DEPENDENCY
		return nil, status.Error(codes.FailedPrecondition, msg)
	}
}

// NodeGetCapabilities implementation
func (p *Plugin) NodeGetCapabilities(
	ctx context.Context,
	req *csi.NodeGetCapabilitiesRequest) (
	*csi.NodeGetCapabilitiesResponse, error) {

	log.Println("start to NodeGetCapabilities")
	defer log.Println("end to NodeGetCapabilities")

	return &csi.NodeGetCapabilitiesResponse{
		Capabilities: []*csi.NodeServiceCapability{
			&csi.NodeServiceCapability{
				Type: &csi.NodeServiceCapability_Rpc{
					Rpc: &csi.NodeServiceCapability_RPC{
						Type: csi.NodeServiceCapability_RPC_UNKNOWN,
					},
				},
			},
		},
	}, nil
}
<|endoftext|>