<commit_before>/*
Copyright 2019 The Rook Authors. 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 csi

import (
	"context"
	"encoding/json"
	"os"
	"sync"

	"github.com/coreos/pkg/capnslog"
	"github.com/pkg/errors"
	cephclient "github.com/rook/rook/pkg/daemon/ceph/client"
	"github.com/rook/rook/pkg/operator/k8sutil"
	v1 "k8s.io/api/core/v1"
	k8serrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
)

var (
	logger      = capnslog.NewPackageLogger("github.com/rook/rook", "ceph-csi")
	configMutex sync.Mutex
)

type CsiClusterConfigEntry struct {
	ClusterID      string         `json:"clusterID"`
	Monitors       []string       `json:"monitors"`
	CephFS         *CsiCephFSSpec `json:"cephFS,omitempty"`
	RadosNamespace string         `json:"radosNamespace,omitempty"`
}

type CsiCephFSSpec struct {
	SubvolumeGroup string `json:"subvolumeGroup,omitempty"`
}

type csiClusterConfig []CsiClusterConfigEntry

// FormatCsiClusterConfig returns a json-formatted string containing
// the cluster-to-mon mapping required to configure ceph csi.
func FormatCsiClusterConfig(
	clusterKey string, mons map[string]*cephclient.MonInfo) (string, error) {

	cc := make(csiClusterConfig, 1)
	cc[0].ClusterID = clusterKey
	cc[0].Monitors = []string{}
	for _, m := range mons {
		cc[0].Monitors = append(cc[0].Monitors, m.Endpoint)
	}

	ccJson, err := json.Marshal(cc)
	if err != nil {
		return "", errors.Wrap(err, "failed to marshal csi cluster config")
	}
	return string(ccJson), nil
}

func parseCsiClusterConfig(c string) (csiClusterConfig, error) {
	var cc csiClusterConfig
	err := json.Unmarshal([]byte(c), &cc)
	if err != nil {
		return cc, errors.Wrap(err, "failed to parse csi cluster config")
	}
	return cc, nil
}

func formatCsiClusterConfig(cc csiClusterConfig) (string, error) {
	ccJson, err := json.Marshal(cc)
	if err != nil {
		return "", errors.Wrap(err, "failed to marshal csi cluster config")
	}
	return string(ccJson), nil
}

func MonEndpoints(mons map[string]*cephclient.MonInfo) []string {
	endpoints := make([]string, 0)
	for _, m := range mons {
		endpoints = append(endpoints, m.Endpoint)
	}
	return endpoints
}

// updateCsiClusterConfig returns a json-formatted string containing
// the cluster-to-mon mapping required to configure ceph csi.
func updateCsiClusterConfig(curr, clusterKey string, newCsiClusterConfigEntry *CsiClusterConfigEntry) (string, error) {
	var (
		cc     csiClusterConfig
		centry CsiClusterConfigEntry
		found  bool
	)

	cc, err := parseCsiClusterConfig(curr)
	if err != nil {
		return "", errors.Wrap(err, "failed to parse current csi cluster config")
	}

	// Regardless of which controllers call updateCsiClusterConfig(), the values will be preserved since
	// a lock is acquired for the update operation. So concurrent updates (rare event) will block and
	// wait for the other update to complete. Monitors and Subvolumegroup will be updated
	// independently and won't collide.
	for i, centry := range cc {
		if centry.ClusterID == clusterKey {
			// If the new entry is nil, this means the entry is being deleted so remove it from the list
			if newCsiClusterConfigEntry == nil {
				cc = append(cc[:i], cc[i+1:]...)
				found = true
				break
			}
			centry.Monitors = newCsiClusterConfigEntry.Monitors
			if newCsiClusterConfigEntry.CephFS != nil && newCsiClusterConfigEntry.CephFS.SubvolumeGroup != "" {
				centry.CephFS = newCsiClusterConfigEntry.CephFS
			}
			if newCsiClusterConfigEntry.RadosNamespace != "" {
				centry.RadosNamespace = newCsiClusterConfigEntry.RadosNamespace
			}
			found = true
			cc[i] = centry
			break
		}
	}
	if !found {
		// If it's the first time we create the cluster, the entry does not exist, so the removal
		// will fail with a dangling pointer
		if newCsiClusterConfigEntry != nil {
			centry.ClusterID = clusterKey
			centry.Monitors = newCsiClusterConfigEntry.Monitors
			// Add a condition not to fill with empty values
			if newCsiClusterConfigEntry.CephFS != nil && newCsiClusterConfigEntry.CephFS.SubvolumeGroup != "" {
				centry.CephFS = newCsiClusterConfigEntry.CephFS
			}
			if newCsiClusterConfigEntry.RadosNamespace != "" {
				centry.RadosNamespace = newCsiClusterConfigEntry.RadosNamespace
			}
			cc = append(cc, centry)
		}
	}

	return formatCsiClusterConfig(cc)
}

// CreateCsiConfigMap creates an empty config map that will be later used
// to provide cluster configuration to ceph-csi. If a config map already
// exists, it will return it.
func CreateCsiConfigMap(namespace string, clientset kubernetes.Interface, ownerInfo *k8sutil.OwnerInfo) error {
	ctx := context.TODO()
	configMap := &v1.ConfigMap{
		ObjectMeta: metav1.ObjectMeta{
			Name:      ConfigName,
			Namespace: namespace,
		},
	}
	configMap.Data = map[string]string{
		ConfigKey: "[]",
	}

	err := ownerInfo.SetControllerReference(configMap)
	if err != nil {
		return errors.Wrapf(err, "failed to set owner reference to csi configmap %q", configMap.Name)
	}
	_, err = clientset.CoreV1().ConfigMaps(namespace).Create(ctx, configMap, metav1.CreateOptions{})
	if err != nil {
		if !k8serrors.IsAlreadyExists(err) {
			return errors.Wrapf(err, "failed to create initial csi config map %q (in %q)", configMap.Name, namespace)
		}
	}

	logger.Infof("successfully created csi config map %q", configMap.Name)
	return nil
}

// SaveClusterConfig updates the config map used to provide ceph-csi with
// basic cluster configuration. The clusterNamespace and clusterInfo are
// used to determine what "cluster" in the config map will be updated and
// and the clusterNamespace value is expected to match the clusterID
// value that is provided to ceph-csi uses in the storage class.
// The locker l is typically a mutex and is used to prevent the config
// map from being updated for multiple clusters simultaneously.
func SaveClusterConfig(clientset kubernetes.Interface, clusterNamespace string, clusterInfo *cephclient.ClusterInfo, newCsiClusterConfigEntry *CsiClusterConfigEntry) error {
	if !CSIEnabled() {
		logger.Debug("skipping csi config update because csi is not enabled")
		return nil
	}

	// csi is deployed into the same namespace as the operator
	csiNamespace := os.Getenv(k8sutil.PodNamespaceEnvVar)
	if csiNamespace == "" {
		logger.Warningf("cannot save csi config due to missing env var %q", k8sutil.PodNamespaceEnvVar)
		return nil
	}
	logger.Debugf("using %q for csi configmap namespace", csiNamespace)

	configMutex.Lock()
	defer configMutex.Unlock()

	// fetch current ConfigMap contents
	configMap, err := clientset.CoreV1().ConfigMaps(csiNamespace).Get(clusterInfo.Context, ConfigName, metav1.GetOptions{})
	if err != nil {
		return errors.Wrap(err, "failed to fetch current csi config map")
	}

	// update ConfigMap contents for current cluster
	currData := configMap.Data[ConfigKey]
	if currData == "" {
		currData = "[]"
	}

	newData, err := updateCsiClusterConfig(currData, clusterNamespace, newCsiClusterConfigEntry)
	if err != nil {
		return errors.Wrap(err, "failed to update csi config map data")
	}
	configMap.Data[ConfigKey] = newData

	// update ConfigMap with new contents
	if _, err := clientset.CoreV1().ConfigMaps(csiNamespace).Update(clusterInfo.Context, configMap, metav1.UpdateOptions{}); err != nil {
		return errors.Wrap(err, "failed to update csi config map")
	}

	return nil
}
<commit_msg>csi: populate mon endpoints even if driver not enabled<commit_after>/*
Copyright 2019 The Rook Authors. 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 csi

import (
	"context"
	"encoding/json"
	"os"
	"sync"

	"github.com/coreos/pkg/capnslog"
	"github.com/pkg/errors"
	cephclient "github.com/rook/rook/pkg/daemon/ceph/client"
	"github.com/rook/rook/pkg/operator/k8sutil"
	v1 "k8s.io/api/core/v1"
	k8serrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
)

var (
	logger      = capnslog.NewPackageLogger("github.com/rook/rook", "ceph-csi")
	configMutex sync.Mutex
)

type CsiClusterConfigEntry struct {
	ClusterID      string         `json:"clusterID"`
	Monitors       []string       `json:"monitors"`
	CephFS         *CsiCephFSSpec `json:"cephFS,omitempty"`
	RadosNamespace string         `json:"radosNamespace,omitempty"`
}

type CsiCephFSSpec struct {
	SubvolumeGroup string `json:"subvolumeGroup,omitempty"`
}

type csiClusterConfig []CsiClusterConfigEntry

// FormatCsiClusterConfig returns a json-formatted string containing
// the cluster-to-mon mapping required to configure ceph csi.
func FormatCsiClusterConfig(
	clusterKey string, mons map[string]*cephclient.MonInfo) (string, error) {

	cc := make(csiClusterConfig, 1)
	cc[0].ClusterID = clusterKey
	cc[0].Monitors = []string{}
	for _, m := range mons {
		cc[0].Monitors = append(cc[0].Monitors, m.Endpoint)
	}

	ccJson, err := json.Marshal(cc)
	if err != nil {
		return "", errors.Wrap(err, "failed to marshal csi cluster config")
	}
	return string(ccJson), nil
}

func parseCsiClusterConfig(c string) (csiClusterConfig, error) {
	var cc csiClusterConfig
	err := json.Unmarshal([]byte(c), &cc)
	if err != nil {
		return cc, errors.Wrap(err, "failed to parse csi cluster config")
	}
	return cc, nil
}

func formatCsiClusterConfig(cc csiClusterConfig) (string, error) {
	ccJson, err := json.Marshal(cc)
	if err != nil {
		return "", errors.Wrap(err, "failed to marshal csi cluster config")
	}
	return string(ccJson), nil
}

func MonEndpoints(mons map[string]*cephclient.MonInfo) []string {
	endpoints := make([]string, 0)
	for _, m := range mons {
		endpoints = append(endpoints, m.Endpoint)
	}
	return endpoints
}

// updateCsiClusterConfig returns a json-formatted string containing
// the cluster-to-mon mapping required to configure ceph csi.
func updateCsiClusterConfig(curr, clusterKey string, newCsiClusterConfigEntry *CsiClusterConfigEntry) (string, error) {
	var (
		cc     csiClusterConfig
		centry CsiClusterConfigEntry
		found  bool
	)

	cc, err := parseCsiClusterConfig(curr)
	if err != nil {
		return "", errors.Wrap(err, "failed to parse current csi cluster config")
	}

	// Regardless of which controllers call updateCsiClusterConfig(), the values will be preserved since
	// a lock is acquired for the update operation. So concurrent updates (rare event) will block and
	// wait for the other update to complete. Monitors and Subvolumegroup will be updated
	// independently and won't collide.
	for i, centry := range cc {
		if centry.ClusterID == clusterKey {
			// If the new entry is nil, this means the entry is being deleted so remove it from the list
			if newCsiClusterConfigEntry == nil {
				cc = append(cc[:i], cc[i+1:]...)
				found = true
				break
			}
			centry.Monitors = newCsiClusterConfigEntry.Monitors
			if newCsiClusterConfigEntry.CephFS != nil && newCsiClusterConfigEntry.CephFS.SubvolumeGroup != "" {
				centry.CephFS = newCsiClusterConfigEntry.CephFS
			}
			if newCsiClusterConfigEntry.RadosNamespace != "" {
				centry.RadosNamespace = newCsiClusterConfigEntry.RadosNamespace
			}
			found = true
			cc[i] = centry
			break
		}
	}
	if !found {
		// If it's the first time we create the cluster, the entry does not exist, so the removal
		// will fail with a dangling pointer
		if newCsiClusterConfigEntry != nil {
			centry.ClusterID = clusterKey
			centry.Monitors = newCsiClusterConfigEntry.Monitors
			// Add a condition not to fill with empty values
			if newCsiClusterConfigEntry.CephFS != nil && newCsiClusterConfigEntry.CephFS.SubvolumeGroup != "" {
				centry.CephFS = newCsiClusterConfigEntry.CephFS
			}
			if newCsiClusterConfigEntry.RadosNamespace != "" {
				centry.RadosNamespace = newCsiClusterConfigEntry.RadosNamespace
			}
			cc = append(cc, centry)
		}
	}

	return formatCsiClusterConfig(cc)
}

// CreateCsiConfigMap creates an empty config map that will be later used
// to provide cluster configuration to ceph-csi. If a config map already
// exists, it will return it.
func CreateCsiConfigMap(namespace string, clientset kubernetes.Interface, ownerInfo *k8sutil.OwnerInfo) error {
	ctx := context.TODO()
	configMap := &v1.ConfigMap{
		ObjectMeta: metav1.ObjectMeta{
			Name:      ConfigName,
			Namespace: namespace,
		},
	}
	configMap.Data = map[string]string{
		ConfigKey: "[]",
	}

	err := ownerInfo.SetControllerReference(configMap)
	if err != nil {
		return errors.Wrapf(err, "failed to set owner reference to csi configmap %q", configMap.Name)
	}
	_, err = clientset.CoreV1().ConfigMaps(namespace).Create(ctx, configMap, metav1.CreateOptions{})
	if err != nil {
		if !k8serrors.IsAlreadyExists(err) {
			return errors.Wrapf(err, "failed to create initial csi config map %q (in %q)", configMap.Name, namespace)
		}
	}

	logger.Infof("successfully created csi config map %q", configMap.Name)
	return nil
}

// SaveClusterConfig updates the config map used to provide ceph-csi with
// basic cluster configuration. The clusterNamespace and clusterInfo are
// used to determine what "cluster" in the config map will be updated and
// and the clusterNamespace value is expected to match the clusterID
// value that is provided to ceph-csi uses in the storage class.
// The locker l is typically a mutex and is used to prevent the config
// map from being updated for multiple clusters simultaneously.
func SaveClusterConfig(clientset kubernetes.Interface, clusterNamespace string, clusterInfo *cephclient.ClusterInfo, newCsiClusterConfigEntry *CsiClusterConfigEntry) error {
	// csi is deployed into the same namespace as the operator
	csiNamespace := os.Getenv(k8sutil.PodNamespaceEnvVar)
	if csiNamespace == "" {
		logger.Warningf("cannot save csi config due to missing env var %q", k8sutil.PodNamespaceEnvVar)
		return nil
	}
	logger.Debugf("using %q for csi configmap namespace", csiNamespace)

	configMutex.Lock()
	defer configMutex.Unlock()

	// fetch current ConfigMap contents
	configMap, err := clientset.CoreV1().ConfigMaps(csiNamespace).Get(clusterInfo.Context, ConfigName, metav1.GetOptions{})
	if err != nil {
		return errors.Wrap(err, "failed to fetch current csi config map")
	}

	// update ConfigMap contents for current cluster
	currData := configMap.Data[ConfigKey]
	if currData == "" {
		currData = "[]"
	}

	newData, err := updateCsiClusterConfig(currData, clusterNamespace, newCsiClusterConfigEntry)
	if err != nil {
		return errors.Wrap(err, "failed to update csi config map data")
	}
	configMap.Data[ConfigKey] = newData

	// update ConfigMap with new contents
	if _, err := clientset.CoreV1().ConfigMaps(csiNamespace).Update(clusterInfo.Context, configMap, metav1.UpdateOptions{}); err != nil {
		return errors.Wrap(err, "failed to update csi config map")
	}

	return nil
}
<|endoftext|>


<commit_before>package frames

import (
	"errors"
	"fmt"
	"io"
	"log"
	"net"
	"time"
)

// ChannelDialer is the client interface from which one builds
// connections.
type ChannelDialer interface {
	io.Closer
	Dial() (net.Conn, error)
	GetInfo() Info
}

// Info provides basic state of a client.
type Info struct {
	BytesRead    uint64 `json:"read"`
	BytesWritten uint64 `json:"written"`
	ChannelsOpen int    `json:"channels"`
}

func (i Info) String() string {
	return fmt.Sprintf("{FrameInfo Wrote: %v, Read: %v, Open: %v}",
		i.BytesWritten, i.BytesRead, i.ChannelsOpen)
}

type queueResult struct {
	conn net.Conn
	err  error
}

type frameClient struct {
	c           net.Conn
	channels    map[uint16]*clientChannel
	egress      chan *FramePacket
	closeMarker chan bool
	connqueue   chan chan queueResult
	info        Info
}

func (fc *frameClient) GetInfo() Info {
	rv := fc.info
	rv.ChannelsOpen = len(fc.channels)
	return rv
}

func (fc *frameClient) handleOpened(pkt *FramePacket) {
	var opening chan queueResult
	select {
	case opening = <-fc.connqueue:
	default:
		log.Panicf("Opening response, but nobody's opening")
		return
	}

	if pkt.Status != FrameSuccess {
		err := frameError(*pkt)
		select {
		case opening <- queueResult{err: err}:
		case <-fc.closeMarker:
		}
		return
	}

	fc.channels[pkt.Channel] = &clientChannel{
		fc,
		pkt.Channel,
		make(chan []byte),
		nil,
		make(chan bool),
	}
	select {
	case opening <- queueResult{fc.channels[pkt.Channel], nil}:
	case <-fc.closeMarker:
	}
}

func (fc *frameClient) handleClosed(pkt *FramePacket) {
	log.Panicf("Closing channel on %v %v (unhandled)",
		fc.c.LocalAddr(), pkt.Channel)
}

func (fc *frameClient) handleData(pkt *FramePacket) {
	ch := fc.channels[pkt.Channel]
	if ch == nil {
		log.Printf("Data on non-existent channel on %v %v: %v",
			fc.c.LocalAddr(), ch, pkt)
		return
	}

	select {
	case ch.incoming <- pkt.Data:
	case <-ch.closeMarker:
		log.Printf("Data on closed channel on %v: %v: %v",
			fc.c.LocalAddr(), ch, pkt)
	}
}

func (fc *frameClient) readResponses() {
	defer fc.Close()
	for {
		hdr := make([]byte, minPktLen)
		r, err := io.ReadFull(fc.c, hdr)
		fc.info.BytesRead += uint64(r)
		if err != nil {
			log.Printf("Error reading pkt header from %v: %v",
				fc.c.RemoteAddr(), err)
			return
		}
		pkt := PacketFromHeader(hdr)
		r, err = io.ReadFull(fc.c, pkt.Data)
		fc.info.BytesRead += uint64(r)
		if err != nil {
			log.Printf("Error reading pkt body from %v: %v",
				fc.c.RemoteAddr(), err)
			return
		}

		switch pkt.Cmd {
		case FrameOpen:
			fc.handleOpened(&pkt)
		case FrameClose:
			fc.handleClosed(&pkt)
		case FrameData:
			fc.handleData(&pkt)
		default:
			panic("unhandled msg")
		}
	}
}

func (fc *frameClient) writeRequests() {
	for {
		var e *FramePacket
		select {
		case e = <-fc.egress:
		case <-fc.closeMarker:
			return
		}
		written, err := fc.c.Write(e.Bytes())
		fc.info.BytesWritten += uint64(written)
		// Clean up on close
		if e.Cmd == FrameClose {
			delete(fc.channels, e.Channel)
		}
		if err != nil {
			log.Printf("write error: %v", err)
			fc.c.Close()
			return
		}
	}
}

// NewClient converts a socket into a channel dialer.
func NewClient(c net.Conn) ChannelDialer {
	fc := &frameClient{
		c,
		map[uint16]*clientChannel{},
		make(chan *FramePacket, 16),
		make(chan bool),
		make(chan chan queueResult, 16),
		Info{},
	}

	go fc.readResponses()
	go fc.writeRequests()

	return fc
}

func (fc *frameClient) Close() error {
	select {
	case <-fc.closeMarker:
		return nil // already closed
	default:
	}

	for _, c := range fc.channels {
		c.terminate()
	}

	close(fc.closeMarker)
	return fc.c.Close()
}

func (fc *frameClient) Dial() (net.Conn, error) {
	pkt := &FramePacket{Cmd: FrameOpen}

	ch := make(chan queueResult)

	select {
	case fc.connqueue <- ch:
	case <-fc.closeMarker:
		return nil, errors.New("closed client")
	}

	select {
	case fc.egress <- pkt:
	case <-fc.closeMarker:
		return nil, errors.New("closed client")
	}

	select {
	case qr := <-ch:
		return qr.conn, qr.err
	case <-fc.closeMarker:
		return nil, io.EOF
	}
}

type clientChannel struct {
	fc          *frameClient
	channel     uint16
	incoming    chan []byte
	current     []byte
	closeMarker chan bool
}

func (f *clientChannel) isClosed() bool {
	select {
	case <-f.closeMarker:
		return true
	default:
	}
	return false
}

func (f *clientChannel) Read(b []byte) (n int, err error) {
	if f.isClosed() {
		return 0, errors.New("read on closed channel")
	}
	read := 0
	for len(b) > 0 {
		if f.current == nil || len(f.current) == 0 {
			var ok bool
			if read == 0 {
				select {
				case f.current, ok = <-f.incoming:
				case <-f.closeMarker:
				case <-f.fc.closeMarker:
				}
			} else {
				select {
				case f.current, ok = <-f.incoming:
				case <-f.closeMarker:
				case <-f.fc.closeMarker:
				default:
					return read, nil
				}
			}
			if !ok {
				return read, io.EOF
			}
		}
		copied := copy(b, f.current)
		read += copied
		f.current = f.current[copied:]
		b = b[copied:]
	}
	return read, nil
}

func (f *clientChannel) Write(b []byte) (n int, err error) {
	if len(b) > maxWriteLen {
		b = b[0:maxWriteLen]
	}

	bc := make([]byte, len(b))
	copy(bc, b)
	pkt := &FramePacket{
		Cmd:     FrameData,
		Channel: f.channel,
		Data:    bc,
	}

	select {
	case f.fc.egress <- pkt:
	case <-f.closeMarker:
		return 0, errors.New("write on closed channel")
	case <-f.fc.closeMarker:
		return 0, errors.New("write on closed connection")
	}
	return len(b), nil
}

func (f *clientChannel) Close() error {
	select {
	case <-f.fc.closeMarker:
		// Socket's closed, we're done
	case <-f.closeMarker:
		// This channel is already closed.
	case f.fc.egress <- &FramePacket{
		Cmd:     FrameClose,
		Channel: f.channel,
	}:
		// Send intent to close.
	}
	f.terminate()
	return nil
}

func (f *clientChannel) terminate() {
	if !f.isClosed() {
		close(f.closeMarker)
	}
}

type frameAddr struct {
	a  net.Addr
	ch uint16
}

func (f frameAddr) Network() string {
	return f.a.String()
}

func (f frameAddr) String() string {
	return fmt.Sprintf("%v#%v", f.Network(), f.ch)
}

func (f *clientChannel) LocalAddr() net.Addr {
	return frameAddr{f.fc.c.LocalAddr(), f.channel}
}

func (f *clientChannel) RemoteAddr() net.Addr {
	return frameAddr{f.fc.c.RemoteAddr(), f.channel}
}

func (f *clientChannel) SetDeadline(t time.Time) error {
	return errors.New("not Implemented")
}

func (f *clientChannel) SetReadDeadline(t time.Time) error {
	return errors.New("not Implemented")
}

func (f *clientChannel) SetWriteDeadline(t time.Time) error {
	return errors.New("not Implemented")
}

func (f *clientChannel) String() string {
	info := ""
	if f.isClosed() {
		info = " CLOSED"
	}
	return fmt.Sprintf("ClientChannel{%v -> %v #%v%v}",
		f.fc.c.LocalAddr(), f.fc.c.RemoteAddr(), f.channel, info)
}
<commit_msg>Strange unnecessary return after a log.Panicf<commit_after>package frames

import (
	"errors"
	"fmt"
	"io"
	"log"
	"net"
	"time"
)

// ChannelDialer is the client interface from which one builds
// connections.
type ChannelDialer interface {
	io.Closer
	Dial() (net.Conn, error)
	GetInfo() Info
}

// Info provides basic state of a client.
type Info struct {
	BytesRead    uint64 `json:"read"`
	BytesWritten uint64 `json:"written"`
	ChannelsOpen int    `json:"channels"`
}

func (i Info) String() string {
	return fmt.Sprintf("{FrameInfo Wrote: %v, Read: %v, Open: %v}",
		i.BytesWritten, i.BytesRead, i.ChannelsOpen)
}

type queueResult struct {
	conn net.Conn
	err  error
}

type frameClient struct {
	c           net.Conn
	channels    map[uint16]*clientChannel
	egress      chan *FramePacket
	closeMarker chan bool
	connqueue   chan chan queueResult
	info        Info
}

func (fc *frameClient) GetInfo() Info {
	rv := fc.info
	rv.ChannelsOpen = len(fc.channels)
	return rv
}

func (fc *frameClient) handleOpened(pkt *FramePacket) {
	var opening chan queueResult
	select {
	case opening = <-fc.connqueue:
	default:
		log.Panicf("Opening response, but nobody's opening")
	}

	if pkt.Status != FrameSuccess {
		err := frameError(*pkt)
		select {
		case opening <- queueResult{err: err}:
		case <-fc.closeMarker:
		}
		return
	}

	fc.channels[pkt.Channel] = &clientChannel{
		fc,
		pkt.Channel,
		make(chan []byte),
		nil,
		make(chan bool),
	}
	select {
	case opening <- queueResult{fc.channels[pkt.Channel], nil}:
	case <-fc.closeMarker:
	}
}

func (fc *frameClient) handleClosed(pkt *FramePacket) {
	log.Panicf("Closing channel on %v %v (unhandled)",
		fc.c.LocalAddr(), pkt.Channel)
}

func (fc *frameClient) handleData(pkt *FramePacket) {
	ch := fc.channels[pkt.Channel]
	if ch == nil {
		log.Printf("Data on non-existent channel on %v %v: %v",
			fc.c.LocalAddr(), ch, pkt)
		return
	}

	select {
	case ch.incoming <- pkt.Data:
	case <-ch.closeMarker:
		log.Printf("Data on closed channel on %v: %v: %v",
			fc.c.LocalAddr(), ch, pkt)
	}
}

func (fc *frameClient) readResponses() {
	defer fc.Close()
	for {
		hdr := make([]byte, minPktLen)
		r, err := io.ReadFull(fc.c, hdr)
		fc.info.BytesRead += uint64(r)
		if err != nil {
			log.Printf("Error reading pkt header from %v: %v",
				fc.c.RemoteAddr(), err)
			return
		}
		pkt := PacketFromHeader(hdr)
		r, err = io.ReadFull(fc.c, pkt.Data)
		fc.info.BytesRead += uint64(r)
		if err != nil {
			log.Printf("Error reading pkt body from %v: %v",
				fc.c.RemoteAddr(), err)
			return
		}

		switch pkt.Cmd {
		case FrameOpen:
			fc.handleOpened(&pkt)
		case FrameClose:
			fc.handleClosed(&pkt)
		case FrameData:
			fc.handleData(&pkt)
		default:
			panic("unhandled msg")
		}
	}
}

func (fc *frameClient) writeRequests() {
	for {
		var e *FramePacket
		select {
		case e = <-fc.egress:
		case <-fc.closeMarker:
			return
		}
		written, err := fc.c.Write(e.Bytes())
		fc.info.BytesWritten += uint64(written)
		// Clean up on close
		if e.Cmd == FrameClose {
			delete(fc.channels, e.Channel)
		}
		if err != nil {
			log.Printf("write error: %v", err)
			fc.c.Close()
			return
		}
	}
}

// NewClient converts a socket into a channel dialer.
func NewClient(c net.Conn) ChannelDialer {
	fc := &frameClient{
		c,
		map[uint16]*clientChannel{},
		make(chan *FramePacket, 16),
		make(chan bool),
		make(chan chan queueResult, 16),
		Info{},
	}

	go fc.readResponses()
	go fc.writeRequests()

	return fc
}

func (fc *frameClient) Close() error {
	select {
	case <-fc.closeMarker:
		return nil // already closed
	default:
	}

	for _, c := range fc.channels {
		c.terminate()
	}

	close(fc.closeMarker)
	return fc.c.Close()
}

func (fc *frameClient) Dial() (net.Conn, error) {
	pkt := &FramePacket{Cmd: FrameOpen}

	ch := make(chan queueResult)

	select {
	case fc.connqueue <- ch:
	case <-fc.closeMarker:
		return nil, errors.New("closed client")
	}

	select {
	case fc.egress <- pkt:
	case <-fc.closeMarker:
		return nil, errors.New("closed client")
	}

	select {
	case qr := <-ch:
		return qr.conn, qr.err
	case <-fc.closeMarker:
		return nil, io.EOF
	}
}

type clientChannel struct {
	fc          *frameClient
	channel     uint16
	incoming    chan []byte
	current     []byte
	closeMarker chan bool
}

func (f *clientChannel) isClosed() bool {
	select {
	case <-f.closeMarker:
		return true
	default:
	}
	return false
}

func (f *clientChannel) Read(b []byte) (n int, err error) {
	if f.isClosed() {
		return 0, errors.New("read on closed channel")
	}
	read := 0
	for len(b) > 0 {
		if f.current == nil || len(f.current) == 0 {
			var ok bool
			if read == 0 {
				select {
				case f.current, ok = <-f.incoming:
				case <-f.closeMarker:
				case <-f.fc.closeMarker:
				}
			} else {
				select {
				case f.current, ok = <-f.incoming:
				case <-f.closeMarker:
				case <-f.fc.closeMarker:
				default:
					return read, nil
				}
			}
			if !ok {
				return read, io.EOF
			}
		}
		copied := copy(b, f.current)
		read += copied
		f.current = f.current[copied:]
		b = b[copied:]
	}
	return read, nil
}

func (f *clientChannel) Write(b []byte) (n int, err error) {
	if len(b) > maxWriteLen {
		b = b[0:maxWriteLen]
	}

	bc := make([]byte, len(b))
	copy(bc, b)
	pkt := &FramePacket{
		Cmd:     FrameData,
		Channel: f.channel,
		Data:    bc,
	}

	select {
	case f.fc.egress <- pkt:
	case <-f.closeMarker:
		return 0, errors.New("write on closed channel")
	case <-f.fc.closeMarker:
		return 0, errors.New("write on closed connection")
	}
	return len(b), nil
}

func (f *clientChannel) Close() error {
	select {
	case <-f.fc.closeMarker:
		// Socket's closed, we're done
	case <-f.closeMarker:
		// This channel is already closed.
	case f.fc.egress <- &FramePacket{
		Cmd:     FrameClose,
		Channel: f.channel,
	}:
		// Send intent to close.
	}
	f.terminate()
	return nil
}

func (f *clientChannel) terminate() {
	if !f.isClosed() {
		close(f.closeMarker)
	}
}

type frameAddr struct {
	a  net.Addr
	ch uint16
}

func (f frameAddr) Network() string {
	return f.a.String()
}

func (f frameAddr) String() string {
	return fmt.Sprintf("%v#%v", f.Network(), f.ch)
}

func (f *clientChannel) LocalAddr() net.Addr {
	return frameAddr{f.fc.c.LocalAddr(), f.channel}
}

func (f *clientChannel) RemoteAddr() net.Addr {
	return frameAddr{f.fc.c.RemoteAddr(), f.channel}
}

func (f *clientChannel) SetDeadline(t time.Time) error {
	return errors.New("not Implemented")
}

func (f *clientChannel) SetReadDeadline(t time.Time) error {
	return errors.New("not Implemented")
}

func (f *clientChannel) SetWriteDeadline(t time.Time) error {
	return errors.New("not Implemented")
}

func (f *clientChannel) String() string {
	info := ""
	if f.isClosed() {
		info = " CLOSED"
	}
	return fmt.Sprintf("ClientChannel{%v -> %v #%v%v}",
		f.fc.c.LocalAddr(), f.fc.c.RemoteAddr(), f.channel, info)
}
<|endoftext|>


<commit_before>package util

import (
	"bytes"
	"fmt"
	"strings"
)

// PosError is an error associated with a position range.
type PosError struct {
	Begin int
	End   int
	Err   error
}

func (pe *PosError) Error() string {
	return fmt.Sprintf("%d-%d: %s", pe.Begin, pe.End, pe.msg())
}

// Pprint pretty-prints a PosError with a header indicating the source and type
// of the error, the error text and the affected line with an additional line
// that points an arrow at the affected column.
func (pe *PosError) Pprint(srcname, errtype, src string) string {
	lineno, colno, line := FindContext(src, pe.Begin)

	buf := new(bytes.Buffer)
	// Source and type of the error
	fmt.Fprintf(buf, "\033[1m%s:%d:%d: \033[31m%s:", srcname, lineno+1, colno+1, errtype)
	// Message
	fmt.Fprintf(buf, "\033[m\033[1m%s\033[m\n", pe.msg())
	// Affected line
	// TODO Handle long lines
	fmt.Fprintf(buf, "%s\n", line)
	// Column indicator
	// TODO Handle multi-width characters
	fmt.Fprintf(buf, "%s\033[32;1m^\033[m\n", strings.Repeat(" ", colno))
	return buf.String()
}

func (pe *PosError) msg() string {
	if pe.Err != nil {
		return pe.Err.Error()
	} else {
		return "<nil>"
	}
}
<commit_msg>Add a missing space to the pprint of PosError.<commit_after>package util

import (
	"bytes"
	"fmt"
	"strings"
)

// PosError is an error associated with a position range.
type PosError struct {
	Begin int
	End   int
	Err   error
}

func (pe *PosError) Error() string {
	return fmt.Sprintf("%d-%d: %s", pe.Begin, pe.End, pe.msg())
}

// Pprint pretty-prints a PosError with a header indicating the source and type
// of the error, the error text and the affected line with an additional line
// that points an arrow at the affected column.
func (pe *PosError) Pprint(srcname, errtype, src string) string {
	lineno, colno, line := FindContext(src, pe.Begin)

	buf := new(bytes.Buffer)
	// Source and type of the error
	fmt.Fprintf(buf, "\033[1m%s:%d:%d: \033[31m%s: ", srcname, lineno+1, colno+1, errtype)
	// Message
	fmt.Fprintf(buf, "\033[m\033[1m%s\033[m\n", pe.msg())
	// Affected line
	// TODO Handle long lines
	fmt.Fprintf(buf, "%s\n", line)
	// Column indicator
	// TODO Handle multi-width characters
	fmt.Fprintf(buf, "%s\033[32;1m^\033[m\n", strings.Repeat(" ", colno))
	return buf.String()
}

func (pe *PosError) msg() string {
	if pe.Err != nil {
		return pe.Err.Error()
	} else {
		return "<nil>"
	}
}
<|endoftext|>


<commit_before>package githubProvider

import (
	"context"
	"fmt"
	"strconv"
	"strings"

	tu "../tutils"
	"github.com/google/go-github/github"
	"golang.org/x/oauth2"
)

//GithubProvider provides authenticated access to GitHub APIs
type GithubProvider struct {
	ctx    context.Context
	client *github.Client
}

//New create a new GithubProvider that with authentication
func New(accessToken string) *GithubProvider {
	ghp := GithubProvider{}
	ghp.ctx = context.Background()
	ts := oauth2.StaticTokenSource(
		&oauth2.Token{AccessToken: accessToken},
	)
	tc := oauth2.NewClient(ghp.ctx, ts)
	ghp.client = github.NewClient(tc)
	return &ghp
}

//GenerateReleaseText gets the last release form mainRepo then looks for PR on mainRepo, coreRepos,  otherRepos since the last version and genorates a change log
func (ghp *GithubProvider) GenerateReleaseText(mainRepo []string, coreRepos [][]string, otherRepos [][]string, major *bool, minor *bool, patch *bool) ([]string, string) {

	changes, lastRelease, GhpErr := ghp.getChangesSinceLastRelease(mainRepo[0], mainRepo[1])
	tu.CheckWarn(GhpErr)

	currentVersion := lastRelease.TagName
	nextVersion := ghp.calculateNextVersion(currentVersion, major, minor, patch)

	var releaseText []string

	fmt.Println("nextVersion :: " + nextVersion)
	fmt.Println("currentVersion :: " + *currentVersion)
	releaseText = append(releaseText, "# Changes since last version:\n")
	releaseText = append(releaseText, "## Changes to "+mainRepo[0]+"/"+mainRepo[1]+":\n")
	for _, change := range changes {
		releaseText = append(releaseText, fmt.Sprintf("  - %s issue %s/%s/pull/%d \n", change.Title, mainRepo[0], mainRepo[1], change.PrNum))
	}
	releaseText = append(releaseText, "# Changes to core repositories:\n")
	for _, coreRepo := range coreRepos {
		coreChanges, changeErr := ghp.getChangesSinceRelease(lastRelease, coreRepo[0], coreRepo[1])
		tu.CheckWarn(changeErr)
		releaseText = append(releaseText, "## Changes to "+coreRepo[0]+"/"+coreRepo[1]+":\n")
		for _, coreChange := range coreChanges {
			releaseText = append(releaseText, fmt.Sprintf("  - %s issue %s/%s/pull/%d \n", coreChange.Title, coreRepo[0], coreRepo[1], coreChange.PrNum))
		}
	}
	fmt.Println("--------------------------------------------------")
	fmt.Println("The Folowing repos will also be tagged with " + nextVersion)
	fmt.Println("--------------------------------------------------")
	fmt.Println(mainRepo[0] + "/" + mainRepo[1])
	for _, repo := range coreRepos {
		fmt.Println(repo[0] + "/" + repo[1])
	}
	for _, repo := range otherRepos {
		fmt.Println(repo[0] + "/" + repo[1])
	}

	return releaseText, nextVersion
}

func (ghp *GithubProvider) DoRelease(mainRepo []string, coreRepos [][]string, otherRepos [][]string, releaseText []string, nextversion string) {

	//create version file in main repo
	getOpts := github.RepositoryContentGetOptions{}
	fileCont, _, _, getErr := ghp.client.Repositories.GetContents(ghp.ctx, mainRepo[0], mainRepo[1], "Version", &getOpts)
	tu.CheckWarn(getErr)

	if fileCont != nil {
		//update
		msg := "Updating version to " + nextversion
		sha := fileCont.GetSHA()
		fileOpts := github.RepositoryContentFileOptions{
			Message: &msg,
			Content: []byte(nextversion),
			SHA:     &sha,
		}
		_, _, updateError := ghp.client.Repositories.UpdateFile(ghp.ctx, mainRepo[0], mainRepo[1], "Version", &fileOpts)
		tu.CheckWarn(updateError)

	} else {
		//create
		msg := "Creating version file for version " + nextversion
		fileOpts := github.RepositoryContentFileOptions{
			Message: &msg,
			Content: []byte(nextversion),
		}
		_, _, updateError := ghp.client.Repositories.CreateFile(ghp.ctx, mainRepo[0], mainRepo[1], "Version", &fileOpts)
		tu.CheckWarn(updateError)
	}

	//tag core and other repos
	for _, repo := range append(coreRepos, otherRepos...) {
		//msg := "Tagging release " + nextversion

		//get the last commit
		commitOpts := github.CommitsListOptions{}
		commits, _, listCommitErr := ghp.client.Repositories.ListCommits(ghp.ctx, repo[0], repo[1], &commitOpts)
		tu.CheckWarn(listCommitErr)

		//
		objType := "commit"
		objSha := commits[0].GetSHA()
		gitObj := github.GitObject{
			Type: &objType,
			SHA:  &objSha,
		}
		refStr := "refs/tags/" + nextversion
		ref := github.Reference{
			Ref:    &refStr,
			Object: &gitObj,
		}
		_, _, tagErr := ghp.client.Git.CreateRef(ghp.ctx, repo[0], repo[1], &ref)
		tu.CheckWarn(tagErr)
	}

	//create release
	tagName := nextversion
	relName := strings.Title(mainRepo[1]) + " Version: " + nextversion
	relText := strings.Join(releaseText, "")
	release := github.RepositoryRelease{
		TagName: &tagName,
		Name:    &relName,
		Body:    &relText,
	}
	ghp.client.Repositories.CreateRelease(ghp.ctx, mainRepo[0], mainRepo[1], &release)

}

type changes struct {
	Title string
	URL   string
	PrNum int
}

//getChangesSinceLastRelease gets all merged Pull requests on the repo at owner/repo since the last tagged release
func (ghp *GithubProvider) getChangesSinceLastRelease(owner string, repo string) ([]changes, *github.RepositoryRelease, error) {

	tu.Log("getChangesSinceLastRelease " + owner + "/" + repo)

	lastRelease, _, gitErr := ghp.client.Repositories.GetLatestRelease(ghp.ctx, owner, repo)
	if gitErr != nil {
		return nil, nil, gitErr
	}

	res, changesErr := ghp.getChangesSinceRelease(lastRelease, owner, repo)

	return res, lastRelease, changesErr
}

//getChangesSinceRelease gets all merged Pull requests on the repo at owner/repo since the provided release
func (ghp *GithubProvider) getChangesSinceRelease(release *github.RepositoryRelease, owner string, repo string) ([]changes, error) {

	result := []changes{}

	prOpts := github.PullRequestListOptions{
		State: "all",
		Base:  "",
	}
	prs, _, prErr := ghp.client.PullRequests.List(ghp.ctx, owner, repo, &prOpts)
	if prErr != nil {
		return nil, prErr
	}

	for _, pr := range prs {
		if pr.MergedAt != nil && pr.MergedAt.After(release.CreatedAt.Time) {
			PrNumTmp := strings.Split(pr.GetURL(), "/")
			PrNum, _ := strconv.Atoi(PrNumTmp[len(PrNumTmp)-1])
			result = append(result, changes{
				pr.GetTitle(),
				pr.GetURL(),
				PrNum,
			})
		}
	}

	return result, nil

}

func (ghp *GithubProvider) calculateNextVersion(currentVersion *string, major *bool, minor *bool, patch *bool) string {

	var nextVersion string
	if *major {
		sp := strings.Split(*currentVersion, ".")
		verPart, _ := strconv.Atoi(sp[0])
		sp[0] = strconv.Itoa(verPart + 1)
		sp[1] = strconv.Itoa(0)
		sp[2] = strconv.Itoa(0)
		nextVersion = strings.Join(sp, ".")

	} else if *minor {
		sp := strings.Split(*currentVersion, ".")
		verPart, _ := strconv.Atoi(sp[1])
		sp[1] = strconv.Itoa(verPart + 1)
		sp[2] = strconv.Itoa(0)
		nextVersion = strings.Join(sp, ".")
	} else if *patch {
		sp := strings.Split(*currentVersion, ".")
		verPart, _ := strconv.Atoi(sp[2])
		sp[2] = strconv.Itoa(verPart + 1)
		nextVersion = strings.Join(sp, ".")
	}
	return nextVersion
}
<commit_msg>Fix output near links in changelog<commit_after>package githubProvider

import (
	"context"
	"fmt"
	"strconv"
	"strings"

	tu "../tutils"
	"github.com/google/go-github/github"
	"golang.org/x/oauth2"
)

//GithubProvider provides authenticated access to GitHub APIs
type GithubProvider struct {
	ctx    context.Context
	client *github.Client
}

//New create a new GithubProvider that with authentication
func New(accessToken string) *GithubProvider {
	ghp := GithubProvider{}
	ghp.ctx = context.Background()
	ts := oauth2.StaticTokenSource(
		&oauth2.Token{AccessToken: accessToken},
	)
	tc := oauth2.NewClient(ghp.ctx, ts)
	ghp.client = github.NewClient(tc)
	return &ghp
}

//GenerateReleaseText gets the last release form mainRepo then looks for PR on mainRepo, coreRepos,  otherRepos since the last version and genorates a change log
func (ghp *GithubProvider) GenerateReleaseText(mainRepo []string, coreRepos [][]string, otherRepos [][]string, major *bool, minor *bool, patch *bool) ([]string, string) {

	changes, lastRelease, GhpErr := ghp.getChangesSinceLastRelease(mainRepo[0], mainRepo[1])
	tu.CheckWarn(GhpErr)

	currentVersion := lastRelease.TagName
	nextVersion := ghp.calculateNextVersion(currentVersion, major, minor, patch)

	var releaseText []string

	fmt.Println("nextVersion :: " + nextVersion)
	fmt.Println("currentVersion :: " + *currentVersion)
	releaseText = append(releaseText, "# Changes since last version:\n")
	releaseText = append(releaseText, "## Changes to "+mainRepo[0]+"/"+mainRepo[1]+":\n")
	for _, change := range changes {
		releaseText = append(releaseText, fmt.Sprintf("  - %s see %s/%s/pull/%d \n", change.Title, mainRepo[0], mainRepo[1], change.PrNum))
	}
	releaseText = append(releaseText, "# Changes to core repositories:\n")
	for _, coreRepo := range coreRepos {
		coreChanges, changeErr := ghp.getChangesSinceRelease(lastRelease, coreRepo[0], coreRepo[1])
		tu.CheckWarn(changeErr)
		releaseText = append(releaseText, "## Changes to "+coreRepo[0]+"/"+coreRepo[1]+":\n")
		for _, coreChange := range coreChanges {
			releaseText = append(releaseText, fmt.Sprintf("  - %s see %s/%s/pull/%d \n", coreChange.Title, coreRepo[0], coreRepo[1], coreChange.PrNum))
		}
	}
	fmt.Println("--------------------------------------------------")
	fmt.Println("The Folowing repos will also be tagged with " + nextVersion)
	fmt.Println("--------------------------------------------------")
	fmt.Println(mainRepo[0] + "/" + mainRepo[1])
	for _, repo := range coreRepos {
		fmt.Println(repo[0] + "/" + repo[1])
	}
	for _, repo := range otherRepos {
		fmt.Println(repo[0] + "/" + repo[1])
	}

	return releaseText, nextVersion
}

func (ghp *GithubProvider) DoRelease(mainRepo []string, coreRepos [][]string, otherRepos [][]string, releaseText []string, nextversion string) {

	//create version file in main repo
	getOpts := github.RepositoryContentGetOptions{}
	fileCont, _, _, getErr := ghp.client.Repositories.GetContents(ghp.ctx, mainRepo[0], mainRepo[1], "Version", &getOpts)
	tu.CheckWarn(getErr)

	if fileCont != nil {
		//update
		msg := "Updating version to " + nextversion
		sha := fileCont.GetSHA()
		fileOpts := github.RepositoryContentFileOptions{
			Message: &msg,
			Content: []byte(nextversion),
			SHA:     &sha,
		}
		_, _, updateError := ghp.client.Repositories.UpdateFile(ghp.ctx, mainRepo[0], mainRepo[1], "Version", &fileOpts)
		tu.CheckWarn(updateError)

	} else {
		//create
		msg := "Creating version file for version " + nextversion
		fileOpts := github.RepositoryContentFileOptions{
			Message: &msg,
			Content: []byte(nextversion),
		}
		_, _, updateError := ghp.client.Repositories.CreateFile(ghp.ctx, mainRepo[0], mainRepo[1], "Version", &fileOpts)
		tu.CheckWarn(updateError)
	}

	//tag core and other repos
	for _, repo := range append(coreRepos, otherRepos...) {
		//msg := "Tagging release " + nextversion

		//get the last commit
		commitOpts := github.CommitsListOptions{}
		commits, _, listCommitErr := ghp.client.Repositories.ListCommits(ghp.ctx, repo[0], repo[1], &commitOpts)
		tu.CheckWarn(listCommitErr)

		//
		objType := "commit"
		objSha := commits[0].GetSHA()
		gitObj := github.GitObject{
			Type: &objType,
			SHA:  &objSha,
		}
		refStr := "refs/tags/" + nextversion
		ref := github.Reference{
			Ref:    &refStr,
			Object: &gitObj,
		}
		_, _, tagErr := ghp.client.Git.CreateRef(ghp.ctx, repo[0], repo[1], &ref)
		tu.CheckWarn(tagErr)
	}

	//create release
	tagName := nextversion
	relName := strings.Title(mainRepo[1]) + " Version: " + nextversion
	relText := strings.Join(releaseText, "")
	release := github.RepositoryRelease{
		TagName: &tagName,
		Name:    &relName,
		Body:    &relText,
	}
	ghp.client.Repositories.CreateRelease(ghp.ctx, mainRepo[0], mainRepo[1], &release)

}

type changes struct {
	Title string
	URL   string
	PrNum int
}

//getChangesSinceLastRelease gets all merged Pull requests on the repo at owner/repo since the last tagged release
func (ghp *GithubProvider) getChangesSinceLastRelease(owner string, repo string) ([]changes, *github.RepositoryRelease, error) {

	tu.Log("getChangesSinceLastRelease " + owner + "/" + repo)

	lastRelease, _, gitErr := ghp.client.Repositories.GetLatestRelease(ghp.ctx, owner, repo)
	if gitErr != nil {
		return nil, nil, gitErr
	}

	res, changesErr := ghp.getChangesSinceRelease(lastRelease, owner, repo)

	return res, lastRelease, changesErr
}

//getChangesSinceRelease gets all merged Pull requests on the repo at owner/repo since the provided release
func (ghp *GithubProvider) getChangesSinceRelease(release *github.RepositoryRelease, owner string, repo string) ([]changes, error) {

	result := []changes{}

	prOpts := github.PullRequestListOptions{
		State: "all",
		Base:  "",
	}
	prs, _, prErr := ghp.client.PullRequests.List(ghp.ctx, owner, repo, &prOpts)
	if prErr != nil {
		return nil, prErr
	}

	for _, pr := range prs {
		if pr.MergedAt != nil && pr.MergedAt.After(release.CreatedAt.Time) {
			PrNumTmp := strings.Split(pr.GetURL(), "/")
			PrNum, _ := strconv.Atoi(PrNumTmp[len(PrNumTmp)-1])
			result = append(result, changes{
				pr.GetTitle(),
				pr.GetURL(),
				PrNum,
			})
		}
	}

	return result, nil

}

func (ghp *GithubProvider) calculateNextVersion(currentVersion *string, major *bool, minor *bool, patch *bool) string {

	var nextVersion string
	if *major {
		sp := strings.Split(*currentVersion, ".")
		verPart, _ := strconv.Atoi(sp[0])
		sp[0] = strconv.Itoa(verPart + 1)
		sp[1] = strconv.Itoa(0)
		sp[2] = strconv.Itoa(0)
		nextVersion = strings.Join(sp, ".")

	} else if *minor {
		sp := strings.Split(*currentVersion, ".")
		verPart, _ := strconv.Atoi(sp[1])
		sp[1] = strconv.Itoa(verPart + 1)
		sp[2] = strconv.Itoa(0)
		nextVersion = strings.Join(sp, ".")
	} else if *patch {
		sp := strings.Split(*currentVersion, ".")
		verPart, _ := strconv.Atoi(sp[2])
		sp[2] = strconv.Itoa(verPart + 1)
		nextVersion = strings.Join(sp, ".")
	}
	return nextVersion
}
<|endoftext|>


<commit_before>// Copyright 2019 The Berglas 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 berglas

import (
	"cloud.google.com/go/storage"
	"context"
	"encoding/base64"
	"fmt"
	"github.com/pkg/errors"
	kmspb "google.golang.org/genproto/googleapis/cloud/kms/v1"
)

// Create is a top-level package function for creating a secret. For large
// volumes of secrets, please create a client instead.
func Create(ctx context.Context, i *CreateRequest) (*Secret, error) {
	client, err := New(ctx)
	if err != nil {
		return nil, err
	}
	return client.Create(ctx, i)
}

// CreateRequest is used as input to a create a secret.
type CreateRequest struct {
	// Bucket is the name of the bucket where the secret lives.
	Bucket string

	// Object is the name of the object in Cloud Storage.
	Object string

	// Key is the fully qualified KMS key id.
	Key string

	// Plaintext is the plaintext secret to encrypt and store.
	Plaintext []byte
}

// Create reads the contents of the secret from the bucket, decrypting the
// ciphertext using Cloud KMS.
func (c *Client) Create(ctx context.Context, i *CreateRequest) (*Secret, error) {
	if i == nil {
		return nil, errors.New("missing request")
	}

	bucket := i.Bucket
	if bucket == "" {
		return nil, errors.New("missing bucket name")
	}

	object := i.Object
	if object == "" {
		return nil, errors.New("missing object name")
	}

	key := i.Key
	if key == "" {
		return nil, errors.New("missing key name")
	}

	plaintext := i.Plaintext
	if plaintext == nil {
		return nil, errors.New("missing plaintext")
	}

	// Generate a unique DEK and encrypt the plaintext locally (useful for large
	// pieces of data).
	dek, ciphertext, err := envelopeEncrypt(plaintext)
	if err != nil {
		return nil, errors.Wrap(err, "failed to perform envelope encryption")
	}

	// Encrypt the plaintext using a KMS key
	kmsResp, err := c.kmsClient.Encrypt(ctx, &kmspb.EncryptRequest{
		Name:                        key,
		Plaintext:                   dek,
		AdditionalAuthenticatedData: []byte(object),
	})
	if err != nil {
		return nil, errors.Wrap(err, "failed to encrypt secret")
	}
	encDEK := kmsResp.Ciphertext

	// Build the storage object contents. Contents will be of the format:
	//
	//    b64(kms_encrypted_dek):b64(dek_encrypted_plaintext)
	blob := fmt.Sprintf("%s:%s",
		base64.StdEncoding.EncodeToString(encDEK),
		base64.StdEncoding.EncodeToString(ciphertext))

	conds := &storage.Conditions{
		DoesNotExist: true,
	}

	return c.write(ctx, bucket, object, key, blob, conds, plaintext, "secret already exists")
}
<commit_msg>exit early for create conflict<commit_after>// Copyright 2019 The Berglas 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 berglas

import (
	"context"
	"encoding/base64"
	"fmt"

	"cloud.google.com/go/storage"
	"github.com/pkg/errors"
	kmspb "google.golang.org/genproto/googleapis/cloud/kms/v1"
)

// Create is a top-level package function for creating a secret. For large
// volumes of secrets, please create a client instead.
func Create(ctx context.Context, i *CreateRequest) (*Secret, error) {
	client, err := New(ctx)
	if err != nil {
		return nil, err
	}
	return client.Create(ctx, i)
}

// CreateRequest is used as input to a create a secret.
type CreateRequest struct {
	// Bucket is the name of the bucket where the secret lives.
	Bucket string

	// Object is the name of the object in Cloud Storage.
	Object string

	// Key is the fully qualified KMS key id.
	Key string

	// Plaintext is the plaintext secret to encrypt and store.
	Plaintext []byte
}

var alreadyExistsError = "secret already exists"

// Create reads the contents of the secret from the bucket, decrypting the
// ciphertext using Cloud KMS.
func (c *Client) Create(ctx context.Context, i *CreateRequest) (*Secret, error) {
	if i == nil {
		return nil, errors.New("missing request")
	}

	bucket := i.Bucket
	if bucket == "" {
		return nil, errors.New("missing bucket name")
	}

	object := i.Object
	if object == "" {
		return nil, errors.New("missing object name")
	}

	key := i.Key
	if key == "" {
		return nil, errors.New("missing key name")
	}

	plaintext := i.Plaintext
	if plaintext == nil {
		return nil, errors.New("missing plaintext")
	}

	_, err := c.storageClient.
		Bucket(bucket).
		Object(object).
		Attrs(ctx)
	switch err {
	case nil:
		return nil, errors.New(alreadyExistsError)
	case storage.ErrObjectNotExist:
		break
	default:
		return nil, errors.Wrap(err, "failed to get object")
	}

	// Generate a unique DEK and encrypt the plaintext locally (useful for large
	// pieces of data).
	dek, ciphertext, err := envelopeEncrypt(plaintext)
	if err != nil {
		return nil, errors.Wrap(err, "failed to perform envelope encryption")
	}

	// Encrypt the plaintext using a KMS key
	kmsResp, err := c.kmsClient.Encrypt(ctx, &kmspb.EncryptRequest{
		Name:                        key,
		Plaintext:                   dek,
		AdditionalAuthenticatedData: []byte(object),
	})
	if err != nil {
		return nil, errors.Wrap(err, "failed to encrypt secret")
	}
	encDEK := kmsResp.Ciphertext

	// Build the storage object contents. Contents will be of the format:
	//
	//    b64(kms_encrypted_dek):b64(dek_encrypted_plaintext)
	blob := fmt.Sprintf("%s:%s",
		base64.StdEncoding.EncodeToString(encDEK),
		base64.StdEncoding.EncodeToString(ciphertext))

	conds := &storage.Conditions{
		DoesNotExist: true,
	}

	return c.write(ctx, bucket, object, key, blob, conds, plaintext, alreadyExistsError)
}
<|endoftext|>


<commit_before>package order_test

import (
	"reflect"
	"testing"

	"github.com/bitfinexcom/bitfinex-api-go/pkg/models/order"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestNewOrderFromRaw(t *testing.T) {
	t.Run("invalid arguments", func(t *testing.T) {
		payload := []interface{}{33950998275}

		got, err := order.FromRaw(payload)
		require.NotNil(t, err)
		require.Nil(t, got)
	})

	t.Run("valid arguments", func(t *testing.T) {
		payload := []interface{}{
			33950998275,
			nil,
			1573476747887,
			"tETHUSD",
			1573476748000,
			1573476748000,
			-0.5,
			-0.5,
			"LIMIT",
			nil,
			nil,
			nil,
			0,
			"ACTIVE",
			nil,
			nil,
			220,
			0,
			0,
			0,
			nil,
			nil,
			nil,
			0,
			1,
			nil,
			nil,
			nil,
			"BFX",
			nil,
			nil,
			nil,
		}

		got, err := order.FromRaw(payload)
		require.Nil(t, err)

		expected := &order.Order{
			ID:            33950998275,
			GID:           0,
			CID:           1573476747887,
			Symbol:        "tETHUSD",
			MTSCreated:    1573476748000,
			MTSUpdated:    1573476748000,
			Amount:        -0.5,
			AmountOrig:    -0.5,
			Type:          "LIMIT",
			TypePrev:      "",
			MTSTif:        0,
			Flags:         0,
			Status:        "ACTIVE",
			Price:         220,
			PriceAvg:      0,
			PriceTrailing: 0,
			PriceAuxLimit: 0,
			Notify:        false,
			Hidden:        true,
			PlacedID:      0,
			Meta:          map[string]interface{}{},
		}
		assert.Equal(t, expected, got)
	})
}

func TestOrdersSnapshotFromRaw(t *testing.T) {
	t.Run("invalid arguments", func(t *testing.T) {
		payload := []interface{}{}
		got, err := order.SnapshotFromRaw(payload)
		require.NotNil(t, err)
		require.Nil(t, got)
	})

	t.Run("partially valid arguments", func(t *testing.T) {
		payload := []interface{}{
			[]interface{}{
				33950998275,
				nil,
				1573476747887,
				"tETHUSD",
				1573476748000,
				1573476748000,
				-0.5,
				-0.5,
				"LIMIT",
				nil,
				nil,
				nil,
				0,
				"ACTIVE",
				nil,
				nil,
				220,
				0,
				0,
				0,
				nil,
				nil,
				nil,
				0,
				1,
				nil,
				nil,
				nil,
				"BFX",
				nil,
				nil,
				nil,
			},
			[]interface{}{33950998276},
		}
		got, err := order.SnapshotFromRaw(payload)
		require.NotNil(t, err)
		require.Nil(t, got)
	})

	t.Run("valid arguments", func(t *testing.T) {
		payload := []interface{}{
			[]interface{}{
				33950998275,
				nil,
				1573476747887,
				"tETHUSD",
				1573476748000,
				1573476748000,
				-0.5,
				-0.5,
				"LIMIT",
				nil,
				nil,
				nil,
				0,
				"ACTIVE",
				nil,
				nil,
				220,
				0,
				0,
				0,
				nil,
				nil,
				nil,
				0,
				1,
				nil,
				nil,
				nil,
				"BFX",
				nil,
				nil,
				nil,
			},
			[]interface{}{
				33950998276,
				nil,
				1573476747887,
				"tETHUSD",
				1573476748000,
				1573476748000,
				-0.5,
				-0.5,
				"LIMIT",
				nil,
				nil,
				nil,
				0,
				"ACTIVE",
				nil,
				nil,
				220,
				0,
				0,
				0,
				nil,
				nil,
				nil,
				0,
				1,
				nil,
				nil,
				nil,
				"BFX",
				nil,
				nil,
				nil,
			},
		}

		got, err := order.SnapshotFromRaw(payload)
		require.Nil(t, err)

		expected := &order.Snapshot{
			Snapshot: []*order.Order{
				{
					ID:            33950998275,
					GID:           0,
					CID:           1573476747887,
					Symbol:        "tETHUSD",
					MTSCreated:    1573476748000,
					MTSUpdated:    1573476748000,
					Amount:        -0.5,
					AmountOrig:    -0.5,
					Type:          "LIMIT",
					TypePrev:      "",
					MTSTif:        0,
					Flags:         0,
					Status:        "ACTIVE",
					Price:         220,
					PriceAvg:      0,
					PriceTrailing: 0,
					PriceAuxLimit: 0,
					Notify:        false,
					Hidden:        true,
					PlacedID:      0,
					Meta:          map[string]interface{}{},
				},
				{
					ID:            33950998276,
					GID:           0,
					CID:           1573476747887,
					Symbol:        "tETHUSD",
					MTSCreated:    1573476748000,
					MTSUpdated:    1573476748000,
					Amount:        -0.5,
					AmountOrig:    -0.5,
					Type:          "LIMIT",
					TypePrev:      "",
					MTSTif:        0,
					Flags:         0,
					Status:        "ACTIVE",
					Price:         220,
					PriceAvg:      0,
					PriceTrailing: 0,
					PriceAuxLimit: 0,
					Notify:        false,
					Hidden:        true,
					PlacedID:      0,
					Meta:          map[string]interface{}{},
				},
			},
		}

		assert.Equal(t, expected, got)
	})
}

func TestNewFromRaw(t *testing.T) {
	pld := []interface{}{
		33950998276, nil, 1573476747887, "tETHUSD", 1573476748000, 1573476748000, -0.5,
		-0.5, "LIMIT", nil, nil, nil, 0, "ACTIVE", nil, nil, 220, 0, 0, 0, nil, nil,
		nil, 0, 1, nil, nil, nil, "BFX", nil, nil, nil,
	}
	expected := "order.New"
	o, err := order.NewFromRaw(pld)
	assert.Nil(t, err)

	got := reflect.TypeOf(o).String()
	assert.Equal(t, expected, got)
}

func TestUpdateFromRaw(t *testing.T) {
	pld := []interface{}{
		33950998276, nil, 1573476747887, "tETHUSD", 1573476748000, 1573476748000, -0.5,
		-0.5, "LIMIT", nil, nil, nil, 0, "ACTIVE", nil, nil, 220, 0, 0, 0, nil, nil,
		nil, 0, 1, nil, nil, nil, "BFX", nil, nil, nil,
	}
	expected := "order.Update"
	o, err := order.UpdateFromRaw(pld)
	assert.Nil(t, err)

	got := reflect.TypeOf(o).String()
	assert.Equal(t, expected, got)
}

func TestCancelFromRaw(t *testing.T) {
	pld := []interface{}{
		33950998276, nil, 1573476747887, "tETHUSD", 1573476748000, 1573476748000, -0.5,
		-0.5, "LIMIT", nil, nil, nil, 0, "ACTIVE", nil, nil, 220, 0, 0, 0, nil, nil,
		nil, 0, 1, nil, nil, nil, "BFX", nil, nil, nil,
	}
	expected := "order.Cancel"
	o, err := order.CancelFromRaw(pld)
	assert.Nil(t, err)

	got := reflect.TypeOf(o).String()
	assert.Equal(t, expected, got)
}
<commit_msg>updating order model tests<commit_after>package order_test

import (
	"reflect"
	"testing"

	"github.com/bitfinexcom/bitfinex-api-go/pkg/models/order"
	"github.com/stretchr/testify/assert"
)

func TestFromRaw(t *testing.T) {
	cases := map[string]struct {
		pld      []interface{}
		expected *order.Order
		err      func(*testing.T, error)
	}{
		"invalid pld": {
			pld:      []interface{}{"exchange"},
			expected: nil,
			err: func(t *testing.T, err error) {
				assert.NotNil(t, err)
			},
		},
		"rest retire orders": {
			pld: []interface{}{
				33950998275, nil, 1573476747887, "tETHUSD", 1573476748000, 1573476748000,
				-0.5, -0.5, "LIMIT", nil, nil, nil, 0, "ACTIVE", nil, nil, 220, 0, 0, 0, nil, nil,
				nil, 0, 0, nil, nil, nil, "BFX", nil, nil, nil,
			},
			expected: &order.Order{
				ID:         33950998275,
				CID:        1573476747887,
				Symbol:     "tETHUSD",
				MTSCreated: 1573476748000,
				MTSUpdated: 1573476748000,
				Amount:     -0.5,
				AmountOrig: -0.5,
				Type:       "LIMIT",
				Status:     "ACTIVE",
				Price:      220,
				Notify:     false,
				Hidden:     false,
				Routing:    "BFX",
			},
			err: func(t *testing.T, err error) {
				assert.Nil(t, err)
			},
		},
		"rest submit order": {
			pld: []interface{}{
				30630788061, nil, 1567590617439, "tBTCUSD", 1567590617439, 1567590617439,
				0.001, 0.001, "LIMIT", nil, nil, nil, 0, "ACTIVE", nil, nil, 15, 0, 0, 0, nil, nil,
				nil, 0, nil, nil, nil, nil, "API>BFX", nil, nil, nil,
			},
			expected: &order.Order{
				ID:         30630788061,
				CID:        1567590617439,
				Symbol:     "tBTCUSD",
				MTSCreated: 1567590617439,
				MTSUpdated: 1567590617439,
				Amount:     0.001,
				AmountOrig: 0.001,
				Type:       "LIMIT",
				Status:     "ACTIVE",
				Price:      15,
				Notify:     false,
				Hidden:     false,
				Routing:    "API>BFX",
			},
			err: func(t *testing.T, err error) {
				assert.Nil(t, err)
			},
		},
		"rest update order": {
			pld: []interface{}{
				30854813589, nil, 1568109670135, "tBTCUSD", 1568109673000, 1568109866000,
				0.002, 0.002, "LIMIT", "LIMIT", nil, nil, 0, "ACTIVE", nil, nil, 20, 0, 0,
				0, nil, nil, nil, 0, 0, nil, nil, nil, "API>BFX", nil, nil, nil,
			},
			expected: &order.Order{
				ID:         30854813589,
				CID:        1568109670135,
				Symbol:     "tBTCUSD",
				MTSCreated: 1568109673000,
				MTSUpdated: 1568109866000,
				Amount:     0.002,
				AmountOrig: 0.002,
				Type:       "LIMIT",
				TypePrev:   "LIMIT",
				Status:     "ACTIVE",
				Price:      20,
				Notify:     false,
				Hidden:     false,
				Routing:    "API>BFX",
			},
			err: func(t *testing.T, err error) {
				assert.Nil(t, err)
			},
		},
		"rest cancel order": {
			pld: []interface{}{
				30937950333, nil, 1568298279766, "tBTCUSD", 1568298281000,
				1568298281000, 0.001, 0.001, "LIMIT", nil, nil, nil, 0,
				"ACTIVE", nil, nil, 15, 0, 0, 0, nil, nil, nil, 0, 0, nil,
				nil, nil, "API>BFX", nil, nil, nil,
			},
			expected: &order.Order{
				ID:         30937950333,
				CID:        1568298279766,
				Symbol:     "tBTCUSD",
				MTSCreated: 1568298281000,
				MTSUpdated: 1568298281000,
				Amount:     0.001,
				AmountOrig: 0.001,
				Type:       "LIMIT",
				Status:     "ACTIVE",
				Price:      15,
				Notify:     false,
				Hidden:     false,
				Routing:    "API>BFX",
			},
			err: func(t *testing.T, err error) {
				assert.Nil(t, err)
			},
		},
		"rest order multi op": {
			pld: []interface{}{
				31492970019, nil, 1569347312970, "tBTCUSD", 1569347312971, 1569347312971, 0.001,
				0.001, "LIMIT", nil, nil, nil, 4096, "ACTIVE", nil, nil, 15, 0, 0, 0, nil, nil,
				nil, 0, nil, nil, nil, nil, "API>BFX", nil, nil, map[string]interface{}{"$F7": 1},
			},
			expected: &order.Order{
				ID:         31492970019,
				CID:        1569347312970,
				Symbol:     "tBTCUSD",
				MTSCreated: 1569347312971,
				MTSUpdated: 1569347312971,
				Amount:     0.001,
				AmountOrig: 0.001,
				Type:       "LIMIT",
				Flags:      4096,
				Status:     "ACTIVE",
				Price:      15,
				Notify:     false,
				Hidden:     false,
				Routing:    "API>BFX",
				Meta:       map[string]interface{}{"$F7": 1},
			},
			err: func(t *testing.T, err error) {
				assert.Nil(t, err)
			},
		},
		"rest cancel order multi": {
			pld: []interface{}{
				31123704044, nil, 1568711144715, "tBTCUSD", 1568711147000, 1568711147000,
				0.001, 0.001, "LIMIT", nil, nil, nil, 0, "ACTIVE", nil, nil, 15, 0, 0, 0, nil, nil,
				nil, 0, 0, nil, nil, nil, "API>BFX", nil, nil, nil,
			},
			expected: &order.Order{
				ID:         31123704044,
				CID:        1568711144715,
				Symbol:     "tBTCUSD",
				MTSCreated: 1568711147000,
				MTSUpdated: 1568711147000,
				Amount:     0.001,
				AmountOrig: 0.001,
				Type:       "LIMIT",
				Status:     "ACTIVE",
				Price:      15,
				Notify:     false,
				Hidden:     false,
				Routing:    "API>BFX",
			},
			err: func(t *testing.T, err error) {
				assert.Nil(t, err)
			},
		},
		"rest orders history item": {
			pld: []interface{}{
				33961681942, "1227", 1337, "tBTCUSD", 1573482478000, 1573485373000,
				0.001, 0.001, "EXCHANGE LIMIT", nil, nil, nil, "0", "CANCELED", nil, nil,
				15, 0, 0, 0, nil, nil, nil, 0, 0, nil, nil, nil, "API>BFX", nil, nil, nil,
			},
			expected: &order.Order{
				ID:         33961681942,
				CID:        1337,
				Symbol:     "tBTCUSD",
				MTSCreated: 1573482478000,
				MTSUpdated: 1573485373000,
				Amount:     0.001,
				AmountOrig: 0.001,
				Type:       "EXCHANGE LIMIT",
				Status:     "CANCELED",
				Price:      15,
				Notify:     false,
				Hidden:     false,
				Routing:    "API>BFX",
			},
			err: func(t *testing.T, err error) {
				assert.Nil(t, err)
			},
		},
		"ws os item": {
			pld: []interface{}{
				34930659963, nil, 1574955083558, "tETHUSD", 1574955083558, 1574955083573,
				0.201104, 0.201104, "EXCHANGE LIMIT", nil, nil, nil, 0, "ACTIVE", nil, nil, 120,
				0, 0, 0, nil, nil, nil, 0, 0, nil, nil, nil, "BFX", nil, nil, nil,
			},
			expected: &order.Order{
				ID:         34930659963,
				CID:        1574955083558,
				Symbol:     "tETHUSD",
				MTSCreated: 1574955083558,
				MTSUpdated: 1574955083573,
				Amount:     0.201104,
				AmountOrig: 0.201104,
				Type:       "EXCHANGE LIMIT",
				Status:     "ACTIVE",
				Price:      120,
				Notify:     false,
				Hidden:     false,
				Routing:    "BFX",
			},
			err: func(t *testing.T, err error) {
				assert.Nil(t, err)
			},
		},
		"ws on ou oc": {
			pld: []interface{}{
				34930659963, nil, 1574955083558, "tETHUSD", 1574955083558, 1574955354487,
				0.201104, 0.201104, "EXCHANGE LIMIT", nil, nil, nil, 0, "CANCELED", nil, nil,
				120, 0, 0, 0, nil, nil, nil, 0, 0, nil, nil, nil, "BFX", nil, nil, nil,
			},
			expected: &order.Order{
				ID:         34930659963,
				CID:        1574955083558,
				Symbol:     "tETHUSD",
				MTSCreated: 1574955083558,
				MTSUpdated: 1574955354487,
				Amount:     0.201104,
				AmountOrig: 0.201104,
				Type:       "EXCHANGE LIMIT",
				Status:     "CANCELED",
				Price:      120,
				Notify:     false,
				Hidden:     false,
				Routing:    "BFX",
			},
			err: func(t *testing.T, err error) {
				assert.Nil(t, err)
			},
		},
	}

	for k, v := range cases {
		t.Run(k, func(t *testing.T) {
			got, err := order.FromRaw(v.pld)
			v.err(t, err)
			assert.Equal(t, v.expected, got)
		})
	}
}

func TestSnapshotFromRaw(t *testing.T) {
	cases := map[string]struct {
		pld      []interface{}
		expected *order.Snapshot
		err      func(*testing.T, error)
	}{
		"invalid pld": {
			pld:      []interface{}{},
			expected: nil,
			err: func(t *testing.T, err error) {
				assert.NotNil(t, err)
			},
		},
		"rest orders hist": {
			pld: []interface{}{
				[]interface{}{
					33961681942, "1227", 1337, "tBTCUSD", 1573482478000, 1573485373000,
					0.001, 0.001, "EXCHANGE LIMIT", nil, nil, nil, "0", "CANCELED", nil, nil,
					15, 0, 0, 0, nil, nil, nil, 0, 0, nil, nil, nil, "API>BFX", nil, nil, nil,
				},
			},
			expected: &order.Snapshot{
				Snapshot: []*order.Order{
					{
						ID:         33961681942,
						CID:        1337,
						Symbol:     "tBTCUSD",
						MTSCreated: 1573482478000,
						MTSUpdated: 1573485373000,
						Amount:     0.001,
						AmountOrig: 0.001,
						Type:       "EXCHANGE LIMIT",
						Status:     "CANCELED",
						Price:      15,
						Notify:     false,
						Hidden:     false,
						Routing:    "API>BFX",
					},
				},
			},
			err: func(t *testing.T, err error) {
				assert.Nil(t, err)
			},
		},
		"ws orders os": {
			pld: []interface{}{
				[]interface{}{
					34930659963, nil, 1574955083558, "tETHUSD", 1574955083558, 1574955083573,
					0.201104, 0.201104, "EXCHANGE LIMIT", nil, nil, nil, 0, "ACTIVE", nil, nil,
					120, 0, 0, 0, nil, nil, nil, 0, 0, nil, nil, nil, "BFX", nil, nil, nil,
				},
			},
			expected: &order.Snapshot{
				Snapshot: []*order.Order{
					{
						ID:         34930659963,
						CID:        1574955083558,
						Symbol:     "tETHUSD",
						MTSCreated: 1574955083558,
						MTSUpdated: 1574955083573,
						Amount:     0.201104,
						AmountOrig: 0.201104,
						Type:       "EXCHANGE LIMIT",
						Status:     "ACTIVE",
						Price:      120,
						Notify:     false,
						Hidden:     false,
						Routing:    "BFX",
					},
				},
			},
			err: func(t *testing.T, err error) {
				assert.Nil(t, err)
			},
		},
	}

	for k, v := range cases {
		t.Run(k, func(t *testing.T) {
			got, err := order.SnapshotFromRaw(v.pld)
			v.err(t, err)
			assert.Equal(t, v.expected, got)
		})
	}
}

func TestNewFromRaw(t *testing.T) {
	pld := []interface{}{
		33950998276, nil, 1573476747887, "tETHUSD", 1573476748000, 1573476748000, -0.5,
		-0.5, "LIMIT", nil, nil, nil, 0, "ACTIVE", nil, nil, 220, 0, 0, 0, nil, nil,
		nil, 0, 1, nil, nil, nil, "BFX", nil, nil, nil,
	}
	expected := "order.New"
	o, err := order.NewFromRaw(pld)
	assert.Nil(t, err)

	got := reflect.TypeOf(o).String()
	assert.Equal(t, expected, got)
}

func TestUpdateFromRaw(t *testing.T) {
	pld := []interface{}{
		33950998276, nil, 1573476747887, "tETHUSD", 1573476748000, 1573476748000, -0.5,
		-0.5, "LIMIT", nil, nil, nil, 0, "ACTIVE", nil, nil, 220, 0, 0, 0, nil, nil,
		nil, 0, 1, nil, nil, nil, "BFX", nil, nil, nil,
	}
	expected := "order.Update"
	o, err := order.UpdateFromRaw(pld)
	assert.Nil(t, err)

	got := reflect.TypeOf(o).String()
	assert.Equal(t, expected, got)
}

func TestCancelFromRaw(t *testing.T) {
	pld := []interface{}{
		33950998276, nil, 1573476747887, "tETHUSD", 1573476748000, 1573476748000, -0.5,
		-0.5, "LIMIT", nil, nil, nil, 0, "ACTIVE", nil, nil, 220, 0, 0, 0, nil, nil,
		nil, 0, 1, nil, nil, nil, "BFX", nil, nil, nil,
	}
	expected := "order.Cancel"
	o, err := order.CancelFromRaw(pld)
	assert.Nil(t, err)

	got := reflect.TypeOf(o).String()
	assert.Equal(t, expected, got)
}
<|endoftext|>


<commit_before>/*
Copyright 2016 The Rook Authors. 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 object

import (
	"encoding/json"
	"fmt"
	"strings"

	"github.com/pkg/errors"
	ceph "github.com/rook/rook/pkg/daemon/ceph/client"
	"github.com/rook/rook/pkg/daemon/ceph/model"
)

const (
	rootPool = ".rgw.root"

	// AppName is the name Rook uses for the object store's application
	AppName = "rook-ceph-rgw"
)

var (
	metadataPools = []string{
		// .rgw.root (rootPool) is appended to this slice where needed
		"rgw.control",
		"rgw.meta",
		"rgw.log",
		"rgw.buckets.index",
		"rgw.buckets.non-ec",
	}
	dataPools = []string{
		"rgw.buckets.data",
	}
)

type idType struct {
	ID string `json:"id"`
}

type realmType struct {
	Realms []string `json:"realms"`
}

func createObjectStore(context *Context, metadataSpec, dataSpec model.Pool, serviceIP string, port int32) error {
	err := createPools(context, metadataSpec, dataSpec)
	if err != nil {
		return errors.Wrapf(err, "failed to create object pools")
	}

	err = createRealm(context, serviceIP, port)
	if err != nil {
		return errors.Wrapf(err, "failed to create object store realm")
	}
	return nil
}

func deleteRealmAndPools(context *Context, preservePoolsOnDelete bool) error {
	stores, err := getObjectStores(context)
	if err != nil {
		return errors.Wrapf(err, "failed to detect object stores during deletion")
	}
	logger.Infof("Found stores %v when deleting store %s", stores, context.Name)

	err = deleteRealm(context)
	if err != nil {
		return errors.Wrapf(err, "failed to delete realm")
	}

	lastStore := false
	if len(stores) == 1 && stores[0] == context.Name {
		lastStore = true
	}

	if !preservePoolsOnDelete {
		err = deletePools(context, lastStore)
		if err != nil {
			return errors.Wrapf(err, "failed to delete object store pools")
		}
	} else {
		logger.Infof("PreservePoolsOnDelete is set in object store %s. Pools not deleted", context.Name)
	}
	return nil
}

func createRealm(context *Context, serviceIP string, port int32) error {
	zoneArg := fmt.Sprintf("--rgw-zone=%s", context.Name)
	endpointArg := fmt.Sprintf("--endpoints=%s:%d", serviceIP, port)
	updatePeriod := false

	// The first realm must be marked as the default
	defaultArg := ""
	stores, err := getObjectStores(context)
	if err != nil {
		return errors.Wrapf(err, "failed to get object stores")
	}
	if len(stores) == 0 {
		defaultArg = "--default"
	}

	// create the realm if it doesn't exist yet
	output, err := runAdminCommand(context, "realm", "get")
	if err != nil {
		updatePeriod = true
		output, err = runAdminCommand(context, "realm", "create", defaultArg)
		if err != nil {
			return errors.Wrapf(err, "failed to create rgw realm %s", context.Name)
		}
	}

	realmID, err := decodeID(output)
	if err != nil {
		return errors.Wrapf(err, "failed to parse realm id")
	}

	// create the zonegroup if it doesn't exist yet
	output, err = runAdminCommand(context, "zonegroup", "get")
	if err != nil {
		updatePeriod = true
		output, err = runAdminCommand(context, "zonegroup", "create", "--master", endpointArg, defaultArg)
		if err != nil {
			return errors.Wrapf(err, "failed to create rgw zonegroup for %s", context.Name)
		}
	}

	zoneGroupID, err := decodeID(output)
	if err != nil {
		return errors.Wrapf(err, "failed to parse zone group id")
	}

	// create the zone if it doesn't exist yet
	output, err = runAdminCommand(context, "zone", "get", zoneArg)
	if err != nil {
		updatePeriod = true
		output, err = runAdminCommand(context, "zone", "create", "--master", endpointArg, zoneArg, defaultArg)
		if err != nil {
			return errors.Wrapf(err, "failed to create rgw zonegroup for %s", context.Name)
		}
	}
	zoneID, err := decodeID(output)
	if err != nil {
		return errors.Wrapf(err, "failed to parse zone id")
	}

	if updatePeriod {
		// the period will help notify other zones of changes if there are multi-zones
		_, err := runAdminCommandNoRealm(context, "period", "update", "--commit")
		if err != nil {
			return errors.Wrapf(err, "failed to update period")
		}
	}

	logger.Infof("RGW: realm=%s, zonegroup=%s, zone=%s", realmID, zoneGroupID, zoneID)
	return nil
}

func deleteRealm(context *Context) error {
	//  <name>
	_, err := runAdminCommand(context, "realm", "delete", "--rgw-realm", context.Name)
	if err != nil {
		logger.Warningf("failed to delete rgw realm %q. %v", context.Name, err)
	}

	_, err = runAdminCommand(context, "zonegroup", "delete", "--rgw-zonegroup", context.Name)
	if err != nil {
		logger.Warningf("failed to delete rgw zonegroup %q. %v", context.Name, err)
	}

	_, err = runAdminCommand(context, "zone", "delete", "--rgw-zone", context.Name)
	if err != nil {
		logger.Warningf("failed to delete rgw zone %q. %v", context.Name, err)
	}

	return nil
}

func decodeID(data string) (string, error) {
	var id idType
	err := json.Unmarshal([]byte(data), &id)
	if err != nil {
		return "", errors.Wrapf(err, "Failed to unmarshal json")
	}

	return id.ID, err
}

func getObjectStores(context *Context) ([]string, error) {
	output, err := runAdminCommandNoRealm(context, "realm", "list")
	if err != nil {
		if strings.Index(err.Error(), "exit status 2") != 0 {
			return []string{}, nil
		}
		return nil, err
	}

	var r realmType
	err = json.Unmarshal([]byte(output), &r)
	if err != nil {
		return nil, errors.Wrapf(err, "Failed to unmarshal realms")
	}

	return r.Realms, nil
}

func deletePools(context *Context, lastStore bool) error {
	pools := append(metadataPools, dataPools...)
	if lastStore {
		pools = append(pools, rootPool)
	}

	for _, pool := range pools {
		name := poolName(context.Name, pool)
		if err := ceph.DeletePool(context.Context, context.ClusterName, name); err != nil {
			logger.Warningf("failed to delete pool %q. %v", name, err)
		}
	}

	// Delete erasure code profile if any
	erasureCodes, err := ceph.ListErasureCodeProfiles(context.Context, context.ClusterName)
	if err != nil {
		return errors.Wrapf(err, "failed to list erasure code profiles for cluster %s", context.ClusterName)
	}
	// cleans up the EC profile for the data pool only. Metadata pools don't support EC (only replication is supported).
	objectStoreErasureCode := ceph.GetErasureCodeProfileForPool(context.Name)
	for i := range erasureCodes {
		if erasureCodes[i] == objectStoreErasureCode {
			if err := ceph.DeleteErasureCodeProfile(context.Context, context.ClusterName, objectStoreErasureCode); err != nil {
				return errors.Wrapf(err, "failed to delete erasure code profile %s for object store %s", objectStoreErasureCode, context.Name)
			}
			break
		}
	}

	return nil
}

func createPools(context *Context, metadataSpec, dataSpec model.Pool) error {
	if err := createSimilarPools(context, append(metadataPools, rootPool), metadataSpec); err != nil {
		return errors.Wrapf(err, "failed to create metadata pools")
	}

	if err := createSimilarPools(context, dataPools, dataSpec); err != nil {
		return errors.Wrapf(err, "failed to create data pool")
	}

	return nil
}

func createSimilarPools(context *Context, pools []string, poolSpec model.Pool) error {
	poolSpec.Name = context.Name
	cephConfig := ceph.ModelPoolToCephPool(poolSpec)
	isECPool := cephConfig.ErasureCodeProfile != ""
	if isECPool {
		// create a new erasure code profile for the new pool
		if err := ceph.CreateErasureCodeProfile(context.Context, context.ClusterName, poolSpec.ErasureCodedConfig, cephConfig.ErasureCodeProfile,
			poolSpec.FailureDomain, poolSpec.CrushRoot, poolSpec.DeviceClass); err != nil {
			return errors.Wrapf(err, "failed to create erasure code profile for object store %s", context.Name)
		}
	}

	for _, pool := range pools {
		// create the pool if it doesn't exist yet
		name := poolName(context.Name, pool)
		if _, err := ceph.GetPoolDetails(context.Context, context.ClusterName, name); err != nil {
			cephConfig.Name = name
			// If the ceph config has an EC profile, an EC pool must be created. Otherwise, it's necessary
			// to create a replicated pool.
			var err error
			if isECPool {
				// An EC pool backing an object store does not need to enable EC overwrites, so the pool is
				// created with that property disabled to avoid unnecessary performance impact.
				err = ceph.CreateECPoolForApp(context.Context, context.ClusterName, cephConfig, AppName, false /* enableECOverwrite */, poolSpec.ErasureCodedConfig)
			} else {
				err = ceph.CreateReplicatedPoolForApp(context.Context, context.ClusterName, cephConfig, AppName)
			}
			if err != nil {
				return errors.Errorf("failed to create pool %s for object store %s", name, context.Name)
			}
		}
	}
	return nil
}

func poolName(storeName, poolName string) string {
	if strings.HasPrefix(poolName, ".") {
		return poolName
	}
	// the name of the pool is <instance>.<name>, except for the pool ".rgw.root" that spans object stores
	return fmt.Sprintf("%s.%s", storeName, poolName)
}
<commit_msg>cephObjectstore: Replica size will not change<commit_after>/*
Copyright 2016 The Rook Authors. 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 object

import (
	"encoding/json"
	"fmt"
	"strconv"
	"strings"

	"github.com/pkg/errors"
	ceph "github.com/rook/rook/pkg/daemon/ceph/client"
	"github.com/rook/rook/pkg/daemon/ceph/model"
)

const (
	rootPool = ".rgw.root"

	// AppName is the name Rook uses for the object store's application
	AppName = "rook-ceph-rgw"
)

var (
	metadataPools = []string{
		// .rgw.root (rootPool) is appended to this slice where needed
		"rgw.control",
		"rgw.meta",
		"rgw.log",
		"rgw.buckets.index",
		"rgw.buckets.non-ec",
	}
	dataPools = []string{
		"rgw.buckets.data",
	}
)

type idType struct {
	ID string `json:"id"`
}

type realmType struct {
	Realms []string `json:"realms"`
}

func createObjectStore(context *Context, metadataSpec, dataSpec model.Pool, serviceIP string, port int32) error {
	err := createPools(context, metadataSpec, dataSpec)
	if err != nil {
		return errors.Wrapf(err, "failed to create object pools")
	}

	err = createRealm(context, serviceIP, port)
	if err != nil {
		return errors.Wrapf(err, "failed to create object store realm")
	}
	return nil
}

func deleteRealmAndPools(context *Context, preservePoolsOnDelete bool) error {
	stores, err := getObjectStores(context)
	if err != nil {
		return errors.Wrapf(err, "failed to detect object stores during deletion")
	}
	logger.Infof("Found stores %v when deleting store %s", stores, context.Name)

	err = deleteRealm(context)
	if err != nil {
		return errors.Wrapf(err, "failed to delete realm")
	}

	lastStore := false
	if len(stores) == 1 && stores[0] == context.Name {
		lastStore = true
	}

	if !preservePoolsOnDelete {
		err = deletePools(context, lastStore)
		if err != nil {
			return errors.Wrapf(err, "failed to delete object store pools")
		}
	} else {
		logger.Infof("PreservePoolsOnDelete is set in object store %s. Pools not deleted", context.Name)
	}
	return nil
}

func createRealm(context *Context, serviceIP string, port int32) error {
	zoneArg := fmt.Sprintf("--rgw-zone=%s", context.Name)
	endpointArg := fmt.Sprintf("--endpoints=%s:%d", serviceIP, port)
	updatePeriod := false

	// The first realm must be marked as the default
	defaultArg := ""
	stores, err := getObjectStores(context)
	if err != nil {
		return errors.Wrapf(err, "failed to get object stores")
	}
	if len(stores) == 0 {
		defaultArg = "--default"
	}

	// create the realm if it doesn't exist yet
	output, err := runAdminCommand(context, "realm", "get")
	if err != nil {
		updatePeriod = true
		output, err = runAdminCommand(context, "realm", "create", defaultArg)
		if err != nil {
			return errors.Wrapf(err, "failed to create rgw realm %s", context.Name)
		}
	}

	realmID, err := decodeID(output)
	if err != nil {
		return errors.Wrapf(err, "failed to parse realm id")
	}

	// create the zonegroup if it doesn't exist yet
	output, err = runAdminCommand(context, "zonegroup", "get")
	if err != nil {
		updatePeriod = true
		output, err = runAdminCommand(context, "zonegroup", "create", "--master", endpointArg, defaultArg)
		if err != nil {
			return errors.Wrapf(err, "failed to create rgw zonegroup for %s", context.Name)
		}
	}

	zoneGroupID, err := decodeID(output)
	if err != nil {
		return errors.Wrapf(err, "failed to parse zone group id")
	}

	// create the zone if it doesn't exist yet
	output, err = runAdminCommand(context, "zone", "get", zoneArg)
	if err != nil {
		updatePeriod = true
		output, err = runAdminCommand(context, "zone", "create", "--master", endpointArg, zoneArg, defaultArg)
		if err != nil {
			return errors.Wrapf(err, "failed to create rgw zonegroup for %s", context.Name)
		}
	}
	zoneID, err := decodeID(output)
	if err != nil {
		return errors.Wrapf(err, "failed to parse zone id")
	}

	if updatePeriod {
		// the period will help notify other zones of changes if there are multi-zones
		_, err := runAdminCommandNoRealm(context, "period", "update", "--commit")
		if err != nil {
			return errors.Wrapf(err, "failed to update period")
		}
	}

	logger.Infof("RGW: realm=%s, zonegroup=%s, zone=%s", realmID, zoneGroupID, zoneID)
	return nil
}

func deleteRealm(context *Context) error {
	//  <name>
	_, err := runAdminCommand(context, "realm", "delete", "--rgw-realm", context.Name)
	if err != nil {
		logger.Warningf("failed to delete rgw realm %q. %v", context.Name, err)
	}

	_, err = runAdminCommand(context, "zonegroup", "delete", "--rgw-zonegroup", context.Name)
	if err != nil {
		logger.Warningf("failed to delete rgw zonegroup %q. %v", context.Name, err)
	}

	_, err = runAdminCommand(context, "zone", "delete", "--rgw-zone", context.Name)
	if err != nil {
		logger.Warningf("failed to delete rgw zone %q. %v", context.Name, err)
	}

	return nil
}

func decodeID(data string) (string, error) {
	var id idType
	err := json.Unmarshal([]byte(data), &id)
	if err != nil {
		return "", errors.Wrapf(err, "Failed to unmarshal json")
	}

	return id.ID, err
}

func getObjectStores(context *Context) ([]string, error) {
	output, err := runAdminCommandNoRealm(context, "realm", "list")
	if err != nil {
		if strings.Index(err.Error(), "exit status 2") != 0 {
			return []string{}, nil
		}
		return nil, err
	}

	var r realmType
	err = json.Unmarshal([]byte(output), &r)
	if err != nil {
		return nil, errors.Wrapf(err, "Failed to unmarshal realms")
	}

	return r.Realms, nil
}

func deletePools(context *Context, lastStore bool) error {
	pools := append(metadataPools, dataPools...)
	if lastStore {
		pools = append(pools, rootPool)
	}

	for _, pool := range pools {
		name := poolName(context.Name, pool)
		if err := ceph.DeletePool(context.Context, context.ClusterName, name); err != nil {
			logger.Warningf("failed to delete pool %q. %v", name, err)
		}
	}

	// Delete erasure code profile if any
	erasureCodes, err := ceph.ListErasureCodeProfiles(context.Context, context.ClusterName)
	if err != nil {
		return errors.Wrapf(err, "failed to list erasure code profiles for cluster %s", context.ClusterName)
	}
	// cleans up the EC profile for the data pool only. Metadata pools don't support EC (only replication is supported).
	objectStoreErasureCode := ceph.GetErasureCodeProfileForPool(context.Name)
	for i := range erasureCodes {
		if erasureCodes[i] == objectStoreErasureCode {
			if err := ceph.DeleteErasureCodeProfile(context.Context, context.ClusterName, objectStoreErasureCode); err != nil {
				return errors.Wrapf(err, "failed to delete erasure code profile %s for object store %s", objectStoreErasureCode, context.Name)
			}
			break
		}
	}

	return nil
}

func createPools(context *Context, metadataSpec, dataSpec model.Pool) error {
	if err := createSimilarPools(context, append(metadataPools, rootPool), metadataSpec); err != nil {
		return errors.Wrapf(err, "failed to create metadata pools")
	}

	if err := createSimilarPools(context, dataPools, dataSpec); err != nil {
		return errors.Wrapf(err, "failed to create data pool")
	}

	return nil
}

func createSimilarPools(context *Context, pools []string, poolSpec model.Pool) error {
	poolSpec.Name = context.Name
	cephConfig := ceph.ModelPoolToCephPool(poolSpec)
	isECPool := cephConfig.ErasureCodeProfile != ""
	if isECPool {
		// create a new erasure code profile for the new pool
		if err := ceph.CreateErasureCodeProfile(context.Context, context.ClusterName, poolSpec.ErasureCodedConfig, cephConfig.ErasureCodeProfile,
			poolSpec.FailureDomain, poolSpec.CrushRoot, poolSpec.DeviceClass); err != nil {
			return errors.Wrapf(err, "failed to create erasure code profile for object store %s", context.Name)
		}
	}

	for _, pool := range pools {
		// create the pool if it doesn't exist yet
		name := poolName(context.Name, pool)
		if poolDetails, err := ceph.GetPoolDetails(context.Context, context.ClusterName, name); err != nil {
			cephConfig.Name = name
			// If the ceph config has an EC profile, an EC pool must be created. Otherwise, it's necessary
			// to create a replicated pool.
			var err error
			if isECPool {
				// An EC pool backing an object store does not need to enable EC overwrites, so the pool is
				// created with that property disabled to avoid unnecessary performance impact.
				err = ceph.CreateECPoolForApp(context.Context, context.ClusterName, cephConfig, AppName, false /* enableECOverwrite */, poolSpec.ErasureCodedConfig)
			} else {
				err = ceph.CreateReplicatedPoolForApp(context.Context, context.ClusterName, cephConfig, AppName)
			}
			if err != nil {
				return errors.Errorf("failed to create pool %s for object store %s", name, context.Name)
			}
		} else {
			// pools already exist
			if !isECPool {
				// detect if the replication is different from the pool details
				if poolDetails.Size != poolSpec.ReplicatedConfig.Size {
					logger.Infof("pool size is changed from %d to %d", poolDetails.Size, poolSpec.ReplicatedConfig.Size)
					if err := ceph.SetPoolReplicatedSizeProperty(context.Context, context.ClusterName, poolDetails.Name, strconv.FormatUint(uint64(poolDetails.Size), 10), poolDetails.RequireSafeReplicaSize); err != nil {
						return errors.Wrapf(err, "failed to set size property to replicated pool %q to %d", poolDetails.Name, poolSpec.ReplicatedConfig.Size)
					}
				}
			}
		}
	}
	return nil
}

func poolName(storeName, poolName string) string {
	if strings.HasPrefix(poolName, ".") {
		return poolName
	}
	// the name of the pool is <instance>.<name>, except for the pool ".rgw.root" that spans object stores
	return fmt.Sprintf("%s.%s", storeName, poolName)
}
<|endoftext|>